[CalendarServer-changes] [10747] CalendarServer/trunk/twext/who
source_changes at macosforge.org
source_changes at macosforge.org
Fri Feb 15 15:25:56 PST 2013
Revision: 10747
http://trac.calendarserver.org//changeset/10747
Author: wsanchez at apple.com
Date: 2013-02-15 15:25:56 -0800 (Fri, 15 Feb 2013)
Log Message:
-----------
Move index logic into it's own set of directory classes and have XML inherit from that.
Modified Paths:
--------------
CalendarServer/trunk/twext/who/xml.py
Added Paths:
-----------
CalendarServer/trunk/twext/who/index.py
Copied: CalendarServer/trunk/twext/who/index.py (from rev 10746, CalendarServer/trunk/twext/who/xml.py)
===================================================================
--- CalendarServer/trunk/twext/who/index.py (rev 0)
+++ CalendarServer/trunk/twext/who/index.py 2013-02-15 23:25:56 UTC (rev 10747)
@@ -0,0 +1,216 @@
+##
+# Copyright (c) 2006-2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Indexed directory service implementation.
+"""
+
+__all__ = [
+ "DirectoryService",
+ "DirectoryRecord",
+]
+
+from twisted.python.constants import Names, NamedConstant
+from twisted.internet.defer import succeed, inlineCallbacks, returnValue
+
+from twext.who.util import MergedConstants, describe, uniqueResult, iterFlags
+from twext.who.idirectory import FieldName as BaseFieldName
+from twext.who.idirectory import MatchType, QueryFlags
+from twext.who.idirectory import DirectoryQueryMatchExpression
+from twext.who.directory import DirectoryService as BaseDirectoryService
+from twext.who.directory import DirectoryRecord as BaseDirectoryRecord
+
+
+
+##
+# Data type extentions
+##
+
+class FieldName(Names):
+ memberUIDs = NamedConstant()
+ memberUIDs.description = "member UIDs"
+ memberUIDs.multiValue = True
+
+
+
+##
+# Directory Service
+##
+
+class DirectoryService(BaseDirectoryService):
+ """
+ XML directory service.
+ """
+
+ fieldName = MergedConstants(BaseDirectoryService.fieldName, FieldName)
+
+ indexedFields = (
+ BaseFieldName.recordType,
+ BaseFieldName.uid,
+ BaseFieldName.guid,
+ BaseFieldName.shortNames,
+ BaseFieldName.emailAddresses,
+ FieldName.memberUIDs,
+ )
+
+
+ def __init__(self, realmName):
+ BaseDirectoryService.__init__(self, realmName)
+
+ self.flush()
+
+
+ @property
+ def index(self):
+ self.loadRecords()
+ return self._index
+
+
+ @index.setter
+ def index(self, value):
+ self._index = value
+
+
+ def loadRecords(self):
+ """
+ Load records.
+ """
+ raise NotImplementedError("Subclasses should implement loadRecords().")
+
+
+ def flush(self):
+ """
+ Flush the index.
+ """
+ self._index = None
+
+
+ @staticmethod
+ def _queryFlags(flags):
+ predicate = lambda x: x
+ normalize = lambda x: x
+
+ if flags is not None:
+ for flag in iterFlags(flags):
+ if flag == QueryFlags.NOT:
+ predicate = lambda x: not x
+ elif flag == QueryFlags.caseInsensitive:
+ normalize = lambda x: x.lower()
+ else:
+ raise NotImplementedError("Unknown query flag: %s" % (describe(flag),))
+
+ return predicate, normalize
+
+
+ def indexedRecordsFromMatchExpression(self, expression, records=None):
+ """
+ Finds records in the internal indexes matching a single
+ expression.
+ @param expression: an expression
+ @type expression: L{object}
+ """
+ predicate, normalize = self._queryFlags(expression.flags)
+
+ fieldIndex = self.index[expression.fieldName]
+ matchValue = normalize(expression.fieldValue)
+ matchType = expression.matchType
+
+ if matchType == MatchType.startsWith:
+ indexKeys = (key for key in fieldIndex if predicate(normalize(key).startswith(matchValue)))
+ elif matchType == MatchType.contains:
+ indexKeys = (key for key in fieldIndex if predicate(matchValue in normalize(key)))
+ elif matchType == MatchType.equals:
+ if predicate(True):
+ indexKeys = (matchValue,)
+ else:
+ indexKeys = (key for key in fieldIndex if normalize(key) != matchValue)
+ else:
+ raise NotImplementedError("Unknown match type: %s" % (describe(matchType),))
+
+ matchingRecords = set()
+ for key in indexKeys:
+ matchingRecords |= fieldIndex.get(key, frozenset())
+
+ if records is not None:
+ matchingRecords &= records
+
+ return succeed(matchingRecords)
+
+
+ def unIndexedRecordsFromMatchExpression(self, expression, records=None):
+ """
+ Finds records not in the internal indexes matching a single
+ expression.
+ @param expression: an expression
+ @type expression: L{object}
+ """
+ predicate, normalize = self._queryFlags(expression.flags)
+
+ matchValue = normalize(expression.fieldValue)
+ matchType = expression.matchType
+
+ if matchType == MatchType.startsWith:
+ match = lambda fieldValue: predicate(fieldValue.startswith(matchValue))
+ elif matchType == MatchType.contains:
+ match = lambda fieldValue: predicate(matchValue in fieldValue)
+ elif matchType == MatchType.equals:
+ match = lambda fieldValue: predicate(fieldValue == matchValue)
+ else:
+ raise NotImplementedError("Unknown match type: %s" % (describe(matchType),))
+
+ result = set()
+
+ if records is None:
+ records = (uniqueResult(values) for values in self.index[self.fieldName.uid].itervalues())
+
+ for record in records:
+ fieldValues = record.fields.get(expression.fieldName, None)
+
+ if fieldValues is None:
+ continue
+
+ for fieldValue in fieldValues:
+ if match(normalize(fieldValue)):
+ result.add(record)
+
+ return result
+
+
+ def recordsFromExpression(self, expression, records=None):
+ if isinstance(expression, DirectoryQueryMatchExpression):
+ if expression.fieldName in self.indexedFields:
+ return self.indexedRecordsFromMatchExpression(expression, records=records)
+ else:
+ return self.unIndexedRecordsFromMatchExpression(expression, records=records)
+ else:
+ return BaseDirectoryService.recordsFromExpression(self, expression, records=records)
+
+
+
+class DirectoryRecord(BaseDirectoryRecord):
+ """
+ XML directory record
+ """
+ @inlineCallbacks
+ def members(self):
+ members = set()
+ for uid in getattr(self, "memberUIDs", ()):
+ members.add((yield self.service.recordWithUID(uid)))
+ returnValue(members)
+
+
+ def groups(self):
+ return self.service.recordsWithFieldValue(FieldName.memberUIDs, self.uid)
Modified: CalendarServer/trunk/twext/who/xml.py
===================================================================
--- CalendarServer/trunk/twext/who/xml.py 2013-02-15 22:49:55 UTC (rev 10746)
+++ CalendarServer/trunk/twext/who/xml.py 2013-02-15 23:25:56 UTC (rev 10747)
@@ -33,17 +33,15 @@
from xml.etree.ElementTree import tostring as etreeToString
from xml.etree.ElementTree import Element as XMLElement
-from twisted.python.constants import Names, NamedConstant, Values, ValueConstant
-from twisted.internet.defer import succeed, fail, inlineCallbacks, returnValue
+from twisted.python.constants import Values, ValueConstant
+from twisted.internet.defer import fail
from twext.who.idirectory import DirectoryServiceError
from twext.who.idirectory import NoSuchRecordError, UnknownRecordTypeError
from twext.who.idirectory import RecordType, FieldName as BaseFieldName
-from twext.who.idirectory import MatchType, QueryFlags
-from twext.who.idirectory import DirectoryQueryMatchExpression
-from twext.who.directory import DirectoryService as BaseDirectoryService
-from twext.who.directory import DirectoryRecord as BaseDirectoryRecord
-from twext.who.util import MergedConstants, describe, uniqueResult, iterFlags
+from twext.who.index import DirectoryService as BaseDirectoryService
+from twext.who.index import DirectoryRecord
+from twext.who.index import FieldName as IndexFieldName
@@ -59,17 +57,6 @@
##
-# Data type extentions
-##
-
-class FieldName(Names):
- memberUIDs = NamedConstant()
- memberUIDs.description = "member UIDs"
- memberUIDs.multiValue = True
-
-
-
-##
# XML constants
##
@@ -99,7 +86,7 @@
password.fieldName = BaseFieldName.password
memberUID = ValueConstant("member-uid")
- memberUID.fieldName = FieldName.memberUIDs
+ memberUID.fieldName = IndexFieldName.memberUIDs
@@ -136,31 +123,18 @@
XML directory service.
"""
- fieldName = MergedConstants(BaseDirectoryService.fieldName, FieldName)
-
element = Element
attribute = Attribute
value = Value
- indexedFields = (
- BaseFieldName.recordType,
- BaseFieldName.uid,
- BaseFieldName.guid,
- BaseFieldName.shortNames,
- BaseFieldName.emailAddresses,
- FieldName.memberUIDs,
- )
-
def __init__(self, filePath, refreshInterval=4):
BaseDirectoryService.__init__(self, realmName=noRealmName)
self.filePath = filePath
self.refreshInterval = refreshInterval
- self.flush()
-
def __repr__(self):
realmName = self._realmName
if realmName is None:
@@ -179,27 +153,25 @@
self.loadRecords()
return self._realmName
+
@realmName.setter
def realmName(self, value):
if value is not noRealmName:
raise AssertionError("realmName may not be set directly")
+
@property
def unknownRecordTypes(self):
self.loadRecords()
return self._unknownRecordTypes
+
@property
def unknownFieldElements(self):
self.loadRecords()
return self._unknownFieldElements
- @property
- def index(self):
- self.loadRecords()
- return self._index
-
def loadRecords(self, loadNow=False, stat=True):
"""
Load records from L{self.filePath}.
@@ -207,8 +179,13 @@
Does nothing if a successful refresh has happened within the
last L{self.refreshInterval} seconds.
- @param loadNow: Load now (ignore L{self.refreshInterval})
- @type loadNow: boolean
+ @param loadNow: If true, load now (ignoring
+ L{self.refreshInterval})
+ @type loadNow: L{type}
+
+ @param stat: If true, check file metadata and don't reload if
+ unchanged.
+ @type loadNow: L{type}
"""
#
# Punt if we've read the file recently
@@ -289,11 +266,11 @@
self._unknownRecordTypes = unknownRecordTypes
self._unknownFieldElements = unknownFieldElements
- self._index = index
-
self._cacheTag = cacheTag
self._lastRefresh = now
+ self.index = index
+
return etree
@@ -343,115 +320,15 @@
def flush(self):
+ BaseDirectoryService.flush(self)
+
self._realmName = None
self._unknownRecordTypes = None
self._unknownFieldElements = None
- self._index = None
self._cacheTag = None
self._lastRefresh = 0
- @staticmethod
- def _queryFlags(flags):
- predicate = lambda x: x
- normalize = lambda x: x
-
- if flags is not None:
- for flag in iterFlags(flags):
- if flag == QueryFlags.NOT:
- predicate = lambda x: not x
- elif flag == QueryFlags.caseInsensitive:
- normalize = lambda x: x.lower()
- else:
- raise NotImplementedError("Unknown query flag: %s" % (describe(flag),))
-
- return predicate, normalize
-
-
- def indexedRecordsFromMatchExpression(self, expression, records=None):
- """
- Finds records in the internal indexes matching a single
- expression.
- @param expression: an expression
- @type expression: L{object}
- """
- predicate, normalize = self._queryFlags(expression.flags)
-
- fieldIndex = self.index[expression.fieldName]
- matchValue = normalize(expression.fieldValue)
- matchType = expression.matchType
-
- if matchType == MatchType.startsWith:
- indexKeys = (key for key in fieldIndex if predicate(normalize(key).startswith(matchValue)))
- elif matchType == MatchType.contains:
- indexKeys = (key for key in fieldIndex if predicate(matchValue in normalize(key)))
- elif matchType == MatchType.equals:
- if predicate(True):
- indexKeys = (matchValue,)
- else:
- indexKeys = (key for key in fieldIndex if normalize(key) != matchValue)
- else:
- raise NotImplementedError("Unknown match type: %s" % (describe(matchType),))
-
- matchingRecords = set()
- for key in indexKeys:
- matchingRecords |= fieldIndex.get(key, frozenset())
-
- if records is not None:
- matchingRecords &= records
-
- return succeed(matchingRecords)
-
-
- def unIndexedRecordsFromMatchExpression(self, expression, records=None):
- """
- Finds records not in the internal indexes matching a single
- expression.
- @param expression: an expression
- @type expression: L{object}
- """
- predicate, normalize = self._queryFlags(expression.flags)
-
- matchValue = normalize(expression.fieldValue)
- matchType = expression.matchType
-
- if matchType == MatchType.startsWith:
- match = lambda fieldValue: predicate(fieldValue.startswith(matchValue))
- elif matchType == MatchType.contains:
- match = lambda fieldValue: predicate(matchValue in fieldValue)
- elif matchType == MatchType.equals:
- match = lambda fieldValue: predicate(fieldValue == matchValue)
- else:
- raise NotImplementedError("Unknown match type: %s" % (describe(matchType),))
-
- result = set()
-
- if records is None:
- records = (uniqueResult(values) for values in self.index[self.fieldName.uid].itervalues())
-
- for record in records:
- fieldValues = record.fields.get(expression.fieldName, None)
-
- if fieldValues is None:
- continue
-
- for fieldValue in fieldValues:
- if match(normalize(fieldValue)):
- result.add(record)
-
- return result
-
-
- def recordsFromExpression(self, expression, records=None):
- if isinstance(expression, DirectoryQueryMatchExpression):
- if expression.fieldName in self.indexedFields:
- return self.indexedRecordsFromMatchExpression(expression, records=records)
- else:
- return self.unIndexedRecordsFromMatchExpression(expression, records=records)
- else:
- return BaseDirectoryService.recordsFromExpression(self, expression, records=records)
-
-
def updateRecords(self, records, create=False):
# Index the records to update by UID
recordsByUID = dict(((record.uid, record) for record in records))
@@ -554,20 +431,4 @@
-class DirectoryRecord(BaseDirectoryRecord):
- """
- XML directory record
- """
- @inlineCallbacks
- def members(self):
- members = set()
- for uid in getattr(self, "memberUIDs", ()):
- members.add((yield self.service.recordWithUID(uid)))
- returnValue(members)
-
-
- def groups(self):
- return self.service.recordsWithFieldValue(FieldName.memberUIDs, self.uid)
-
-
noRealmName = object()
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130215/ddf7d331/attachment-0001.html>
More information about the calendarserver-changes
mailing list