[CalendarServer-changes] [6168] CalendarServer/branches/generic-sqlstore

source_changes at macosforge.org source_changes at macosforge.org
Mon Aug 23 11:36:43 PDT 2010


Revision: 6168
          http://trac.macosforge.org/projects/calendarserver/changeset/6168
Author:   cdaboo at apple.com
Date:     2010-08-23 11:36:43 -0700 (Mon, 23 Aug 2010)
Log Message:
-----------
Moved data store and transaction into common.

Modified Paths:
--------------
    CalendarServer/branches/generic-sqlstore/calendarserver/tap/util.py
    CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/file.py
    CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/postgres.py
    CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/test/common.py
    CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/test/test_postgres.py
    CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/file.py
    CalendarServer/branches/generic-sqlstore/txdav/common/datastore/file.py

Added Paths:
-----------
    CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql.py
    CalendarServer/branches/generic-sqlstore/txdav/datastore/sql.py

Modified: CalendarServer/branches/generic-sqlstore/calendarserver/tap/util.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/calendarserver/tap/util.py	2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/calendarserver/tap/util.py	2010-08-23 18:36:43 UTC (rev 6168)
@@ -67,8 +67,9 @@
 from calendarserver.webadmin.resource import WebAdminResource
 from calendarserver.webcal.resource import WebCalendarResource
 
-from txdav.common.datastore.file import CommonDataStore
-from txcaldav.calendarstore.postgres import PostgresStore, v1_schema
+from txdav.common.datastore.sql import CommonDataStore as CommonSQLDataStore
+from txdav.common.datastore.file import CommonDataStore as CommonFileDataStore
+from txcaldav.calendarstore.postgres import v1_schema
 from txdav.datastore.subpostgres import PostgresService
 from twext.python.filepath import CachingFilePath
 
@@ -294,11 +295,10 @@
         _dbRoot = CachingFilePath(config.DatabaseRoot)
         _postgresService = PostgresService(_dbRoot, None, v1_schema, "caldav",
             logFile=config.PostgresLogFile)
-        _newStore = PostgresStore(_postgresService.produceConnection,
-            notifierFactory, # config.EnableCalDAV, config.EnableCardDAV)
-            _dbRoot.child("attachments"))
+        _newStore = CommonSQLDataStore(_postgresService.produceConnection,
+            notifierFactory, _dbRoot.child("attachments"), config.EnableCalDAV, config.EnableCardDAV)
     else:
-        _newStore = CommonDataStore(FilePath(config.DocumentRoot),
+        _newStore = CommonFileDataStore(FilePath(config.DocumentRoot),
             notifierFactory, config.EnableCalDAV, config.EnableCardDAV) 
 
     if config.EnableCalDAV:

Modified: CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/file.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/file.py	2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/file.py	2010-08-23 18:36:43 UTC (rev 6168)
@@ -125,7 +125,7 @@
         return self._dataStore
 
 
-    def created(self):
+    def createdHome(self):
         self.createCalendarWithName("calendar")
         defaultCal = self.calendarWithName("calendar")
         props = defaultCal.properties()

Modified: CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/postgres.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/postgres.py	2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/postgres.py	2010-08-23 18:36:43 UTC (rev 6168)
@@ -20,7 +20,6 @@
 """
 
 __all__ = [
-    "PostgresStore",
     "PostgresCalendarHome",
     "PostgresCalendar",
     "PostgresCalendarObject",
@@ -34,10 +33,8 @@
 
 from twistedcaldav.sharing import SharedCollectionRecord #@UnusedImport
 
-from inspect import getargspec
 from zope.interface.declarations import implements
 
-from twisted.application.service import Service
 from twisted.internet.error import ConnectionLost
 from twisted.internet.interfaces import ITransport
 from twisted.python import hashlib
@@ -47,7 +44,6 @@
 
 from twext.web2.dav.element.rfc2518 import ResourceType
 
-from txdav.idav import IDataStore, AlreadyFinishedError
 from txdav.common.inotifications import (INotificationCollection,
     INotificationObject)
 
@@ -58,11 +54,10 @@
     validateAddressBookComponent, dropboxIDFromCalendarObject, CalendarSyncTokenHelper,
     AddressbookSyncTokenHelper)
 from txdav.datastore.file import cached
+from txdav.datastore.sql import memoized
 
-from txcaldav.icalendarstore import (ICalendarTransaction, ICalendarHome,
-    ICalendar, ICalendarObject, IAttachment)
-from txcarddav.iaddressbookstore import (IAddressBookTransaction,
-    IAddressBookHome, IAddressBook, IAddressBookObject)
+from txcaldav.icalendarstore import (ICalendarHome, ICalendar, ICalendarObject, IAttachment)
+from txcarddav.iaddressbookstore import (IAddressBookHome, IAddressBook, IAddressBookObject)
 from txdav.propertystore.base import AbstractPropertyStore, PropertyName
 from txdav.propertystore.none import PropertyStore
 
@@ -74,6 +69,7 @@
 
 from twistedcaldav import carddavxml
 from twistedcaldav.config import config
+from twistedcaldav.caldavxml import ScheduleCalendarTransp, Opaque
 from twistedcaldav.customxml import NotificationType
 from twistedcaldav.dateops import normalizeForIndex
 from twistedcaldav.index import IndexedSearchException, ReservationError,\
@@ -150,70 +146,6 @@
 }
 
 
-def _getarg(argname, argspec, args, kw):
-    """
-    Get an argument from some arguments.
-
-    @param argname: The name of the argument to retrieve.
-
-    @param argspec: The result of L{inspect.getargspec}.
-
-    @param args: positional arguments passed to the function specified by
-        argspec.
-
-    @param kw: keyword arguments passed to the function specified by
-        argspec.
-
-    @return: The value of the argument named by 'argname'.
-    """
-    argnames = argspec[0]
-    try:
-        argpos = argnames.index(argname)
-    except ValueError:
-        argpos = None
-    if argpos is not None:
-        if len(args) > argpos:
-            return args[argpos]
-    if argname in kw:
-        return kw[argname]
-    else:
-        raise TypeError("could not find key argument %r in %r/%r (%r)" %
-            (argname, args, kw, argpos)
-        )
-
-
-
-def memoized(keyArgument, memoAttribute):
-    """
-    Decorator which memoizes the result of a method on that method's instance.
-
-    @param keyArgument: The name of the 'key' argument.
-
-    @type keyArgument: C{str}
-
-    @param memoAttribute: The name of the attribute on the instance which
-        should be used for memoizing the result of this method; the attribute
-        itself must be a dictionary.
-
-    @type memoAttribute: C{str}
-    """
-    def decorate(thunk):
-        spec = getargspec(thunk)
-        def outer(*a, **kw):
-            self = a[0]
-            memo = getattr(self, memoAttribute)
-            key = _getarg(keyArgument, spec, a, kw)
-            if key in memo:
-                return memo[key]
-            result = thunk(*a, **kw)
-            if result is not None:
-                memo[key] = result
-            return result
-        return outer
-    return decorate
-
-
-
 class PropertyStore(AbstractPropertyStore):
 
     def __init__(self, defaultuser, txn, resourceID):
@@ -1693,6 +1625,14 @@
         return names
 
 
+    def createdHome(self):
+        self.createCalendarWithName("calendar")
+        defaultCal = self.calendarWithName("calendar")
+        props = defaultCal.properties()
+        props[PropertyName(*ScheduleCalendarTransp.qname())] = ScheduleCalendarTransp(
+            Opaque())
+        self.createCalendarWithName("inbox")
+
     def calendars(self):
         """
         Retrieve calendars contained in this calendar home.
@@ -2057,151 +1997,6 @@
 
 
 
-class PostgresTransaction(object):
-    """
-    Transaction implementation for postgres database.
-    """
-    implements(ICalendarTransaction, IAddressBookTransaction)
-
-    def __init__(self, store, connection, notifierFactory, label):
-        # print 'STARTING', label
-        self._store = store
-        self._connection = connection
-        self._cursor = connection.cursor()
-        self._completed = False
-        self._calendarHomes = {}
-        self._addressbookHomes = {}
-        self._notificationHomes = {}
-        self._postCommitOperations = []
-        self._notifierFactory = notifierFactory
-        self._label = label
-
-
-    def store(self):
-        return self._store
-
-
-    def __repr__(self):
-        return 'PG-TXN<%s>' % (self._label,)
-
-
-    def execSQL(self, sql, args=[]):
-        # print 'EXECUTE %s: %s' % (self._label, sql)
-        self._cursor.execute(sql, args)
-        if self._cursor.description:
-            return self._cursor.fetchall()
-        else:
-            return None
-
-
-    def __del__(self):
-        if not self._completed:
-            self._connection.rollback()
-            self._connection.close()
-
-
-    @memoized('uid', '_calendarHomes')
-    def calendarHomeWithUID(self, uid, create=False):
-        data = self.execSQL(
-            "select RESOURCE_ID from CALENDAR_HOME where OWNER_UID = %s",
-            [uid]
-        )
-        if not data:
-            if not create:
-                return None
-            self.execSQL(
-                "insert into CALENDAR_HOME (OWNER_UID) values (%s)",
-                [uid]
-            )
-            home = self.calendarHomeWithUID(uid)
-            home.createCalendarWithName("calendar")
-            return home
-        resid = data[0][0]
-
-        if self._notifierFactory:
-            notifier = self._notifierFactory.newNotifier(id=uid)
-        else:
-            notifier = None
-
-        return PostgresCalendarHome(self, uid, resid, notifier)
-
-
-    @memoized('uid', '_addressbookHomes')
-    def addressbookHomeWithUID(self, uid, create=False):
-        data = self.execSQL(
-            "select RESOURCE_ID from ADDRESSBOOK_HOME where OWNER_UID = %s",
-            [uid]
-        )
-        if not data:
-            if not create:
-                return None
-            self.execSQL(
-                "insert into ADDRESSBOOK_HOME (OWNER_UID) values (%s)",
-                [uid]
-            )
-            home = self.addressbookHomeWithUID(uid)
-            home.createAddressBookWithName("addressbook")
-            return home
-        resid = data[0][0]
-
-        if self._notifierFactory:
-            notifier = self._notifierFactory.newNotifier(id=uid)
-        else:
-            notifier = None
-
-        return PostgresAddressBookHome(self, uid, resid, notifier)
-
-
-    @memoized('uid', '_notificationHomes')
-    def notificationsWithUID(self, uid):
-        """
-        Implement notificationsWithUID.
-        """
-        rows = self.execSQL(
-            """
-            select RESOURCE_ID from NOTIFICATION_HOME where
-            OWNER_UID = %s
-            """, [uid])
-        if rows:
-            [[resourceID]] = rows
-        else:
-            [[resourceID]] = self.execSQL("select nextval('RESOURCE_ID_SEQ')")
-            resourceID = str(resourceID)
-            self.execSQL(
-                "insert into NOTIFICATION_HOME (RESOURCE_ID, OWNER_UID) "
-                "values (%s, %s)", [resourceID, uid])
-        return PostgresNotificationCollection(self, uid, resourceID)
-
-
-    def abort(self):
-        if not self._completed:
-            # print 'ABORTING', self._label
-            self._completed = True
-            self._connection.rollback()
-            self._connection.close()
-        else:
-            raise AlreadyFinishedError()
-
-
-    def commit(self):
-        if not self._completed:
-            # print 'COMPLETING', self._label
-            self._completed = True
-            self._connection.commit()
-            self._connection.close()
-            for operation in self._postCommitOperations:
-                operation()
-        else:
-            raise AlreadyFinishedError()
-
-
-    def postCommit(self, operation):
-        """
-        Run things after 'commit.'
-        """
-        self._postCommitOperations.append(operation)
-        # FIXME: implement.
-
 # CARDDAV
 
 class PostgresAddressBookObject(object):
@@ -3130,6 +2925,9 @@
         return names
 
 
+    def createdHome(self):
+        self.createAddressBookWithName("addressbook")
+
     def addressbooks(self):
         """
         Retrieve addressbooks contained in this addressbook home.
@@ -3258,26 +3056,3 @@
             return self._notifier.getID(label)
         else:
             return None
-
-
-#
-
-
-class PostgresStore(Service, object):
-
-    implements(IDataStore)
-
-    def __init__(self, connectionFactory, notifierFactory, attachmentsPath):
-        self.connectionFactory = connectionFactory
-        self.notifierFactory = notifierFactory
-        self.attachmentsPath = attachmentsPath
-
-
-    def newTransaction(self, label="unlabeled"):
-        return PostgresTransaction(
-            self,
-            self.connectionFactory(),
-            self.notifierFactory,
-            label
-        )
-

Modified: CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/test/common.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/test/common.py	2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/test/common.py	2010-08-23 18:36:43 UTC (rev 6168)
@@ -730,8 +730,8 @@
                               home.calendarWithName(calendar.name()))
 
         self.assertEquals(
-            list(c.name() for c in calendars),
-            home1_calendarNames
+            set(c.name() for c in calendars),
+            set(home1_calendarNames)
         )
 
 

Modified: CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/test/test_postgres.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/test/test_postgres.py	2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txcaldav/calendarstore/test/test_postgres.py	2010-08-23 18:36:43 UTC (rev 6168)
@@ -24,11 +24,12 @@
 from txcaldav.calendarstore.test.common import CommonTests as CalendarCommonTests
 from txcarddav.addressbookstore.test.common import CommonTests as AddressBookCommonTests
 from txdav.common.icommondatastore import NoSuchHomeChildError
+from txdav.common.datastore.sql import CommonDataStore
 
 from twisted.trial import unittest
 from txdav.datastore.subpostgres import (PostgresService,
     DiagnosticConnectionWrapper)
-from txcaldav.calendarstore.postgres import PostgresStore, v1_schema
+from txcaldav.calendarstore.postgres import v1_schema
 from twisted.internet.defer import Deferred, inlineCallbacks, succeed
 from twisted.internet import reactor
 from twext.python.filepath import CachingFilePath
@@ -80,7 +81,7 @@
                 except OSError:
                     pass
                 try:
-                    self.store = PostgresStore(
+                    self.store = CommonDataStore(
                         lambda label=None: connectionFactory(
                             label or currentTestID
                         ),
@@ -172,10 +173,11 @@
             calendars = self.requirements[homeUID]
             if calendars is not None:
                 home = populateTxn.calendarHomeWithUID(homeUID, True)
-                # We don't want the default calendar to appear unless it's
+                # We don't want the default calendar or inbox to appear unless it's
                 # explicitly listed.
                 try:
                     home.removeCalendarWithName("calendar")
+                    home.removeCalendarWithName("inbox")
                 except NoSuchHomeChildError:
                     pass
                 for calendarName in calendars:

Modified: CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/file.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/file.py	2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txcarddav/addressbookstore/file.py	2010-08-23 18:36:43 UTC (rev 6168)
@@ -73,7 +73,7 @@
     def _addressbookStore(self):
         return self._dataStore
 
-    def created(self):
+    def createdHome(self):
         self.createAddressBookWithName("addressbook")
 
 class AddressBook(CommonHomeChild):

Modified: CalendarServer/branches/generic-sqlstore/txdav/common/datastore/file.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/common/datastore/file.py	2010-08-23 15:48:59 UTC (rev 6167)
+++ CalendarServer/branches/generic-sqlstore/txdav/common/datastore/file.py	2010-08-23 18:36:43 UTC (rev 6168)
@@ -200,7 +200,7 @@
             notifier)
         self._homes[storeType][(uid, self)] = home
         if creating:
-            home.created()
+            home.createdHome()
 
             # Create notification collection
             if storeType == ECALENDARTYPE:

Added: CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql.py	                        (rev 0)
+++ CalendarServer/branches/generic-sqlstore/txdav/common/datastore/sql.py	2010-08-23 18:36:43 UTC (rev 6168)
@@ -0,0 +1,214 @@
+# -*- test-case-name: txcaldav.calendarstore.test.test_postgres -*-
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+from twext.python.log import Logger
+from txdav.datastore.sql import memoized
+from txcaldav.calendarstore.postgres import PostgresCalendarHome,\
+    PostgresAddressBookHome, PostgresNotificationCollection
+
+"""
+SQL data store.
+"""
+
+__all__ = [
+    "CommonDataStore",
+    "CommonStoreTransaction",
+]
+
+from twistedcaldav.sharing import SharedCollectionRecord #@UnusedImport
+
+from zope.interface.declarations import implements, directlyProvides
+
+from twisted.application.service import Service
+
+from txdav.idav import IDataStore, AlreadyFinishedError
+
+from txcaldav.icalendarstore import ICalendarTransaction
+from txcarddav.iaddressbookstore import IAddressBookTransaction
+
+
+log = Logger()
+
+ECALENDARTYPE = 0
+EADDRESSBOOKTYPE = 1
+
+class CommonDataStore(Service, object):
+
+    implements(IDataStore)
+
+    def __init__(self, connectionFactory, notifierFactory, attachmentsPath,
+                 enableCalendars=True, enableAddressBooks=True):
+        assert enableCalendars or enableAddressBooks
+
+        self.connectionFactory = connectionFactory
+        self.notifierFactory = notifierFactory
+        self.attachmentsPath = attachmentsPath
+        self.enableCalendars = enableCalendars
+        self.enableAddressBooks = enableAddressBooks
+
+
+    def newTransaction(self, label="unlabeled"):
+        return CommonStoreTransaction(
+            self,
+            self.connectionFactory(),
+            self.enableCalendars,
+            self.enableAddressBooks, 
+            self.notifierFactory,
+            label
+        )
+
+class CommonStoreTransaction(object):
+    """
+    Transaction implementation for SQL database.
+    """
+
+    _homeClass = {}
+
+    def __init__(self, store, connection, enableCalendars, enableAddressBooks, notifierFactory, label):
+
+        self._store = store
+        self._connection = connection
+        self._cursor = connection.cursor()
+        self._completed = False
+        self._calendarHomes = {}
+        self._addressbookHomes = {}
+        self._notificationHomes = {}
+        self._postCommitOperations = []
+        self._notifierFactory = notifierFactory
+        self._label = label
+
+        extraInterfaces = []
+        if enableCalendars:
+            extraInterfaces.append(ICalendarTransaction)
+        if enableAddressBooks:
+            extraInterfaces.append(IAddressBookTransaction)
+        directlyProvides(self, *extraInterfaces)
+
+        CommonStoreTransaction._homeClass[ECALENDARTYPE] = PostgresCalendarHome
+        CommonStoreTransaction._homeClass[EADDRESSBOOKTYPE] = PostgresAddressBookHome
+
+    def store(self):
+        return self._store
+
+
+    def __repr__(self):
+        return 'PG-TXN<%s>' % (self._label,)
+
+
+    def execSQL(self, sql, args=[]):
+        # print 'EXECUTE %s: %s' % (self._label, sql)
+        self._cursor.execute(sql, args)
+        if self._cursor.description:
+            return self._cursor.fetchall()
+        else:
+            return None
+
+
+    def __del__(self):
+        if not self._completed:
+            self._connection.rollback()
+            self._connection.close()
+
+
+    @memoized('uid', '_calendarHomes')
+    def calendarHomeWithUID(self, uid, create=False):
+        return self.homeWithUID(ECALENDARTYPE, uid, create=create)
+
+    @memoized('uid', '_addressbookHomes')
+    def addressbookHomeWithUID(self, uid, create=False):
+        return self.homeWithUID(EADDRESSBOOKTYPE, uid, create=create)
+
+    def homeWithUID(self, storeType, uid, create=False):
+        
+        if storeType == ECALENDARTYPE:
+            homeTable = "CALENDAR_HOME"
+        elif storeType == EADDRESSBOOKTYPE:
+            homeTable = "ADDRESSBOOK_HOME"
+
+        data = self.execSQL(
+            "select RESOURCE_ID from %s where OWNER_UID = %%s" % (homeTable,),
+            [uid]
+        )
+        if not data:
+            if not create:
+                return None
+            self.execSQL(
+                "insert into %s (OWNER_UID) values (%%s)" % (homeTable,),
+                [uid]
+            )
+            home = self.homeWithUID(storeType, uid)
+            home.createdHome()
+            return home
+        resid = data[0][0]
+
+        if self._notifierFactory:
+            notifier = self._notifierFactory.newNotifier(id=uid)
+        else:
+            notifier = None
+
+        return self._homeClass[storeType](self, uid, resid, notifier)
+
+
+    @memoized('uid', '_notificationHomes')
+    def notificationsWithUID(self, uid):
+        """
+        Implement notificationsWithUID.
+        """
+        rows = self.execSQL(
+            """
+            select RESOURCE_ID from NOTIFICATION_HOME where
+            OWNER_UID = %s
+            """, [uid])
+        if rows:
+            [[resourceID]] = rows
+        else:
+            [[resourceID]] = self.execSQL("select nextval('RESOURCE_ID_SEQ')")
+            resourceID = str(resourceID)
+            self.execSQL(
+                "insert into NOTIFICATION_HOME (RESOURCE_ID, OWNER_UID) "
+                "values (%s, %s)", [resourceID, uid])
+        return PostgresNotificationCollection(self, uid, resourceID)
+
+
+    def abort(self):
+        if not self._completed:
+            # print 'ABORTING', self._label
+            self._completed = True
+            self._connection.rollback()
+            self._connection.close()
+        else:
+            raise AlreadyFinishedError()
+
+
+    def commit(self):
+        if not self._completed:
+            # print 'COMPLETING', self._label
+            self._completed = True
+            self._connection.commit()
+            self._connection.close()
+            for operation in self._postCommitOperations:
+                operation()
+        else:
+            raise AlreadyFinishedError()
+
+
+    def postCommit(self, operation):
+        """
+        Run things after 'commit.'
+        """
+        self._postCommitOperations.append(operation)
+        # FIXME: implement.
+

Added: CalendarServer/branches/generic-sqlstore/txdav/datastore/sql.py
===================================================================
--- CalendarServer/branches/generic-sqlstore/txdav/datastore/sql.py	                        (rev 0)
+++ CalendarServer/branches/generic-sqlstore/txdav/datastore/sql.py	2010-08-23 18:36:43 UTC (rev 6168)
@@ -0,0 +1,85 @@
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Logic common to SQL implementations.
+"""
+
+from inspect import getargspec
+
+def _getarg(argname, argspec, args, kw):
+    """
+    Get an argument from some arguments.
+
+    @param argname: The name of the argument to retrieve.
+
+    @param argspec: The result of L{inspect.getargspec}.
+
+    @param args: positional arguments passed to the function specified by
+        argspec.
+
+    @param kw: keyword arguments passed to the function specified by
+        argspec.
+
+    @return: The value of the argument named by 'argname'.
+    """
+    argnames = argspec[0]
+    try:
+        argpos = argnames.index(argname)
+    except ValueError:
+        argpos = None
+    if argpos is not None:
+        if len(args) > argpos:
+            return args[argpos]
+    if argname in kw:
+        return kw[argname]
+    else:
+        raise TypeError("could not find key argument %r in %r/%r (%r)" %
+            (argname, args, kw, argpos)
+        )
+
+
+
+def memoized(keyArgument, memoAttribute):
+    """
+    Decorator which memoizes the result of a method on that method's instance.
+
+    @param keyArgument: The name of the 'key' argument.
+
+    @type keyArgument: C{str}
+
+    @param memoAttribute: The name of the attribute on the instance which
+        should be used for memoizing the result of this method; the attribute
+        itself must be a dictionary.
+
+    @type memoAttribute: C{str}
+    """
+    def decorate(thunk):
+        spec = getargspec(thunk)
+        def outer(*a, **kw):
+            self = a[0]
+            memo = getattr(self, memoAttribute)
+            key = _getarg(keyArgument, spec, a, kw)
+            if key in memo:
+                return memo[key]
+            result = thunk(*a, **kw)
+            if result is not None:
+                memo[key] = result
+            return result
+        return outer
+    return decorate
+
+
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100823/4664297e/attachment-0001.html>


More information about the calendarserver-changes mailing list