[CalendarServer-changes] [11252] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Tue May 28 19:20:45 PDT 2013


Revision: 11252
          http://trac.calendarserver.org//changeset/11252
Author:   cdaboo at apple.com
Date:     2013-05-28 19:20:45 -0700 (Tue, 28 May 2013)
Log Message:
-----------
Fix open directory backer PROPFINDs. Cleanup after PyOD removal.

Modified Paths:
--------------
    CalendarServer/trunk/conf/caldavd-apple.plist
    CalendarServer/trunk/twistedcaldav/directory/appleopendirectory.py
    CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py
    CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectory.py
    CalendarServer/trunk/twistedcaldav/directorybackedaddressbook.py
    CalendarServer/trunk/twistedcaldav/resource.py
    CalendarServer/trunk/twistedcaldav/sharing.py
    CalendarServer/trunk/twistedcaldav/stdconfig.py

Modified: CalendarServer/trunk/conf/caldavd-apple.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-apple.plist	2013-05-28 20:22:31 UTC (rev 11251)
+++ CalendarServer/trunk/conf/caldavd-apple.plist	2013-05-29 02:20:45 UTC (rev 11252)
@@ -534,9 +534,6 @@
     <key>EnableSearchAddressBook</key>
     <false/>
 
-    <key>OpenDirectoryModule</key>
-    <string>calendarserver.platform.darwin.od.opendirectory</string>
-
     <key>Includes</key>
     <array>
         <string>/Library/Server/Calendar and Contacts/Config/caldavd-system.plist</string>

Modified: CalendarServer/trunk/twistedcaldav/directory/appleopendirectory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/appleopendirectory.py	2013-05-28 20:22:31 UTC (rev 11251)
+++ CalendarServer/trunk/twistedcaldav/directory/appleopendirectory.py	2013-05-29 02:20:45 UTC (rev 11252)
@@ -33,19 +33,17 @@
 from twext.web2.auth.digest import DigestedCredentials
 
 from twistedcaldav.config import config
-from twistedcaldav.directory.cachingdirectory import CachingDirectoryService,\
+from twistedcaldav.directory.cachingdirectory import CachingDirectoryService, \
     CachingDirectoryRecord
 from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
 from twistedcaldav.directory.directory import DirectoryError, UnknownRecordTypeError
 from twistedcaldav.directory.util import splitIntoBatches
 from twistedcaldav.directory.principal import cuAddressConverter
 
-from calendarserver.platform.darwin.od import dsattributes, dsquery
-from twisted.python.reflect import namedModule
+from calendarserver.platform.darwin.od import opendirectory, dsattributes, dsquery
 
 
 
-
 class OpenDirectoryService(CachingDirectoryService):
     """
     OpenDirectory implementation of L{IDirectoryService}.
@@ -96,7 +94,7 @@
                                                    params['negativeCaching'])
 
         if odModule is None:
-            odModule = namedModule(config.OpenDirectoryModule)
+            odModule = opendirectory
         self.odModule = odModule
 
         try:
@@ -121,6 +119,7 @@
             self.restrictToGUID = True
         self.restrictedTimestamp = 0
 
+
     @property
     def restrictedGUIDs(self):
         """
@@ -140,7 +139,7 @@
                     dsattributes.eDSExact,
                     False,
                     dsattributes.kDSStdRecordTypeGroups,
-                    [dsattributes.kDSNAttrGroupMembers, dsattributes.kDSNAttrNestedGroups,],
+                    [dsattributes.kDSNAttrGroupMembers, dsattributes.kDSNAttrNestedGroups, ],
                 ))
                 results = self.odModule.queryRecordsWithAttribute_list(
                     self.directory,
@@ -149,7 +148,7 @@
                     dsattributes.eDSExact,
                     False,
                     dsattributes.kDSStdRecordTypeGroups,
-                    [dsattributes.kDSNAttrGroupMembers, dsattributes.kDSNAttrNestedGroups,],
+                    [dsattributes.kDSNAttrGroupMembers, dsattributes.kDSNAttrNestedGroups, ],
                 )
 
                 if len(results) == 1:
@@ -166,6 +165,7 @@
             # No restrictions
             return None
 
+
     def __cmp__(self, other):
         if not isinstance(other, DirectoryRecord):
             return super(DirectoryRecord, self).__eq__(other)
@@ -176,12 +176,14 @@
                 return diff
         return 0
 
+
     def __hash__(self):
         h = hash(self.__class__.__name__)
         for attr in ("node",):
             h = (h + hash(getattr(self, attr))) & sys.maxint
         return h
 
+
     def _expandGroupMembership(self, members, nestedGroups, processedGUIDs=None, returnGroups=False):
 
         if processedGUIDs is None:
@@ -240,9 +242,11 @@
             ):
                 yield GUID
 
+
     def recordTypes(self):
         return self._recordTypes
 
+
     def listRecords(self, recordType):
         """
         Retrieve all the records of recordType from the directory, but for
@@ -298,7 +302,6 @@
 
             proxyGUIDs = ()
             readOnlyProxyGUIDs = ()
-            autoSchedule = False
 
             if recordType in (
                 DirectoryService.recordType_resources,
@@ -310,7 +313,7 @@
                         resourceInfo = resourceInfo[0]
                     try:
                         (
-                            autoSchedule,
+                            _ignore_autoSchedule,
                             proxy,
                             readOnlyProxy
                         ) = self.parseResourceInfo(
@@ -345,20 +348,20 @@
                 nestedGUIDs = ()
 
             record = OpenDirectoryRecord(
-                service               = self,
-                recordType            = recordType,
-                guid                  = recordGUID,
-                nodeName              = "",
-                shortNames            = recordShortNames,
-                authIDs               = (),
-                fullName              = recordFullName,
-                firstName             = "",
-                lastName              = "",
-                emailAddresses        = "",
-                memberGUIDs           = memberGUIDs,
-                nestedGUIDs           = nestedGUIDs,
-                extProxies            = proxyGUIDs,
-                extReadOnlyProxies    = readOnlyProxyGUIDs,
+                service=self,
+                recordType=recordType,
+                guid=recordGUID,
+                nodeName="",
+                shortNames=recordShortNames,
+                authIDs=(),
+                fullName=recordFullName,
+                firstName="",
+                lastName="",
+                emailAddresses="",
+                memberGUIDs=memberGUIDs,
+                nestedGUIDs=nestedGUIDs,
+                extProxies=proxyGUIDs,
+                extReadOnlyProxies=readOnlyProxyGUIDs,
             )
 
             # (Copied from below)
@@ -369,7 +372,7 @@
             if self.augmentService is not None:
                 d = self.augmentService.getAugmentRecord(record.guid,
                     recordType)
-                d.addCallback(lambda x:record.addAugmentInformation(x))
+                d.addCallback(lambda x: record.addAugmentInformation(x))
             records.append(record)
 
         self.log_debug("ListRecords returning %d %s records" % (len(records),
@@ -453,9 +456,6 @@
 
         return guids
 
-
-
-
     _ODFields = {
         'fullName' : {
             'odField' : dsattributes.kDS1AttrDistinguishedName,
@@ -528,6 +528,7 @@
         else:
             return ()
 
+
     def _setFromAttribute(self, attribute, lower=False):
         if attribute:
             if isinstance(attribute, str):
@@ -537,6 +538,7 @@
         else:
             return ()
 
+
     def recordsMatchingTokens(self, tokens, context=None, lookupMethod=None):
         """
         @param tokens: The tokens to search on
@@ -558,7 +560,7 @@
         """
 
         if lookupMethod is None:
-            lookupMethod=self.odModule.queryRecordsWithAttributes_list
+            lookupMethod = self.odModule.queryRecordsWithAttributes_list
 
         def collectResults(results):
             self.log_debug("Got back %d records from OD" % (len(results),))
@@ -620,20 +622,20 @@
                     # objects that are used to generate the REPORT result.
 
                     record = OpenDirectoryRecord(
-                        service               = self,
-                        recordType            = recordType,
-                        guid                  = recordGUID,
-                        nodeName              = recordNodeName,
-                        shortNames            = recordShortNames,
-                        authIDs               = recordAuthIDs,
-                        fullName              = recordFullName,
-                        firstName             = recordFirstName,
-                        lastName              = recordLastName,
-                        emailAddresses        = recordEmailAddresses,
-                        memberGUIDs           = memberGUIDs,
-                        nestedGUIDs           = nestedGUIDs,
-                        extProxies            = (),
-                        extReadOnlyProxies    = (),
+                        service=self,
+                        recordType=recordType,
+                        guid=recordGUID,
+                        nodeName=recordNodeName,
+                        shortNames=recordShortNames,
+                        authIDs=recordAuthIDs,
+                        fullName=recordFullName,
+                        firstName=recordFirstName,
+                        lastName=recordLastName,
+                        emailAddresses=recordEmailAddresses,
+                        memberGUIDs=memberGUIDs,
+                        nestedGUIDs=nestedGUIDs,
+                        extProxies=(),
+                        extReadOnlyProxies=(),
                     )
 
                     # (Copied from below)
@@ -644,15 +646,16 @@
                     if self.augmentService is not None:
                         d = self.augmentService.getAugmentRecord(record.guid,
                             recordType)
-                        d.addCallback(lambda x:record.addAugmentInformation(x))
+                        d.addCallback(lambda x: record.addAugmentInformation(x))
 
                     yield record
 
                 except KeyError:
                     pass
 
+
         def multiQuery(directory, queries, recordTypes, attrs):
-            byGUID = { }
+            byGUID = {}
             sets = []
 
             caseInsensitive = True
@@ -729,7 +732,7 @@
         lookupMethod=None):
 
         if lookupMethod is None:
-            lookupMethod=self.odModule.queryRecordsWithAttribute_list
+            lookupMethod = self.odModule.queryRecordsWithAttribute_list
 
         # Note that OD applies case-sensitivity globally across the entire
         # query, not per expression, so the current code uses whatever is
@@ -795,20 +798,20 @@
                     # objects that are used to generate the REPORT result.
 
                     record = OpenDirectoryRecord(
-                        service               = self,
-                        recordType            = recordType,
-                        guid                  = recordGUID,
-                        nodeName              = recordNodeName,
-                        shortNames            = recordShortNames,
-                        authIDs               = recordAuthIDs,
-                        fullName              = recordFullName,
-                        firstName             = recordFirstName,
-                        lastName              = recordLastName,
-                        emailAddresses        = recordEmailAddresses,
-                        memberGUIDs           = memberGUIDs,
-                        nestedGUIDs           = nestedGUIDs,
-                        extProxies            = (),
-                        extReadOnlyProxies    = (),
+                        service=self,
+                        recordType=recordType,
+                        guid=recordGUID,
+                        nodeName=recordNodeName,
+                        shortNames=recordShortNames,
+                        authIDs=recordAuthIDs,
+                        fullName=recordFullName,
+                        firstName=recordFirstName,
+                        lastName=recordLastName,
+                        emailAddresses=recordEmailAddresses,
+                        memberGUIDs=memberGUIDs,
+                        nestedGUIDs=nestedGUIDs,
+                        extProxies=(),
+                        extReadOnlyProxies=(),
                     )
 
                     # (Copied from below)
@@ -819,15 +822,16 @@
                     if self.augmentService is not None:
                         d = self.augmentService.getAugmentRecord(record.guid,
                             recordType)
-                        d.addCallback(lambda x:record.addAugmentInformation(x))
+                        d.addCallback(lambda x: record.addAugmentInformation(x))
 
                     yield record
 
                 except KeyError:
                     pass
 
+
         def multiQuery(directory, queries, attrs, operand):
-            byGUID = { }
+            byGUID = {}
             sets = []
 
             for query, recordTypes in queries.iteritems():
@@ -884,7 +888,6 @@
                         results.append((data[dsattributes.kDSNAttrRecordName], data))
                 return results
 
-
         operand = (dsquery.expression.OR if operand == "or"
             else dsquery.expression.AND)
 
@@ -922,7 +925,7 @@
         lookupMethod=None):
 
         if lookupMethod is None:
-            lookupMethod=self.odModule.queryRecordsWithAttribute_list
+            lookupMethod = self.odModule.queryRecordsWithAttribute_list
 
         origIndexKey = indexKey
         if indexType == self.INDEX_TYPE_CUA:
@@ -983,9 +986,8 @@
             else:
                 raise UnknownRecordTypeError("Unknown OpenDirectory record type: %s" % (recordType))
 
-
             # Because we're getting transient OD error -14987, try 3 times:
-            for i in xrange(3):
+            for _ignore in xrange(3):
                 try:
                     self.log_debug("opendirectory.queryRecordsWithAttribute_list(%r,%r,%r,%r,%r,%r,%r)" % (
                         self.directory,
@@ -1023,24 +1025,23 @@
 
         self.log_debug("opendirectory.queryRecordsWithAttribute_list matched records: %s" % (len(results),))
 
-
         enabledRecords = []
         disabledRecords = []
 
         for (recordShortName, value) in results:
 
             # Now get useful record info.
-            recordGUID           = value.get(dsattributes.kDS1AttrGeneratedUID)
-            recordShortNames     = self._uniqueTupleFromAttribute(value.get(dsattributes.kDSNAttrRecordName))
-            recordType           = value.get(dsattributes.kDSNAttrRecordType)
+            recordGUID = value.get(dsattributes.kDS1AttrGeneratedUID)
+            recordShortNames = self._uniqueTupleFromAttribute(value.get(dsattributes.kDSNAttrRecordName))
+            recordType = value.get(dsattributes.kDSNAttrRecordType)
             if isinstance(recordType, list):
                 recordType = recordType[0]
-            recordAuthIDs        = self._setFromAttribute(value.get(dsattributes.kDSNAttrAltSecurityIdentities))
-            recordFullName       = value.get(dsattributes.kDS1AttrDistinguishedName)
-            recordFirstName      = value.get(dsattributes.kDS1AttrFirstName)
-            recordLastName       = value.get(dsattributes.kDS1AttrLastName)
+            recordAuthIDs = self._setFromAttribute(value.get(dsattributes.kDSNAttrAltSecurityIdentities))
+            recordFullName = value.get(dsattributes.kDS1AttrDistinguishedName)
+            recordFirstName = value.get(dsattributes.kDS1AttrFirstName)
+            recordLastName = value.get(dsattributes.kDS1AttrLastName)
             recordEmailAddresses = self._setFromAttribute(value.get(dsattributes.kDSNAttrEMailAddress), lower=True)
-            recordNodeName       = value.get(dsattributes.kDSNAttrMetaNodeLocation)
+            recordNodeName = value.get(dsattributes.kDSNAttrMetaNodeLocation)
 
             if not recordType:
                 self.log_debug("Record (unknown)%s in node %s has no recordType; ignoring."
@@ -1069,7 +1070,6 @@
             else:
                 unrestricted = True
 
-
             # Special case for groups, which have members.
             if recordType == self.recordType_groups:
                 memberGUIDs = value.get(dsattributes.kDSNAttrGroupMembers)
@@ -1107,20 +1107,20 @@
                         readOnlyProxyGUIDs = (read_only_proxy,)
 
             record = OpenDirectoryRecord(
-                service               = self,
-                recordType            = recordType,
-                guid                  = recordGUID,
-                nodeName              = recordNodeName,
-                shortNames            = recordShortNames,
-                authIDs               = recordAuthIDs,
-                fullName              = recordFullName,
-                firstName             = recordFirstName,
-                lastName              = recordLastName,
-                emailAddresses        = recordEmailAddresses,
-                memberGUIDs           = memberGUIDs,
-                nestedGUIDs           = nestedGUIDs,
-                extProxies            = proxyGUIDs,
-                extReadOnlyProxies    = readOnlyProxyGUIDs,
+                service=self,
+                recordType=recordType,
+                guid=recordGUID,
+                nodeName=recordNodeName,
+                shortNames=recordShortNames,
+                authIDs=recordAuthIDs,
+                fullName=recordFullName,
+                firstName=recordFirstName,
+                lastName=recordLastName,
+                emailAddresses=recordEmailAddresses,
+                memberGUIDs=memberGUIDs,
+                nestedGUIDs=nestedGUIDs,
+                extProxies=proxyGUIDs,
+                extReadOnlyProxies=readOnlyProxyGUIDs,
             )
 
             # Look up augment information
@@ -1129,7 +1129,7 @@
             if self.augmentService is not None:
                 d = self.augmentService.getAugmentRecord(record.guid,
                     recordType)
-                d.addCallback(lambda x:record.addAugmentInformation(x))
+                d.addCallback(lambda x: record.addAugmentInformation(x))
 
             # Override based on ResourceInfo
             if autoSchedule:
@@ -1277,6 +1277,8 @@
 
         returnValue(recordsByGUID.values())
 
+
+
 def buildQueries(recordTypes, fields, mapping):
     """
     Determine how many queries need to be performed in order to work around opendirectory
@@ -1296,6 +1298,7 @@
     return queries
 
 
+
 def buildQueriesFromTokens(tokens, mapping):
     """
     OD /Local doesn't support nested complex queries, so create a list of
@@ -1336,17 +1339,17 @@
         extProxies, extReadOnlyProxies,
     ):
         super(OpenDirectoryRecord, self).__init__(
-            service               = service,
-            recordType            = recordType,
-            guid                  = guid,
-            shortNames            = shortNames,
-            authIDs               = authIDs,
-            fullName              = fullName,
-            firstName             = firstName,
-            lastName              = lastName,
-            emailAddresses        = emailAddresses,
-            extProxies            = extProxies,
-            extReadOnlyProxies    = extReadOnlyProxies,
+            service=service,
+            recordType=recordType,
+            guid=guid,
+            shortNames=shortNames,
+            authIDs=authIDs,
+            fullName=fullName,
+            firstName=firstName,
+            lastName=lastName,
+            emailAddresses=emailAddresses,
+            extProxies=extProxies,
+            extReadOnlyProxies=extReadOnlyProxies,
         )
         self.nodeName = nodeName
 
@@ -1371,6 +1374,7 @@
             self.fullName
         )
 
+
     def members(self):
         if self.recordType != self.service.recordType_groups:
             return
@@ -1380,6 +1384,7 @@
             if userRecord is not None:
                 yield userRecord
 
+
     def groups(self):
         if self._groupMembershipGUIDs is None:
             self._groupMembershipGUIDs = self.service.groupsForGUID(self.guid)
@@ -1389,12 +1394,15 @@
             if record:
                 yield record
 
+
     def memberGUIDs(self):
         return set(self._memberGUIDs)
 
+
     def nestedGUIDs(self):
         return set(self._nestedGUIDs)
 
+
     def verifyCredentials(self, credentials):
         if isinstance(credentials, UsernamePassword):
             # Check cached password
@@ -1486,6 +1494,8 @@
 
         return super(OpenDirectoryRecord, self).verifyCredentials(credentials)
 
+
+
 class OpenDirectoryInitError(DirectoryError):
     """
     OpenDirectory initialization error.

Modified: CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py	2013-05-28 20:22:31 UTC (rev 11251)
+++ CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py	2013-05-29 02:20:45 UTC (rev 11252)
@@ -61,8 +61,7 @@
 
 from xmlrpclib import datetime
 
-from calendarserver.platform.darwin.od import dsattributes, dsquery
-from twisted.python.reflect import namedModule
+from calendarserver.platform.darwin.od import opendirectory, dsattributes, dsquery
 
 class OpenDirectoryBackingService(DirectoryService):
     """
@@ -70,34 +69,36 @@
     """
 
     baseGUID = "BF07A1A2-5BB5-4A4D-A59A-67260EA7E143"
-    
+
     def __repr__(self):
-        return "<%s %r>" % (self.__class__.__name__, self.realmName, )
+        return "<%s %r>" % (self.__class__.__name__, self.realmName,)
 
+
     def __init__(self, params):
         self._actuallyConfigure(**params)
 
+
     def _actuallyConfigure(
-        self, queryPeopleRecords=True, 
-        peopleNode = "/Search/Contacts",
-        queryUserRecords=True, 
-        userNode = "/Search",
-        maxDSQueryRecords = 0,            # maximum number of records requested for any ds query
-        
-        queryDSLocal = False,              #query in DSLocal -- debug
-        dsLocalCacheTimeout = 30,
-        ignoreSystemRecords = True,
-        
-        liveQuery = True,                    # query directory service as needed
-        fakeETag = True,                # eTag is not reliable if True 
-        
-        cacheQuery = False,
-        cacheTimeout=30,                # cache timeout            
-        
+        self, queryPeopleRecords=True,
+        peopleNode="/Search/Contacts",
+        queryUserRecords=True,
+        userNode="/Search",
+        maxDSQueryRecords=0,            # maximum number of records requested for any ds query
+
+        queryDSLocal=False,             # query in DSLocal -- debug
+        dsLocalCacheTimeout=30,
+        ignoreSystemRecords=True,
+
+        liveQuery=True,                 # query directory service as needed
+        fakeETag=True,                  # eTag is not reliable if True
+
+        cacheQuery=False,
+        cacheTimeout=30,                # cache timeout
+
         addDSAttrXProperties=False,        # add dsattributes to vcards as "X-" attributes
-        standardizeSyntheticUIDs = False,  # use simple synthetic UIDs --- good for testing
+        standardizeSyntheticUIDs=False,  # use simple synthetic UIDs --- good for testing
         appleInternalServer=False,
-        
+
         additionalAttributes=[],
         allowedAttributes=[],
         directoryBackedAddressBook=None
@@ -105,14 +106,14 @@
         """
         @queryPeopleRecords: C{True} to query for People records
         @queryUserRecords: C{True} to query for User records
-        @maxDSQueryRecords: maximum number of (unfiltered) ds records retrieved before raising 
+        @maxDSQueryRecords: maximum number of (unfiltered) ds records retrieved before raising
             NumberOfMatchesWithinLimits exception or returning results
         @dsLocalCacheTimeout: how log to keep cache of DSLocal records
         @liveQuery: C{True} to query the directory as needed
         @fakeETag: C{True} to use a fake eTag; allows ds queries with partial attributes
         @cacheQuery: C{True} to query the directory and cache results
         @cacheTimeout: if caching, the average cache timeout
-        @standardizeSyntheticUIDs: C{True} when creating synthetic UID (==f(Node, Type, Record Name)), 
+        @standardizeSyntheticUIDs: C{True} when creating synthetic UID (==f(Node, Type, Record Name)),
             use a standard Node name. This allows testing with the same UID on different hosts
         @allowedAttributes: list of DSAttributes that are used to create VCards
 
@@ -124,16 +125,14 @@
         self.peopleNode = None
         self.userDirectory = None
         self.userNode = None
-        
+
         self.realmName = None # needed for super
 
-        self.odModule = namedModule(config.OpenDirectoryModule)
-        
         if queryPeopleRecords or not queryUserRecords:
             self.peopleNode = peopleNode
             try:
-                self.peopleDirectory = self.odModule.odInit(peopleNode)
-            except self.odModule.ODError, e:
+                self.peopleDirectory = opendirectory.odInit(peopleNode)
+            except opendirectory.ODError, e:
                 self.log_error("Open Directory (node=%s) Initialization error: %s" % (peopleNode, e))
                 raise
             self.realmName = peopleNode
@@ -145,16 +144,15 @@
             else:
                 self.userNode = userNode
                 try:
-                    self.userDirectory = self.odModule.odInit(userNode)
-                except self.odModule.ODError, e:
+                    self.userDirectory = opendirectory.odInit(userNode)
+                except opendirectory.ODError, e:
                     self.log_error("Open Directory (node=%s) Initialization error: %s" % (userNode, e))
                     raise
                 if self.realmName:
                     self.realmName += "+" + userNode
                 else:
                     self.realmName = userNode
-        
-        
+
         self.maxDSQueryRecords = maxDSQueryRecords
 
         self.ignoreSystemRecords = ignoreSystemRecords
@@ -165,13 +163,13 @@
         self.fakeETag = fakeETag
 
         self.cacheQuery = cacheQuery
-        
+
         self.cacheTimeout = cacheTimeout if cacheTimeout > 0 else 30
-        
+
         self.addDSAttrXProperties = addDSAttrXProperties
         self.standardizeSyntheticUIDs = standardizeSyntheticUIDs
         self.appleInternalServer = appleInternalServer
-        
+
         self.additionalAttributes = additionalAttributes
         # filter allows attributes, but make sure there are a minimum of attributes for functionality
         if allowedAttributes:
@@ -182,38 +180,35 @@
                                                 VCardRecord.dsqueryAttributesForProperty.get("X-INTERNAL-REQUIRED")
                                                 )))
             if (self.allowedDSQueryAttributes != VCardRecord.allDSQueryAttributes):
-                self.log_info("Allowed DS query attributes = %r" % (self.allowedDSQueryAttributes, ))
+                self.log_info("Allowed DS query attributes = %r" % (self.allowedDSQueryAttributes,))
         else:
             self.allowedDSQueryAttributes = VCardRecord.allDSQueryAttributes
-        
+
         #self.returnedAttributes = VCardRecord.allDSQueryAttributes
         self.returnedAttributes = self.allowedDSQueryAttributes
-        
-            
-        
-        
+
         self._dsLocalRecords = []
         self._nextDSLocalQueryTime = 0
-        
+
         # get this now once
         hostname = getfqdn()
         if hostname:
             self.defaultNodeName = "/LDAPv3/" + hostname
         else:
             self.defaultNodeName = None
-        
+
         #cleanup
         self._cleanupTime = time.time()
-        
+
         # file system locks
         self._initLockPath = join(config.DocumentRoot, ".directory_address_book_create_lock")
         self._createdLockPath = join(config.DocumentRoot, ".directory_address_book_created_lock")
         self._updateLockPath = join(config.DocumentRoot, ".directory_address_book_update_lock")
         self._tmpDirAddressBookLockPath = join(config.DocumentRoot, ".directory_address_book_tmpFolder_lock")
-        
+
         self._updateLock = MemcacheLock("OpenDirectoryBacker", self._updateLockPath)
-        self._tmpDirAddressBookLock = MemcacheLock("OpenDirectoryBacker", self._tmpDirAddressBookLockPath)        
-                
+        self._tmpDirAddressBookLock = MemcacheLock("OpenDirectoryBacker", self._tmpDirAddressBookLockPath)
+
         # optimization so we don't have to always get create lock
         self._triedCreateLock = False
         self._created = False
@@ -229,37 +224,39 @@
                 return diff
         return 0
 
+
     def __hash__(self):
         h = hash(self.__class__.__name__)
         for attr in ("node",):
             h = (h + hash(getattr(self, attr))) & sys.maxint
         return h
-    
+
+
     @inlineCallbacks
     def available(self):
         if not self._triedCreateLock:
-            returnValue( False )
+            returnValue(False)
         elif not self._created:
             createdLock = MemcacheLock("OpenDirectoryBacker", self._createdLockPath)
             self.log_debug("blocking on lock of: \"%s\")" % self._createdLockPath)
             self._created = (yield createdLock.locked())
-        
+
         returnValue(self._created)
-        
-    
+
+
     def updateLock(self):
         return self._updateLock
 
-    
+
     @inlineCallbacks
     def createCache(self):
         """
         If caching, create the cache for the first time.
         """
-        
+
         if not self.liveQuery:
             self.log_info("loading directory address book")
-    
+
             # get init lock
             initLock = MemcacheLock("OpenDirectoryBacker", self._initLockPath, timeout=0)
             self.log_debug("Attempt lock of: \"%s\")" % self._initLockPath)
@@ -269,19 +266,18 @@
                 gotCreateLock = True
             except MemcacheLockTimeoutError:
                 pass
-                
+
             self._triedCreateLock = True
-            
+
             if gotCreateLock:
                 self.log_debug("Got lock!")
-                yield self._refreshCache( flushCache=False, creating=True )
+                yield self._refreshCache(flushCache=False, creating=True)
             else:
                 self.log_debug("Could not get lock - directory address book will be filled by peer")
-                        
-        
 
+
     @inlineCallbacks
-    def _refreshCache(self, flushCache=False, creating=False, reschedule=True, query=None, attributes=None, keepLock=False, clear=False, maxRecords=0 ):
+    def _refreshCache(self, flushCache=False, creating=False, reschedule=True, query=None, attributes=None, keepLock=False, clear=False, maxRecords=0):
         """
         refresh the cache.
         """
@@ -296,24 +292,24 @@
             cacheTimeout = (cacheTimeout * random()) - (cacheTimeout / 2)
             cacheTimeout += self.cacheTimeout * 60
             reactor.callLater(cacheTimeout, self._refreshCache) #@UndefinedVariable
-            self.log_info("Refresh directory address book in %d minutes %d seconds" % divmod(cacheTimeout, 60))          
+            self.log_info("Refresh directory address book in %d minutes %d seconds" % divmod(cacheTimeout, 60))
 
         def cleanupLater():
-            
+
             # try to cancel previous call if last clean up was less than 15 minutes ago
-            if (time.time() - self._cleanupTime) < 15*60:
+            if (time.time() - self._cleanupTime) < 15 * 60:
                 try:
                     self._lastCleanupCall.cancel()
                 except:
                     pass
-            
+
             #
-            # Add jitter/fuzz factor 
+            # Add jitter/fuzz factor
             #
             nom = 120
-            later = nom* (random() + .5)
+            later = nom * (random() + .5)
             self._lastCleanupCall = reactor.callLater(later, removeTmpAddressBooks) #@UndefinedVariable
-            self.log_info("Remove temporary directory address books in %d minutes %d seconds" % divmod(later, 60))          
+            self.log_info("Remove temporary directory address books in %d minutes %d seconds" % divmod(later, 60))
 
 
         def getTmpDirAndTmpFilePrefixSuffix():
@@ -325,16 +321,16 @@
             else:
                 tmpDir = gettempdir()
                 prefix = "directoryAddressBook-"
-            
+
             return (tmpDir, prefix, ".tmp")
-            
+
         def makeTmpFilename():
             tmpDir, prefix, suffix = getTmpDirAndTmpFilePrefixSuffix()
             fd, fname = mkstemp(suffix=suffix, prefix=prefix, dir=tmpDir)
             os.close(fd)
             os.remove(fname)
             return fname
-        
+
         @inlineCallbacks
         def removeTmpAddressBooks():
             self.log_info("Checking for temporary directory address books")
@@ -343,13 +339,13 @@
             tmpDirLock = self._tmpDirAddressBookLock
             self.log_debug("blocking on lock of: \"%s\")" % self._tmpDirAddressBookLockPath)
             yield tmpDirLock.acquire()
-            
+
             try:
                 for name in listdir(tmpDir):
                     if name.startswith(prefix) and name.endswith(suffix):
                         try:
                             path = join(tmpDir, name)
-                            self.log_info("Deleting temporary directory address book at: %s" %    path)
+                            self.log_info("Deleting temporary directory address book at: %s" % path)
                             FilePath(path).remove()
                             self.log_debug("Done deleting")
                         except:
@@ -357,107 +353,104 @@
             finally:
                 self.log_debug("unlocking: \"%s\")" % self._tmpDirAddressBookLockPath)
                 yield tmpDirLock.release()
-            
+
             self._cleanupTime = time.time()
 
-        
         updateLock = None
         limited = False
         try:
-            
+
             try:
                 # get the records
                 if clear:
                     records = {}
                 else:
                     records, limited = (yield self._getDirectoryRecords(query, attributes, maxRecords))
-                
+
                 # calculate the hash
-                # simple for now, could use MD5 digest if too many collisions     
+                # simple for now, could use MD5 digest if too many collisions
                 newAddressBookCTag = customxml.GETCTag(str(hash(self.baseGUID + ":" + self.realmName + ":" + "".join(str(hash(records[key])) for key in records.keys()))))
-                
+
                 # get the old hash
                 oldAddressBookCTag = ""
                 updateLock = self.updateLock()
                 self.log_debug("blocking on lock of: \"%s\")" % self._updateLockPath)
                 yield updateLock.acquire()
-                
+
                 if not flushCache:
                     # get update lock
                     try:
                         oldAddressBookCTag = self.directoryBackedAddressBook.readDeadProperty((calendarserver_namespace, "getctag"))
                     except:
                         oldAddressBookCTag = ""
-    
+
                 self.log_debug("Comparing {http://calendarserver.org/ns/}getctag: new = %s, old = %s" % (newAddressBookCTag, oldAddressBookCTag))
                 if str(newAddressBookCTag) != str(oldAddressBookCTag):
-                    
+
                     self.log_debug("unlocking: \"%s\")" % self._updateLockPath)
                     yield updateLock.release()
                     updateLock = None
-                    
 
                 if not keepLock:
                     self.log_debug("unlocking: \"%s\")" % self._updateLockPath)
                     yield updateLock.release()
                     updateLock = None
-                    
+
             except:
                 cleanupLater()
                 if reschedule:
-                    refreshLater() 
+                    refreshLater()
                 raise
-            
+
             if creating:
                 createdLock = MemcacheLock("OpenDirectoryBacker", self._createdLockPath)
                 self.log_debug("blocking on lock of: \"%s\")" % self._createdLockPath)
                 yield createdLock.acquire()
-            
+
             cleanupLater()
             if reschedule:
-                refreshLater() 
-        
+                refreshLater()
+
         except:
             if updateLock:
                 yield updateLock.release()
             raise
 
-        returnValue( (updateLock, limited) )
+        returnValue((updateLock, limited))
 
 
-
     def _getDSLocalRecords(self):
-        
+
         def generateDSLocalRecords():
-                        
+
             records = {}
-            
+
             recordTypes = [dsattributes.kDSStdRecordTypePeople, dsattributes.kDSStdRecordTypeUsers, ]
             try:
-                localNodeDirectory = self.odModule.odInit("/Local/Default")
+                localNodeDirectory = opendirectory.odInit("/Local/Default")
                 self.log_debug("opendirectory.listAllRecordsWithAttributes_list(%r,%r,%r)" % (
                         "/DSLocal",
                         recordTypes,
                         self.returnedAttributes,
                     ))
-                results = list(self.odModule.listAllRecordsWithAttributes_list(
+                results = list(opendirectory.listAllRecordsWithAttributes_list(
                         localNodeDirectory,
                         recordTypes,
                         self.returnedAttributes,
                     ))
-            except self.odModule.ODError, ex:
+            except opendirectory.ODError, ex:
                 self.log_error("Open Directory (node=%s) error: %s" % ("/Local/Default", str(ex)))
                 raise
-            
-            self._dsLocalRecords = []        
+
+            self._dsLocalRecords = []
             for (recordShortName, value) in results: #@UnusedVariable
-                
+
                 record = VCardRecord(self, value, "/Local/Default")
 
                 if self.ignoreSystemRecords:
                     # remove system users and people
                     if record.guid.startswith("FFFFEEEE-DDDD-CCCC-BBBB-AAAA"):
-                        self.log_info("Ignoring vcard for system record %s"  % (record,))
+                        self.log_info("Ignoring vcard for system record %s" % (record,))
                         continue
 
                 if record.guid in records:
@@ -469,79 +462,77 @@
                         traceback.print_exc()
                         self.log_info("Could not get vcard for record %s" % (record,))
                     else:
-                        self.log_debug("VCard text =\n%s" % (vCardText, ))
-                        records[record.guid] = record                   
-    
+                        self.log_debug("VCard text =\n%s" % (vCardText,))
+                        records[record.guid] = record
+
             return records
-        
 
         if not self.liveQuery or not self.queryDSLocal:
             return {}
-        
+
         if time.time() > self._nextDSLocalQueryTime:
             self._dsLocalRecords = generateDSLocalRecords()
-            # Add jitter/fuzz factor 
-            self._nextDSLocalQueryTime = time.time() + self.dsLocalCacheTimeout * (random() + 0.5)  * 60
+            # Add jitter/fuzz factor
+            self._nextDSLocalQueryTime = time.time() + self.dsLocalCacheTimeout * (random() + 0.5) * 60
 
         return self._dsLocalRecords
-    
 
+
     @inlineCallbacks
-    def _getDirectoryRecords(self, query=None, attributes=None, maxRecords=0 ):
+    def _getDirectoryRecords(self, query=None, attributes=None, maxRecords=0):
         """
         Get a list of filtered VCardRecord for the given query with the given attributes.
         query == None gets all records. attribute == None gets VCardRecord.allDSQueryAttributes
         """
         limited = False
-        queryResults = (yield self._queryDirectory(query, attributes, maxRecords ))
+        queryResults = (yield self._queryDirectory(query, attributes, maxRecords))
         if maxRecords and len(queryResults) >= maxRecords:
             limited = True
-            self.log_debug("Directory address book record limit (= %d) reached." % (maxRecords, ))
+            self.log_debug("Directory address book record limit (= %d) reached." % (maxRecords,))
 
         self.log_debug("Query done. Inspecting %s results" % len(queryResults))
 
         records = self._getDSLocalRecords().copy()
         self.log_debug("Adding %s DSLocal results" % len(records.keys()))
-        
+
         for (recordShortName, value) in queryResults: #@UnusedVariable
-            
+
             record = VCardRecord(self, value, self.defaultNodeName)
 
             if self.ignoreSystemRecords:
                 # remove system users and people
                 if record.guid.startswith("FFFFEEEE-DDDD-CCCC-BBBB-AAAA"):
-                    self.log_info("Ignoring vcard for system record %s"  % (record,))
+                    self.log_info("Ignoring vcard for system record %s" % (record,))
                     continue
-        
+
             if record.guid in records:
                 self.log_info("Ignoring vcard for record due to conflict (duplicate uuid): %s" % (record,))
             else:
-                records[record.guid] = record                   
-        
+                records[record.guid] = record
+
         self.log_debug("After filtering, %s records (limited=%s)." % (len(records), limited))
-        returnValue((records, limited, ))
+        returnValue((records, limited,))
 
 
-    def _queryDirectory(self, query=None, attributes=None, maxRecords=0 ):
-        
+    def _queryDirectory(self, query=None, attributes=None, maxRecords=0):
+
         startTime = time.time()
 
-        
         if not attributes:
             attributes = self.returnedAttributes
-            
+
         attributes = list(set(attributes + self.additionalAttributes)) # remove duplicates
-        
+
         directoryAndRecordTypes = []
         if self.peopleDirectory == self.userDirectory:
             # use single ds query if possible for best performance
-            directoryAndRecordTypes.append( (self.peopleDirectory, self.peopleNode, (dsattributes.kDSStdRecordTypePeople, dsattributes.kDSStdRecordTypeUsers) ) )
+            directoryAndRecordTypes.append((self.peopleDirectory, self.peopleNode, (dsattributes.kDSStdRecordTypePeople, dsattributes.kDSStdRecordTypeUsers)))
         else:
             if self.peopleDirectory:
-                directoryAndRecordTypes.append( (self.peopleDirectory, self.peopleNode, dsattributes.kDSStdRecordTypePeople) )
+                directoryAndRecordTypes.append((self.peopleDirectory, self.peopleNode, dsattributes.kDSStdRecordTypePeople))
             if self.userDirectory:
-                directoryAndRecordTypes.append( (self.userDirectory, self.userNode, dsattributes.kDSStdRecordTypeUsers) )
-        
+                directoryAndRecordTypes.append((self.userDirectory, self.userNode, dsattributes.kDSStdRecordTypeUsers))
+
         allResults = []
         for directory, node, recordType in directoryAndRecordTypes:
             try:
@@ -558,7 +549,7 @@
                             maxRecords,
                         ))
                         results = list(
-                            self.odModule.queryRecordsWithAttribute_list(
+                            opendirectory.queryRecordsWithAttribute_list(
                                 directory,
                                 query.attribute,
                                 query.value,
@@ -578,7 +569,7 @@
                             maxRecords,
                         ))
                         results = list(
-                            self.odModule.queryRecordsWithAttributes_list(
+                            opendirectory.queryRecordsWithAttributes_list(
                                 directory,
                                 query.generate(),
                                 False,
@@ -594,32 +585,32 @@
                         maxRecords,
                     ))
                     results = list(
-                        self.odModule.listAllRecordsWithAttributes_list(
+                        opendirectory.listAllRecordsWithAttributes_list(
                             directory,
                             recordType,
                             attributes,
                             maxRecords,
                         ))
-            except self.odModule.ODError, ex:
+            except opendirectory.ODError, ex:
                 self.log_error("Open Directory (node=%s) error: %s" % (self.realmName, str(ex)))
                 raise
-            
+
             allResults.extend(results)
-            
+
             if maxRecords:
                 maxRecords -= len(results)
                 if maxRecords <= 0:
                     break
-        
 
-        elaspedTime = time.time()-startTime
-        self.log_info("Timing: Directory query: %.1f ms (%d records, %.2f records/sec)" % (elaspedTime*1000, len(allResults), len(allResults)/elaspedTime))
+        elaspedTime = time.time() - startTime
+        self.log_info("Timing: Directory query: %.1f ms (%d records, %.2f records/sec)" % (elaspedTime * 1000, len(allResults), len(allResults) / elaspedTime))
         return succeed(allResults)
-    
+
+
     def _getDSFilter(self, addressBookFilter):
         """
         Convert the supplied addressbook-query into an expression tree.
-    
+
         @param filter: the L{Filter} for the addressbook-query to convert.
         @return: (needsAllRecords, espressionAttributes, expression) tuple
         """
@@ -629,13 +620,13 @@
                 #print("propFilterExpression")
                 """
                 Create an expression for a single prop-filter element.
-                
+
                 @param propFilter: the L{PropertyFilter} element.
                 @return: (needsAllRecords, espressionAttributes, expressions) tuple
                 """
-                
-                def definedExpression( defined, allOf, filterName, constant, queryAttributes, allAttrStrings):
-                    if constant or filterName in ("N" , "FN", "UID", ):
+
+                def definedExpression(defined, allOf, filterName, constant, queryAttributes, allAttrStrings):
+                    if constant or filterName in ("N" , "FN", "UID",):
                         return (defined, [], [])     # all records have this property so no records do not have it
                     else:
                         matchList = list(set([dsquery.match(attrName, "", dsattributes.eDSStartsWith) for attrName in allAttrStrings]))
@@ -643,84 +634,83 @@
                             return andOrExpression(allOf, queryAttributes, matchList)
                         else:
                             if len(matchList) > 1:
-                                expr = dsquery.expression( dsquery.expression.OR, matchList )
+                                expr = dsquery.expression(dsquery.expression.OR, matchList)
                             else:
                                 expr = matchList
-                            return (False, queryAttributes, [dsquery.expression( dsquery.expression.NOT, expr),])
+                            return (False, queryAttributes, [dsquery.expression(dsquery.expression.NOT, expr), ])
                     #end isNotDefinedExpression()
-    
-    
+
+
                 def andOrExpression(propFilterAllOf, queryAttributes, matchList):
                     #print("andOrExpression(propFilterAllOf=%r, queryAttributes%r, matchList%r)" % (propFilterAllOf, queryAttributes, matchList))
                     if propFilterAllOf and len(matchList):
                         # add OR expression because parent will AND
-                        return (False, queryAttributes, [dsquery.expression( dsquery.expression.OR, matchList),])
+                        return (False, queryAttributes, [dsquery.expression(dsquery.expression.OR, matchList), ])
                     else:
                         return (False, queryAttributes, matchList)
                     #end andOrExpression()
-                    
-    
+
+
                 # short circuit parameter filters
-                def supportedParamter( filterName, paramFilters, propFilterAllOf ):
-                    
-                    def supported( paramFilterName, paramFilterDefined, params ):
+                def supportedParamter(filterName, paramFilters, propFilterAllOf):
+
+                    def supported(paramFilterName, paramFilterDefined, params):
                         paramFilterName = paramFilterName.upper()
                         if len(params.keys()) and ((paramFilterName in params.keys()) != paramFilterDefined):
                             return False
                         if len(params[paramFilterName]) and str(paramFilter.qualifier).upper() not in params[paramFilterName]:
                             return False
-                        return True 
+                        return True
                         #end supported()
-                
-                    
+
                     oneSupported = False
                     for paramFilter in paramFilters:
                         if filterName == "PHOTO":
-                            if propFilterAllOf != supported( paramFilter.filter_name, paramFilter.defined, { "ENCODING": ["B",], "TYPE": ["JPEG",], }):
+                            if propFilterAllOf != supported(paramFilter.filter_name, paramFilter.defined, {"ENCODING": ["B", ], "TYPE": ["JPEG", ], }):
                                 return not propFilterAllOf
                             oneSupported |= propFilterAllOf
                         elif filterName == "ADR":
-                            if propFilterAllOf != supported( paramFilter.filter_name, paramFilter.defined, { "TYPE": ["WORK", "PREF", "POSTAL", "PARCEL",], }):
+                            if propFilterAllOf != supported(paramFilter.filter_name, paramFilter.defined, {"TYPE": ["WORK", "PREF", "POSTAL", "PARCEL", ], }):
                                 return not propFilterAllOf
                             oneSupported |= propFilterAllOf
                         elif filterName == "LABEL":
-                            if propFilterAllOf != supported( paramFilter.filter_name, paramFilter.defined, { "TYPE": ["POSTAL", "PARCEL",]}):
+                            if propFilterAllOf != supported(paramFilter.filter_name, paramFilter.defined, {"TYPE": ["POSTAL", "PARCEL", ]}):
                                 return not propFilterAllOf
                             oneSupported |= propFilterAllOf
                         elif filterName == "TEL":
-                            if propFilterAllOf != supported( paramFilter.filter_name, paramFilter.defined, { "TYPE": [], }): # has params derived from ds attributes
+                            if propFilterAllOf != supported(paramFilter.filter_name, paramFilter.defined, {"TYPE": [], }): # has params derived from ds attributes
                                 return not propFilterAllOf
                             oneSupported |= propFilterAllOf
                         elif filterName == "EMAIL":
-                            if propFilterAllOf != supported( paramFilter.filter_name, paramFilter.defined, { "TYPE": [], }): # has params derived from ds attributes
+                            if propFilterAllOf != supported(paramFilter.filter_name, paramFilter.defined, {"TYPE": [], }): # has params derived from ds attributes
                                 return not propFilterAllOf
                             oneSupported |= propFilterAllOf
                         elif filterName == "URL":
-                            if propFilterAllOf != supported( paramFilter.filter_name, paramFilter.defined, {}):
+                            if propFilterAllOf != supported(paramFilter.filter_name, paramFilter.defined, {}):
                                 return not propFilterAllOf
                             oneSupported |= propFilterAllOf
                         elif filterName == "KEY":
-                            if propFilterAllOf != supported( paramFilter.filter_name, paramFilter.defined, { "ENCODING": ["B",], "TYPE": ["PGPPUBILICKEY", "USERCERTIFICATE", "USERPKCS12DATA", "USERSMIMECERTIFICATE",] }):
+                            if propFilterAllOf != supported(paramFilter.filter_name, paramFilter.defined, {"ENCODING": ["B", ], "TYPE": ["PGPPUBILICKEY", "USERCERTIFICATE", "USERPKCS12DATA", "USERSMIMECERTIFICATE", ]}):
                                 return not propFilterAllOf
                             oneSupported |= propFilterAllOf
                         elif not filterName.startswith("X-"): #X- IMHandles X-ABRELATEDNAMES excepted, no other params are used
                             if propFilterAllOf == paramFilter.defined:
                                 return not propFilterAllOf
                             oneSupported |= propFilterAllOf
-                    
+
                     if propFilterAllOf:
                         return True
                     else:
                         return oneSupported
                     #end supportedParamter()
-                    
-                    
-                def textMatchElementExpression( propFilterAllOf, textMatchElement ):
-    
-                    # pre process text match strings for ds query 
-                    def getMatchStrings( propFilter, matchString ):
-                                            
-                        if propFilter.filter_name in ("REV" , "BDAY", ):
+
+
+                def textMatchElementExpression(propFilterAllOf, textMatchElement):
+
+                    # pre process text match strings for ds query
+                    def getMatchStrings(propFilter, matchString):
+
+                        if propFilter.filter_name in ("REV" , "BDAY",):
                             rawString = matchString
                             matchString = ""
                             for c in rawString:
@@ -728,32 +718,32 @@
                                     matchString += c
                         elif propFilter.filter_name == "GEO":
                             matchString = ",".join(matchString.split(";"))
-                        
-                        if propFilter.filter_name in ("N" , "ADR", "ORG", ):
+
+                        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(",")
+                                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,]
+                                    matchStrings += [oneString, ]
                             return matchStrings
-                        
+
                         elif len(matchString):
-                            return [matchString,]
+                            return [matchString, ]
                         else:
                             return []
                         # end getMatchStrings
-    
+
                     if constant:
                         # do the match right now!  Return either all or none.
-                        return( textMatchElement.test([constant,]), [], [] )
+                        return(textMatchElement.test([constant, ]), [], [])
                     else:
 
                         matchStrings = getMatchStrings(propFilter, textMatchElement.text)
@@ -764,7 +754,7 @@
                                 return definedExpression(False, propFilterAllOf, propFilter.filter_name, constant, queryAttributes, allAttrStrings)
                             # else fall through to attribute exists case below
                         else:
-                            
+
                             # special case UID's formed from node and record name
                             if propFilter.filter_name == "UID":
                                 matchString = matchStrings[0]
@@ -772,62 +762,61 @@
                                 if seperatorIndex > 1:
                                     recordNameStart = seperatorIndex + len(VCardRecord.peopleUIDSeparator)
                                 else:
-                                    seperatorIndex = matchString.find(VCardRecord.userUIDSeparator)                        
+                                    seperatorIndex = matchString.find(VCardRecord.userUIDSeparator)
                                     if seperatorIndex > 1:
                                         recordNameStart = seperatorIndex + len(VCardRecord.userUIDSeparator)
                                     else:
                                         recordNameStart = sys.maxint
-        
-                                if recordNameStart < len(matchString)-1:
+
+                                if recordNameStart < len(matchString) - 1:
                                     try:
                                         recordNameQualifier = matchString[recordNameStart:].decode("base64").decode("utf8")
                                     except Exception, e:
                                         self.log_debug("Could not decode UID string %r in %r: %r" % (matchString[recordNameStart:], matchString, e,))
                                     else:
                                         if textMatchElement.negate:
-                                            return (False, queryAttributes, 
-                                                    [dsquery.expression(dsquery.expression.NOT, dsquery.match(dsattributes.kDSNAttrRecordName, recordNameQualifier, dsattributes.eDSExact)),]
+                                            return (False, queryAttributes,
+                                                    [dsquery.expression(dsquery.expression.NOT, dsquery.match(dsattributes.kDSNAttrRecordName, recordNameQualifier, dsattributes.eDSExact)), ]
                                                     )
                                         else:
-                                            return (False, queryAttributes, 
-                                                    [dsquery.match(dsattributes.kDSNAttrRecordName, recordNameQualifier, dsattributes.eDSExact),]
+                                            return (False, queryAttributes,
+                                                    [dsquery.match(dsattributes.kDSNAttrRecordName, recordNameQualifier, dsattributes.eDSExact), ]
                                                     )
-                            
+
                             # use match_type where possible depending on property/attribute mapping
                             # Note that case sensitive negate will not work
                             #        Should return all records in that case
                             matchType = dsattributes.eDSContains
-                            if propFilter.filter_name in ("NICKNAME" , "TITLE" , "NOTE" , "UID", "URL", "N", "ADR", "ORG", "REV",  "LABEL", ):
+                            if propFilter.filter_name in ("NICKNAME" , "TITLE" , "NOTE" , "UID", "URL", "N", "ADR", "ORG", "REV", "LABEL",):
                                 if textMatchElement.match_type == "equals":
                                         matchType = dsattributes.eDSExact
                                 elif textMatchElement.match_type == "starts-with":
                                         matchType = dsattributes.eDSStartsWith
                                 elif textMatchElement.match_type == "ends-with":
                                         matchType = dsattributes.eDSEndsWith
-                            
+
                             matchList = []
                             for matchString in matchStrings:
                                 matchList += [dsquery.match(attrName, matchString, matchType) for attrName in stringAttrStrs]
-                            
+
                             matchList = list(set(matchList))
-    
+
                             if textMatchElement.negate:
                                 if len(matchList) > 1:
-                                    expr = dsquery.expression( dsquery.expression.OR, matchList )
+                                    expr = dsquery.expression(dsquery.expression.OR, matchList)
                                 else:
                                     expr = matchList
-                                return (False, queryAttributes, [dsquery.expression( dsquery.expression.NOT, expr),])
+                                return (False, queryAttributes, [dsquery.expression(dsquery.expression.NOT, expr), ])
                             else:
                                 return andOrExpression(propFilterAllOf, queryAttributes, matchList)
-    
+
                     # attribute exists search
                     return definedExpression(True, propFilterAllOf, propFilter.filter_name, constant, queryAttributes, allAttrStrings)
                     #end textMatchElementExpression()
-                    
-    
-                # get attribute strings from dsqueryAttributesForProperty list 
+
+                # get attribute strings from dsqueryAttributesForProperty list
                 queryAttributes = list(set(VCardRecord.dsqueryAttributesForProperty.get(propFilter.filter_name, [])).intersection(set(self.allowedDSQueryAttributes)))
-                
+
                 binaryAttrStrs = []
                 stringAttrStrs = []
                 for attr in queryAttributes:
@@ -836,33 +825,33 @@
                     else:
                         stringAttrStrs.append(attr)
                 allAttrStrings = stringAttrStrs + binaryAttrStrs
-                                        
+
                 constant = VCardRecord.constantProperties.get(propFilter.filter_name)
-                if not constant and not allAttrStrings: 
+                if not constant and not allAttrStrings:
                     return (False, [], [])
-                
+
                 if propFilter.qualifier and isinstance(propFilter.qualifier, addressbookqueryfilter.IsNotDefined):
                     return definedExpression(False, filterAllOf, propFilter.filter_name, constant, queryAttributes, allAttrStrings)
-                
+
                 paramFilterElements = [paramFilterElement for paramFilterElement in propFilter.filters if isinstance(paramFilterElement, addressbookqueryfilter.ParameterFilter)]
                 textMatchElements = [textMatchElement for textMatchElement in propFilter.filters if isinstance(textMatchElement, addressbookqueryfilter.TextMatch)]
                 propFilterAllOf = propFilter.propfilter_test == "allof"
-                
+
                 # handle parameter filter elements
                 if len(paramFilterElements) > 0:
-                    if supportedParamter(propFilter.filter_name, paramFilterElements, propFilterAllOf ):
+                    if supportedParamter(propFilter.filter_name, paramFilterElements, propFilterAllOf):
                         if len(textMatchElements) == 0:
                             return definedExpression(True, filterAllOf, propFilter.filter_name, constant, queryAttributes, allAttrStrings)
                     else:
                         if propFilterAllOf:
                             return (False, [], [])
-                
+
                 # handle text match elements
                 propFilterNeedsAllRecords = propFilterAllOf
                 propFilterAttributes = []
                 propFilterExpressionList = []
                 for textMatchElement in textMatchElements:
-                    
+
                     textMatchNeedsAllRecords, textMatchExpressionAttributes, textMatchExpression = textMatchElementExpression(propFilterAllOf, textMatchElement)
                     if propFilterAllOf:
                         propFilterNeedsAllRecords &= textMatchNeedsAllRecords
@@ -870,20 +859,19 @@
                         propFilterNeedsAllRecords |= textMatchNeedsAllRecords
                     propFilterAttributes += textMatchExpressionAttributes
                     propFilterExpressionList += textMatchExpression
-    
 
                 if (len(propFilterExpressionList) > 1) and (filterAllOf != propFilterAllOf):
                     propFilterExpressions = [dsquery.expression(dsquery.expression.AND if propFilterAllOf else dsquery.expression.OR , list(set(propFilterExpressionList)))] # remove duplicates
                 else:
                     propFilterExpressions = list(set(propFilterExpressionList))
-                
+
                 return (propFilterNeedsAllRecords, propFilterAttributes, propFilterExpressions)
                 #end propFilterExpression
 
             #print("propFilterListQuery: filterAllOf=%r, propFilters=%r" % (filterAllOf, propFilters,))
             """
             Create an expression for a list of prop-filter elements.
-            
+
             @param filterAllOf: the C{True} if parent filter test is "allof"
             @param propFilters: the C{list} of L{ComponentFilter} elements.
             @return: (needsAllRecords, espressionAttributes, expression) tuple
@@ -892,7 +880,7 @@
             attributes = []
             expressions = []
             for propFilter in propFilters:
-                
+
                 propNeedsAllRecords, propExpressionAttributes, propExpression = propFilterExpression(filterAllOf, propFilter)
                 if filterAllOf:
                     needsAllRecords &= propNeedsAllRecords
@@ -907,13 +895,12 @@
                 expr = expressions[0]
             else:
                 expr = None
-            
+
             return (needsAllRecords, attributes, expr)
-        
-                
+
         #print("_getDSFilter")
         # Lets assume we have a valid filter from the outset
-        
+
         # Top-level filter contains zero or more prop-filters
         if addressBookFilter:
             filterAllOf = addressBookFilter.filter_test == "allof"
@@ -922,17 +909,16 @@
             else:
                 return (filterAllOf, [], [])
         else:
-            return (False, [], [])    
-    
-                        
+            return (False, [], [])
 
-    def _attributesForAddressBookQuery(self, addressBookQuery ):
-                        
-        propertyNames = [] 
+
+    def _attributesForAddressBookQuery(self, addressBookQuery):
+
+        propertyNames = []
         #print( "addressBookQuery.qname=%r" % addressBookQuery.qname)
         if addressBookQuery.qname() == ("DAV:", "prop"):
-        
-            for property in addressBookQuery.children:                
+
+            for property in addressBookQuery.children:
                 #print("property = %r" % property )
                 if isinstance(property, carddavxml.AddressData):
                     for addressProperty in property.children:
@@ -940,17 +926,16 @@
                         if isinstance(addressProperty, carddavxml.Property):
                             #print("Adding property %r", addressProperty.attributes["name"])
                             propertyNames.append(addressProperty.attributes["name"])
-                        
+
                 elif not self.fakeETag and property.qname() == ("DAV:", "getetag"):
                     # for a real etag == md5(vCard), we need all attributes
                     propertyNames = None
-                    break;
-                            
-        
+                    break
+
         if not len(propertyNames):
             #print("using all attributes")
             return self.returnedAttributes
-        
+
         else:
             propertyNames.append("X-INTERNAL-MINIMUM-VCARD-PROPERTIES") # these properties are required to make a vCard
             queryAttributes = []
@@ -961,74 +946,72 @@
 
             return list(set(queryAttributes).intersection(set(self.returnedAttributes)))
 
-    
+
     @inlineCallbacks
-    def cacheVCardsForAddressBookQuery(self, addressBookFilter, addressBookQuery, maxResults ):
+    def cacheVCardsForAddressBookQuery(self, addressBookFilter, addressBookQuery, maxResults):
         """
         Cache the vCards for a given addressBookFilder and addressBookQuery
         """
         startTime = time.time()
         #print("Timing: cacheVCardsForAddressBookQuery.starttime=%f" % startTime)
-        
-    
-        allRecords, filterAttributes, dsFilter  = self._getDSFilter( addressBookFilter );
+
+        allRecords, filterAttributes, dsFilter = self._getDSFilter(addressBookFilter)
         #print("allRecords = %s, query = %s" % (allRecords, "None" if dsFilter is None else dsFilter.generate(),))
-    
+
         if allRecords:
             dsFilter = None #  None expression == all Records
         clear = not allRecords and not dsFilter
-        
+
         #get unique list of requested attributes
         if clear:
             attributes = None
         else:
-            queryAttributes = self._attributesForAddressBookQuery( addressBookQuery )
+            queryAttributes = self._attributesForAddressBookQuery(addressBookQuery)
             attributes = filterAttributes + queryAttributes
-        
+
         #calc maxRecords from passed in maxResults allowing extra for second stage filtering in caller
         maxRecords = int(maxResults * 1.2)
         if self.maxDSQueryRecords and maxRecords > self.maxDSQueryRecords:
             maxRecords = self.maxDSQueryRecords
-            
-        updateLock, limited = (yield self._refreshCache(reschedule=False, query=dsFilter, attributes=attributes, keepLock=True, clear=clear, maxRecords=maxRecords ))
 
-        elaspedTime = time.time()-startTime
-        self.log_info("Timing: Cache fill: %.1f ms" % (elaspedTime*1000,))
-        
+        updateLock, limited = (yield self._refreshCache(reschedule=False, query=dsFilter, attributes=attributes, keepLock=True, clear=clear, maxRecords=maxRecords))
 
+        elaspedTime = time.time() - startTime
+        self.log_info("Timing: Cache fill: %.1f ms" % (elaspedTime * 1000,))
+
         returnValue((updateLock, limited))
 
 
     @inlineCallbacks
-    def vCardRecordsForAddressBookQuery(self, addressBookFilter, addressBookQuery, maxResults ):
+    def vCardRecordsForAddressBookQuery(self, addressBookFilter, addressBookQuery, maxResults):
         """
         Get vCards for a given addressBookFilder and addressBookQuery
         """
-    
-        allRecords, filterAttributes, dsFilter  = self._getDSFilter( addressBookFilter );
+
+        allRecords, filterAttributes, dsFilter = self._getDSFilter(addressBookFilter)
         #print("allRecords = %s, query = %s" % (allRecords, "None" if dsFilter is None else dsFilter.generate(),))
-        
+
         # testing:
         # allRecords = True
-        
+
         if allRecords:
             dsFilter = None #  None expression == all Records
         clear = not allRecords and not dsFilter
-        
+
         queryRecords = []
         limited = False
 
         if not clear:
-            queryAttributes = self._attributesForAddressBookQuery( addressBookQuery )
+            queryAttributes = self._attributesForAddressBookQuery(addressBookQuery)
             attributes = filterAttributes + queryAttributes
-            
+
             #calc maxRecords from passed in maxResults allowing extra for second stage filtering in caller
             maxRecords = int(maxResults * 1.2)
             if self.maxDSQueryRecords and maxRecords > self.maxDSQueryRecords:
                 maxRecords = self.maxDSQueryRecords
 
             records, limited = (yield self._getDirectoryRecords(dsFilter, attributes, maxRecords))
-            
+
             #filter out bad records --- should only happen during development
             for record in records.values():
                 try:
@@ -1038,12 +1021,13 @@
                     self.log_info("Could not get vcard for record %s" % (record,))
                 else:
                     if not record.firstValueForAttribute(dsattributes.kDSNAttrMetaNodeLocation).startswith("/Local"):
-                        self.log_debug("VCard text =\n%s" % (vCardText, ))
+                        self.log_debug("VCard text =\n%s" % (vCardText,))
                     queryRecords.append(record)
-                        
-        returnValue((queryRecords, limited,))        
 
+        returnValue((queryRecords, limited,))
 
+
+
 class VCardRecord(DirectoryRecord, DAVPropertyMixIn):
     """
     Open Directory implementation of L{IDirectoryRecord}.
@@ -1053,10 +1037,10 @@
     # will be used to translate vCard queries to od queries
 
     dsqueryAttributesForProperty = {
-                             
+
         "FN" : [
-               dsattributes.kDS1AttrFirstName, 
-               dsattributes.kDS1AttrLastName, 
+               dsattributes.kDS1AttrFirstName,
+               dsattributes.kDS1AttrLastName,
                dsattributes.kDS1AttrMiddleName,
                dsattributes.kDSNAttrNamePrefix,
                dsattributes.kDSNAttrNameSuffix,
@@ -1064,8 +1048,8 @@
                dsattributes.kDSNAttrRecordName,
                ],
         "N" : [
-               dsattributes.kDS1AttrFirstName, 
-               dsattributes.kDS1AttrLastName, 
+               dsattributes.kDS1AttrFirstName,
+               dsattributes.kDS1AttrLastName,
                dsattributes.kDS1AttrMiddleName,
                dsattributes.kDSNAttrNamePrefix,
                dsattributes.kDSNAttrNameSuffix,
@@ -1150,26 +1134,26 @@
          "X-AIM" : [
                 dsattributes.kDSNAttrIMHandle,
                 ],
-         "X-JABBER" :    [
+         "X-JABBER" : [
                 dsattributes.kDSNAttrIMHandle,
                 ],
-         "X-MSN" :    [
+         "X-MSN" : [
                 dsattributes.kDSNAttrIMHandle,
                 ],
-         "X-YAHOO" :  [
+         "X-YAHOO" : [
                 dsattributes.kDSNAttrIMHandle,
                 ],
-         "X-ICQ" :    [
+         "X-ICQ" : [
                 dsattributes.kDSNAttrIMHandle,
                 ],
-         "X-ABRELATEDNAMES" :  [
+         "X-ABRELATEDNAMES" : [
                 dsattributes.kDSNAttrRelationships,
                 ],
           "X-INTERNAL-MINIMUM-VCARD-PROPERTIES" : [
                 dsattributes.kDS1AttrGeneratedUID,
                 dsattributes.kDSNAttrMetaNodeLocation,
-                dsattributes.kDS1AttrFirstName, 
-                 dsattributes.kDS1AttrLastName, 
+                dsattributes.kDS1AttrFirstName,
+                 dsattributes.kDS1AttrLastName,
                 dsattributes.kDS1AttrMiddleName,
                    dsattributes.kDSNAttrNamePrefix,
                   dsattributes.kDSNAttrNameSuffix,
@@ -1184,30 +1168,28 @@
                 dsattributes.kDSNAttrMetaNodeLocation,
                  dsattributes.kDS1AttrDistinguishedName,
                 dsattributes.kDSNAttrRecordName,
-                dsattributes.kDS1AttrFirstName, 
-                 dsattributes.kDS1AttrLastName, 
+                dsattributes.kDS1AttrFirstName,
+                 dsattributes.kDS1AttrLastName,
                 dsattributes.kDSNAttrRecordType,
                 ],
-  
+
     }
-    
 
     allDSQueryAttributes = sorted(list(set([attr for lookupAttributes in dsqueryAttributesForProperty.values()
                                       for attr in lookupAttributes])))
 
     binaryDSAttributeStrs = [attr[0] for attr in allDSQueryAttributes
-                                if isinstance(attr, tuple) ]
+                                if isinstance(attr, tuple)]
 
     stringDSAttributeStrs = [attr for attr in allDSQueryAttributes
-                                if isinstance(attr, str) ]
+                                if isinstance(attr, str)]
 
     allDSAttributeStrs = stringDSAttributeStrs + binaryDSAttributeStrs
-    
+
     #peopleUIDSeparator = "-" + OpenDirectoryBackingService.baseGUID + "-"
     userUIDSeparator = "-bf07a1a2-"
     peopleUIDSeparator = "-cf07a1a2-"
 
-    
     constantProperties = {
         # 3.6.3 PRODID Type Definition
         "PRODID": vCardProductID,
@@ -1215,11 +1197,10 @@
         "VERSION": "3.0",
         }
 
-    
+
     def __init__(self, service, recordAttributes, defaultNodeName=None):
-        
 
-        self.log_debug("service=%s, attributes=%s"    % (service, recordAttributes))
+        self.log_debug("service=%s, attributes=%s" % (service, recordAttributes))
 
         #save off for debugging
         if service.addDSAttrXProperties:
@@ -1230,7 +1211,7 @@
         self._vCardText = None
         self._uriName = None
         self._hRef = None
-        
+
         self.attributes = {}
         for key, values in recordAttributes.items():
             if key in VCardRecord.stringDSAttributeStrs:
@@ -1240,20 +1221,20 @@
                     self.attributes[key] = removeControlChars(values).decode("utf8")
             else:
                 self.attributes[key] = values
-                                                        
+
         # fill in  missing essential attributes used for filtering
         fullName = self.firstValueForAttribute(dsattributes.kDS1AttrDistinguishedName)
         if not fullName:
             fullName = self.firstValueForAttribute(dsattributes.kDSNAttrRecordName)
             self.attributes[dsattributes.kDS1AttrDistinguishedName] = fullName
-            
+
         node = self.firstValueForAttribute(dsattributes.kDSNAttrMetaNodeLocation)
-        
+
         # use a better node name -- makes better synthetic GUIDS
         if not node or node == "/LDAPv3/127.0.0.1":
             node = defaultNodeName if defaultNodeName else service.realmName
             self.attributes[dsattributes.kDSNAttrMetaNodeLocation] = node
-        
+
         guid = self.firstValueForAttribute(dsattributes.kDS1AttrGeneratedUID)
         if not guid:
             if service.standardizeSyntheticUIDs:
@@ -1262,37 +1243,35 @@
                 nodeUUIDStr = "%x" % abs(hash(node))
             nameUUIDStr = "".join(self.firstValueForAttribute(dsattributes.kDSNAttrRecordName).encode("utf8").encode("base64").split("\n"))
             if self.firstValueForAttribute(dsattributes.kDSNAttrRecordType) != dsattributes.kDSStdRecordTypePeople:
-                guid =  VCardRecord.userUIDSeparator.join([nodeUUIDStr, nameUUIDStr,])
+                guid = VCardRecord.userUIDSeparator.join([nodeUUIDStr, nameUUIDStr, ])
             else:
-                guid =  VCardRecord.peopleUIDSeparator.join([nodeUUIDStr, nameUUIDStr,])
+                guid = VCardRecord.peopleUIDSeparator.join([nodeUUIDStr, nameUUIDStr, ])
 
-            
         # since guid is used as file name, normalize so uid uniqueness == fine name uniqueness
         #guid = "/".join(guid.split(":")).upper()
         self.attributes[dsattributes.kDS1AttrGeneratedUID] = guid
-        
+
         if self.firstValueForAttribute(dsattributes.kDS1AttrLastName) == "99":
             del self.attributes[dsattributes.kDS1AttrLastName]
-        
+
         if self.firstValueForAttribute(dsattributes.kDSNAttrRecordType) != dsattributes.kDSStdRecordTypePeople:
             recordType = DirectoryService.recordType_users
         else:
             recordType = DirectoryService.recordType_people
-                        
+
         super(VCardRecord, self).__init__(
-            service                  = service,
-            recordType              = recordType,
-            guid                  = guid,
-            shortNames              = tuple(self.valuesForAttribute(dsattributes.kDSNAttrRecordName)),
-            fullName              = fullName,
-            firstName              = self.firstValueForAttribute(dsattributes.kDS1AttrFirstName, None),
-            lastName              = self.firstValueForAttribute(dsattributes.kDS1AttrLastName, None),
-            emailAddresses        = (),
-            calendarUserAddresses = (),
-            autoSchedule          = False,
-            enabledForCalendaring = False,
+            service=service,
+            recordType=recordType,
+            guid=guid,
+            shortNames=tuple(self.valuesForAttribute(dsattributes.kDSNAttrRecordName)),
+            fullName=fullName,
+            firstName=self.firstValueForAttribute(dsattributes.kDS1AttrFirstName, None),
+            lastName=self.firstValueForAttribute(dsattributes.kDS1AttrLastName, None),
+            emailAddresses=(),
+            calendarUserAddresses=(),
+            autoSchedule=False,
+            enabledForCalendaring=False,
         )
-        
 
 
     def __repr__(self):
@@ -1304,7 +1283,8 @@
             self.shortNames,
             self.fullName
         )
-    
+
+
     def __hash__(self):
         s = "".join([
               "%s:%s" % (attribute, self.valuesForAttribute(attribute),)
@@ -1318,29 +1298,29 @@
         self.fileName = self.baseFileName + "-" + str(self.renameCounter)
         self.fileNameLower = self.fileName.lower()
     """
-    
-    def hasAttribute(self, attributeName ):
+
+    def hasAttribute(self, attributeName):
         return self.valuesForAttribute(attributeName, None) is not None
 
 
-    def valuesForAttribute(self, attributeName, default_values=[] ):
+    def valuesForAttribute(self, attributeName, default_values=[]):
         values = self.attributes.get(attributeName)
         if (values is None):
             return default_values
         elif not isinstance(values, list):
-            values = [values, ] 
-        
+            values = [values, ]
+
         # ds templates often return empty attribute values
         #     get rid of them here
-        nonEmptyValues = [(value.encode("utf-8") if isinstance(value, unicode) else value) for value in values if len(value) > 0 ]
-        
+        nonEmptyValues = [(value.encode("utf-8") if isinstance(value, unicode) else value) for value in values if len(value) > 0]
+
         if len(nonEmptyValues) > 0:
             return nonEmptyValues
         else:
             return default_values
-        
 
-    def firstValueForAttribute(self, attributeName, default_value="" ):
+
+    def firstValueForAttribute(self, attributeName, default_value=""):
         values = self.attributes.get(attributeName)
         if values is None:
             return default_value
@@ -1349,32 +1329,32 @@
         else:
             return values.encode("utf_8") if isinstance(values, unicode) else values
 
-    def joinedValuesForAttribute(self, attributeName, separator=",", default_string="" ):
+
+    def joinedValuesForAttribute(self, attributeName, separator=",", default_string=""):
         values = self.valuesForAttribute(attributeName, None)
         if not values:
             return default_string
         else:
             return separator.join(values)
-            
 
-    def isoDateStringForDateAttribute(self, attributeName, default_string="" ):
+
+    def isoDateStringForDateAttribute(self, attributeName, default_string=""):
         modDate = self.firstValueForAttribute(attributeName, default_string)
         revDate = None
         if modDate:
             if len(modDate) >= len("YYYYMMDD") and modDate[:8].isdigit():
-                revDate = "%s-%s-%s" % (modDate[:4],modDate[4:6],modDate[6:8], )
+                revDate = "%s-%s-%s" % (modDate[:4], modDate[4:6], modDate[6:8],)
             if len(modDate) >= len("YYYYMMDDHHMMSS") and modDate[8:14].isdigit():
-                revDate += "T%s:%s:%sZ" % (modDate[8:10],modDate[10:12],modDate[12:14], )
+                revDate += "T%s:%s:%sZ" % (modDate[8:10], modDate[10:12], modDate[12:14],)
         return revDate
 
-    
-                
+
     def vCard(self):
-        
-        
+
+
         def generateVCard():
-            
-            def isUniqueProperty(vcard, newProperty, ignoreParams = None):
+
+            def isUniqueProperty(vcard, newProperty, ignoreParams=None):
                 existingProperties = vcard.properties(newProperty.name())
                 for existingProperty in existingProperties:
                     if ignoreParams:
@@ -1385,14 +1365,14 @@
                         return False
                 return True
 
-            def addUniqueProperty(vcard, newProperty, ignoreParams = None, attrType = None, attrValue = None):
+            def addUniqueProperty(vcard, newProperty, ignoreParams=None, attrType=None, attrValue=None):
                 if isUniqueProperty(vcard, newProperty, ignoreParams):
                     vcard.addProperty(newProperty)
                 else:
                     if attrType and attrValue:
-                        self.log_info("Ignoring attribute %r with value %r in creating property %r. A duplicate property already exists." % (attrType, attrValue, newProperty, ))
-                            
-            def addPropertyAndLabel(groupCount, label, propertyName, propertyValue, parameters = None ):
+                        self.log_info("Ignoring attribute %r with value %r in creating property %r. A duplicate property already exists." % (attrType, attrValue, newProperty,))
+
+            def addPropertyAndLabel(groupCount, label, propertyName, propertyValue, parameters=None):
                 groupCount[0] += 1
                 groupPrefix = "item%d" % groupCount[0]
                 vcard.addProperty(Property(propertyName, propertyValue, params=parameters, group=groupPrefix))
@@ -1406,30 +1386,30 @@
                         # special case for Apple
                         if self.service.appleInternalServer and attrType == dsattributes.kDSNAttrIMHandle:
                             splitValue = attrValue.split("|")
-                            if len (splitValue) > 1:
+                            if len(splitValue) > 1:
                                 attrValue = splitValue[0]
 
                         colonIndex = attrValue.find(":")
-                        if (colonIndex > len(attrValue)-2):
+                        if (colonIndex > len(attrValue) - 2):
                             raise ValueError("Nothing after colon.")
 
-                        propertyValue = attrValue[colonIndex+1:]
+                        propertyValue = attrValue[colonIndex + 1:]
                         labelString = attrValue[:colonIndex] if colonIndex > 0 else defaultLabel
                         paramTypeString = labelString.upper()
-                        
+
                         # add PREF to first prop's parameters
-                        paramTypeStrings = [paramTypeString,]
+                        paramTypeStrings = [paramTypeString, ]
                         if preferred and "PREF" != paramTypeString:
-                            paramTypeStrings += ["PREF",]
-                        parameters = { "TYPE": paramTypeStrings, }
+                            paramTypeStrings += ["PREF", ]
+                        parameters = {"TYPE": paramTypeStrings, }
 
-                        #special case for IMHandles which the param is the last part of the property like X-AIM or X-JABBER 
+                        #special case for IMHandles which the param is the last part of the property like X-AIM or X-JABBER
                         if propertyPrefix:
                             propertyName = propertyPrefix + paramTypeString
 
                         # only add label prop if needed
                         if paramTypeString in nolabelParamTypes:
-                            addUniqueProperty(vcard, Property(propertyName, attrValue[colonIndex+1:], params=parameters), None, attrValue, attrType)
+                            addUniqueProperty(vcard, Property(propertyName, attrValue[colonIndex + 1:], params=parameters), None, attrValue, attrType)
                         else:
                             # use special localizable addressbook labels where possible
                             abLabelString = labelMap.get(labelString, labelString)
@@ -1441,88 +1421,85 @@
                         self.log_debug("addPropertiesAndLabelsForPrefixedAttribute(): groupCount=%r, propertyPrefix=%r, propertyName=%r, nolabelParamTypes=%r, labelMap=%r, attrType=%r" % (groupCount[0], propertyPrefix, propertyName, nolabelParamTypes, labelMap, attrType,))
                         self.log_error("addPropertiesAndLabelsForPrefixedAttribute(): Trouble parsing attribute %s, with value \"%s\".  Error = %s" % (attrType, attrValue, e,))
 
-            
             #print("VCardRecord.vCard")
             # create vCard
             vcard = Component("VCARD")
             groupCount = [0]
-            
+
             # add constant properties - properties that are the same regardless of the record attributes
             for key, value in VCardRecord.constantProperties.items():
                 vcard.addProperty(Property(key, value))
-    
+
             # 3.1 IDENTIFICATION TYPES http://tools.ietf.org/html/rfc2426#section-3.1
             # 3.1.1 FN Type Definition
             # dsattributes.kDS1AttrDistinguishedName,      # Users distinguished or real name
             #
             # full name is required but this is set in OpenDiretoryBackingRecord.__init__
             #vcard.addProperty(Property("FN", self.firstValueForAttribute(dsattributes.kDS1AttrDistinguishedName)))
-            
+
             # 3.1.2 N Type Definition
             # dsattributes.kDS1AttrFirstName,            # Used for first name of user or person record.
             # dsattributes.kDS1AttrLastName,            # Used for the last name of user or person record.
             # dsattributes.kDS1AttrMiddleName,            # Used for the middle name of user or person record.
             # dsattributes.kDSNAttrNameSuffix,            # Represents the name suffix of a user or person.
                                                         #      ie. Jr., Sr., etc.
-                                                        #      Usually found in user or people records (kDSStdRecordTypeUsers or 
+                                                        #      Usually found in user or people records (kDSStdRecordTypeUsers or
                                                         #      dsattributes.kDSStdRecordTypePeople).
             # dsattributes.kDSNAttrNamePrefix,            # Represents the title prefix of a user or person.
                                                         #      ie. Mr., Ms., Mrs., Dr., etc.
-                                                        #      Usually found in user or people records (kDSStdRecordTypeUsers or 
+                                                        #      Usually found in user or people records (kDSStdRecordTypeUsers or
                                                         #      dsattributes.kDSStdRecordTypePeople).
-                                                        
+
             # name is required, so make sure we have one
             # vcard says: Each name attribute can be a string or a list of strings.
             if not self.hasAttribute(dsattributes.kDS1AttrFirstName) and not self.hasAttribute(dsattributes.kDS1AttrLastName):
                 familyName = self.firstValueForAttribute(dsattributes.kDS1AttrDistinguishedName)
             else:
                 familyName = self.valuesForAttribute(dsattributes.kDS1AttrLastName, "")
-            
+
             nameObject = N(
-                first = self.valuesForAttribute(dsattributes.kDS1AttrFirstName, ""),
-                last = familyName, 
-                middle = self.valuesForAttribute(dsattributes.kDS1AttrMiddleName, ""),
-                prefix = self.valuesForAttribute(dsattributes.kDSNAttrNamePrefix, ""),
-                suffix = self.valuesForAttribute(dsattributes.kDSNAttrNameSuffix, ""),
+                first=self.valuesForAttribute(dsattributes.kDS1AttrFirstName, ""),
+                last=familyName,
+                middle=self.valuesForAttribute(dsattributes.kDS1AttrMiddleName, ""),
+                prefix=self.valuesForAttribute(dsattributes.kDSNAttrNamePrefix, ""),
+                suffix=self.valuesForAttribute(dsattributes.kDSNAttrNameSuffix, ""),
             )
             vcard.addProperty(Property("N", nameObject))
-            
+
             # set full name to Name with contiguous spaces stripped
             # it turns out that Address Book.app ignores FN and creates it fresh from N in ABRecord
             # so no reason to have FN distinct from N
-            vcard.addProperty(Property("FN", nameObject.getFullName() ))
-            
+            vcard.addProperty(Property("FN", nameObject.getFullName()))
+
             # 3.1.3 NICKNAME Type Definition
             # dsattributes.kDSNAttrNickName,            # Represents the nickname of a user or person.
-                                                        #    Usually found in user or people records (kDSStdRecordTypeUsers or 
+                                                        #    Usually found in user or people records (kDSStdRecordTypeUsers or
                                                         #    dsattributes.kDSStdRecordTypePeople).
             for nickname in self.valuesForAttribute(dsattributes.kDSNAttrNickName):
                 addUniqueProperty(vcard, Property("NICKNAME", nickname), None, dsattributes.kDSNAttrNickName, nickname)
-            
+
             # 3.1.4 PHOTO Type Definition
-            # dsattributes.kDSNAttrJPEGPhoto,            # Used to store binary picture data in JPEG format. 
-                                                        #      Usually found in user, people or group records (kDSStdRecordTypeUsers, 
+            # dsattributes.kDSNAttrJPEGPhoto,            # Used to store binary picture data in JPEG format.
+                                                        #      Usually found in user, people or group records (kDSStdRecordTypeUsers,
                                                         #      dsattributes.kDSStdRecordTypePeople,dsattributes.kDSStdRecordTypeGroups).
-            # pyOpenDirectory always returns binary-encoded string                                       
-                                                        
+            # pyOpenDirectory always returns binary-encoded string
+
             for photo in self.valuesForAttribute(dsattributes.kDSNAttrJPEGPhoto):
-                addUniqueProperty(vcard, Property("PHOTO", photo, params={"ENCODING": ["b",], "TYPE": ["JPEG",],}), None, dsattributes.kDSNAttrJPEGPhoto, photo)
-    
-    
+                addUniqueProperty(vcard, Property("PHOTO", photo, params={"ENCODING": ["b", ], "TYPE": ["JPEG", ], }), None, dsattributes.kDSNAttrJPEGPhoto, photo)
+
             # 3.1.5 BDAY Type Definition
             # dsattributes.kDS1AttrBirthday,            # Single-valued attribute that defines the user's birthday.
                                                         #      Format is x.208 standard YYYYMMDDHHMMSSZ which we will require as GMT time.
                                                         #                               012345678901234
-            
+
             birthdate = self.isoDateStringForDateAttribute(dsattributes.kDS1AttrBirthday)
             if birthdate:
                 vcard.addProperty(Property("BDAY", PyCalendarDateTime.parseText(birthdate, fullISO=True)))
-    
-    
+
             # 3.2 Delivery Addressing Types http://tools.ietf.org/html/rfc2426#section-3.2
             #
             # 3.2.1 ADR Type Definition
-    
+
             #address
             # vcard says: Each address attribute can be a string or a list of strings.
             extended = self.valuesForAttribute(dsattributes.kDSNAttrBuilding, "")
@@ -1531,37 +1508,36 @@
             region = self.valuesForAttribute(dsattributes.kDSNAttrState, "")
             code = self.valuesForAttribute(dsattributes.kDSNAttrPostalCode, "")
             country = self.valuesForAttribute(dsattributes.kDSNAttrCountry, "")
-            
+
             if len(extended) > 0 or len(street) > 0 or len(city) > 0 or len(region) > 0 or len(code) > 0 or len(country) > 0:
                 vcard.addProperty(Property("ADR",
                     Adr(
                         #pobox = box,
-                        extended = extended,
-                        street = street,
-                        locality = city,
-                        region = region,
-                        postalcode = code,
-                        country = country,
+                        extended=extended,
+                        street=street,
+                        locality=city,
+                        region=region,
+                        postalcode=code,
+                        country=country,
                     ),
-                    params = {"TYPE": ["WORK", "PREF", "POSTAL", "PARCEL",],}
+                    params={"TYPE": ["WORK", "PREF", "POSTAL", "PARCEL", ], }
                 ))
-    
-    
+
             # 3.2.2 LABEL Type Definition
-            
+
             # dsattributes.kDSNAttrPostalAddress,            # The postal address usually excluding postal code.
             # dsattributes.kDSNAttrPostalAddressContacts,    # multi-valued attribute that defines a record's alternate postal addresses .
                                                             #      found in user records (kDSStdRecordTypeUsers) and resource records (kDSStdRecordTypeResources).
             # dsattributes.kDSNAttrAddressLine1,            # Line one of multiple lines of address data for a user.
             # dsattributes.kDSNAttrAddressLine2,            # Line two of multiple lines of address data for a user.
             # dsattributes.kDSNAttrAddressLine3,            # Line three of multiple lines of address data for a user.
-            
+
             for label in self.valuesForAttribute(dsattributes.kDSNAttrPostalAddress):
-                addUniqueProperty(vcard, Property("LABEL", label, params={"TYPE": ["POSTAL", "PARCEL",]}), None, dsattributes.kDSNAttrPostalAddress, label)
-                
+                addUniqueProperty(vcard, Property("LABEL", label, params={"TYPE": ["POSTAL", "PARCEL", ]}), None, dsattributes.kDSNAttrPostalAddress, label)
+
             for label in self.valuesForAttribute(dsattributes.kDSNAttrPostalAddressContacts):
-                addUniqueProperty(vcard, Property("LABEL", label, params={"TYPE": ["POSTAL", "PARCEL",]}), None, dsattributes.kDSNAttrPostalAddressContacts, label)
-                
+                addUniqueProperty(vcard, Property("LABEL", label, params={"TYPE": ["POSTAL", "PARCEL", ]}), None, dsattributes.kDSNAttrPostalAddressContacts, label)
+
             address = self.joinedValuesForAttribute(dsattributes.kDSNAttrAddressLine1)
             addressLine2 = self.joinedValuesForAttribute(dsattributes.kDSNAttrAddressLine2)
             if len(addressLine2) > 0:
@@ -1569,83 +1545,83 @@
             addressLine3 = self.joinedValuesForAttribute(dsattributes.kDSNAttrAddressLine3)
             if len(addressLine3) > 0:
                 address += "\n" + addressLine3
-            
+
             if len(address) > 0:
-                vcard.addProperty(Property("LABEL", address, params={"TYPE": ["POSTAL", "PARCEL",]}))
-    
+                vcard.addProperty(Property("LABEL", address, params={"TYPE": ["POSTAL", "PARCEL", ]}))
+
             # 3.3 TELECOMMUNICATIONS ADDRESSING TYPES http://tools.ietf.org/html/rfc2426#section-3.3
             # 3.3.1 TEL Type Definition
             #          TEL;TYPE=work,voice,pref,msg:+1-213-555-1234
-    
+
             # dsattributes.kDSNAttrPhoneNumber,            # Telephone number of a user.
             # dsattributes.kDSNAttrMobileNumber,        # Represents the mobile numbers of a user or person.
-                                                        #      Usually found in user or people records (kDSStdRecordTypeUsers or 
+                                                        #      Usually found in user or people records (kDSStdRecordTypeUsers or
                                                         #      dsattributes.kDSStdRecordTypePeople).
             # dsattributes.kDSNAttrFaxNumber,            # Represents the FAX numbers of a user or person.
-                                                        # Usually found in user or people records (kDSStdRecordTypeUsers or 
+                                                        # Usually found in user or people records (kDSStdRecordTypeUsers or
                                                         # kDSStdRecordTypePeople).
             # dsattributes.kDSNAttrPagerNumber,            # Represents the pager numbers of a user or person.
-                                                        #      Usually found in user or people records (kDSStdRecordTypeUsers or 
+                                                        #      Usually found in user or people records (kDSStdRecordTypeUsers or
                                                         #      dsattributes.kDSStdRecordTypePeople).
             # dsattributes.kDSNAttrHomePhoneNumber,        # Home telephone number of a user or person.
             # dsattributes.kDSNAttrPhoneContacts,        # multi-valued attribute that defines a record's custom phone numbers .
-                                                        #      found in user records (kDSStdRecordTypeUsers). 
+                                                        #      found in user records (kDSStdRecordTypeUsers).
                                                         #      Example: home fax:408-555-4444
-            
-            params = {"TYPE": ["WORK", "PREF", "VOICE",],}
+
+            params = {"TYPE": ["WORK", "PREF", "VOICE", ], }
             for phone in self.valuesForAttribute(dsattributes.kDSNAttrPhoneNumber):
                 addUniqueProperty(vcard, Property("TEL", phone, params=params), (("TYPE", "PREF"),), phone, dsattributes.kDSNAttrPhoneNumber)
-                params = {"TYPE": ["WORK", "VOICE",],}
-    
-            params = { "TYPE": ["WORK", "PREF", "CELL",], }
+                params = {"TYPE": ["WORK", "VOICE", ], }
+
+            params = {"TYPE": ["WORK", "PREF", "CELL", ], }
             for phone in self.valuesForAttribute(dsattributes.kDSNAttrMobileNumber):
                 addUniqueProperty(vcard, Property("TEL", phone, params=params), (("TYPE", "PREF"),), phone, dsattributes.kDSNAttrMobileNumber)
-                params = { "TYPE": ["WORK", "CELL",], }
-    
-            params = { "TYPE": ["WORK", "PREF", "FAX",], }
+                params = {"TYPE": ["WORK", "CELL", ], }
+
+            params = {"TYPE": ["WORK", "PREF", "FAX", ], }
             for phone in self.valuesForAttribute(dsattributes.kDSNAttrFaxNumber):
                 addUniqueProperty(vcard, Property("TEL", phone, params=params), (("TYPE", "PREF"),), phone, dsattributes.kDSNAttrFaxNumber)
-                params = { "TYPE": ["WORK", "FAX",], }
-    
-            params = { "TYPE": ["WORK", "PREF", "PAGER",], }
+                params = {"TYPE": ["WORK", "FAX", ], }
+
+            params = {"TYPE": ["WORK", "PREF", "PAGER", ], }
             for phone in self.valuesForAttribute(dsattributes.kDSNAttrPagerNumber):
                 addUniqueProperty(vcard, Property("TEL", phone, params=params), (("TYPE", "PREF"),), phone, dsattributes.kDSNAttrPagerNumber)
-                params = { "TYPE": ["WORK", "PAGER",], }
-    
-            params = { "TYPE": ["HOME", "PREF", "VOICE",], }
+                params = {"TYPE": ["WORK", "PAGER", ], }
+
+            params = {"TYPE": ["HOME", "PREF", "VOICE", ], }
             for phone in self.valuesForAttribute(dsattributes.kDSNAttrHomePhoneNumber):
                 addUniqueProperty(vcard, Property("TEL", phone, params=params), (("TYPE", "PREF"),), phone, dsattributes.kDSNAttrHomePhoneNumber)
-                params = { "TYPE": ["HOME", "VOICE",], }
-                    
+                params = {"TYPE": ["HOME", "VOICE", ], }
+
             addPropertiesAndLabelsForPrefixedAttribute(groupCount, None, "TEL", "work",
-                                                        ["VOICE", "CELL", "FAX", "PAGER",], {},
-                                                        dsattributes.kDSNAttrPhoneContacts, )
+                                                        ["VOICE", "CELL", "FAX", "PAGER", ], {},
+                                                        dsattributes.kDSNAttrPhoneContacts,)
 
             """
             # EXTEND:  Use this attribute
             # dsattributes.kDSNAttrAreaCode,            # Area code of a user's phone number.
             """
-    
+
             # 3.3.2 EMAIL Type Definition
             # dsattributes.kDSNAttrEMailAddress,        # Email address of usually a user record.
-    
+
             # setup some params
-            preferredWorkParams = { "TYPE": ["WORK", "PREF", "INTERNET",], }
-            workParams = { "TYPE": ["WORK", "INTERNET",], }
+            preferredWorkParams = {"TYPE": ["WORK", "PREF", "INTERNET", ], }
+            workParams = {"TYPE": ["WORK", "INTERNET", ], }
             params = preferredWorkParams
             for emailAddress in self.valuesForAttribute(dsattributes.kDSNAttrEMailAddress):
                 addUniqueProperty(vcard, Property("EMAIL", emailAddress, params=params), (("TYPE", "PREF"),), emailAddress, dsattributes.kDSNAttrEMailAddress)
                 params = workParams
-                
+
             # dsattributes.kDSNAttrEMailContacts,        # multi-valued attribute that defines a record's custom email addresses .
-                                                        #    found in user records (kDSStdRecordTypeUsers). 
+                                                        #    found in user records (kDSStdRecordTypeUsers).
                                                         #      Example: home:johndoe at mymail.com
-    
+
             # check to see if parameters type are open ended. Could be any string
             addPropertiesAndLabelsForPrefixedAttribute(groupCount, None, "EMAIL", "work",
-                                                        ["WORK", "HOME",], {}, 
-                                                        dsattributes.kDSNAttrEMailContacts, )
-    
+                                                        ["WORK", "HOME", ], {},
+                                                        dsattributes.kDSNAttrEMailContacts,)
+
             """
             # UNIMPLEMENTED:
             # 3.3.3 MAILER Type Definition
@@ -1671,7 +1647,7 @@
             # 3.5.1 TITLE Type Definition
             for jobTitle in self.valuesForAttribute(dsattributes.kDSNAttrJobTitle):
                 addUniqueProperty(vcard, Property("TITLE", jobTitle), None, dsattributes.kDSNAttrJobTitle, jobTitle)
-    
+
             """
             # UNIMPLEMENTED:
             # 3.5.2 ROLE Type Definition
@@ -1685,8 +1661,8 @@
             department = self.joinedValuesForAttribute(dsattributes.kDSNAttrDepartment)
             extra = self.joinedValuesForAttribute(dsattributes.kDSNAttrOrganizationInfo)
             if len(company) > 0 or len(department) > 0:
-                vcard.addProperty(Property("ORG", (company, department, extra, ),))
-            
+                vcard.addProperty(Property("ORG", (company, department, extra,),))
+
             # 3.6 EXPLANATORY TYPES http://tools.ietf.org/html/rfc2426#section-3.6
             """
             # UNIMPLEMENTED:
@@ -1697,79 +1673,78 @@
             # dsattributes.kDS1AttrNote,                  # Note attribute. Commonly used in printer records.
             for comment in self.valuesForAttribute(dsattributes.kDS1AttrComment):
                 addUniqueProperty(vcard, Property("NOTE", comment), None, dsattributes.kDS1AttrComment, comment)
-    
+
             for note in self.valuesForAttribute(dsattributes.kDS1AttrNote):
                 addUniqueProperty(vcard, Property("NOTE", note), None, dsattributes.kDS1AttrNote, note)
-            
+
             # 3.6.3 PRODID Type Definition
             #vcard.addProperty(Property("PRODID", vCardProductID + "//BUILD %s" % twistedcaldav.__version__))
             #vcard.addProperty(Property("PRODID", vCardProductID))
             # ADDED WITH CONTSTANT PROPERTIES
-            
+
             # 3.6.4 REV Type Definition
             revDate = self.isoDateStringForDateAttribute(dsattributes.kDS1AttrModificationTimestamp)
             if revDate:
                 vcard.addProperty(Property("REV", PyCalendarDateTime.parseText(revDate, fullISO=True)))
-            
+
             """
             # UNIMPLEMENTED:
             # 3.6.5 SORT-STRING Type Definition
             # 3.6.6 SOUND Type Definition
             """
             # 3.6.7 UID Type Definition
-            # dsattributes.kDS1AttrGeneratedUID,        # Used for 36 character (128 bit) unique ID. Usually found in user, 
+            # dsattributes.kDS1AttrGeneratedUID,        # Used for 36 character (128 bit) unique ID. Usually found in user,
                                                         #      group, and computer records. An example value is "A579E95E-CDFE-4EBC-B7E7-F2158562170F".
                                                         #      The standard format contains 32 hex characters and four hyphen characters.
             # !! don't use self.guid which is URL encoded
             vcard.addProperty(Property("UID", self.firstValueForAttribute(dsattributes.kDS1AttrGeneratedUID)))
-    
-            # 3.6.8 URL Type Definition 
+
+            # 3.6.8 URL Type Definition
             # dsattributes.kDSNAttrURL,                    # List of URLs.
             # dsattributes.kDS1AttrWeblogURI,            # Single-valued attribute that defines the URI of a user's weblog.
-                                                        #     Usually found in user records (kDSStdRecordTypeUsers). 
+                                                        #     Usually found in user records (kDSStdRecordTypeUsers).
                                                         #      Example: http://example.com/blog/jsmith
             for url in self.valuesForAttribute(dsattributes.kDS1AttrWeblogURI):
-                addPropertyAndLabel(groupCount, "weblog", "URL", url, parameters = {"TYPE": ["Weblog",]})
-    
+                addPropertyAndLabel(groupCount, "weblog", "URL", url, parameters={"TYPE": ["Weblog", ]})
+
             for url in self.valuesForAttribute(dsattributes.kDSNAttrURL):
-                addPropertyAndLabel(groupCount, "_$!<HomePage>!$_", "URL", url, parameters = {"TYPE": ["Homepage",]})
-    
-    
+                addPropertyAndLabel(groupCount, "_$!<HomePage>!$_", "URL", url, parameters={"TYPE": ["Homepage", ]})
+
             # 3.6.9 VERSION Type Definition
             # ALREADY ADDED
-    
+
             # 3.7 SECURITY TYPES http://tools.ietf.org/html/rfc2426#section-3.7
             # 3.7.1 CLASS Type Definition
             # ALREADY ADDED
-            
+
             # 3.7.2 KEY Type Definition
-            
+
             # dsattributes.kDSNAttrPGPPublicKey,        # Pretty Good Privacy public encryption key.
             # dsattributes.kDS1AttrUserCertificate,        # Attribute containing the binary of the user's certificate.
                                                         #       Usually found in user records. The certificate is data which identifies a user.
-                                                        #       This data is attested to by a known party, and can be independently verified 
+                                                        #       This data is attested to by a known party, and can be independently verified
                                                         #       by a third party.
-            # dsattributes.kDS1AttrUserPKCS12Data,        # Attribute containing binary data in PKCS #12 format. 
+            # dsattributes.kDS1AttrUserPKCS12Data,        # Attribute containing binary data in PKCS #12 format.
                                                         #       Usually found in user records. The value can contain keys, certificates,
                                                         #      and other related information and is encrypted with a passphrase.
             # dsattributes.kDS1AttrUserSMIMECertificate,# Attribute containing the binary of the user's SMIME certificate.
                                                         #       Usually found in user records. The certificate is data which identifies a user.
-                                                        #       This data is attested to by a known party, and can be independently verified 
+                                                        #       This data is attested to by a known party, and can be independently verified
                                                         #       by a third party. SMIME certificates are often used for signed or encrypted
                                                         #       emails.
-    
+
             for key in self.valuesForAttribute(dsattributes.kDSNAttrPGPPublicKey):
-                addUniqueProperty(vcard, Property("KEY", key, params = {"ENCODING": ["b",], "TYPE": ["PGPPublicKey",]}), None, dsattributes.kDSNAttrPGPPublicKey, key)
-    
+                addUniqueProperty(vcard, Property("KEY", key, params={"ENCODING": ["b", ], "TYPE": ["PGPPublicKey", ]}), None, dsattributes.kDSNAttrPGPPublicKey, key)
+
             for key in self.valuesForAttribute(dsattributes.kDS1AttrUserCertificate):
-                addUniqueProperty(vcard, Property("KEY", key, params = {"ENCODING": ["b",], "TYPE": ["UserCertificate",]}), None, dsattributes.kDS1AttrUserCertificate, key)
-    
+                addUniqueProperty(vcard, Property("KEY", key, params={"ENCODING": ["b", ], "TYPE": ["UserCertificate", ]}), None, dsattributes.kDS1AttrUserCertificate, key)
+
             for key in self.valuesForAttribute(dsattributes.kDS1AttrUserPKCS12Data):
-                addUniqueProperty(vcard, Property("KEY", key, params = {"ENCODING": ["b",], "TYPE": ["UserPKCS12Data",]}), None, dsattributes.kDS1AttrUserPKCS12Data, key)
-    
+                addUniqueProperty(vcard, Property("KEY", key, params={"ENCODING": ["b", ], "TYPE": ["UserPKCS12Data", ]}), None, dsattributes.kDS1AttrUserPKCS12Data, key)
+
             for key in self.valuesForAttribute(dsattributes.kDS1AttrUserSMIMECertificate):
-                addUniqueProperty(vcard, Property("KEY", key, params = {"ENCODING": ["b",], "TYPE": ["UserSMIMECertificate",]}), None, dsattributes.kDS1AttrUserSMIMECertificate, key)
-    
+                addUniqueProperty(vcard, Property("KEY", key, params={"ENCODING": ["b", ], "TYPE": ["UserSMIMECertificate", ]}), None, dsattributes.kDS1AttrUserSMIMECertificate, key)
+
             """
             X- attributes, Address Book support
             """
@@ -1779,32 +1754,31 @@
                                                         #      Values should be prefixed with the appropriate IM type
                                                         #       ie. AIM:, Jabber:, MSN:, Yahoo:, or ICQ:
                                                         #       Usually found in user records (kDSStdRecordTypeUsers).
-    
+
             addPropertiesAndLabelsForPrefixedAttribute(groupCount, "X-", None, "aim",
-                                                        ["AIM", "JABBER", "MSN", "YAHOO", "ICQ"], 
-                                                        {}, 
+                                                        ["AIM", "JABBER", "MSN", "YAHOO", "ICQ"],
+                                                        {},
                                                         dsattributes.kDSNAttrIMHandle,)
-                    
+
             # X-ABRELATEDNAMES
             # dsattributes.kDSNAttrRelationships,        #      multi-valued attribute that defines the relationship to the record type .
-                                                        #      found in user records (kDSStdRecordTypeUsers). 
+                                                        #      found in user records (kDSStdRecordTypeUsers).
                                                         #      Example: brother:John
             addPropertiesAndLabelsForPrefixedAttribute(groupCount, None, "X-ABRELATEDNAMES", "friend",
-                                                        [],  
-                                                        {   "FATHER":"_$!<Father>!$_",
-                                                            "MOTHER":"_$!<Mother>!$_",
-                                                            "PARENT":"_$!<Parent>!$_",
-                                                            "BROTHER":"_$!<Brother>!$_",
-                                                            "SISTER":"_$!<Sister>!$_",
-                                                            "CHILD":"_$!<Child>!$_",
-                                                            "FRIEND":"_$!<Friend>!$_",
-                                                            "SPOUSE":"_$!<Spouse>!$_",
-                                                            "PARTNER":"_$!<Partner>!$_",
-                                                            "ASSISTANT":"_$!<Assistant>!$_",
-                                                            "MANAGER":"_$!<Manager>!$_", },
-                                                        dsattributes.kDSNAttrRelationships, )
-            
-            
+                                                        [],
+                                                        {"FATHER": "_$!<Father>!$_",
+                                                         "MOTHER": "_$!<Mother>!$_",
+                                                         "PARENT": "_$!<Parent>!$_",
+                                                         "BROTHER": "_$!<Brother>!$_",
+                                                         "SISTER": "_$!<Sister>!$_",
+                                                         "CHILD": "_$!<Child>!$_",
+                                                         "FRIEND": "_$!<Friend>!$_",
+                                                         "SPOUSE": "_$!<Spouse>!$_",
+                                                         "PARTNER": "_$!<Partner>!$_",
+                                                         "ASSISTANT": "_$!<Assistant>!$_",
+                                                         "MANAGER": "_$!<Manager>!$_", },
+                                                        dsattributes.kDSNAttrRelationships,)
+
             # special case for Apple
             if self.service.appleInternalServer:
                 for manager in self.valuesForAttribute("dsAttrTypeNative:appleManager"):
@@ -1815,81 +1789,82 @@
                         managerValue = "%s %s" % (splitManager[0], splitManager[1])
                     else:
                         managerValue = manager
-                    addPropertyAndLabel( groupCount, "_$!<Manager>!$_", "X-ABRELATEDNAMES", managerValue, parameters={ "TYPE": ["Manager",]} )
-            
+                    addPropertyAndLabel(groupCount, "_$!<Manager>!$_", "X-ABRELATEDNAMES", managerValue, parameters={"TYPE": ["Manager", ]})
+
             """
             # UNIMPLEMENTED: X- attributes
-            
+
             X-MAIDENNAME
             X-PHONETIC-FIRST-NAME
             X-PHONETIC-MIDDLE-NAME
             X-PHONETIC-LAST-NAME
-        
+
             sattributes.kDS1AttrPicture,                # Represents the path of the picture for each user displayed in the login window.
                                                         #      Found in user records (kDSStdRecordTypeUsers).
-           
+
             dsattributes.kDS1AttrMapGUID,                # Represents the GUID for a record's map.
             dsattributes.kDSNAttrMapURI,                # attribute that defines the URI of a user's location.
-    
+
             dsattributes.kDSNAttrOrganizationInfo,        # Usually the organization info of a user.
             dsattributes.kDSNAttrAreaCode,                # Area code of a user's phone number.
-    
-            dsattributes.kDSNAttrMIME,                    # Data contained in this attribute type is a fully qualified MIME Type. 
-            
+
+            dsattributes.kDSNAttrMIME,                    # Data contained in this attribute type is a fully qualified MIME Type.
+
             """
-            
+
             # debug, create x attributes for all ds attributes
             if self.service.addDSAttrXProperties:
                 for attribute in self.originalAttributes:
                     for value in self.valuesForAttribute(attribute):
-                        vcard.addProperty(Property("X-"+"-".join(attribute.split(":")), removeControlChars(value)))
-    
+                        vcard.addProperty(Property("X-" + "-".join(attribute.split(":")), removeControlChars(value)))
+
             return vcard
 
-        
         if not self._vCard:
             self._vCard = generateVCard()
-        
+
         return self._vCard
-    
+
+
     def vCardText(self):
         if not self._vCardText:
             self._vCardText = str(self.vCard())
-        
+
         return self._vCardText
 
+
     def uriName(self):
         if not self._uriName:
             self._uriName = self.vCard().getProperty("UID").value() + ".vcf"
         #print("uriName():self._uriName=%s" % self._uriName)
         return self._uriName
-        
-    
+
+
     def hRef(self, parentURI="/directory/"):
         if not self._hRef:
             self._hRef = davxml.HRef.fromString(joinURL(parentURI, self.uriName()))
-            
+
         return self._hRef
 
 
     def readProperty(self, property, request):
-        
+
         if type(property) is tuple:
             qname = property
         else:
             qname = property.qname()
 
         namespace, name = qname
-        
+
         #print("VCardResource.readProperty: qname = %s" % (qname, ))
-        
+
         if namespace == dav_namespace:
             if name == "resourcetype":
                 result = davxml.ResourceType.empty #@UndefinedVariable
                 #print("VCardResource.readProperty: qname = %s, result = %s" % (qname, result))
                 return result
             elif name == "getetag":
-                result = davxml.GETETag( ETag(hashlib.md5(self.vCardText()).hexdigest()).generate() )
+                result = davxml.GETETag(ETag(hashlib.md5(self.vCardText()).hexdigest()).generate())
                 #print("VCardResource.readProperty: qname = %s, result = %s" % (qname, result))
                 return result
             elif name == "getcontenttype":
@@ -1942,14 +1917,15 @@
 
         return self.directoryBackedAddressBook.readProperty(property, request)
 
+
     def listProperties(self, request):
         #print("VCardResource.listProperties()")
         qnames = set(self.liveProperties())
 
         # Add dynamic live properties that exist
         dynamicLiveProperties = (
-            (dav_namespace, "quota-available-bytes"     ),
-            (dav_namespace, "quota-used-bytes"          ),
+            (dav_namespace, "quota-available-bytes"),
+            (dav_namespace, "quota-used-bytes"),
         )
         for dqname in dynamicLiveProperties:
             #print("VCardResource.listProperties: removing dqname=%s" % (dqname,))
@@ -1963,22 +1939,22 @@
         #for qn in qnames: print("VCardResource.listProperties: qn=%s" % (qn,))
 
         yield qnames
-        
+
     listProperties = deferredGenerator(listProperties)
-    
+
+
+
 # utility
 #remove control characters because vCard does not support them
-def removeControlChars( utf8String ):
+def removeControlChars(utf8String):
     result = utf8String
     for a in utf8String:
         if '\x00' <= a <= '\x1F':
             result = ""
             for c in utf8String:
                 if '\x00' <= c <= '\x1F':
-                    pass 
+                    pass
                 else:
                     result += c
     #if utf8String != result: print ("changed %r to %r" % (utf8String, result))
     return result
-
-

Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectory.py	2013-05-28 20:22:31 UTC (rev 11251)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_opendirectory.py	2013-05-29 02:20:45 UTC (rev 11252)
@@ -19,17 +19,17 @@
 except ImportError:
     pass
 else:
+    from calendarserver.platform.darwin.od import dsattributes
     from collections import defaultdict
-    from twisted.trial.unittest import SkipTest
+    from twext.web2.auth.digest import DigestedCredentials
     from twisted.internet.defer import inlineCallbacks
     from twisted.python.runtime import platform
-    from twext.web2.auth.digest import DigestedCredentials
-    import twistedcaldav.directory.test.util
+    from twisted.trial.unittest import SkipTest
     from twistedcaldav.directory import augment
-    from twistedcaldav.directory.directory import DirectoryService
     from twistedcaldav.directory.appleopendirectory import OpenDirectoryRecord
-    from calendarserver.platform.darwin.od import dsattributes
+    from twistedcaldav.directory.directory import DirectoryService
     from txdav.common.datastore.test.util import deriveValue, withSpecialValue
+    import twistedcaldav.directory.test.util
 
     class DigestAuthModule(object):
         """
@@ -47,12 +47,13 @@
             val = (response == self.response)
             return val
 
+
     # Wonky hack to prevent unclean reactor shutdowns
     class DummyReactor(object):
         @staticmethod
         def callLater(*args):
             pass
-    import twistedcaldav.directory.appleopendirectory
+
     twistedcaldav.directory.appleopendirectory.reactor = DummyReactor
 
     class OpenDirectory (
@@ -89,20 +90,20 @@
 
         def test_fullNameNone(self):
             record = OpenDirectoryRecord(
-                service               = self.service(),
-                recordType            = DirectoryService.recordType_users,
-                guid                  = "B1F93EB1-DA93-4772-9141-81C250DA36C2",
-                nodeName              = "/LDAPv2/127.0.0.1",
-                shortNames            = ("user",),
-                authIDs               = set(),
-                fullName              = None,
-                firstName             = "Some",
-                lastName              = "User",
-                emailAddresses        = set(("someuser at example.com",)),
-                memberGUIDs           = [],
-                nestedGUIDs           = [],
-                extProxies            = [],
-                extReadOnlyProxies    = [],
+                service=self.service(),
+                recordType=DirectoryService.recordType_users,
+                guid="B1F93EB1-DA93-4772-9141-81C250DA36C2",
+                nodeName="/LDAPv2/127.0.0.1",
+                shortNames=("user",),
+                authIDs=set(),
+                fullName=None,
+                firstName="Some",
+                lastName="User",
+                emailAddresses=set(("someuser at example.com",)),
+                memberGUIDs=[],
+                nestedGUIDs=[],
+                extProxies=[],
+                extReadOnlyProxies=[],
             )
             self.assertEquals(record.fullName, "")
 
@@ -110,20 +111,20 @@
         @withSpecialValue("odModule", DigestAuthModule())
         def test_invalidODDigest(self):
             record = OpenDirectoryRecord(
-                service               = self.service(),
-                recordType            = DirectoryService.recordType_users,
-                guid                  = "B1F93EB1-DA93-4772-9141-81C250DA35B3",
-                nodeName              = "/LDAPv2/127.0.0.1",
-                shortNames            = ("user",),
-                authIDs               = set(),
-                fullName              = "Some user",
-                firstName             = "Some",
-                lastName              = "User",
-                emailAddresses        = set(("someuser at example.com",)),
-                memberGUIDs           = [],
-                nestedGUIDs           = [],
-                extProxies            = [],
-                extReadOnlyProxies    = [],
+                service=self.service(),
+                recordType=DirectoryService.recordType_users,
+                guid="B1F93EB1-DA93-4772-9141-81C250DA35B3",
+                nodeName="/LDAPv2/127.0.0.1",
+                shortNames=("user",),
+                authIDs=set(),
+                fullName="Some user",
+                firstName="Some",
+                lastName="User",
+                emailAddresses=set(("someuser at example.com",)),
+                memberGUIDs=[],
+                nestedGUIDs=[],
+                extProxies=[],
+                extReadOnlyProxies=[],
             )
 
             digestFields = defaultdict(lambda: "...")
@@ -138,29 +139,29 @@
         @withSpecialValue("odModule", DigestAuthModule())
         def test_validODDigest(self):
             record = OpenDirectoryRecord(
-                service               = self.service(),
-                recordType            = DirectoryService.recordType_users,
-                guid                  = "B1F93EB1-DA93-4772-9141-81C250DA35B3",
-                nodeName              = "/LDAPv2/127.0.0.1",
-                shortNames            = ("user",),
-                authIDs               = set(),
-                fullName              = "Some user",
-                firstName             = "Some",
-                lastName              = "User",
-                emailAddresses        = set(("someuser at example.com",)),
-                memberGUIDs           = [],
-                nestedGUIDs           = [],
-                extProxies            = [],
-                extReadOnlyProxies    = [],
+                service=self.service(),
+                recordType=DirectoryService.recordType_users,
+                guid="B1F93EB1-DA93-4772-9141-81C250DA35B3",
+                nodeName="/LDAPv2/127.0.0.1",
+                shortNames=("user",),
+                authIDs=set(),
+                fullName="Some user",
+                firstName="Some",
+                lastName="User",
+                emailAddresses=set(("someuser at example.com",)),
+                memberGUIDs=[],
+                nestedGUIDs=[],
+                extProxies=[],
+                extReadOnlyProxies=[],
             )
 
             digestFields = {
-                "username":"user",
-                "realm":"/Search",
-                "nonce":"ABC",
-                "uri":"/",
-                "response":"123",
-                "algorithm":"md5",
+                "username": "user",
+                "realm": "/Search",
+                "nonce": "ABC",
+                "uri": "/",
+                "response": "123",
+                "algorithm": "md5",
             }
             od = deriveValue(self, "odModule", lambda self: None)
             od.response = (
@@ -393,7 +394,6 @@
 
                 return results
 
-
             #
             # OR
             #
@@ -428,7 +428,6 @@
             results = list(results)
             self.assertEquals(len(results), 1)
 
-
             #
             # AND
             #

Modified: CalendarServer/trunk/twistedcaldav/directorybackedaddressbook.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directorybackedaddressbook.py	2013-05-28 20:22:31 UTC (rev 11251)
+++ CalendarServer/trunk/twistedcaldav/directorybackedaddressbook.py	2013-05-29 02:20:45 UTC (rev 11252)
@@ -62,26 +62,27 @@
 #                uid = pwd.getpwnam(config.UserName)[2]
 #                gid = grp.getgrnam(config.GroupName)[2]
 #                os.chown(path, uid, gid)
-# 
+#
 #            log.info("Created %s" % (path,))
-#            
+#
 #        except (OSError,), e:
 #            # this is caused by multiprocessor race and is harmless
 #            if e.errno != errno.EEXIST:
 #                raise
 
-        
+
     def makeChild(self, name):
         from twistedcaldav.simpleresource import SimpleCalDAVResource
         return SimpleCalDAVResource(principalCollections=self.principalCollections())
 
+
     def provisionDirectory(self):
         if self.directory is None:
             directoryClass = namedClass(config.DirectoryAddressBook.type)
-        
+
             log.info("Configuring: %s:%r"
                  % (config.DirectoryAddressBook.type, config.DirectoryAddressBook.params))
-        
+
             #add self as "directoryBackedAddressBook" parameter
             params = config.DirectoryAddressBook.params.copy()
             params["directoryBackedAddressBook"] = self
@@ -93,9 +94,9 @@
                 return succeed(None)
 
             return self.directory.createCache()
-            
+
             #print ("DirectoryBackedAddressBookResource.provisionDirectory: provisioned")
-        
+
         return succeed(None)
 
 
@@ -120,6 +121,7 @@
            ),
         )
 
+
     def supportedReports(self):
         result = super(DirectoryBackedAddressBookResource, self).supportedReports()
         if config.EnableSyncReport:
@@ -127,9 +129,11 @@
             result.remove(davxml.Report(davxml.SyncCollection(),))
         return result
 
+
     def resourceType(self):
         return davxml.ResourceType.directory
 
+
     def resourceID(self):
         if self.directory:
             resource_id = uuid.uuid5(uuid.UUID("5AAD67BF-86DD-42D7-9161-6AF977E4DAA3"), self.directory.baseGUID).urn
@@ -137,40 +141,44 @@
             resource_id = "tag:unknown"
         return resource_id
 
+
     def isDirectoryBackedAddressBookCollection(self):
         return True
 
+
     def isAddressBookCollection(self):
         #print( "DirectoryBackedAddressBookResource.isAddressBookCollection: return True" )
         return True
 
+
     def isCollection(self):
         return True
 
+
     def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None):
         # Permissions here are fixed, and are not subject to inheritance rules, etc.
         return succeed(self.defaultAccessControlList())
-    
+
+
     @inlineCallbacks
     def renderHTTP(self, request):
         if not self.directory:
-            raise HTTPError(StatusResponse(responsecode.SERVICE_UNAVAILABLE,"Service is starting up" ))
+            raise HTTPError(StatusResponse(responsecode.SERVICE_UNAVAILABLE, "Service is starting up"))
         elif self.directory.liveQuery:
             response = (yield maybeDeferred(super(DirectoryBackedAddressBookResource, self).renderHTTP, request))
             returnValue(response)
         else:
-            available = (yield maybeDeferred(self.directory.available, ))
-        
+            available = (yield maybeDeferred(self.directory.available,))
+
             if not available:
-                raise HTTPError(StatusResponse(responsecode.SERVICE_UNAVAILABLE,"Service is starting up" ))
+                raise HTTPError(StatusResponse(responsecode.SERVICE_UNAVAILABLE, "Service is starting up"))
             else:
                 updateLock = self.directory.updateLock()
                 yield updateLock.acquire()
                 try:
                     response = (yield maybeDeferred(super(DirectoryBackedAddressBookResource, self).renderHTTP, request))
-    
+
                 finally:
                     yield updateLock.release()
-                
+
                 returnValue(response)
-

Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py	2013-05-28 20:22:31 UTC (rev 11251)
+++ CalendarServer/trunk/twistedcaldav/resource.py	2013-05-29 02:20:45 UTC (rev 11252)
@@ -419,7 +419,7 @@
                 customxml.PubSubXMPPPushKeyProperty.qname(),
             )
 
-        if self.isAddressBookCollection():
+        if self.isAddressBookCollection() and not self.isDirectoryBackedAddressBookCollection():
             baseProperties += (
                 element.ResourceID.qname(),
                 carddavxml.SupportedAddressData.qname(),
@@ -431,6 +431,12 @@
                     carddavxml.MaxResourceSize.qname(),
                 )
 
+        if self.isDirectoryBackedAddressBookCollection():
+            baseProperties += (
+                element.ResourceID.qname(),
+                carddavxml.SupportedAddressData.qname(),
+            )
+
         if self.isNotificationCollection():
             baseProperties += (
                 customxml.GETCTag.qname(),
@@ -444,7 +450,7 @@
         if config.EnableSyncReport and (element.Report(element.SyncCollection(),) in self.supportedReports()):
             baseProperties += (element.SyncToken.qname(),)
 
-        if config.EnableAddMember and (self.isCalendarCollection() or self.isAddressBookCollection()):
+        if config.EnableAddMember and (self.isCalendarCollection() or self.isAddressBookCollection() and not self.isDirectoryBackedAddressBookCollection()):
             baseProperties += (element.AddMember.qname(),)
 
         if config.Sharing.Enabled:
@@ -455,7 +461,7 @@
                     customxml.SharedURL.qname(),
                 )
 
-            elif config.Sharing.AddressBooks.Enabled and self.isAddressBookCollection():
+            elif config.Sharing.AddressBooks.Enabled and self.isAddressBookCollection() and not self.isDirectoryBackedAddressBookCollection():
                 baseProperties += (
                     customxml.Invite.qname(),
                     customxml.AllowedSharingModes.qname(),
@@ -525,16 +531,13 @@
         else:
             qname = property.qname()
 
-        if self.isCalendarCollection() or self.isAddressBookCollection():
+        if self.isCalendarCollection() or (self.isAddressBookCollection() and not self.isDirectoryBackedAddressBookCollection()):
 
             # Push notification DAV property "pushkey"
             if qname == customxml.PubSubXMPPPushKeyProperty.qname():
 
-                # FIXME: is there a better way to get back to the associated
-                # datastore object?
-                dataObject = getattr(self, "_newStoreObject")
-                if dataObject is not None:
-                    notifier = dataObject.getNotifier("push")
+                if hasattr(self, "_newStoreObject"):
+                    notifier = self._newStoreObject.getNotifier("push")
                     if notifier is not None:
                         propVal = customxml.PubSubXMPPPushKeyProperty(notifier.nodeName())
                         returnValue(propVal)
@@ -567,7 +570,7 @@
 
         elif qname == customxml.GETCTag.qname() and (
             self.isPseudoCalendarCollection() or
-            self.isAddressBookCollection() or
+            self.isAddressBookCollection() and not self.isDirectoryBackedAddressBookCollection() or
             self.isNotificationCollection()
         ):
             returnValue(customxml.GETCTag.fromString((yield self.getInternalSyncToken())))
@@ -578,7 +581,7 @@
             returnValue(element.SyncToken.fromString((yield self.getSyncToken())))
 
         elif qname == element.AddMember.qname() and config.EnableAddMember and (
-            self.isCalendarCollection() or self.isAddressBookCollection()
+            self.isCalendarCollection() or self.isAddressBookCollection() and not self.isDirectoryBackedAddressBookCollection()
         ):
             url = (yield self.canonicalURL(request))
             returnValue(element.AddMember(element.HRef.fromString(url + "/;add-member")))
@@ -631,7 +634,7 @@
                 }),
             ))
 
-        elif qname == carddavxml.MaxResourceSize.qname() and self.isAddressBookCollection():
+        elif qname == carddavxml.MaxResourceSize.qname() and self.isAddressBookCollection() and not self.isDirectoryBackedAddressBookCollection():
             # CardDAV, section 6.2.3
             if config.MaxResourceSize:
                 returnValue(carddavxml.MaxResourceSize.fromString(
@@ -641,7 +644,7 @@
         elif qname == customxml.Invite.qname():
             if config.Sharing.Enabled and (
                 config.Sharing.Calendars.Enabled and self.isCalendarCollection() or
-                config.Sharing.AddressBooks.Enabled and self.isAddressBookCollection()
+                config.Sharing.AddressBooks.Enabled and self.isAddressBookCollection() and not self.isDirectoryBackedAddressBookCollection()
             ):
                 result = (yield self.inviteProperty(request))
                 returnValue(result)
@@ -649,7 +652,7 @@
         elif qname == customxml.AllowedSharingModes.qname():
             if config.Sharing.Enabled and config.Sharing.Calendars.Enabled and self.isCalendarCollection():
                 returnValue(customxml.AllowedSharingModes(customxml.CanBeShared()))
-            elif config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled and self.isAddressBookCollection():
+            elif config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled and self.isAddressBookCollection() and not self.isDirectoryBackedAddressBookCollection():
                 returnValue(customxml.AllowedSharingModes(customxml.CanBeShared()))
 
         elif qname == customxml.SharedURL.qname():
@@ -1109,8 +1112,9 @@
 
     def supportedReports(self):
         result = super(CalDAVResource, self).supportedReports()
-        result.append(element.Report(caldavxml.CalendarQuery(),))
-        result.append(element.Report(caldavxml.CalendarMultiGet(),))
+        if config.EnableCalDAV:
+            result.append(element.Report(caldavxml.CalendarQuery(),))
+            result.append(element.Report(caldavxml.CalendarMultiGet(),))
         if self.isCollection():
             # Only allowed on collections
             result.append(element.Report(caldavxml.FreeBusyQuery(),))

Modified: CalendarServer/trunk/twistedcaldav/sharing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/sharing.py	2013-05-28 20:22:31 UTC (rev 11251)
+++ CalendarServer/trunk/twistedcaldav/sharing.py	2013-05-29 02:20:45 UTC (rev 11252)
@@ -239,7 +239,10 @@
         """
         Return True if this is an owner shared calendar collection.
         """
-        return self._newStoreObject.isShared() if self._newStoreObject else False
+        try:
+            return self._newStoreObject.isShared() if self._newStoreObject else False
+        except AttributeError:
+            return False
 
 
     def setShare(self, share):
@@ -498,7 +501,7 @@
 
 
     def uninviteUserToShare(self, userid, ace, request):
-        """ 
+        """
         Send out in uninvite first, and then remove this user from the share list.
         """
         # Do not validate the userid - we want to allow invalid users to be removed because they
@@ -573,7 +576,7 @@
             returnValue([])
 
         #TODO: Cache
-        if True:  #not hasattr(self, "_invitations"):
+        if True:  # not hasattr(self, "_invitations"):
 
             acceptedHomeChildren = yield self._newStoreObject.asShared()
             # remove direct shares (it might be OK not to remove these, but that would be different from legacy code)
@@ -582,7 +585,7 @@
             invitedHomeChildren = (yield self._newStoreObject.asInvited()) + indirectAccceptedHomeChildren
 
             self._invitations = sorted([Invitation(homeChild) for homeChild in invitedHomeChildren],
-                                 key=lambda invitation:invitation.shareeUID())
+                                 key=lambda invitation: invitation.shareeUID())
 
         returnValue(self._invitations)
 
@@ -612,7 +615,7 @@
 
 
     @inlineCallbacks
-    def inviteSingleUserToShare(self, userid, cn, ace, summary, request):  #@UnusedVariable
+    def inviteSingleUserToShare(self, userid, cn, ace, summary, request): #@UnusedVariable
 
         # We currently only handle local users
         sharee = self.principalForCalendarUserAddress(userid)
@@ -637,7 +640,7 @@
 
 
     @inlineCallbacks
-    def uninviteSingleUserFromShare(self, userid, aces, request):  #@UnusedVariable
+    def uninviteSingleUserFromShare(self, userid, aces, request): #@UnusedVariable
         # Cancel invites - we'll just use whatever userid we are given
 
         sharee = self.principalForCalendarUserAddress(userid)
@@ -682,7 +685,7 @@
         returnValue(True)
 
 
-    def inviteSingleUserUpdateToShare(self, userid, commonName, acesOLD, aceNEW, summary, request):  #@UnusedVariable
+    def inviteSingleUserUpdateToShare(self, userid, commonName, acesOLD, aceNEW, summary, request): #@UnusedVariable
 
         # Just update existing
         return self.inviteSingleUserToShare(userid, commonName, aceNEW, summary, request)
@@ -1076,7 +1079,8 @@
 
         if not request:
             # FIXEME:  Fake up a request that can be used to get the owner home resource
-            class _FakeRequest(object):pass
+            class _FakeRequest(object):
+                pass
             fakeRequest = _FakeRequest()
             setattr(fakeRequest, TRANSACTION_KEY, self._newStoreHome._txn)
             request = fakeRequest
@@ -1411,6 +1415,7 @@
             return self.declineShare(request, hostUrl, replytoUID)
 
 
+
 class Share(object):
     """
     A L{Share} represents information about a collection which has been shared

Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/stdconfig.py	2013-05-28 20:22:31 UTC (rev 11251)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py	2013-05-29 02:20:45 UTC (rev 11252)
@@ -970,13 +970,6 @@
     "FreeBusyIndexExpandMaxDays": 5 * 365,
     "FreeBusyIndexDelayedExpand": True,
 
-    # Specify which opendirectory module to use:
-    # "opendirectory" is PyOpenDirectory (the old one which uses
-    # DirectoryService.framework)
-    # "calendarserver.platform.darwin.od.opendirectory" is the new PyObjC
-    # version which uses OpenDirectory.framework
-    "OpenDirectoryModule": "opendirectory",
-
     # The RootResource uses a twext property store. Specify the class here
     "RootResourcePropStoreClass": "twext.web2.dav.xattrprops.xattrPropertyStore",
 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130528/aea2ed6d/attachment-0001.html>


More information about the calendarserver-changes mailing list