def recordsWithDirectoryBasedDelegates(self): """ Fetch calendar-enabled locations and resources which have proxy groups assigned. """ expression = CompoundExpression( ( BooleanExpression(CalFieldName.hasCalendars), CompoundExpression( ( ExistsExpression(CalFieldName.readOnlyProxy), ExistsExpression(CalFieldName.readWriteProxy) ), Operand.OR ) ), Operand.AND ) records = yield self.recordsFromExpression( expression, recordTypes=(self.recordType.location, self.recordType.resource) ) returnValue(records)
def test_compoundWithMultipleExplicitRecordTypes(self): expression = CompoundExpression([ CompoundExpression([ MatchExpression(self.service.fieldName.fullNames, u"be", matchType=MatchType.contains), MatchExpression(self.service.fieldName.emailAddresses, u"be", matchType=MatchType.startsWith), ], Operand.OR), CompoundExpression([ MatchExpression(self.service.fieldName.fullNames, u"test", matchType=MatchType.contains), MatchExpression(self.service.fieldName.emailAddresses, u"test", matchType=MatchType.startsWith), ], Operand.OR), ], Operand.AND) records = yield self.service.recordsFromExpression( expression, recordTypes=[ self.service.recordType.user, self.service.recordType.group ]) # We should get back users and groups: self.verifyResults(records, [ "odtestbetty", "odtestalbert", "anotherodtestalbert", "odtestgroupbetty", "odtestgroupalbert" ], ["odtestamanda", "odtestbill", "odtestgroupa", "odtestgroupb"])
def recordsMatchingTokens(self, tokens, context=None, limitResults=50, timeoutSeconds=10): fields = [ ("fullNames", MatchType.contains), ("emailAddresses", MatchType.startsWith), ] outer = [] for token in tokens: inner = [] for name, matchType in fields: inner.append( MatchExpression(self.fieldName.lookupByName(name), token, matchType, MatchFlags.caseInsensitive)) outer.append(CompoundExpression(inner, Operand.OR)) if len(outer) == 1: expression = outer[0] else: expression = CompoundExpression(outer, Operand.AND) if context is not None: recordTypes = self.recordTypesForSearchContext(context) else: recordTypes = None results = yield self.recordsFromExpression(expression, recordTypes=recordTypes) log.debug("Tokens ({t}) matched {n} records", t=tokens, n=len(results)) returnValue(results)
def recordsMatchingTokens(self, tokens, context=None, limitResults=None, timeoutSeconds=None): fields = [ ("fullNames", MatchType.contains), ("emailAddresses", MatchType.startsWith), ] outer = [] for token in tokens: if token: token = token.strip() inner = [] for name, matchType in fields: inner.append( MatchExpression( self.fieldName.lookupByName(name), token, matchType, MatchFlags.caseInsensitive ) ) outer.append( CompoundExpression( inner, Operand.OR ) ) if len(outer) == 1: expression = outer[0] else: expression = CompoundExpression(outer, Operand.AND) if context is not None: recordTypes = self.recordTypesForSearchContext(context) else: recordTypes = None # If a filter has been set, pass self.recordsFromExpression to it for # result processing if getattr(self, "_resultFilter", None): results = yield self._resultFilter( self.recordsFromExpression, tokens, expression, recordTypes=recordTypes, limitResults=limitResults, timeoutSeconds=timeoutSeconds ) else: results = yield self.recordsFromExpression( expression, recordTypes=recordTypes, limitResults=limitResults, timeoutSeconds=timeoutSeconds ) log.debug( "Tokens ({t}) matched {n} records", t=tokens, n=len(results) ) returnValue(results)
def test_compoundWithEmbeddedMultipleRecordTypes(self): expression = CompoundExpression( [ CompoundExpression( [ CompoundExpression( [ MatchExpression( self.service.fieldName.fullNames, u"be", matchType=MatchType.contains ), MatchExpression( self.service.fieldName.emailAddresses, u"be", matchType=MatchType.startsWith ), ], Operand.OR ), CompoundExpression( [ MatchExpression( self.service.fieldName.fullNames, u"test", matchType=MatchType.contains ), MatchExpression( self.service.fieldName.emailAddresses, u"test", matchType=MatchType.startsWith ), ], Operand.OR ), ], Operand.AND ), CompoundExpression( [ MatchExpression( self.service.fieldName.recordType, self.service.recordType.user, ), MatchExpression( self.service.fieldName.recordType, self.service.recordType.group, ), ], Operand.OR ), ], Operand.AND ) try: yield self.service.recordsFromExpression(expression) except QueryNotSupportedError: pass else: self.fail("This should have raised")
def test_compoundExpressionAsJSON_expressions( self, serialize=compoundExpressionAsJSON): """ L{compoundExpressionAsJSON} with 0, 1 and 2 sub-expressions. """ for uids in ( (), (u"UID1", ), (u"UID1", u"UID2"), ): subExpressions = [ MatchExpression(FieldName.uid, uid) for uid in uids ] subExpressionsText = [ matchExpressionAsJSON(e) for e in subExpressions ] expression = CompoundExpression(subExpressions, Operand.AND) json = compoundExpressionAsJSON(expression) expected = { u"type": u"CompoundExpression", u"expressions": subExpressionsText, u"operand": u"AND", } self.assertEquals(json, expected)
def recordsMatchingFields(self, fields, operand=Operand.OR, recordType=None): """ @param fields: a iterable of tuples, each tuple consisting of: directory field name (C{unicode}) search term (C{unicode}) match flags (L{twext.who.expression.MatchFlags}) match type (L{twext.who.expression.MatchType}) """ subExpressions = [] for fieldName, searchTerm, matchFlags, matchType in fields: try: field = self.fieldName.lookupByName(fieldName) except ValueError: log.debug("Unsupported field name: {fieldName}", fieldName=fieldName) continue subExpression = MatchExpression(field, searchTerm, matchType, matchFlags) subExpressions.append(subExpression) if len(subExpressions) == 1: expression = subExpressions[0] else: expression = CompoundExpression(subExpressions, operand) if recordType is not None: recordTypes = [recordType] else: recordTypes = None return self.recordsFromExpression(expression, recordTypes=recordTypes)
def andOrExpression(propFilterAllOf, matchList): matchList = list(set(matchList)) if propFilterAllOf and len(matchList) > 1: # add OR expression because parent will AND return [CompoundExpression(matchList, Operand.OR), ] else: return matchList
def compoundExpressionFromJSON(json): try: expressions_json = json["expressions"] operand_json = json["operand"] except KeyError as e: raise ValueError("JSON compound expression must have {!r} key.".format( e[0])) expressions = tuple(expressionFromJSON(e) for e in expressions_json) operand = Operand.lookupByName(operand_json) return CompoundExpression(expressions, operand)
def lookup(shortNames): service = DirectoryService() print( "Service = {service}\n" "Session = {service.session}\n" "Node = {service.node}\n" # "Local node = {service.localNode}\n" .format(service=service) ) print("-" * 80) for shortName in shortNames: print("Looking up short name: {0}".format(shortName)) record = yield service.recordWithShortName(service.recordType.user, shortName) if record: print(record.description()) continue matchExpression = MatchExpression( service.fieldName.shortNames, shortName, matchType=MatchType.equals, ) records = yield service.recordsFromExpression(matchExpression) for record in records: print(record.description()) compoundExpression = CompoundExpression( [ MatchExpression( service.fieldName.shortNames, shortName, matchType=MatchType.contains ), MatchExpression( service.fieldName.emailAddresses, shortName, matchType=MatchType.contains ), ], Operand.OR ) records = yield service.recordsFromExpression(compoundExpression) for record in records: print(record.description())
def test_compoundExpressionAsJSON_operands( self, serialize=compoundExpressionAsJSON): """ L{compoundExpressionAsJSON} with different operands. """ for operand, operandText in ( (Operand.AND, u"AND"), (Operand.OR, u"OR"), ): expression = CompoundExpression((), operand) json = compoundExpressionAsJSON(expression) expected = { u"type": u"CompoundExpression", u"expressions": [], u"operand": operandText, } self.assertEquals(json, expected)
def propFilterExpression(filterAllOf, propFilter): """ Create an expression for a single prop-filter element. @param propFilter: the L{PropertyFilter} element. @return: (filterProperyNames, expressions) tuple. expression==True means list all results, expression==False means no results """ def matchExpression(fieldName, matchString, matchType, matchFlags): # special case recordType field if fieldName == FieldName.recordType: # change kind to record type matchValue = vCardKindToRecordTypeMap.get( matchString.lower()) if matchValue is None: matchValue = NamedConstant() matchValue.description = u"" # change types and flags matchFlags &= ~MatchFlags.caseInsensitive matchType = MatchType.equals else: matchValue = matchString.decode("utf-8") return MatchExpression(fieldName, matchValue, matchType, matchFlags) def definedExpression(defined, allOf): if constant or propFilter.filter_name in ( "N", "FN", "UID", "KIND", ): return defined # all records have this property so no records do not have it else: # FIXME: The startsWith expression below, which works with LDAP and OD. is not currently supported return True ''' # this may generate inefficient LDAP query string matchFlags = MatchFlags_none if defined else MatchFlags.NOT matchList = [matchExpression(fieldName, "", MatchType.startsWith, matchFlags) for fieldName in searchableFields] return andOrExpression(allOf, matchList) ''' def andOrExpression(propFilterAllOf, matchList): matchList = list(set(matchList)) if propFilterAllOf and len(matchList) > 1: # add OR expression because parent will AND return [ CompoundExpression(matchList, Operand.OR), ] else: return matchList def paramFilterElementExpression( propFilterAllOf, paramFilterElement): # @UnusedVariable params = vCardPropToParamMap.get( propFilter.filter_name.upper()) defined = params and paramFilterElement.filter_name.upper( ) in params # defined test if defined != paramFilterElement.defined: return False # parameter value text match if defined and paramFilterElement.filters: paramValues = params[ paramFilterElement.filter_name.upper()] if paramValues and paramFilterElement.filters[ 0].text.upper() not in paramValues: return False return True def textMatchElementExpression(propFilterAllOf, textMatchElement): # preprocess text match strings for ds query def getMatchStrings(propFilter, matchString): if propFilter.filter_name in ( "REV", "BDAY", ): rawString = matchString matchString = "" for c in rawString: if c not in "TZ-:": matchString += c elif propFilter.filter_name == "GEO": matchString = ",".join(matchString.split(";")) if propFilter.filter_name in ( "N", "ADR", "ORG", ): # for structured properties, change into multiple strings for ds query if propFilter.filter_name == "ADR": # split by newline and comma rawStrings = ",".join( matchString.split("\n")).split(",") else: # split by space rawStrings = matchString.split(" ") # remove empty strings matchStrings = [] for oneString in rawStrings: if len(oneString): matchStrings += [ oneString, ] return matchStrings elif len(matchString): return [ matchString, ] else: return [] # end getMatchStrings if constant: # FIXME: match is not implemented in twisteddaldav.query.Filter.TextMatch so use _match for now return textMatchElement._match([ constant, ]) else: matchStrings = getMatchStrings(propFilter, textMatchElement.text) if not len(matchStrings): # no searching text in binary ds attributes, so change to defined/not defined case if textMatchElement.negate: return definedExpression(False, propFilterAllOf) # else fall through to attribute exists case below else: # use match_type where possible depending on property/attribute mapping # FIXME: case-sensitive negate will not work. This should return all all records in that case matchType = MatchType.contains if propFilter.filter_name in ( "NICKNAME", "TITLE", "NOTE", "UID", "URL", "N", "ADR", "ORG", "REV", "LABEL", ): if textMatchElement.match_type == "equals": matchType = MatchType.equals elif textMatchElement.match_type == "starts-with": matchType = MatchType.startsWith elif textMatchElement.match_type == "ends-with": matchType = MatchType.endsWith matchList = [] for matchString in matchStrings: matchFlags = None if textMatchElement.collation == "i;unicode-casemap" and textMatchElement.negate: matchFlags = MatchFlags.caseInsensitive | MatchFlags.NOT elif textMatchElement.collation == "i;unicode-casemap": matchFlags = MatchFlags.caseInsensitive elif textMatchElement.negate: matchFlags = MatchFlags.NOT else: matchFlags = MatchFlags_none matchList = [ matchExpression(fieldName, matchString, matchType, matchFlags) for fieldName in searchableFields ] matchList.extend(matchList) return andOrExpression(propFilterAllOf, matchList) # attribute exists search return definedExpression(True, propFilterAllOf) # end textMatchElementExpression() # searchablePropFilterAttrNames are attributes to be used by this propfilter's expression searchableFields = vcardPropToSearchableFieldMap.get( propFilter.filter_name, []) if isinstance(searchableFields, NamedConstant): searchableFields = (searchableFields, ) constant = constantProperties.get(propFilter.filter_name) if not searchableFields and not constant: # not allAttrNames means propFilter.filter_name is not mapped # return None to try to match all items if this is the only property filter return None # create a textMatchElement for the IsNotDefined qualifier if isinstance(propFilter.qualifier, IsNotDefined): textMatchElement = TextMatch( carddavxml.TextMatch.fromString("")) textMatchElement.negate = True propFilter.filters.append(textMatchElement) # if only one propFilter, then use filterAllOf as propFilterAllOf to reduce subexpressions and simplify generated query string if len(propFilter.filters) == 1: propFilterAllOf = filterAllOf else: propFilterAllOf = propFilter.propfilter_test == "allof" propFilterExpressions = None for propFilterElement in propFilter.filters: propFilterExpression = None if isinstance(propFilterElement, ParameterFilter): propFilterExpression = paramFilterElementExpression( propFilterAllOf, propFilterElement) elif isinstance(propFilterElement, TextMatch): propFilterExpression = textMatchElementExpression( propFilterAllOf, propFilterElement) propFilterExpressions = combineExpressionLists( propFilterExpressions, propFilterAllOf, propFilterExpression) if isinstance( propFilterExpressions, bool) and propFilterAllOf != propFilterExpression: break if isinstance(propFilterExpressions, list): propFilterExpressions = list(set(propFilterExpressions)) if propFilterExpressions and (filterAllOf != propFilterAllOf): propFilterExpressions = [ CompoundExpression( propFilterExpressions, Operand.AND if propFilterAllOf else Operand.OR) ] return propFilterExpressions