Changeset 49304

Show
Ignore:
Timestamp:
10/04/06 15:40:37 (3 years ago)
Author:
gracinet
Message:

Merged [49174]: utils.QueryMatcher? knows about None, booleans, ints, and negated

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • CPS3/products/CPSDirectory/trunk/CHANGES

    r49141 r49304  
    44New features 
    55~~~~~~~~~~~~ 
     6- utils.QueryMatcher knows about None, booleans, ints, and negated 
    67- 
    78Bug fixes 
     
    1011New internal features 
    1112~~~~~~~~~~~~~~~~~~~~~ 
    12 - templates succession fy creation now customizable  
     13- templates succession fy creation now customizable 
  • CPS3/products/CPSDirectory/trunk/tests/test_utils.py

    r31560 r49304  
    2222from Testing.ZopeTestCase import doctest 
    2323 
     24from Products.CPSDirectory.utils import QueryMatcher 
     25 
    2426class DirectoryUtilsTestCase(unittest.TestCase): 
    25     pass 
     27 
     28    def testNegate_str(self): 
     29        query = {'sn': {'query': 'tutu', 'negate': True}} 
     30        matcher = QueryMatcher(query) 
     31        self.assert_(matcher.match({'sn': 'blob', 'givenName': '1'})) 
     32        self.failIf(matcher.match({'sn': 'tutu', 'givenName': '2'})) 
     33 
     34    def testNegate_int(self): 
     35        query = {'sn': {'query': 1, 'negate': True}} 
     36        matcher = QueryMatcher(query) 
     37        self.assert_(matcher.match({'sn': 34, 'givenName': '1'})) 
     38        self.failIf(matcher.match({'sn': 1, 'givenName': '2'})) 
     39 
     40    def testNegate_list(self): 
     41        query = {'sn': {'query': ('a', 'b'), 'negate': True}} 
     42        matcher = QueryMatcher(query) 
     43        self.assert_(matcher.match({'sn': 'c', 'givenName': '1'})) 
     44        self.failIf(matcher.match({'sn': 'a', 'givenName': '2'})) 
    2645 
    2746 
  • CPS3/products/CPSDirectory/trunk/utils.py

    r48968 r49304  
    2222 
    2323from types import ListType, TupleType, StringType 
     24import re 
     25import operator 
     26from types import NoneType 
     27 
    2428from Products.CPSUtil.text import toAscii 
    2529 
    26 class QueryMatcher: 
     30def operator_in(a, b): 
     31    # operator.contains with reversed operands 
     32    return a in b 
     33 
     34def operator_notin(a, b): 
     35    return a not in b 
     36 
     37 
     38class QueryMatcher(object): 
    2739    """ Hold/prepare a query and allow to match entries against it. 
    2840 
     
    6072 
    6173    def __init__(self, query, accepted_keys=None, substring_keys=None): 
     74        self._substring_keys = substring_keys 
    6275        _query = {} 
    63         match_types = {} 
     76        ops = {} 
    6477        for key, value in query.items(): 
    6578            if accepted_keys is not None and key not in accepted_keys: 
    6679                continue 
    67             if not value: 
    68                 # Ignore empty searches 
     80            value, op = self._findType(key, value) 
     81            if op is None: 
    6982                continue 
    70             if isinstance(value, StringType): 
    71                 if substring_keys is not None and key in substring_keys: 
    72                     match_types[key] = 'substring' 
    73                     value = toAscii(value).lower() 
    74                 else: 
    75                     match_types[key] = 'exact' 
    76             elif isinstance(value, ListType) or isinstance(value, TupleType): 
    77                 match_types[key] = 'list' 
    78             else: 
    79                 raise ValueError("Bad value %s for '%s'" % (`value`, key)) 
     83            ops[key] = op 
    8084            _query[key] = value 
    8185        self.query = _query 
    82         self.match_types = match_types 
     86        self.ops = ops 
     87 
     88    def _findType(self, key, value, negate=False): 
     89        """Find op and value. 
     90        """ 
     91        if value in ('', None): # XXX 
     92            if negate: 
     93                raise NotImplementedError 
     94            # Ignore empty searches 
     95            return value, None 
     96        elif isinstance(value, basestring): 
     97            if (self._substring_keys is not None 
     98                and key in self._substring_keys): 
     99                if negate: 
     100                    raise NotImplementedError 
     101                op = 'substring' 
     102                value = value.lower() 
     103            else: 
     104                if negate: 
     105                    op = operator.ne 
     106                else: 
     107                    op = operator.eq 
     108        elif isinstance(value, (int, long, bool)): 
     109            if negate: 
     110                op = operator.ne 
     111            else: 
     112                op = operator.eq 
     113        elif isinstance(value, (list, tuple)): 
     114            if negate: 
     115                op = operator_notin 
     116            else: 
     117                op = operator_in 
     118        elif isinstance(value, dict) and 'query' in value: 
     119            if negate and value.get('negate'): 
     120                raise ValueError("Cannot double negate") 
     121            query = value['query'] 
     122            return self._findType(key, query, negate=True) 
     123        else: 
     124            raise ValueError("Bad value %s for '%s'" % (`value`, key)) 
     125        return value, op 
    83126 
    84127    def getKeysSet(self): 
     
    88131        """ Does the entry match the query ? Boolean valued. 
    89132        """ 
    90         search_types = self.match_type
    91         ok = 1 
     133        ops = self.op
     134        ok = True 
    92135        for key, value in self.query.items(): 
    93             searched = entry.get(key) 
    94             if searched is None: 
    95                 ok = 0 
     136            if key not in entry: 
     137                ok = False 
    96138                break 
    97             if isinstance(searched, StringType): 
     139            searched = entry[key] 
     140            if isinstance(searched, (basestring, int, long, NoneType)): 
     141                # bool subclasses int 
    98142                searched = (searched,) 
    99143            matched = 0 
     144            value_re = None 
     145            op = ops[key] 
     146            if isinstance(value, basestring): 
     147                if op == 'substring': 
     148                    value = toAscii(value).lower() 
     149                if '*' in value or '?' in value: 
     150                    regexp = re.escape(value) 
     151                    regexp = regexp.replace('\\?', '.?') 
     152                    regexp = regexp.replace('\\*', '.*') 
     153                    value_re = re.compile(regexp) 
     154 
    100155            for item in searched: 
    101156                # Wild cards like * are currently accepted 
    102                 if search_types[key] == 'list': 
    103                     matched = item in value 
    104                 elif search_types[key] == 'substring': 
     157                if op == 'substring': 
    105158                    matched = value in toAscii(item).lower() or value == '*' 
    106                 else: # search_types[key] == 'exact': 
    107                     matched = item == value 
     159                else: # op is an operator 
     160                    matched = op(item, value) 
    108161                if matched: 
    109162                    break 
    110163            if not matched: 
    111                 ok = 0 
     164                ok = False 
    112165                break 
    113         return ok == 1 
     166        return ok