[CalendarServer-changes] [11900] CalendarServer/branches/users/cdaboo/reverse-proxy-pods

source_changes at macosforge.org source_changes at macosforge.org
Wed Mar 12 11:23:22 PDT 2014


Revision: 11900
          http://trac.calendarserver.org//changeset/11900
Author:   cdaboo at apple.com
Date:     2013-11-06 18:00:24 -0800 (Wed, 06 Nov 2013)
Log Message:
-----------
Merge from trunk.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/reverse-proxy-pods/HACKING
    CalendarServer/branches/users/cdaboo/reverse-proxy-pods/twext/who/directory.py
    CalendarServer/branches/users/cdaboo/reverse-proxy-pods/twext/who/idirectory.py
    CalendarServer/branches/users/cdaboo/reverse-proxy-pods/twext/who/test/test_aggregate.py
    CalendarServer/branches/users/cdaboo/reverse-proxy-pods/twext/who/test/test_directory.py
    CalendarServer/branches/users/cdaboo/reverse-proxy-pods/twext/who/test/test_util.py
    CalendarServer/branches/users/cdaboo/reverse-proxy-pods/twext/who/test/test_xml.py
    CalendarServer/branches/users/cdaboo/reverse-proxy-pods/txdav/common/datastore/upgrade/sql/upgrades/calendar_upgrade_from_3_to_4.py
    CalendarServer/branches/users/cdaboo/reverse-proxy-pods/txdav/common/datastore/upgrade/sql/upgrades/calendar_upgrade_from_4_to_5.py

Property Changed:
----------------
    CalendarServer/branches/users/cdaboo/reverse-proxy-pods/


Property changes on: CalendarServer/branches/users/cdaboo/reverse-proxy-pods
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalDAVTester/trunk:11193-11198
/CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/generic-sqlstore:6167-6191
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/release/CalendarServer-4.3-dev:10180-10190,10192
/CalendarServer/branches/release/CalendarServer-5.1-dev:11846
/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/component-set-fixes:8130-8346
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/fix-no-ischedule:11607-11871
/CalendarServer/branches/users/cdaboo/implicituidrace:8137-8141
/CalendarServer/branches/users/cdaboo/ischedule-dkim:9747-9979
/CalendarServer/branches/users/cdaboo/managed-attachments:9985-10145
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/performance-tweaks:11824-11836
/CalendarServer/branches/users/cdaboo/pods:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
/CalendarServer/branches/users/cdaboo/pycard:7227-7237
/CalendarServer/branches/users/cdaboo/queued-attendee-refreshes:7740-8287
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/cdaboo/store-scheduling:10876-11129
/CalendarServer/branches/users/cdaboo/timezones:7443-7699
/CalendarServer/branches/users/cdaboo/txn-debugging:8730-8743
/CalendarServer/branches/users/gaya/sharedgroups-3:11088-11204
/CalendarServer/branches/users/glyph/always-abort-txn-on-error:9958-9969
/CalendarServer/branches/users/glyph/case-insensitive-uid:8772-8805
/CalendarServer/branches/users/glyph/conn-limit:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/dalify:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
/CalendarServer/branches/users/glyph/deploybuild:7563-7572
/CalendarServer/branches/users/glyph/digest-auth-redux:10624-10635
/CalendarServer/branches/users/glyph/disable-quota:7718-7727
/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
/CalendarServer/branches/users/glyph/enforce-max-requests:11640-11643
/CalendarServer/branches/users/glyph/hang-fix:11465-11491
/CalendarServer/branches/users/glyph/imip-and-admin-html:7866-7984
/CalendarServer/branches/users/glyph/ipv6-client:9054-9105
/CalendarServer/branches/users/glyph/launchd-wrapper-bis:11413-11436
/CalendarServer/branches/users/glyph/linux-tests:6893-6900
/CalendarServer/branches/users/glyph/log-cleanups:11691-11731
/CalendarServer/branches/users/glyph/migrate-merge:8690-8713
/CalendarServer/branches/users/glyph/misc-portability-fixes:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/multiget-delete:8321-8330
/CalendarServer/branches/users/glyph/new-export:7444-7485
/CalendarServer/branches/users/glyph/one-home-list-api:10048-10073
/CalendarServer/branches/users/glyph/oracle:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls:7340-7351
/CalendarServer/branches/users/glyph/other-html:8062-8091
/CalendarServer/branches/users/glyph/parallel-sim:8240-8251
/CalendarServer/branches/users/glyph/parallel-upgrade:8376-8400
/CalendarServer/branches/users/glyph/parallel-upgrade_to_1:8571-8583
/CalendarServer/branches/users/glyph/q:9560-9688
/CalendarServer/branches/users/glyph/queue-locking-and-timing:10204-10289
/CalendarServer/branches/users/glyph/quota:7604-7637
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/shared-pool-fixes:8436-8443
/CalendarServer/branches/users/glyph/shared-pool-take2:8155-8174
/CalendarServer/branches/users/glyph/sharedpool:6490-6550
/CalendarServer/branches/users/glyph/sharing-api:9192-9205
/CalendarServer/branches/users/glyph/skip-lonely-vtimezones:8524-8535
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/start-service-start-loop:11060-11065
/CalendarServer/branches/users/glyph/subtransactions:7248-7258
/CalendarServer/branches/users/glyph/table-alias:8651-8664
/CalendarServer/branches/users/glyph/uidexport:7673-7676
/CalendarServer/branches/users/glyph/unshare-when-access-revoked:10562-10595
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/glyph/uuid-normalize:9268-9296
/CalendarServer/branches/users/glyph/warning-cleanups:11347-11357
/CalendarServer/branches/users/glyph/xattrs-from-files:7757-7769
/CalendarServer/branches/users/sagen/applepush:8126-8184
/CalendarServer/branches/users/sagen/inboxitems:7380-7381
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/sagen/testing:10827-10851,10853-10855
/CalendarServer/branches/users/wsanchez/transations:5515-5593
   + /CalDAVTester/trunk:11193-11198
/CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/generic-sqlstore:6167-6191
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/release/CalendarServer-4.3-dev:10180-10190,10192
/CalendarServer/branches/release/CalendarServer-5.1-dev:11846
/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/component-set-fixes:8130-8346
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/fix-no-ischedule:11607-11871
/CalendarServer/branches/users/cdaboo/implicituidrace:8137-8141
/CalendarServer/branches/users/cdaboo/ischedule-dkim:9747-9979
/CalendarServer/branches/users/cdaboo/managed-attachments:9985-10145
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/performance-tweaks:11824-11836
/CalendarServer/branches/users/cdaboo/pods:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
/CalendarServer/branches/users/cdaboo/pycard:7227-7237
/CalendarServer/branches/users/cdaboo/queued-attendee-refreshes:7740-8287
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/cdaboo/store-scheduling:10876-11129
/CalendarServer/branches/users/cdaboo/timezones:7443-7699
/CalendarServer/branches/users/cdaboo/txn-debugging:8730-8743
/CalendarServer/branches/users/gaya/sharedgroups-3:11088-11204
/CalendarServer/branches/users/glyph/always-abort-txn-on-error:9958-9969
/CalendarServer/branches/users/glyph/case-insensitive-uid:8772-8805
/CalendarServer/branches/users/glyph/conn-limit:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/dalify:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
/CalendarServer/branches/users/glyph/deploybuild:7563-7572
/CalendarServer/branches/users/glyph/digest-auth-redux:10624-10635
/CalendarServer/branches/users/glyph/disable-quota:7718-7727
/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
/CalendarServer/branches/users/glyph/enforce-max-requests:11640-11643
/CalendarServer/branches/users/glyph/hang-fix:11465-11491
/CalendarServer/branches/users/glyph/imip-and-admin-html:7866-7984
/CalendarServer/branches/users/glyph/ipv6-client:9054-9105
/CalendarServer/branches/users/glyph/launchd-wrapper-bis:11413-11436
/CalendarServer/branches/users/glyph/linux-tests:6893-6900
/CalendarServer/branches/users/glyph/log-cleanups:11691-11731
/CalendarServer/branches/users/glyph/migrate-merge:8690-8713
/CalendarServer/branches/users/glyph/misc-portability-fixes:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/multiget-delete:8321-8330
/CalendarServer/branches/users/glyph/new-export:7444-7485
/CalendarServer/branches/users/glyph/one-home-list-api:10048-10073
/CalendarServer/branches/users/glyph/oracle:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls:7340-7351
/CalendarServer/branches/users/glyph/other-html:8062-8091
/CalendarServer/branches/users/glyph/parallel-sim:8240-8251
/CalendarServer/branches/users/glyph/parallel-upgrade:8376-8400
/CalendarServer/branches/users/glyph/parallel-upgrade_to_1:8571-8583
/CalendarServer/branches/users/glyph/q:9560-9688
/CalendarServer/branches/users/glyph/queue-locking-and-timing:10204-10289
/CalendarServer/branches/users/glyph/quota:7604-7637
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/shared-pool-fixes:8436-8443
/CalendarServer/branches/users/glyph/shared-pool-take2:8155-8174
/CalendarServer/branches/users/glyph/sharedpool:6490-6550
/CalendarServer/branches/users/glyph/sharing-api:9192-9205
/CalendarServer/branches/users/glyph/skip-lonely-vtimezones:8524-8535
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/start-service-start-loop:11060-11065
/CalendarServer/branches/users/glyph/subtransactions:7248-7258
/CalendarServer/branches/users/glyph/table-alias:8651-8664
/CalendarServer/branches/users/glyph/uidexport:7673-7676
/CalendarServer/branches/users/glyph/unshare-when-access-revoked:10562-10595
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/glyph/uuid-normalize:9268-9296
/CalendarServer/branches/users/glyph/warning-cleanups:11347-11357
/CalendarServer/branches/users/glyph/xattrs-from-files:7757-7769
/CalendarServer/branches/users/sagen/applepush:8126-8184
/CalendarServer/branches/users/sagen/inboxitems:7380-7381
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/sagen/testing:10827-10851,10853-10855
/CalendarServer/branches/users/wsanchez/transations:5515-5593
/CalendarServer/trunk:11875-11895

Modified: CalendarServer/branches/users/cdaboo/reverse-proxy-pods/HACKING
===================================================================
--- CalendarServer/branches/users/cdaboo/reverse-proxy-pods/HACKING	2013-11-06 23:41:28 UTC (rev 11899)
+++ CalendarServer/branches/users/cdaboo/reverse-proxy-pods/HACKING	2013-11-07 02:00:24 UTC (rev 11900)
@@ -144,10 +144,6 @@
 
 PEP-8 items we do not follow:
 
- * Lines need not be limited to 79 spaces, but longer lines are
-   undesirable.  If you can easily do so, try to keep lines under 80
-   columns.
-
  * PEP-8 recommends using a backslash to break long lines up:
 
    ::
@@ -181,14 +177,6 @@
 
    Because that's just silly.
 
- * Lining up assignments is OK, within reason:
-
-   ::
-
-     cars       =  4
-     motorbikes =  8
-     bicycles   = 18
-
 Additions:
 
  * Close parentheses and brackets such as ``()``, ``[]`` and ``{}`` at the
@@ -248,9 +236,8 @@
 
      process = subprocess.Popen(...)
 
-   This makes code shorter and removes the runtime indirection (which
-   can be relevant in tight loops). It also makes it easier to replace
-   one implementation with another.
+   This makes code shorter and makes it easier to replace one implementation
+   with another.
 
  * All files should have an ``__all__`` specification.  Put them at the
    top of the file, before imports (PEP-8 puts them at the top, but
@@ -259,8 +246,8 @@
 
  * It is more important that symbol names are meaningful than it is
    that they be concise.  ``x`` is rarely an appropriate name for a
-   variable.  ``transmogrifierStatus`` is more useful to the reader
-   than ``trmgStat``; avoid contractions.
+   variable.  Avoid contractions: ``transmogrifierStatus`` is more useful
+   to the reader than ``trmgStat``.
 
  * A deferred that will be immediately returned may be called ``d``:
 
@@ -271,9 +258,7 @@
      d.addErrback(onError)
      return d
 
- * We prefer ``inlineCallbacks`` over ``deferredGenerator``.
-   ``inlineCallbacks`` are more readable, and we do not support Python
-   versions old enough that ``deferredGenerator`` would be necessary.
+ * Do not use ``deferredGenerator``.  Use ``inlineCallbacks`` instead.
 
  * That said, avoid using ``inlineCallbacks`` when chaining deferreds
    is straightforward, as they are more expensive.  Use
@@ -306,17 +291,29 @@
    Use of underscores is reserved for implied dispatching and the like
    (eg. ``http_FOO()``).  See the Twisted Coding Standard for details.
 
- * Always use a tuple when using ``%``-formatting, even when only one
-   value is being provided:
+ * Do not use ``%``-formatting:
 
    ::
 
      error = "Unexpected value: %s" % (value,)
 
-   Do not use the non-tuple form:
+   Use PEP-3101 formatting instead:
 
    ::
 
+     error = "Unexpected value: {value}".format(value=value)
+
+ * If you must use ``%``-formatting for some reason, always use a tuple as
+   the format argument, even when only one value is being provided:
+
+   ::
+
+     error = "Unexpected value: %s" % (value,)
+
+   Never use the non-tuple form:
+
+   ::
+
      error = "Unexpected value: %s" % value
 
    Which is allowed in Python, but results in a programming error if
@@ -329,8 +326,9 @@
      numbers = (1,2,3,) # No
      numbers = (1,2,3)  # Yes
 
-   It's desirable on multiple lines, though, as that makes re-ordering
-   items easy, and avoids a diff on the last line when adding another:
+   The trailing comma is desirable on multiple lines, though, as that makes
+   re-ordering items easy, and avoids a diff on the last line when adding
+   another:
 
    ::
 
@@ -368,11 +366,11 @@
 ==============
 
  * If a callable is going to return a Deferred some of the time, it
-   should probably return a deferred all of the time.  Return
-   ``succeed(value)`` instead of ``value`` if necessary.  This avoids
-   forcing the caller to check as to whether the value is a deferred
-   or not (eg. by using ``maybeDeferred()``), which is both annoying
-   to code and potentially expensive at runtime.
+   should return a deferred all of the time.  Return ``succeed(value)``
+   instead of ``value`` if necessary.  This avoids forcing the caller
+   to check as to whether the value is a deferred or not (eg. by using
+   ``maybeDeferred()``), which is both annoying to code and potentially
+   expensive at runtime.
 
  * Be proactive about closing files and file-like objects.
 

Modified: CalendarServer/branches/users/cdaboo/reverse-proxy-pods/twext/who/directory.py
===================================================================
--- CalendarServer/branches/users/cdaboo/reverse-proxy-pods/twext/who/directory.py	2013-11-06 23:41:28 UTC (rev 11899)
+++ CalendarServer/branches/users/cdaboo/reverse-proxy-pods/twext/who/directory.py	2013-11-07 02:00:24 UTC (rev 11900)
@@ -26,7 +26,7 @@
 
 from uuid import UUID
 
-from zope.interface import implements
+from zope.interface import implementer
 
 from twisted.internet.defer import inlineCallbacks, returnValue
 from twisted.internet.defer import succeed, fail
@@ -40,19 +40,63 @@
 
 
 
+ at implementer(IDirectoryService)
 class DirectoryService(object):
-    implements(IDirectoryService)
+    """
+    Generic implementation of L{IDirectoryService}.
 
+    This is a complete implementation of L{IDirectoryService}, with support for
+    the query operands in L{Operand}.
+
+    The C{recordsWith*} methods are all implemented in terms of
+    L{recordsWithFieldValue}, which is in turn implemented in terms of
+    L{recordsFromExpression}.
+    L{recordsFromQuery} is also implemented in terms of
+    {recordsFromExpression}.
+
+    L{recordsFromExpression} (and therefore most uses of the other methods)
+    will always fail with a L{QueryNotSupportedError}.
+
+    A subclass should therefore override L{recordsFromExpression} with an
+    implementation that handles any queries that it can support and its
+    superclass' implementation with any query it cannot support.
+
+    A subclass may override L{recordsFromQuery} if it is to support additional
+    operands.
+
+    L{updateRecords} and L{removeRecords} will fail with L{NotAllowedError}
+    when asked to modify data.
+    A subclass should override these methods if is to allow editing of
+    directory information.
+
+    @cvar recordType: a L{Names} class or compatible object (eg.
+        L{ConstantsContainer}) which contains the L{NamedConstant}s denoting
+        the record types that are supported by this directory service.
+
+    @cvar fieldName: a L{Names} class or compatible object (eg.
+        L{ConstantsContainer}) which contains the L{NamedConstant}s denoting
+        the record field names that are supported by this directory service.
+
+    @cvar normalizedFields: a L{dict} mapping of (ie. L{NamedConstant}s
+        contained in the C{fieldName} class variable) to callables that take
+        a field value (a L{unicode}) and return a normalized field value (also
+        a L{unicode}).
+    """
+
     recordType = RecordType
     fieldName  = FieldName
 
     normalizedFields = {
         FieldName.guid: lambda g: UUID(g).hex,
-        FieldName.emailAddresses: lambda e: e.lower(),
+        FieldName.emailAddresses: lambda e: bytes(e).lower(),
     }
 
 
     def __init__(self, realmName):
+        """
+        @param realmName: a realm name
+        @type realmName: unicode
+        """
         self.realmName = realmName
 
 
@@ -70,11 +114,30 @@
     def recordsFromExpression(self, expression, records=None):
         """
         Finds records matching a single expression.
-        @param expression: an expression
+
+        @note: The implementation in L{DirectoryService} always raises
+            L{QueryNotSupportedError}.
+
+        @note: This L{DirectoryService} adds a C{records} keyword argument to
+            the interface defined by L{IDirectoryService}.
+            This allows the implementation of
+            L{DirectoryService.recordsFromQuery} to narrow the scope of records
+            being searched as it applies expressions.
+            This is therefore relevant to subclasses, which need to support the
+            added parameter, but not to users of L{IDirectoryService}.
+
+        @param expression: an expression to apply
         @type expression: L{object}
-        @param records: a set of records to search within. C{None} if
+
+        @param records: a set of records to limit the search to. C{None} if
             the whole directory should be searched.
         @type records: L{set} or L{frozenset}
+
+        @return: The matching records.
+        @rtype: deferred iterable of L{IDirectoryRecord}s
+
+        @raises: L{QueryNotSupportedError} if the expression is not
+            supported by this directory service.
         """
         return fail(QueryNotSupportedError(
             "Unknown expression: {0}".format(expression)
@@ -158,17 +221,32 @@
     def updateRecords(self, records, create=False):
         for record in records:
             return fail(NotAllowedError("Record updates not allowed."))
+        return succeed(None)
 
 
     def removeRecords(self, uids):
         for uid in uids:
             return fail(NotAllowedError("Record removal not allowed."))
+        return succeed(None)
 
 
 
+ at implementer(IDirectoryRecord)
 class DirectoryRecord(object):
-    implements(IDirectoryRecord)
+    """
+    Generic implementation of L{IDirectoryService}.
 
+    This is an incomplete implementation of L{IDirectoryRecord}.
+
+    L{groups} will always fail with L{NotImplementedError} and L{members} will
+    do so if this is a group record.
+    A subclass should override these methods to support group membership and
+    complete this implementation.
+
+    @cvar requiredFields: an iterable of field names that must be present in
+        all directory records.
+    """
+
     requiredFields = (
         FieldName.uid,
         FieldName.recordType,
@@ -285,9 +363,11 @@
 
     def members(self):
         if self.recordType == RecordType.group:
-            raise NotImplementedError("Subclasses must implement members()")
+            return fail(
+                NotImplementedError("Subclasses must implement members()")
+            )
         return succeed(())
 
 
     def groups(self):
-        raise NotImplementedError("Subclasses must implement groups()")
+        return fail(NotImplementedError("Subclasses must implement groups()"))

Modified: CalendarServer/branches/users/cdaboo/reverse-proxy-pods/twext/who/idirectory.py
===================================================================
--- CalendarServer/branches/users/cdaboo/reverse-proxy-pods/twext/who/idirectory.py	2013-11-06 23:41:28 UTC (rev 11899)
+++ CalendarServer/branches/users/cdaboo/reverse-proxy-pods/twext/who/idirectory.py	2013-11-07 02:00:24 UTC (rev 11900)
@@ -42,9 +42,9 @@
 
 
 
-##
+#
 # Exceptions
-##
+#
 
 class DirectoryServiceError(Exception):
     """
@@ -55,7 +55,7 @@
 
 class DirectoryConfigurationError(DirectoryServiceError):
     """
-    Directory configurtion error.
+    Directory configuration error.
     """
 
 
@@ -93,16 +93,19 @@
 
 class NotAllowedError(DirectoryServiceError):
     """
-    Apparently, you can't do that.
+    It seems you aren't permitted to do that.
     """
 
 
 
-##
+#
 # Data Types
-##
+#
 
 class RecordType(Names):
+    """
+    Constants for common directory record types.
+    """
     user  = NamedConstant()
     group = NamedConstant()
 
@@ -113,7 +116,31 @@
 
 class FieldName(Names):
     """
-    Constants for common field names.
+    Constants for common directory record field names.
+
+    Fields as assciated with either a single value or an iterable of values.
+
+    @cvar uid: The primary unique identifier for a directory record.
+        The associated value must be a L{unicode}.
+
+    @cvar guid: The globally unique identifier for a directory record.
+        The associated value must be a L{UUID} or C{None}.
+
+    @cvar recordType: The type of a directory record.
+        The associated value must be a L{NamedConstant}.
+
+    @cvar shortNames: The short names for a directory record.
+        The associated values must L{unicode}s and there must be at least
+        one associated value.
+
+    @cvar fullNames: The full names for a directory record.
+        The associated values must be L{unicode}s.
+
+    @cvar emailAddresses: The email addresses for a directory record.
+        The associated values must be L{unicodes}.
+
+    @cvar password: The clear text password for a directory record.
+        The associated value must be a L{unicode} or C{None}.
     """
     uid            = NamedConstant()
     guid           = NamedConstant()
@@ -138,11 +165,20 @@
 
     @staticmethod
     def isMultiValue(name):
+        """
+        Check for whether a field is multi-value (as opposed to single-value).
+
+        @return: C{True} if the field is multi-value, C{False} otherwise.
+        @rtype: L{BOOL}
+        """
         return getattr(name, "multiValue", False)
 
 
 
 class Operand(Names):
+    """
+    Contants for common operands.
+    """
     OR  = NamedConstant()
     AND = NamedConstant()
 
@@ -151,9 +187,9 @@
 
 
 
-##
+#
 # Interfaces
-##
+#
 
 class IDirectoryService(Interface):
     """
@@ -169,7 +205,15 @@
 
     A directory service may allow support the editing, removal and
     addition of records.
+    Services are read-only should fail with L{NotAllowedError} in editing
+    methods.
+
+    The L{FieldName.uid} field, the L{FieldName.guid} field (if not C{None}),
+    and the combination of the L{FieldName.recordType} and
+    L{FieldName.shortName} fields must be unique to each directory record
+    vended by a directory service.
     """
+
     realmName = Attribute(
         "The name of the authentication realm this service represents."
     )
@@ -177,8 +221,10 @@
 
     def recordTypes():
         """
-        @return: an iterable of L{NamedConstant}s denoting the record
-            types that are kept in this directory.
+        Get the record types supported by this directory service.
+
+        @return: The record types that are supported by this directory service.
+        @rtype: iterable of L{NamedConstant}s
         """
 
 
@@ -189,7 +235,8 @@
         @param expression: an expression to apply
         @type expression: L{object}
 
-        @return: a deferred iterable of matching L{IDirectoryRecord}s.
+        @return: The matching records.
+        @rtype: deferred iterable of L{IDirectoryRecord}s
 
         @raises: L{QueryNotSupportedError} if the expression is not
             supported by this directory service.
@@ -207,7 +254,8 @@
         @param operand: an operand
         @type operand: a L{NamedConstant}
 
-        @return: a deferred iterable of matching L{IDirectoryRecord}s.
+        @return: The matching records.
+        @rtype: deferred iterable of L{IDirectoryRecord}s
 
         @raises: L{QueryNotSupportedError} if the query is not
             supported by this directory service.
@@ -225,7 +273,8 @@
         @param value: a value to match
         @type value: L{bytes}
 
-        @return: a deferred iterable of L{IDirectoryRecord}s.
+        @return: The matching records.
+        @rtype: deferred iterable of L{IDirectoryRecord}s
         """
 
 
@@ -236,8 +285,8 @@
         @param uid: a UID
         @type uid: L{bytes}
 
-        @return: a deferred iterable of L{IDirectoryRecord}s, or
-            C{None} if there is no such record.
+        @return: The matching record or C{None} if there is no match.
+        @rtype: deferred L{IDirectoryRecord}s or C{None}
         """
 
 
@@ -248,8 +297,8 @@
         @param guid: a GUID
         @type guid: L{bytes}
 
-        @return: a deferred iterable of L{IDirectoryRecord}s, or
-            C{None} if there is no such record.
+        @return: The matching record or C{None} if there is no match.
+        @rtype: deferred L{IDirectoryRecord}s or C{None}
         """
 
 
@@ -260,7 +309,8 @@
         @param recordType: a record type
         @type recordType: L{NamedConstant}
 
-        @return: a deferred iterable of L{IDirectoryRecord}s.
+        @return: The matching records.
+        @rtype: deferred iterable of L{IDirectoryRecord}s
         """
 
 
@@ -274,8 +324,8 @@
         @param shortName: a short name
         @type shortName: L{bytes}
 
-        @return: a deferred iterable of L{IDirectoryRecord}s, or
-            C{None} if there is no such record.
+        @return: The matching record or C{None} if there is no match.
+        @rtype: deferred L{IDirectoryRecord}s or C{None}
         """
 
 
@@ -286,8 +336,8 @@
         @param emailAddress: an email address
         @type emailAddress: L{bytes}
 
-        @return: a deferred iterable of L{IDirectoryRecord}s, or
-            C{None} if there is no such record.
+        @return: The matching records.
+        @rtype: deferred iterable of L{IDirectoryRecord}s
         """
 
 
@@ -300,6 +350,12 @@
 
         @param create: if true, create records if necessary
         @type create: boolean
+
+        @return: unspecifiied
+        @rtype: deferred object
+
+        @raises L{NotAllowedError}: if the update is not allowed by the
+            directory service.
         """
 
 
@@ -309,6 +365,12 @@
 
         @param uids: the UIDs of the records to remove
         @type uids: iterable of L{bytes}
+
+        @return: unspecifiied
+        @rtype: deferred object
+
+        @raises L{NotAllowedError}: if the removal is not allowed by the
+            directory service.
         """
 
 
@@ -349,6 +411,7 @@
         """
         Find the records that are members of this group.  Only direct
         members are included; members of members are not expanded.
+
         @return: a deferred iterable of L{IDirectoryRecord}s which are
             direct members of this group.
         """
@@ -359,6 +422,7 @@
         Find the group records that this record is a member of.  Only
         groups for which this record is a direct member is are
         included; membership is not expanded.
+
         @return: a deferred iterable of L{IDirectoryRecord}s which are
             groups that this record is a member of.
         """

Modified: CalendarServer/branches/users/cdaboo/reverse-proxy-pods/twext/who/test/test_aggregate.py
===================================================================
--- CalendarServer/branches/users/cdaboo/reverse-proxy-pods/twext/who/test/test_aggregate.py	2013-11-06 23:41:28 UTC (rev 11899)
+++ CalendarServer/branches/users/cdaboo/reverse-proxy-pods/twext/who/test/test_aggregate.py	2013-11-07 02:00:24 UTC (rev 11900)
@@ -26,7 +26,8 @@
 from twext.who.util import ConstantsContainer
 
 from twext.who.test import test_directory, test_xml
-from twext.who.test.test_xml import QueryMixIn, xmlService, TestService as XMLTestService
+from twext.who.test.test_xml import QueryMixIn, xmlService
+from twext.who.test.test_xml import TestService as XMLTestService
 
 
 
@@ -68,7 +69,10 @@
 
 
 
-class DirectoryServiceImmutableTest(BaseTest, test_directory.DirectoryServiceImmutableTest):
+class DirectoryServiceImmutableTest(
+    BaseTest,
+    test_directory.BaseDirectoryServiceImmutableTest,
+):
     pass
 
 
@@ -81,24 +85,39 @@
         class GroupsDirectoryService(XMLTestService):
             recordType = ConstantsContainer((XMLTestService.recordType.group,))
 
-        usersService  = self.xmlService(testXMLConfigUsers, UsersDirectoryService)
-        groupsService = self.xmlService(testXMLConfigGroups, GroupsDirectoryService)
+        usersService = self.xmlService(
+            testXMLConfigUsers,
+            UsersDirectoryService
+        )
+        groupsService = self.xmlService(
+            testXMLConfigGroups,
+            GroupsDirectoryService
+        )
 
         return BaseTest.service(self, (usersService, groupsService))
 
 
 
-class DirectoryServiceAggregatedBaseTest(AggregatedBaseTest, DirectoryServiceBaseTest):
+class DirectoryServiceAggregatedBaseTest(
+    AggregatedBaseTest,
+    DirectoryServiceBaseTest,
+):
     pass
 
 
 
-class DirectoryServiceAggregatedQueryTest(AggregatedBaseTest, test_xml.DirectoryServiceQueryTest):
+class DirectoryServiceAggregatedQueryTest(
+    AggregatedBaseTest,
+    test_xml.DirectoryServiceQueryTest,
+):
     pass
 
 
 
-class DirectoryServiceAggregatedImmutableTest(AggregatedBaseTest, test_directory.DirectoryServiceImmutableTest):
+class DirectoryServiceAggregatedImmutableTest(
+    AggregatedBaseTest,
+    test_directory.BaseDirectoryServiceImmutableTest,
+):
     pass
 
 

Modified: CalendarServer/branches/users/cdaboo/reverse-proxy-pods/twext/who/test/test_directory.py
===================================================================
--- CalendarServer/branches/users/cdaboo/reverse-proxy-pods/twext/who/test/test_directory.py	2013-11-06 23:41:28 UTC (rev 11899)
+++ CalendarServer/branches/users/cdaboo/reverse-proxy-pods/twext/who/test/test_directory.py	2013-11-07 02:00:24 UTC (rev 11900)
@@ -31,7 +31,7 @@
 
 
 
-class BaseTest(unittest.TestCase):
+class ServiceMixIn(object):
     realmName = "xyzzy"
 
 
@@ -42,7 +42,7 @@
 
 
 
-class DirectoryServiceTest(BaseTest):
+class BaseDirectoryServiceTest(ServiceMixIn):
     def test_interface(self):
         service = self.service()
         try:
@@ -69,13 +69,6 @@
         )
 
 
-    # @inlineCallbacks
-    # def test_recordsFromExpression(self):
-    #     service = self.service()
-    #     result = yield(service.recordsFromExpression(None))
-    #     self.assertIsInstance(result, QueryNotSupportedError)
-
-
     @inlineCallbacks
     def test_recordsFromQueryNone(self):
         service = self.service()
@@ -113,7 +106,55 @@
 
 
 
-class DirectoryServiceImmutableTest(BaseTest):
+class DirectoryServiceTest(unittest.TestCase, BaseDirectoryServiceTest):
+    def test_recordsFromExpression(self):
+        service = self.service()
+        result = yield(service.recordsFromExpression(None))
+        self.assertFailure(result, QueryNotSupportedError)
+
+
+    def test_recordWithUID(self):
+        service = self.service()
+        self.assertFailure(
+            service.recordWithUID(None),
+            QueryNotSupportedError
+        )
+
+
+    def test_recordWithGUID(self):
+        service = self.service()
+        self.assertFailure(
+            service.recordWithGUID(None),
+            QueryNotSupportedError
+        )
+
+
+    def test_recordsWithRecordType(self):
+        service = self.service()
+        self.assertFailure(
+            service.recordsWithRecordType(None),
+            QueryNotSupportedError
+        )
+
+
+    def test_recordWithShortName(self):
+        service = self.service()
+        self.assertFailure(
+            service.recordWithShortName(None, None),
+            QueryNotSupportedError
+        )
+
+
+    def test_recordsWithEmailAddress(self):
+        service = self.service()
+        self.assertFailure(
+            service.recordsWithEmailAddress(None),
+            QueryNotSupportedError
+        )
+
+
+
+class BaseDirectoryServiceImmutableTest(ServiceMixIn):
     def test_updateRecordsNotAllowed(self):
         service = self.service()
 
@@ -148,7 +189,15 @@
 
 
 
-class DirectoryRecordTest(BaseTest):
+class DirectoryServiceImmutableTest(
+    unittest.TestCase,
+    BaseDirectoryServiceImmutableTest,
+):
+    pass
+
+
+
+class BaseDirectoryRecordTest(ServiceMixIn):
     fields_wsanchez = {
         FieldName.uid: "UID:wsanchez",
         FieldName.recordType: RecordType.user,
@@ -179,8 +228,16 @@
         FieldName.emailAddresses: ("sagen at CalendarServer.org",)
     }
 
+    fields_staff = {
+        FieldName.uid: "UID:staff",
+        FieldName.recordType: RecordType.group,
+        FieldName.shortNames: ("staff",),
+        FieldName.fullNames: ("Staff",),
+        FieldName.emailAddresses: ("staff at CalendarServer.org",)
+    }
 
-    def _testRecord(self, fields=None, service=None):
+
+    def makeRecord(self, fields=None, service=None):
         if fields is None:
             fields = self.fields_wsanchez
         if service is None:
@@ -189,7 +246,7 @@
 
 
     def test_interface(self):
-        record = self._testRecord()
+        record = self.makeRecord()
         try:
             verifyObject(IDirectoryRecord, record)
         except BrokenMethodImplementation as e:
@@ -198,7 +255,7 @@
 
     def test_init(self):
         service  = self.service()
-        wsanchez = self._testRecord(self.fields_wsanchez, service=service)
+        wsanchez = self.makeRecord(self.fields_wsanchez, service=service)
 
         self.assertEquals(wsanchez.service, service)
         self.assertEquals(wsanchez.fields, self.fields_wsanchez)
@@ -207,49 +264,49 @@
     def test_initWithNoUID(self):
         fields = self.fields_wsanchez.copy()
         del fields[FieldName.uid]
-        self.assertRaises(ValueError, self._testRecord, fields)
+        self.assertRaises(ValueError, self.makeRecord, fields)
 
         fields = self.fields_wsanchez.copy()
         fields[FieldName.uid] = ""
-        self.assertRaises(ValueError, self._testRecord, fields)
+        self.assertRaises(ValueError, self.makeRecord, fields)
 
 
     def test_initWithNoRecordType(self):
         fields = self.fields_wsanchez.copy()
         del fields[FieldName.recordType]
-        self.assertRaises(ValueError, self._testRecord, fields)
+        self.assertRaises(ValueError, self.makeRecord, fields)
 
         fields = self.fields_wsanchez.copy()
         fields[FieldName.recordType] = ""
-        self.assertRaises(ValueError, self._testRecord, fields)
+        self.assertRaises(ValueError, self.makeRecord, fields)
 
 
     def test_initWithNoShortNames(self):
         fields = self.fields_wsanchez.copy()
         del fields[FieldName.shortNames]
-        self.assertRaises(ValueError, self._testRecord, fields)
+        self.assertRaises(ValueError, self.makeRecord, fields)
 
         fields = self.fields_wsanchez.copy()
         fields[FieldName.shortNames] = ()
-        self.assertRaises(ValueError, self._testRecord, fields)
+        self.assertRaises(ValueError, self.makeRecord, fields)
 
         fields = self.fields_wsanchez.copy()
         fields[FieldName.shortNames] = ("",)
-        self.assertRaises(ValueError, self._testRecord, fields)
+        self.assertRaises(ValueError, self.makeRecord, fields)
 
         fields = self.fields_wsanchez.copy()
         fields[FieldName.shortNames] = ("wsanchez", "")
-        self.assertRaises(ValueError, self._testRecord, fields)
+        self.assertRaises(ValueError, self.makeRecord, fields)
 
 
     def test_initWithBogusRecordType(self):
         fields = self.fields_wsanchez.copy()
         fields[FieldName.recordType] = object()
-        self.assertRaises(ValueError, self._testRecord, fields)
+        self.assertRaises(ValueError, self.makeRecord, fields)
 
 
     def test_initNormalize(self):
-        sagen = self._testRecord(self.fields_sagen)
+        sagen = self.makeRecord(self.fields_sagen)
 
         self.assertEquals(
             sagen.fields[FieldName.emailAddresses],
@@ -263,10 +320,10 @@
 
         plugh = DirectoryService("plugh")
 
-        wsanchez    = self._testRecord(self.fields_wsanchez)
-        wsanchezmod = self._testRecord(self.fields_wsanchez, plugh)
-        glyph       = self._testRecord(self.fields_glyph)
-        glyphmod    = self._testRecord(fields_glyphmod)
+        wsanchez    = self.makeRecord(self.fields_wsanchez)
+        wsanchezmod = self.makeRecord(self.fields_wsanchez, plugh)
+        glyph       = self.makeRecord(self.fields_glyph)
+        glyphmod    = self.makeRecord(fields_glyphmod)
 
         self.assertEquals(wsanchez, wsanchez)
         self.assertNotEqual(wsanchez, glyph)
@@ -276,7 +333,7 @@
 
 
     def test_attributeAccess(self):
-        wsanchez = self._testRecord(self.fields_wsanchez)
+        wsanchez = self.makeRecord(self.fields_wsanchez)
 
         self.assertEquals(
             wsanchez.recordType,
@@ -295,10 +352,10 @@
             wsanchez.fields[FieldName.emailAddresses]
         )
 
+
     @inlineCallbacks
     def test_members(self):
-        wsanchez = self._testRecord(self.fields_wsanchez)
-
+        wsanchez = self.makeRecord(self.fields_wsanchez)
         self.assertEquals(
             set((yield wsanchez.members())),
             set()
@@ -306,5 +363,24 @@
 
         raise SkipTest("Subclasses should implement this test.")
 
+
     def test_groups(self):
         raise SkipTest("Subclasses should implement this test.")
+
+
+
+class DirectoryRecordTest(unittest.TestCase, BaseDirectoryRecordTest):
+    def test_members(self):
+        wsanchez = self.makeRecord(self.fields_wsanchez)
+        self.assertEquals(
+            set((yield wsanchez.members())),
+            set()
+        )
+
+        staff = self.makeRecord(self.fields_staff)
+        self.assertFailure(staff.members(), NotImplementedError)
+
+
+    def test_groups(self):
+        wsanchez = self.makeRecord(self.fields_wsanchez)
+        self.assertFailure(wsanchez.groups(), NotImplementedError)

Modified: CalendarServer/branches/users/cdaboo/reverse-proxy-pods/twext/who/test/test_util.py
===================================================================
--- CalendarServer/branches/users/cdaboo/reverse-proxy-pods/twext/who/test/test_util.py	2013-11-06 23:41:28 UTC (rev 11899)
+++ CalendarServer/branches/users/cdaboo/reverse-proxy-pods/twext/who/test/test_util.py	2013-11-07 02:00:24 UTC (rev 11900)
@@ -82,7 +82,11 @@
         )
 
     def test_lookupByName(self):
-        constants = set((Instruments.hammer, Tools.screwdriver, Instruments.chisel))
+        constants = set((
+            Instruments.hammer,
+            Tools.screwdriver,
+            Instruments.chisel,
+        ))
         container = ConstantsContainer(constants)
 
         self.assertEquals(
@@ -108,7 +112,7 @@
 class UtilTest(unittest.TestCase):
     def test_uniqueResult(self):
         self.assertEquals(1, uniqueResult((1,)))
-        self.assertRaises(DirectoryServiceError, uniqueResult, (1,2,3))
+        self.assertRaises(DirectoryServiceError, uniqueResult, (1, 2, 3))
 
     def test_describe(self):
         self.assertEquals("nail pounder", describe(Tools.hammer))
@@ -116,5 +120,5 @@
 
     def test_describeFlags(self):
         self.assertEquals("blue", describe(Switches.b))
-        self.assertEquals("red|green", describe(Switches.r|Switches.g))
-        self.assertEquals("blue|black", describe(Switches.b|Switches.black))
+        self.assertEquals("red|green", describe(Switches.r | Switches.g))
+        self.assertEquals("blue|black", describe(Switches.b | Switches.black))

Modified: CalendarServer/branches/users/cdaboo/reverse-proxy-pods/twext/who/test/test_xml.py
===================================================================
--- CalendarServer/branches/users/cdaboo/reverse-proxy-pods/twext/who/test/test_xml.py	2013-11-06 23:41:28 UTC (rev 11899)
+++ CalendarServer/branches/users/cdaboo/reverse-proxy-pods/twext/who/test/test_xml.py	2013-11-07 02:00:24 UTC (rev 11900)
@@ -47,7 +47,10 @@
 
 
 
-class DirectoryServiceBaseTest(BaseTest, test_directory.DirectoryServiceTest):
+class DirectoryServiceBaseTest(
+    BaseTest,
+    test_directory.BaseDirectoryServiceTest,
+):
     def test_repr(self):
         service = self.service()
 
@@ -70,7 +73,11 @@
     @inlineCallbacks
     def test_recordWithGUID(self):
         service = self.service()
-        record = (yield service.recordWithGUID("6C495FCD-7E78-4D5C-AA66-BC890AD04C9D"))
+        record = (
+            yield service.recordWithGUID(
+                "6C495FCD-7E78-4D5C-AA66-BC890AD04C9D"
+            )
+        )
         self.assertEquals(record, None)
 
     @inlineCallbacks
@@ -80,8 +87,11 @@
         records = (yield service.recordsWithRecordType(object()))
         self.assertEquals(set(records), set())
 
-        records = (yield service.recordsWithRecordType(service.recordType.user))
-        self.assertRecords(records,
+        records = (
+            yield service.recordsWithRecordType(service.recordType.user)
+        )
+        self.assertRecords(
+            records,
             (
                 "__wsanchez__",
                 "__glyph__",
@@ -95,8 +105,11 @@
             ),
         )
 
-        records = (yield service.recordsWithRecordType(service.recordType.group))
-        self.assertRecords(records,
+        records = (
+            yield service.recordsWithRecordType(service.recordType.group)
+        )
+        self.assertRecords(
+            records,
             (
                 "__calendar-dev__",
                 "__twisted__",
@@ -109,13 +122,28 @@
     def test_recordWithShortName(self):
         service = self.service()
 
-        record = (yield service.recordWithShortName(service.recordType.user, "null"))
+        record = (
+            yield service.recordWithShortName(
+                service.recordType.user,
+                "null",
+            )
+        )
         self.assertEquals(record, None)
 
-        record = (yield service.recordWithShortName(service.recordType.user, "wsanchez"))
+        record = (
+            yield service.recordWithShortName(
+                service.recordType.user,
+                "wsanchez",
+            )
+        )
         self.assertEquals(record.uid, "__wsanchez__")
 
-        record = (yield service.recordWithShortName(service.recordType.user, "wilfredo_sanchez"))
+        record = (
+            yield service.recordWithShortName(
+                service.recordType.user,
+                "wilfredo_sanchez",
+            )
+        )
         self.assertEquals(record.uid, "__wsanchez__")
 
 
@@ -123,13 +151,25 @@
     def test_recordsWithEmailAddress(self):
         service = self.service()
 
-        records = (yield service.recordsWithEmailAddress("wsanchez at bitbucket.calendarserver.org"))
+        records = (
+            yield service.recordsWithEmailAddress(
+                "wsanchez at bitbucket.calendarserver.org"
+            )
+        )
         self.assertRecords(records, ("__wsanchez__",))
 
-        records = (yield service.recordsWithEmailAddress("wsanchez at devnull.twistedmatrix.com"))
+        records = (
+            yield service.recordsWithEmailAddress(
+                "wsanchez at devnull.twistedmatrix.com"
+            )
+        )
         self.assertRecords(records, ("__wsanchez__",))
 
-        records = (yield service.recordsWithEmailAddress("shared at example.com"))
+        records = (
+            yield service.recordsWithEmailAddress(
+                "shared at example.com"
+            )
+        )
         self.assertRecords(records, ("__sagen__", "__dre__"))
 
 
@@ -176,12 +216,12 @@
 
 
     def test_badRootElement(self):
-        service = self.service(xmlData=
+        service = self.service(xmlData=(
 """<?xml version="1.0" encoding="utf-8"?>
 
 <frobnitz />
 """
-        )
+        ))
 
         self.assertRaises(ParseError, service.loadRecords)
         try:
@@ -193,12 +233,12 @@
 
 
     def test_noRealmName(self):
-        service = self.service(xmlData=
+        service = self.service(xmlData=(
 """<?xml version="1.0" encoding="utf-8"?>
 
 <directory />
 """
-        )
+        ))
 
         self.assertRaises(ParseError, service.loadRecords)
         try:
@@ -215,7 +255,7 @@
 
 
     def test_unknownFieldElementsDirty(self):
-        service = self.service(xmlData=
+        service = self.service(xmlData=(
 """<?xml version="1.0" encoding="utf-8"?>
 
 <directory realm="Unknown Record Types">
@@ -226,8 +266,11 @@
   </record>
 </directory>
 """
+        ))
+        self.assertEquals(
+            set(service.unknownFieldElements),
+            set(("political-affiliation",))
         )
-        self.assertEquals(set(service.unknownFieldElements), set(("political-affiliation",)))
 
 
     def test_unknownRecordTypesClean(self):
@@ -236,7 +279,7 @@
 
 
     def test_unknownRecordTypesDirty(self):
-        service = self.service(xmlData=
+        service = self.service(xmlData=(
 """<?xml version="1.0" encoding="utf-8"?>
 
 <directory realm="Unknown Record Types">
@@ -247,7 +290,7 @@
   </record>
 </directory>
 """
-        )
+        ))
         self.assertEquals(set(service.unknownRecordTypes), set(("camera",)))
 
 
@@ -314,7 +357,10 @@
         records = yield service.recordsFromQuery(
             (
                 service.query("emailAddresses", "shared at example.com"),
-                service.query("fullNames", "Andre LaBranche", flags=MatchFlags.NOT),
+                service.query(
+                    "fullNames", "Andre LaBranche",
+                    flags=MatchFlags.NOT
+                ),
             ),
             operand=Operand.AND
         )
@@ -325,7 +371,10 @@
     def test_queryCaseInsensitive(self):
         service = self.service()
         records = yield service.recordsFromQuery((
-            service.query("shortNames", "SagEn", flags=MatchFlags.caseInsensitive),
+            service.query(
+                "shortNames", "SagEn",
+                flags=MatchFlags.caseInsensitive
+            ),
         ))
         self.assertRecords(records, ("__sagen__",))
 
@@ -334,7 +383,10 @@
     def test_queryCaseInsensitiveNoIndex(self):
         service = self.service()
         records = yield service.recordsFromQuery((
-            service.query("fullNames", "moRGen SAGen", flags=MatchFlags.caseInsensitive),
+            service.query(
+                "fullNames", "moRGen SAGen",
+                flags=MatchFlags.caseInsensitive
+            ),
         ))
         self.assertRecords(records, ("__sagen__",))
 
@@ -352,7 +404,10 @@
     def test_queryStartsWithNoIndex(self):
         service = self.service()
         records = yield service.recordsFromQuery((
-            service.query("fullNames", "Wilfredo", matchType=MatchType.startsWith),
+            service.query(
+                "fullNames", "Wilfredo",
+                matchType=MatchType.startsWith
+            ),
         ))
         self.assertRecords(records, ("__wsanchez__",))
 
@@ -363,8 +418,8 @@
         records = yield service.recordsFromQuery((
             service.query(
                 "shortNames", "w",
-                matchType = MatchType.startsWith,
-                flags = MatchFlags.NOT,
+                matchType=MatchType.startsWith,
+                flags=MatchFlags.NOT,
             ),
         ))
         self.assertRecords(
@@ -397,8 +452,8 @@
         records = yield service.recordsFromQuery((
             service.query(
                 "shortNames", "wil",
-                matchType = MatchType.startsWith,
-                flags = MatchFlags.NOT,
+                matchType=MatchType.startsWith,
+                flags=MatchFlags.NOT,
             ),
         ))
         self.assertRecords(
@@ -426,8 +481,8 @@
         records = yield service.recordsFromQuery((
             service.query(
                 "fullNames", "Wilfredo",
-                matchType = MatchType.startsWith,
-                flags = MatchFlags.NOT,
+                matchType=MatchType.startsWith,
+                flags=MatchFlags.NOT,
             ),
         ))
         self.assertRecords(
@@ -454,8 +509,8 @@
         records = yield service.recordsFromQuery((
             service.query(
                 "shortNames", "WIL",
-                matchType = MatchType.startsWith,
-                flags = MatchFlags.caseInsensitive,
+                matchType=MatchType.startsWith,
+                flags=MatchFlags.caseInsensitive,
             ),
         ))
         self.assertRecords(records, ("__wsanchez__",))
@@ -467,8 +522,8 @@
         records = yield service.recordsFromQuery((
             service.query(
                 "fullNames", "wilfrEdo",
-                matchType = MatchType.startsWith,
-                flags = MatchFlags.caseInsensitive,
+                matchType=MatchType.startsWith,
+                flags=MatchFlags.caseInsensitive,
             ),
         ))
         self.assertRecords(records, ("__wsanchez__",))
@@ -478,7 +533,10 @@
     def test_queryContains(self):
         service = self.service()
         records = yield service.recordsFromQuery((
-            service.query("shortNames", "sanchez", matchType=MatchType.contains),
+            service.query(
+                "shortNames", "sanchez",
+                matchType=MatchType.contains
+            ),
         ))
         self.assertRecords(records, ("__wsanchez__",))
 
@@ -498,8 +556,8 @@
         records = yield service.recordsFromQuery((
             service.query(
                 "shortNames", "sanchez",
-                matchType = MatchType.contains,
-                flags = MatchFlags.NOT,
+                matchType=MatchType.contains,
+                flags=MatchFlags.NOT,
             ),
         ))
         self.assertRecords(
@@ -526,8 +584,8 @@
         records = yield service.recordsFromQuery((
             service.query(
                 "fullNames", "fred",
-                matchType = MatchType.contains,
-                flags = MatchFlags.NOT,
+                matchType=MatchType.contains,
+                flags=MatchFlags.NOT,
             ),
         ))
         self.assertRecords(
@@ -590,12 +648,18 @@
 
         # Verify change is present immediately
         record = (yield service.recordWithUID("__wsanchez__"))
-        self.assertEquals(set(record.fullNames), set(("Wilfredo Sanchez Vega",)))
+        self.assertEquals(
+            set(record.fullNames),
+            set(("Wilfredo Sanchez Vega",))
+        )
 
         # Verify change is persisted
         service.flush()
         record = (yield service.recordWithUID("__wsanchez__"))
-        self.assertEquals(set(record.fullNames), set(("Wilfredo Sanchez Vega",)))
+        self.assertEquals(
+            set(record.fullNames),
+            set(("Wilfredo Sanchez Vega",))
+        )
 
 
     @inlineCallbacks
@@ -604,7 +668,7 @@
 
         newRecord = DirectoryRecord(
             service,
-            fields = {
+            fields={
                 service.fieldName.uid:        "__plugh__",
                 service.fieldName.recordType: service.recordType.user,
                 service.fieldName.shortNames: ("plugh",),
@@ -628,14 +692,17 @@
 
         newRecord = DirectoryRecord(
             service,
-            fields = {
+            fields={
                 service.fieldName.uid:        "__plugh__",
                 service.fieldName.recordType: service.recordType.user,
                 service.fieldName.shortNames: ("plugh",),
             }
         )
 
-        self.assertFailure(service.updateRecords((newRecord,)), NoSuchRecordError)
+        self.assertFailure(
+            service.updateRecords((newRecord,)),
+            NoSuchRecordError
+        )
 
 
     @inlineCallbacks
@@ -659,7 +726,7 @@
 
 
 
-class DirectoryRecordTest(BaseTest, test_directory.DirectoryRecordTest):
+class DirectoryRecordTest(BaseTest, test_directory.BaseDirectoryRecordTest):
     @inlineCallbacks
     def test_members(self):
         service = self.service()
@@ -714,8 +781,8 @@
         assert name is not None
         return MatchExpression(
             name, value,
-            matchType = matchType,
-            flags = flags,
+            matchType=matchType,
+            flags=flags,
         )
 
 

Modified: CalendarServer/branches/users/cdaboo/reverse-proxy-pods/txdav/common/datastore/upgrade/sql/upgrades/calendar_upgrade_from_3_to_4.py
===================================================================
--- CalendarServer/branches/users/cdaboo/reverse-proxy-pods/txdav/common/datastore/upgrade/sql/upgrades/calendar_upgrade_from_3_to_4.py	2013-11-06 23:41:28 UTC (rev 11899)
+++ CalendarServer/branches/users/cdaboo/reverse-proxy-pods/txdav/common/datastore/upgrade/sql/upgrades/calendar_upgrade_from_3_to_4.py	2013-11-07 02:00:24 UTC (rev 11900)
@@ -94,27 +94,28 @@
     """
 
     inbox = (yield home.calendarWithName("inbox"))
-    prop = inbox.properties().get(PropertyName.fromElement(propname))
-    if prop is not None:
-        defaultCalendar = str(prop.children[0])
-        parts = defaultCalendar.split("/")
-        if len(parts) == 5:
+    if inbox is not None:
+        prop = inbox.properties().get(PropertyName.fromElement(propname))
+        if prop is not None:
+            defaultCalendar = str(prop.children[0])
+            parts = defaultCalendar.split("/")
+            if len(parts) == 5:
 
-            calendarName = parts[-1]
-            calendarHomeUID = parts[-2]
-            if calendarHomeUID == home.uid():
+                calendarName = parts[-1]
+                calendarHomeUID = parts[-2]
+                if calendarHomeUID == home.uid():
 
-                calendar = (yield home.calendarWithName(calendarName))
-                if calendar is not None:
-                    try:
-                        yield home.setDefaultCalendar(
-                            calendar, tasks=(propname == customxml.ScheduleDefaultTasksURL)
-                        )
-                    except InvalidDefaultCalendar:
-                        # Ignore these - the server will recover
-                        pass
+                    calendar = (yield home.calendarWithName(calendarName))
+                    if calendar is not None:
+                        try:
+                            yield home.setDefaultCalendar(
+                                calendar, tasks=(propname == customxml.ScheduleDefaultTasksURL)
+                            )
+                        except InvalidDefaultCalendar:
+                            # Ignore these - the server will recover
+                            pass
 
-        del inbox.properties()[PropertyName.fromElement(propname)]
+            del inbox.properties()[PropertyName.fromElement(propname)]
 
 
 
@@ -130,15 +131,13 @@
     calendars = (yield home.loadChildren())
     for calendar in calendars:
         if calendar.isInbox():
-            continue
+            prop = calendar.properties().get(PropertyName.fromElement(caldavxml.CalendarFreeBusySet))
+            if prop is not None:
+                del calendar.properties()[PropertyName.fromElement(caldavxml.CalendarFreeBusySet)]
         prop = calendar.properties().get(PropertyName.fromElement(caldavxml.ScheduleCalendarTransp))
         if prop is not None:
             yield calendar.setUsedForFreeBusy(prop == caldavxml.ScheduleCalendarTransp(caldavxml.Opaque()))
             del calendar.properties()[PropertyName.fromElement(caldavxml.ScheduleCalendarTransp)]
-    inbox = (yield home.calendarWithName("inbox"))
-    prop = inbox.properties().get(PropertyName.fromElement(caldavxml.CalendarFreeBusySet))
-    if prop is not None:
-        del inbox.properties()[PropertyName.fromElement(caldavxml.CalendarFreeBusySet)]
 
 
 

Modified: CalendarServer/branches/users/cdaboo/reverse-proxy-pods/txdav/common/datastore/upgrade/sql/upgrades/calendar_upgrade_from_4_to_5.py
===================================================================
--- CalendarServer/branches/users/cdaboo/reverse-proxy-pods/txdav/common/datastore/upgrade/sql/upgrades/calendar_upgrade_from_4_to_5.py	2013-11-06 23:41:28 UTC (rev 11899)
+++ CalendarServer/branches/users/cdaboo/reverse-proxy-pods/txdav/common/datastore/upgrade/sql/upgrades/calendar_upgrade_from_4_to_5.py	2013-11-07 02:00:24 UTC (rev 11900)
@@ -102,10 +102,11 @@
     the new value from the XML property.
     """
     inbox = (yield home.calendarWithName("inbox"))
-    prop = inbox.properties().get(PropertyName.fromElement(customxml.CalendarAvailability))
-    if prop is not None:
-        yield home.setAvailability(prop.calendar())
-        del inbox.properties()[PropertyName.fromElement(customxml.CalendarAvailability)]
+    if inbox is not None:
+        prop = inbox.properties().get(PropertyName.fromElement(customxml.CalendarAvailability))
+        if prop is not None:
+            yield home.setAvailability(prop.calendar())
+            del inbox.properties()[PropertyName.fromElement(customxml.CalendarAvailability)]
 
 
 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140312/7087e7f7/attachment.html>


More information about the calendarserver-changes mailing list