[Zope3-checkins] SVN: Zope3/trunk/src/zope/app/locking/ commit
simple locking package
Brian Lloyd
brian at zope.com
Thu Feb 3 13:20:33 EST 2005
Log message for revision 29029:
commit simple locking package
Changed:
A Zope3/trunk/src/zope/app/locking/
A Zope3/trunk/src/zope/app/locking/README.txt
A Zope3/trunk/src/zope/app/locking/__init__.py
A Zope3/trunk/src/zope/app/locking/adapter.py
A Zope3/trunk/src/zope/app/locking/configure.zcml
A Zope3/trunk/src/zope/app/locking/interfaces.py
A Zope3/trunk/src/zope/app/locking/lockinfo.py
A Zope3/trunk/src/zope/app/locking/storage.py
A Zope3/trunk/src/zope/app/locking/tests.py
-=-
Added: Zope3/trunk/src/zope/app/locking/README.txt
===================================================================
--- Zope3/trunk/src/zope/app/locking/README.txt 2005-02-03 16:34:52 UTC (rev 29028)
+++ Zope3/trunk/src/zope/app/locking/README.txt 2005-02-03 18:20:33 UTC (rev 29029)
@@ -0,0 +1,366 @@
+Object Locking
+==============
+
+This package provides a framework for object locking. The implementation
+is intended to provide a simple general-purpose locking architecture upon
+which other locking applications can be built (WebDAV locking, for example).
+
+The locking system is purely *advisory* - it provides a way to associate a
+lock with an object, but it does not enforce locking in any way. It is up
+to application-level code to ensure that locked objects are restricted in
+a way appropriate to the application.
+
+The Zope 3 locking model defines interfaces and default implemenations
+that:
+
+ - allows for a single lock on an object, owned by a specific
+ principal
+
+ - does not necessarily impose inherent semantic meaning (exclusive
+ vs. non-exclusive, write vs. read) on locks, though it will
+ provide fields that higher-level application components can use
+ to implement and enforce such semantics
+
+ - can potentially be be used to build more ambitious locking
+ mechanisms (such as WebDAV locking equivalent to Zope 2)
+
+ - supports common use cases that have been uncovered in several years
+ of development of real-world applications (such as reporting all of
+ the objects locked by a given user)
+
+
+The Zope3 locking architecture defines an `ILockable` interface and
+provides a default adapter implementation that requires only that an
+object be adaptable to `IKeyReference`. All persistent objects can be
+adapted to this interface by default in Zope 3, so in practice all
+persistent objects are lockable.
+
+The default `ILockable` adapter implementation provides support for:
+
+ - locking and unlocking an object
+
+ - breaking an existing lock on an object
+
+ - obtaining the lock information for an object
+
+
+Locking operations (lock, unlock, break lock) fire events that may be
+handled by applications or other components to interact with the locking
+system in a loosely-coupled way.
+
+Lock information is accessible through an object that supports the
+`ILockInfo` interface. The `ILockInfo` interface implies IAnnotatable,
+so that other locking implementations (superceding or complementing the
+default implementation) can store more information if needed to support
+extended locking semantics.
+
+The locking architecture also supports an efficient method of lock tracking
+that allows you to determine what locks are held on objects. The default
+implementation provides an `ILockTracker` utility that can be used by
+applications to quickly find all objects locked by a particular principal.
+
+
+Locking essentials
+------------------
+
+Normally, locking is provided by the default locking implementation. In
+this example, we'll create a simple content class. The content class
+is persistent, which allows us to use the default locking adapters and
+utilities.
+
+ >>> import persistent
+
+ >>> class Content(persistent.Persistent):
+ ... """A sample content object"""
+
+ ... def __init__(self, value):
+ ... self.value = value
+
+ ... def __call__(self):
+ ... return self
+
+ ... def __hash__(self):
+ ... return self.value
+
+ ... def __cmp__(self, other):
+ ... return cmp(self.value, other.value)
+
+
+Now we will create a few sample objects to work with:
+
+ >>> item1 = Content("item1")
+ >>> item1.__name__ = "item1"
+
+ >>> item2 = Content("item2")
+ >>> item2.__name__ = "item2"
+
+ >>> item3 = Content("item3")
+ >>> item3.__name__ = "item3"
+
+
+It is possible to test whether an object supports locking by attempting
+to adapt it to the ILockable interface:
+
+ >>> from zope.app.locking.interfaces import ILockable, ILockTracker
+ >>> from zope.app.locking.interfaces import ILockInfo
+
+ >>> ILockable(item1, None)
+ <Locking adapter for...
+
+ >>> ILockable(42, None)
+ ...
+
+
+There must be an active interaction to use locking, to allow the framework
+to determine the principal performing locking operations. This example sets
+up some sample principals and a helper to switch principals for further
+examples:
+
+ >>> class FauxPrincipal:
+ ... def __init__(self, id):
+ ... self.id = id
+
+ >>> britney = FauxPrincipal('britney')
+ >>> tim = FauxPrincipal('tim')
+
+ >>> class FauxParticipation:
+ ... interaction=None
+ ... def __init__(self, principal):
+ ... self.principal = principal
+
+ >>> import zope.security.management
+ >>> def set_principal(principal):
+ ... if zope.security.management.queryInteraction():
+ ... zope.security.management.endInteraction()
+ ... participation = FauxParticipation(principal)
+ ... zope.security.management.newInteraction(participation)
+
+ >>> set_principal(britney)
+
+
+Now, let's look at basic locking. To perform locking operations, we first
+have to adapt an object to `ILockable`:
+
+ >>> obj = ILockable(item1)
+
+
+We can ask if the object is locked:
+
+ >>> obj.locked()
+ False
+
+
+If it were locked, we could get the id of the principal that owns the
+lock. Since it is not locked, this will return None:
+
+ >>> obj.locker()
+ ...
+
+
+Now lets lock the object. Note that the lock method return an instance
+of an object that implements `ILockInfo` on success:
+
+ >>> info = obj.lock()
+ >>> ILockInfo.providedBy(info)
+ True
+
+ >>> obj.locked()
+ True
+
+ >>> obj.locker()
+ 'britney'
+
+
+Methods are provided to check whether the current principal already has
+the lock on an object and whether the lock is already owned by a different
+principal:
+
+ >>> obj.ownLock()
+ True
+
+ >>> obj.isLockedOut()
+ False
+
+
+If we switch principals, we see that the answers reflect the current
+principal:
+
+ >>> set_principal(tim)
+ >>> obj.ownLock()
+ False
+
+ >>> obj.isLockedOut()
+ True
+
+
+A principal can only release his or her own locks:
+
+ >>> obj.unlock()
+ Traceback (most recent call last):
+ ...
+ LockingError: Principal is not lock owner
+
+
+If we switch back to the original principal, we see that the original
+principal can unlock the object:
+
+ >>> set_principal(britney)
+ >>> obj.unlock()
+ ...
+
+
+There is a mechanism for breaking locks that does not take the current
+principal into account. This will break any existing lock on an object:
+
+ >>> obj.lock()
+ <...LockInfo...>
+
+ >>> set_principal(tim)
+ >>> obj.locked()
+ True
+
+ >>> obj.breaklock()
+ >>> obj.locked()
+ False
+
+
+Locks can be created with an optional timeout. If a timeout is provided,
+it should be an integer number of seconds from the time the lock is
+created.
+
+ >>> # fake time function to avoid a time.sleep in tests!
+ >>> import time
+ >>> def faketime():
+ ... return time.time() + 3600.0
+
+ >>> obj.lock(timeout=10)
+ <...LockInfo...>
+
+ >>> obj.locked()
+ True
+
+ >>> import zope.app.locking.storage
+ >>> zope.app.locking.storage.timefunc = faketime
+ >>> obj.locked()
+ False
+
+ >>> # undo our time hack
+ >>> zope.app.locking.storage.timefunc = time.time
+
+
+Finally, it is possible to explicitly get an `ILockInfo` object that
+contains the lock information for the object. Note that locks that do
+not have a timeout set have a timeout value of None.
+
+ >>> obj = ILockable(item2)
+ >>> obj.lock()
+ <...LockInfo...>
+
+ >>> info = obj.getLockInfo()
+ >>> info.principal_id
+ 'tim'
+ >>> info.timeout
+ ...
+
+
+It is possible to get the object associated with a lock directly from
+an ILockInfo instance:
+
+ >>> target = info.target
+ >>> target.__name__ == 'item2'
+ True
+
+
+The `ILockInfo` interface extends the IMapping interface, so application
+code can store extra information on locks if necessary. It is recommended
+that keys for extra data use qualified names following the convention that
+is commonly used for annotations:
+
+ >>> info['my.namespace.extra'] = 'spam'
+ >>> info['my.namespace.extra'] == 'spam'
+ True
+ >>> obj.unlock()
+ >>> obj.locked()
+ False
+
+
+Lock tracking
+-------------
+
+It is often desirable to be able to report on the currently held locks in
+a system (particularly on a per-user basis), without requiring an expensive
+brute-force search. An `ILockTracker` utility allows an application to get
+the current locks for a principal, or all current locks:
+
+ >>> set_principal(tim)
+ >>> obj = ILockable(item2)
+ >>> obj.lock()
+ <...LockInfo...>
+
+ >>> set_principal(britney)
+ >>> obj = ILockable(item3)
+ >>> obj.lock()
+ <...LockInfo...>
+
+ >>> from zope.app.locking.interfaces import ILockTracker
+ >>> from zope.app.zapi import getUtility
+ >>> util = getUtility(ILockTracker)
+
+ >>> items = util.getLocksForPrincipal('britney')
+ >>> len(items) == 1
+ True
+
+ >>> items = util.getAllLocks()
+ >>> len(items) >= 2
+ True
+
+
+These methods allow an application to create forms and other code that
+performs unlocking or breaking of locks on sets of objects:
+
+ >>> items = util.getAllLocks()
+ >>> for item in items:
+ ... obj = ILockable(item.target)
+ ... obj.breaklock()
+
+ >>> items = util.getAllLocks()
+ >>> len(items) == 0
+ True
+
+
+Locking events
+--------------
+
+Locking operations (lock, unlock, break lock) fire events that can be used
+by applications. Note that expiration of a lock *does not* fire an event
+(because the current implementation uses a lazy expiration approach).
+
+ >>> import zope.event
+
+ >>> def log_event(event):
+ ... print event
+
+ >>> zope.event.subscribers.append(log_event)
+
+ >>> obj = ILockable(item2)
+ >>> obj.lock()
+ LockedEvent ...
+
+ >>> obj.unlock()
+ UnlockedEvent ...
+
+ >>> obj.lock()
+ LockedEvent ...
+
+ >>> obj.breaklock()
+ BreakLockEvent ...
+
+
+
+
+
+
+
+
+
+
Property changes on: Zope3/trunk/src/zope/app/locking/README.txt
___________________________________________________________________
Name: svn:executable
+ *
Added: Zope3/trunk/src/zope/app/locking/__init__.py
===================================================================
--- Zope3/trunk/src/zope/app/locking/__init__.py 2005-02-03 16:34:52 UTC (rev 29028)
+++ Zope3/trunk/src/zope/app/locking/__init__.py 2005-02-03 18:20:33 UTC (rev 29029)
@@ -0,0 +1 @@
+# This is a Python package
Property changes on: Zope3/trunk/src/zope/app/locking/__init__.py
___________________________________________________________________
Name: svn:executable
+ *
Added: Zope3/trunk/src/zope/app/locking/adapter.py
===================================================================
--- Zope3/trunk/src/zope/app/locking/adapter.py 2005-02-03 16:34:52 UTC (rev 29028)
+++ Zope3/trunk/src/zope/app/locking/adapter.py 2005-02-03 18:20:33 UTC (rev 29029)
@@ -0,0 +1,151 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+Locking adapter implementation.
+
+$Id: $
+"""
+
+from zope.app.locking.interfaces import ILockable, ILockedEvent
+from zope.app.locking.interfaces import IUnlockedEvent, IBreakLockEvent
+from zope.app.keyreference.interfaces import IKeyReference
+from zope.component.exceptions import ComponentLookupError
+from zope.app.event.objectevent import ObjectEvent
+from zope.app.locking.interfaces import LockingError
+from zope.app.locking.storage import ILockStorage
+from zope.app.locking.lockinfo import LockInfo
+from zope.app.locking.interfaces import _
+from zope.component import getUtility
+import zope.security.management
+from zope.event import notify
+import zope.interface
+
+
+
+def LockingAdapterFactory(target):
+ """
+ Return target adapted to ILockable, or None. This should be registered
+ against zope.interface.Interface to provide adaptation to ILockable.
+ """
+ if IKeyReference(target, None) is None:
+ return None
+ return LockingAdapter(target)
+
+
+class LockingAdapter(object):
+ """
+ Default ILockable adapter implementation.
+ """
+
+ zope.interface.implements(ILockable)
+
+ def __init__(self, context):
+ try:
+ self.storage = getUtility(ILockStorage, context=context)
+ except ComponentLookupError:
+ self.storage = getUtility(ILockStorage)
+ self.context = context
+
+ def _findPrincipal(self):
+ # Find the current principal. Note that it is possible for there
+ # to be more than one principal - in this case we throw an error.
+ interaction = zope.security.management.getInteraction()
+ principal = None
+ for p in interaction.participations:
+ if principal is None:
+ principal = p.principal
+ else:
+ raise LockingError(_("Multiple principals found"))
+ if principal is None:
+ raise LockingError(_("No principal found"))
+ return principal
+
+ def lock(self, principal=None, timeout=None):
+ if principal is None:
+ principal = self._findPrincipal()
+ principal_id = principal.id
+ lock = self.storage.getLock(self.context)
+ if lock is not None:
+ raise LockingError(_("Object is already locked"))
+ lock = LockInfo(self.context, principal_id, timeout)
+ self.storage.setLock(self.context, lock)
+ notify(LockedEvent(self.context, lock))
+ return lock
+
+ def unlock(self):
+ lock = self.storage.getLock(self.context)
+ if lock is None:
+ raise LockingError(_("Object is not locked"))
+ principal = self._findPrincipal()
+ if lock.principal_id != principal.id:
+ raise LockingError(_("Principal is not lock owner"))
+ self.storage.delLock(self.context)
+ notify(UnlockedEvent(self.context))
+
+ def breaklock(self):
+ lock = self.storage.getLock(self.context)
+ if lock is None:
+ raise LockingError(_("Object is not locked"))
+ self.storage.delLock(self.context)
+ notify(BreakLockEvent(self.context))
+
+ def locked(self):
+ lock = self.storage.getLock(self.context)
+ return lock is not None
+
+ def locker(self):
+ lock = self.storage.getLock(self.context)
+ if lock is not None:
+ return lock.principal_id
+ return None
+
+ def getLockInfo(self):
+ return self.storage.getLock(self.context)
+
+ def ownLock(self):
+ lock = self.storage.getLock(self.context)
+ if lock is not None:
+ principal = self._findPrincipal()
+ return lock.principal_id == principal.id
+ return False
+
+ def isLockedOut(self):
+ lock = self.storage.getLock(self.context)
+ if lock is not None:
+ principal = self._findPrincipal()
+ return lock.principal_id != principal.id
+ return False
+
+ def __repr__(self):
+ return '<Locking adapter for %s>' % repr(self.context)
+
+
+
+class EventBase(ObjectEvent):
+ def __repr__(self):
+ return '%s for %s' % (self.__class__.__name__, `self.object`)
+
+class LockedEvent(EventBase):
+ zope.interface.implements(ILockedEvent)
+
+ def __init__(self, object, lock):
+ self.object = object
+ self.lock = lock
+
+
+class UnlockedEvent(EventBase):
+ zope.interface.implements(IUnlockedEvent)
+
+class BreakLockEvent(UnlockedEvent):
+ zope.interface.implements(IBreakLockEvent)
Property changes on: Zope3/trunk/src/zope/app/locking/adapter.py
___________________________________________________________________
Name: svn:executable
+ *
Added: Zope3/trunk/src/zope/app/locking/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/locking/configure.zcml 2005-02-03 16:34:52 UTC (rev 29028)
+++ Zope3/trunk/src/zope/app/locking/configure.zcml 2005-02-03 18:20:33 UTC (rev 29029)
@@ -0,0 +1,6 @@
+<configure xmlns="http://namespaces.zope.org/zope"
+ i18n_domain="zope.app.locking">
+
+ <permission id="zope.app.locking.UseLocking" title="Use locking" />
+
+</configure>
Property changes on: Zope3/trunk/src/zope/app/locking/configure.zcml
___________________________________________________________________
Name: svn:executable
+ *
Added: Zope3/trunk/src/zope/app/locking/interfaces.py
===================================================================
--- Zope3/trunk/src/zope/app/locking/interfaces.py 2005-02-03 16:34:52 UTC (rev 29028)
+++ Zope3/trunk/src/zope/app/locking/interfaces.py 2005-02-03 18:20:33 UTC (rev 29029)
@@ -0,0 +1,142 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+Locking interfaces
+
+$Id: $
+"""
+
+from zope.app.annotation.interfaces import IAttributeAnnotatable
+from zope.app.event.interfaces import IObjectEvent
+from zope.interface import Interface, Attribute
+from zope.i18nmessageid import MessageIDFactory
+from zope.interface.common.mapping import IMapping
+import zope.interface
+import zope.schema
+
+_ = MessageIDFactory('zope.app.locking')
+
+
+class ILockable(Interface):
+ """
+ The ILockable interface defines the locking operations that are
+ supported for lockable objects.
+ """
+
+ def lock(timeout=None):
+ """
+ Lock the object in the name of the current principal. This method
+ raises a LockingError if the object cannot be locked by the current
+ principal.
+ """
+
+ def unlock():
+ """
+ Unlock the object. If the current principal does not hold a lock
+ on the object, this method raises a LockingError.
+ """
+
+ def breaklock():
+ """
+ Break all existing locks on an object for all principals.
+ """
+
+ def locked():
+ """
+ Returns true if the object is locked.
+ """
+
+ def locker():
+ """
+ Return the principal id of the principal that owns the lock on
+ the object, or None if the object is not locked.
+ """
+
+ def getLockInfo(obj):
+ """
+ Return a (possibly empty) sequence of ILockInfo objects describing
+ the current locks on the object.
+ """
+
+ def ownLock():
+ """
+ Returns true if the object is locked by the current principal.
+ """
+
+ def isLockedOut():
+ """
+ Returns true if the object is locked by a principal other than
+ the current principal.
+ """
+
+
+class ILockTracker(Interface):
+ """
+ An ILockTracker implementation is responsible for tracking what
+ objects are locked within its scope.
+ """
+
+ def getLocksForPrincipal(principal_id):
+ """
+ Return a sequence of all locks held by the given principal.
+ """
+
+ def getAllLocks():
+ """
+ Return a sequence of all currently held locks.
+ """
+
+
+class ILockInfo(IMapping):
+ """
+ An ILockInfo implementation is responsible for
+ """
+
+ def getObject():
+ """Return the actual locked object."""
+
+ creator = zope.schema.TextLine(
+ description=_("id of the principal owning the lock")
+ )
+
+ created = zope.schema.Float(
+ description=_("time value indicating the creation time"),
+ required=False
+ )
+
+ timeout = zope.schema.Float(
+ description=_("time value indicating the lock timeout from creation"),
+ required=False
+ )
+
+
+
+class ILockedEvent(IObjectEvent):
+ """An object has been locked"""
+
+ lock = Attribute("The lock set on the object")
+
+class IUnlockedEvent(IObjectEvent):
+ """An object has been unlocked"""
+
+class IBreakLockEvent(IUnlockedEvent):
+ """Lock has been broken on an object"""
+
+
+
+class LockingError(Exception):
+ """
+ The exception raised for locking errors.
+ """
+
Property changes on: Zope3/trunk/src/zope/app/locking/interfaces.py
___________________________________________________________________
Name: svn:executable
+ *
Added: Zope3/trunk/src/zope/app/locking/lockinfo.py
===================================================================
--- Zope3/trunk/src/zope/app/locking/lockinfo.py 2005-02-03 16:34:52 UTC (rev 29028)
+++ Zope3/trunk/src/zope/app/locking/lockinfo.py 2005-02-03 18:20:33 UTC (rev 29029)
@@ -0,0 +1,64 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+ILockInfo implementation.
+
+$Id: $
+"""
+
+from zope.app.locking.interfaces import ILockInfo, LockingError
+import zope.interface, time
+
+
+class LockInfo(object):
+
+ zope.interface.implements(ILockInfo)
+
+ def __init__(self, target, principal_id, timeout=None):
+ self.target = target
+ self.principal_id = principal_id
+ self.created = time.time()
+ self.timeout = timeout
+ self.data = {}
+
+ def get(self, key, default=None):
+ return self.data.get(key, default)
+
+ def keys(self):
+ return self.data.keys()
+
+ def values(self):
+ return self.data.values()
+
+ def items(self):
+ return self.data.items()
+
+ def __getitem__(self, key):
+ return self.data[key]
+
+ def __setitem__(self, key, value):
+ self.data[key] = value
+
+ def __delitem__(self, key):
+ del self.data[key]
+
+ def __contains__(self, key):
+ return self.data.has_key(key)
+
+ def __iter__(self):
+ return iter(self.data)
+
+ def __len__(self):
+ return len(self.data)
+
Property changes on: Zope3/trunk/src/zope/app/locking/lockinfo.py
___________________________________________________________________
Name: svn:executable
+ *
Added: Zope3/trunk/src/zope/app/locking/storage.py
===================================================================
--- Zope3/trunk/src/zope/app/locking/storage.py 2005-02-03 16:34:52 UTC (rev 29028)
+++ Zope3/trunk/src/zope/app/locking/storage.py 2005-02-03 18:20:33 UTC (rev 29029)
@@ -0,0 +1,117 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+Lock storage implementation.
+
+$Id: $
+"""
+
+from zope.app.keyreference.interfaces import IKeyReference
+from zope.app.locking.interfaces import ILockTracker
+from zope.app.locking.interfaces import LockingError
+from BTrees.OOBTree import OOBTree
+from BTrees.IOBTree import IOBTree
+import zope.interface, time
+
+
+timefunc = time.time
+
+
+class ILockStorage(zope.interface.Interface):
+ """
+ This interface is internal to the default locking implementation. It
+ lets us store lock information in a central place rather than store
+ it on individual objects.
+ """
+
+
+class LockStorage(object):
+ """
+ This class implements both the ILockTracker utility as well as the
+ internal ILockStorage utility which is used by the ILockable adapter
+ implementation. It acts as the persistent storage for locks.
+ """
+
+ zope.interface.implements(ILockStorage, ILockTracker)
+
+ def __init__(self):
+ self.timeouts = IOBTree()
+ self.locks = OOBTree()
+
+ # ILockTracker implementation
+
+ def getLocksForPrincipal(self, principal_id):
+ return self.currentLocks(principal_id)
+
+ def getAllLocks(self):
+ return self.currentLocks()
+
+ # ILockStorage implementation
+
+ def currentLocks(self, principal_id=None):
+ """
+ Return the currently active locks, possibly filtered by principal.
+ """
+ result = []
+ for lock in self.locks.values():
+ if principal_id is None or principal_id == lock.principal_id:
+ if (lock.timeout is None or
+ (lock.created + lock.timeout > timefunc())
+ ):
+ result.append(lock)
+ return result
+
+ def getLock(self, object):
+ """
+ Get the current lock for an object.
+ """
+ keyref = IKeyReference(object)
+ lock = self.locks.get(keyref, None)
+ if lock is not None and lock.timeout is not None:
+ if lock.created + lock.timeout < timefunc():
+ return None
+ return lock
+
+ def setLock(self, object, lock):
+ """
+ Set the current lock for an object.
+ """
+ keyref = IKeyReference(object)
+ self.locks[keyref] = lock
+ pid = lock.principal_id
+ if lock.timeout:
+ ts = int(lock.created + lock.timeout)
+ value = self.timeouts.get(ts, [])
+ value.append(keyref)
+ self.timeouts[ts] = value
+ self.cleanup()
+
+ def delLock(self, object):
+ """
+ Delete the current lock for an object.
+ """
+ keyref = IKeyReference(object)
+ del self.locks[keyref]
+
+ def cleanup(self):
+ # We occasionally want to clean up expired locks to keep them
+ # from accumulating over time and slowing things down.
+ for key in self.timeouts.keys(max=int(timefunc())):
+ for keyref in self.timeouts[key]:
+ if self.locks.get(keyref, None) is not None:
+ del self.locks[keyref]
+ del self.timeouts[key]
+
+
+
Property changes on: Zope3/trunk/src/zope/app/locking/storage.py
___________________________________________________________________
Name: svn:executable
+ *
Added: Zope3/trunk/src/zope/app/locking/tests.py
===================================================================
--- Zope3/trunk/src/zope/app/locking/tests.py 2005-02-03 16:34:52 UTC (rev 29028)
+++ Zope3/trunk/src/zope/app/locking/tests.py 2005-02-03 18:20:33 UTC (rev 29029)
@@ -0,0 +1,97 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+Locking tests
+
+$Id:$
+"""
+
+import sys, unittest
+from zope.component.tests.placelesssetup import PlacelessSetup
+from zope.testing import doctest
+from transaction import abort
+
+
+class FakeModule:
+ def __init__(self, dict):
+ self.__dict = dict
+ def __getattr__(self, name):
+ try:
+ return self.__dict[name]
+ except KeyError:
+ raise AttributeError, name
+
+name = 'zope.app.locking.README'
+
+ps = PlacelessSetup()
+
+
+from zope.app.keyreference.interfaces import IKeyReference
+
+class FakeKeyReference(object):
+ """Fake keyref for testing"""
+ def __init__(self, object):
+ self.object = object
+
+ def __call__(self):
+ return self.object
+
+ def __hash__(self):
+ return id(self.object)
+
+ def __cmp__(self, other):
+ return cmp(id(self.object), id(other.object))
+
+
+
+def setUp(test):
+ ps.setUp()
+ dict = test.globs
+ dict.clear()
+ dict['__name__'] = name
+ sys.modules[name] = FakeModule(dict)
+
+ from zope.app.tests import ztapi
+ from zope.interface import Interface
+ from zope.app.locking.interfaces import ILockable, ILockTracker
+ from zope.app.locking.adapter import LockingAdapterFactory
+ from zope.app.locking.storage import ILockStorage, LockStorage
+
+ ztapi.provideAdapter(Interface, IKeyReference, FakeKeyReference)
+ ztapi.provideAdapter(Interface, ILockable, LockingAdapterFactory)
+ storage = LockStorage()
+ ztapi.provideUtility(ILockStorage, storage)
+ ztapi.provideUtility(ILockTracker, storage)
+ test._storage = storage # keep-alive
+
+
+def tearDown(test):
+ del sys.modules[name]
+ abort()
+ db = test.globs.get('db')
+ if db is not None:
+ db.close()
+ ps.tearDown()
+ del test._storage
+
+
+def test_suite():
+ return doctest.DocFileSuite('README.txt', setUp=setUp, tearDown=tearDown,
+ optionflags=(doctest.ELLIPSIS)
+ )
+
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
+
Property changes on: Zope3/trunk/src/zope/app/locking/tests.py
___________________________________________________________________
Name: svn:executable
+ *
More information about the Zope3-Checkins
mailing list