{ "info": { "author": "Zope Project", "author_email": "zope3-dev@zope.org", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Framework :: Zope3", "Intended Audience :: Developers", "License :: OSI Approved :: Zope Public License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Internet :: WWW/HTTP" ], "description": "=======================================================================\nAdvisory exclusive locks, shared locks, and freezes (locked to no-one).\n=======================================================================\n\nThe zope.locking package provides three main features:\n\n- advisory exclusive locks for individual objects;\n\n- advisory shared locks for individual objects; and\n\n- frozen objects (locked to no one).\n\nLocks and freezes by themselves are advisory tokens and inherently\nmeaningless. They must be given meaning by other software, such as a security\npolicy.\n\nThis package approaches these features primarily from the perspective of a\nsystem API, largely free of policy; and then provides a set of adapters for\nmore common interaction with users, with some access policy. We will first\nlook at the system API, and then explain the policy and suggested use of the\nprovided adapters.\n\n\n==========\nSystem API\n==========\n\nThe central approach for the package is that locks and freeze tokens must be\ncreated and then registered by a token utility. The tokens will not work\nuntil they have been registered. This gives the ability to definitively know,\nand thus manipulate, all active tokens in a system.\n\nThe first object we'll introduce, then, is the TokenUtility: the utility that\nis responsible for the registration and the retrieving of tokens.\n\n >>> from zope import component, interface\n >>> from zope.locking import interfaces, utility, tokens\n >>> util = utility.TokenUtility()\n >>> from zope.interface.verify import verifyObject\n >>> verifyObject(interfaces.ITokenUtility, util)\n True\n\nThe utility only has a few methods--`get`, `iterForPrincipalId`,\n`__iter__`, and `register`--which we will look at below. It is expected to be\npersistent, and the included implementation is in fact persistent.Persistent,\nand expects to be installed as a local utility. The utility needs a\nconnection to the database before it can register persistent tokens.\n\n >>> from zope.locking.testing import Demo\n >>> lock = tokens.ExclusiveLock(Demo(), 'Fantomas')\n >>> util.register(lock)\n Traceback (most recent call last):\n ...\n AttributeError: 'NoneType' object has no attribute 'add'\n\n >>> conn = get_connection()\n >>> conn.add(util)\n\nIf the token provides IPersistent, the utility will add it to its connection.\n\n >>> lock._p_jar is None\n True\n\n >>> lock = util.register(lock)\n >>> lock._p_jar is util._p_jar\n True\n\n >>> lock.end()\n >>> lock = util.register(lock)\n\n\nThe standard token utility can accept tokens for any object that is adaptable\nto IKeyReference.\n\n >>> import datetime\n >>> import pytz\n >>> before_creation = datetime.datetime.now(pytz.utc)\n >>> demo = Demo()\n\nNow, with an instance of the demo class, it is possible to register lock and\nfreeze tokens for demo instances with the token utility.\n\nAs mentioned above, the general pattern for making a lock or freeze token is\nto create it--at which point most of its methods and attributes are\nunusable--and then to register it with the token utility. After registration,\nthe lock is effective and in place.\n\nThe TokenUtility can actually be used with anything that implements\nzope.locking.interfaces.IAbstractToken, but we'll look at the four tokens that\ncome with the zope.locking package: an exclusive lock, a shared lock, a\npermanent freeze, and an endable freeze.\n\n---------------\nExclusive Locks\n---------------\n\nExclusive locks are tokens that are owned by a single principal. No principal\nmay be added or removed: the lock token must be ended and another started for\nanother principal to get the benefits of the lock (whatever they have been\nconfigured to be).\n\nHere's an example of creating and registering an exclusive lock: the principal\nwith an id of 'john' locks the demo object.\n\n >>> lock = tokens.ExclusiveLock(demo, 'john')\n >>> res = util.register(lock)\n >>> res is lock\n True\n\nThe lock token is now in effect. Registering the token (the lock) fired an\nITokenStartedEvent, which we'll look at now.\n\n(Note that this example uses an events list to look at events that have fired.\nThis is simply a list whose `append` method has been added as a subscriber\nto the zope.event.subscribers list. It's included as a global when this file\nis run as a test.)\n\n >>> from zope.component.eventtesting import events\n >>> ev = events[-1]\n >>> verifyObject(interfaces.ITokenStartedEvent, ev)\n True\n >>> ev.object is lock\n True\n\nNow that the lock token is created and registered, the token utility knows\nabout it. The utilities `get` method simply returns the active token for an\nobject or None--it never returns an ended token, and in fact none of the\nutility methods do.\n\n >>> util.get(demo) is lock\n True\n >>> util.get(Demo()) is None\n True\n\nNote that `get` accepts alternate defaults, like a dictionary.get:\n\n >>> util.get(Demo(), util) is util\n True\n\nThe `iterForPrincipalId` method returns an iterator of active locks for the\ngiven principal id.\n\n >>> list(util.iterForPrincipalId('john')) == [lock]\n True\n >>> list(util.iterForPrincipalId('mary')) == []\n True\n\nThe util's `__iter__` method simply iterates over all active (non-ended)\ntokens.\n\n >>> list(util) == [lock]\n True\n\nThe token utility disallows registration of multiple active tokens for the\nsame object.\n\n >>> util.register(tokens.ExclusiveLock(demo, 'mary'))\n ... # doctest: +ELLIPSIS\n Traceback (most recent call last):\n ...\n zope.locking.interfaces.RegistrationError: ...\n >>> util.register(tokens.SharedLock(demo, ('mary', 'jane')))\n ... # doctest: +ELLIPSIS\n Traceback (most recent call last):\n ...\n zope.locking.interfaces.RegistrationError: ...\n >>> util.register(tokens.Freeze(demo))\n ... # doctest: +ELLIPSIS\n Traceback (most recent call last):\n ...\n zope.locking.interfaces.RegistrationError: ...\n\nIt's also worth looking at the lock token itself. The registered lock token\nimplements IExclusiveLock.\n\n >>> verifyObject(interfaces.IExclusiveLock, lock)\n True\n\nIt provides a number of capabilities. Arguably the most important attribute is\nwhether the token is in effect or not: `ended`. This token is active, so it\nhas not yet ended:\n\n >>> lock.ended is None\n True\n\nWhen it does end, the ended attribute is a datetime in UTC of when the token\nended. We'll demonstrate that below.\n\nLater, the `creation`, `expiration`, `duration`, and `remaining_duration` will\nbe important; for now we merely note their existence.\n\n >>> before_creation <= lock.started <= datetime.datetime.now(pytz.utc)\n True\n >>> lock.expiration is None # == forever\n True\n >>> lock.duration is None # == forever\n True\n >>> lock.remaining_duration is None # == forever\n True\n\nThe `end` method and the related ending and expiration attributes are all part\nof the IEndable interface--an interface that not all tokens must implement,\nas we will also discuss later.\n\n >>> interfaces.IEndable.providedBy(lock)\n True\n\nThe `context` and `__parent__` attributes point to the locked object--demo in\nour case. `context` is the intended standard API for obtaining the object,\nbut `__parent__` is important for the Zope 3 security set up, as discussed\ntowards the end of this document.\n\n >>> lock.context is demo\n True\n >>> lock.__parent__ is demo # important for security\n True\n\nRegistering the lock with the token utility set the utility attribute and\ninitialized the started attribute to the datetime that the lock began. The\nutility attribute should never be set by any code other than the token\nutility.\n\n >>> lock.utility is util\n True\n\nTokens always provide a `principal_ids` attribute that provides an iterable of\nthe principals that are part of a token. In our case, this is an exclusive\nlock for 'john', so the value is simple.\n\n >>> sorted(lock.principal_ids)\n ['john']\n\nThe only method on a basic token like the exclusive lock is `end`. Calling it\nwithout arguments permanently and explicitly ends the life of the token.\n\n >>> lock.end()\n\nLike registering a token, ending a token fires an event.\n\n >>> ev = events[-1]\n >>> verifyObject(interfaces.ITokenEndedEvent, ev)\n True\n >>> ev.object is lock\n True\n\nIt affects attributes on the token. Again, the most important of these is\nended, which is now the datetime of ending.\n\n >>> lock.ended >= lock.started\n True\n >>> lock.remaining_duration == datetime.timedelta()\n True\n\nIt also affects queries of the token utility.\n\n >>> util.get(demo) is None\n True\n >>> list(util.iterForPrincipalId('john')) == []\n True\n >>> list(util) == []\n True\n\nDon't try to end an already-ended token.\n\n >>> lock.end()\n Traceback (most recent call last):\n ...\n zope.locking.interfaces.EndedError\n\nThe other way of ending a token is with an expiration datetime. As we'll see,\none of the most important caveats about working with timeouts is that a token\nthat expires because of a timeout does not fire any expiration event. It\nsimply starts providing the `expiration` value for the `ended` attribute.\n\n >>> one = datetime.timedelta(hours=1)\n >>> two = datetime.timedelta(hours=2)\n >>> three = datetime.timedelta(hours=3)\n >>> four = datetime.timedelta(hours=4)\n >>> lock = util.register(tokens.ExclusiveLock(demo, 'john', three))\n >>> lock.duration\n datetime.timedelta(0, 10800)\n >>> three >= lock.remaining_duration >= two\n True\n >>> lock.ended is None\n True\n >>> util.get(demo) is lock\n True\n >>> list(util.iterForPrincipalId('john')) == [lock]\n True\n >>> list(util) == [lock]\n True\n\nThe expiration time of an endable token is always the creation date plus the\ntimeout.\n\n >>> lock.expiration == lock.started + lock.duration\n True\n >>> ((before_creation + three) <=\n ... (lock.expiration) <= # this value is the expiration date\n ... (before_creation + four))\n True\n\nExpirations can be changed while a lock is still active, using any of\nthe `expiration`, `remaining_duration` or `duration` attributes. All changes\nfire events. First we'll change the expiration attribute.\n\n >>> lock.expiration = lock.started + one\n >>> lock.expiration == lock.started + one\n True\n >>> lock.duration == one\n True\n >>> ev = events[-1]\n >>> verifyObject(interfaces.IExpirationChangedEvent, ev)\n True\n >>> ev.object is lock\n True\n >>> ev.old == lock.started + three\n True\n\nNext we'll change the duration attribute.\n\n >>> lock.duration = four\n >>> lock.duration\n datetime.timedelta(0, 14400)\n >>> four >= lock.remaining_duration >= three\n True\n >>> ev = events[-1]\n >>> verifyObject(interfaces.IExpirationChangedEvent, ev)\n True\n >>> ev.object is lock\n True\n >>> ev.old == lock.started + one\n True\n\nNow we'll hack our code to make it think that it is two hours later, and then\ncheck and modify the remaining_duration attribute.\n\n >>> def hackNow():\n ... return (datetime.datetime.now(pytz.utc) +\n ... datetime.timedelta(hours=2))\n ...\n >>> import zope.locking.utils\n >>> oldNow = zope.locking.utils.now\n >>> zope.locking.utils.now = hackNow # make code think it's 2 hours later\n >>> lock.duration\n datetime.timedelta(0, 14400)\n >>> two >= lock.remaining_duration >= one\n True\n >>> lock.remaining_duration -= one\n >>> one >= lock.remaining_duration >= datetime.timedelta()\n True\n >>> three + datetime.timedelta(minutes=1) >= lock.duration >= three\n True\n >>> ev = events[-1]\n >>> verifyObject(interfaces.IExpirationChangedEvent, ev)\n True\n >>> ev.object is lock\n True\n >>> ev.old == lock.started + four\n True\n\nNow, we'll hack our code to make it think that it's a day later. It is very\nimportant to remember that a lock ending with a timeout ends silently--that\nis, no event is fired.\n\n >>> def hackNow():\n ... return (\n ... datetime.datetime.now(pytz.utc) + datetime.timedelta(days=1))\n ...\n >>> zope.locking.utils.now = hackNow # make code think it is a day later\n >>> lock.ended == lock.expiration\n True\n >>> util.get(demo) is None\n True\n >>> util.get(demo, util) is util # alternate default works\n True\n >>> lock.remaining_duration == datetime.timedelta()\n True\n >>> lock.end()\n Traceback (most recent call last):\n ...\n zope.locking.interfaces.EndedError\n\nOnce a lock has ended, the timeout can no longer be changed.\n\n >>> lock.duration = datetime.timedelta(days=2)\n Traceback (most recent call last):\n ...\n zope.locking.interfaces.EndedError\n\nWe'll undo the hacks, and also end the lock (that is no longer ended once\nthe hack is finished).\n\n >>> zope.locking.utils.now = oldNow # undo the hack\n >>> lock.end()\n\nMake sure to register tokens. Creating a lock but not registering it puts it\nin a state that is not fully initialized.\n\n >>> lock = tokens.ExclusiveLock(demo, 'john')\n >>> lock.started # doctest: +ELLIPSIS\n Traceback (most recent call last):\n ...\n zope.locking.interfaces.UnregisteredError: ...\n >>> lock.ended # doctest: +ELLIPSIS\n Traceback (most recent call last):\n ...\n zope.locking.interfaces.UnregisteredError: ...\n\n------------\nShared Locks\n------------\n\nShared locks are very similar to exclusive locks, but take an iterable of one\nor more principals at creation, and can have principals added or removed while\nthey are active.\n\nIn this example, also notice a convenient characteristic of the TokenUtility\n`register` method: it also returns the token, so creation, registration, and\nvariable assignment can be chained, if desired.\n\n >>> lock = util.register(tokens.SharedLock(demo, ('john', 'mary')))\n >>> ev = events[-1]\n >>> verifyObject(interfaces.ITokenStartedEvent, ev)\n True\n >>> ev.object is lock\n True\n\nHere, principals with ids of 'john' and 'mary' have locked the demo object.\nThe returned token implements ISharedLock and provides a superset of the\nIExclusiveLock capabilities. These next operations should all look familiar\nfrom the discussion of the ExclusiveLock tokens above.\n\n >>> verifyObject(interfaces.ISharedLock, lock)\n True\n >>> lock.context is demo\n True\n >>> lock.__parent__ is demo # important for security\n True\n >>> lock.utility is util\n True\n >>> sorted(lock.principal_ids)\n ['john', 'mary']\n >>> lock.ended is None\n True\n >>> before_creation <= lock.started <= datetime.datetime.now(pytz.utc)\n True\n >>> lock.expiration is None\n True\n >>> lock.duration is None\n True\n >>> lock.remaining_duration is None\n True\n >>> lock.end()\n >>> lock.ended >= lock.started\n True\n\nAs mentioned, though, the SharedLock capabilities are a superset of the\nExclusiveLock ones. There are two extra methods: `add` and `remove`. These\nare able to add and remove principal ids as shared owners of the lock token.\n\n >>> lock = util.register(tokens.SharedLock(demo, ('john',)))\n >>> sorted(lock.principal_ids)\n ['john']\n >>> lock.add(('mary',))\n >>> sorted(lock.principal_ids)\n ['john', 'mary']\n >>> lock.add(('alice',))\n >>> sorted(lock.principal_ids)\n ['alice', 'john', 'mary']\n >>> lock.remove(('john',))\n >>> sorted(lock.principal_ids)\n ['alice', 'mary']\n >>> lock.remove(('mary',))\n >>> sorted(lock.principal_ids)\n ['alice']\n\nAdding and removing principals fires appropriate events, as you might expect.\n\n >>> lock.add(('mary',))\n >>> sorted(lock.principal_ids)\n ['alice', 'mary']\n >>> ev = events[-1]\n >>> verifyObject(interfaces.IPrincipalsChangedEvent, ev)\n True\n >>> ev.object is lock\n True\n >>> sorted(ev.old)\n ['alice']\n >>> lock.remove(('alice',))\n >>> sorted(lock.principal_ids)\n ['mary']\n >>> ev = events[-1]\n >>> verifyObject(interfaces.IPrincipalsChangedEvent, ev)\n True\n >>> ev.object is lock\n True\n >>> sorted(ev.old)\n ['alice', 'mary']\n\nRemoving all participants in a lock ends the lock, making it ended.\n\n >>> lock.remove(('mary',))\n >>> sorted(lock.principal_ids)\n []\n >>> lock.ended >= lock.started\n True\n >>> ev = events[-1]\n >>> verifyObject(interfaces.IPrincipalsChangedEvent, ev)\n True\n >>> ev.object is lock\n True\n >>> sorted(ev.old)\n ['mary']\n >>> ev = events[-2]\n >>> verifyObject(interfaces.ITokenEndedEvent, ev)\n True\n >>> ev.object is lock\n True\n\nAs you might expect, trying to add (or remove!) users from an ended lock is\nan error.\n\n >>> lock.add(('john',))\n Traceback (most recent call last):\n ...\n zope.locking.interfaces.EndedError\n >>> lock.remove(('john',))\n Traceback (most recent call last):\n ...\n zope.locking.interfaces.EndedError\n\nThe token utility keeps track of shared lock tokens the same as exclusive lock\ntokens. Here's a quick summary in code.\n\n >>> lock = util.register(tokens.SharedLock(demo, ('john', 'mary')))\n >>> util.get(demo) is lock\n True\n >>> list(util.iterForPrincipalId('john')) == [lock]\n True\n >>> list(util.iterForPrincipalId('mary')) == [lock]\n True\n >>> list(util) == [lock]\n True\n >>> util.register(tokens.ExclusiveLock(demo, 'mary'))\n ... # doctest: +ELLIPSIS\n Traceback (most recent call last):\n ...\n zope.locking.interfaces.RegistrationError: ...\n >>> util.register(tokens.SharedLock(demo, ('mary', 'jane')))\n ... # doctest: +ELLIPSIS\n Traceback (most recent call last):\n ...\n zope.locking.interfaces.RegistrationError: ...\n >>> util.register(tokens.Freeze(demo))\n ... # doctest: +ELLIPSIS\n Traceback (most recent call last):\n ...\n zope.locking.interfaces.RegistrationError: ...\n >>> lock.end()\n\nTimed expirations work the same as with exclusive locks. We won't repeat that\nhere, though look in the annoying.txt document in this package for the actual\nrepeated tests.\n\n--------------\nEndableFreezes\n--------------\n\nAn endable freeze token is similar to a lock token except that it grants the\n'lock' to no one.\n\n >>> token = util.register(tokens.EndableFreeze(demo))\n >>> verifyObject(interfaces.IEndableFreeze, token)\n True\n >>> ev = events[-1]\n >>> verifyObject(interfaces.ITokenStartedEvent, ev)\n True\n >>> ev.object is token\n True\n >>> sorted(token.principal_ids)\n []\n >>> token.end()\n\nEndable freezes are otherwise identical to exclusive locks. See annoying.txt\nfor the comprehensive copy-and-paste tests duplicating the exclusive lock\ntests. Notice that an EndableFreeze will never be a part of an iterable of\ntokens by principal: by definition, a freeze is associated with no principals.\n\n-------\nFreezes\n-------\n\nFreezes are similar to EndableFreezes, except they are not endable. They are\nintended to be used by system level operations that should permanently disable\ncertain changes, such as changes to the content of an archived object version.\n\nCreating them is the same...\n\n >>> token = util.register(tokens.Freeze(demo))\n >>> verifyObject(interfaces.IFreeze, token)\n True\n >>> ev = events[-1]\n >>> verifyObject(interfaces.ITokenStartedEvent, ev)\n True\n >>> ev.object is token\n True\n >>> sorted(token.principal_ids)\n []\n\nBut they can't go away...\n\n >>> token.end()\n Traceback (most recent call last):\n ...\n AttributeError: 'Freeze' object has no attribute 'end'\n\nThey also do not have expirations, duration, remaining durations, or ended\ndates. They are permanent, unless you go into the database to muck with\nimplementation-specific data structures.\n\nThere is no API way to end a Freeze. We'll need to make a new object for the\nrest of our demonstrations, and this token will exist through the\nremaining examples.\n\n >>> old_demo = demo\n >>> demo = Demo()\n\n===============================\nUser API, Adapters and Security\n===============================\n\nThe API discussed so far makes few concessions to some of the common use cases\nfor locking. Here are some particular needs as yet unfulfilled by the\ndiscussion so far.\n\n- It should be possible to allow and deny per object whether users may\n create and register tokens for the object.\n\n- It should often be easier to register an endable token than a permanent\n token.\n\n- All users should be able to unlock or modify some aspects of their own\n tokens, or remove their own participation in shared tokens; but it should be\n possible to restrict access to ending tokens that users do not own (often\n called \"breaking locks\").\n\nIn the context of the Zope 3 security model, the first two needs are intended\nto be addressed by the ITokenBroker interface, and associated adapter; the last\nneed is intended to be addressed by the ITokenHandler, and associated\nadapters.\n\n------------\nTokenBrokers\n------------\n\nToken brokers adapt an object, which is the object whose tokens are\nbrokered, and uses this object as a security context. They provide a few\nuseful methods: `lock`, `lockShared`, `freeze`, and `get`. The TokenBroker\nexpects to be a trusted adapter.\n\nlock\n----\n\nThe lock method creates and registers an exclusive lock. Without arguments,\nit tries to create it for the user in the current interaction.\n\nThis won't work without an interaction, of course. Notice that we start the\nexample by registering the utility. We would normally be required to put the\nutility in a site package, so that it would be persistent, but for this\ndemonstration we are simplifying the registration.\n\n >>> component.provideUtility(util, provides=interfaces.ITokenUtility)\n\n >>> import zope.component.interfaces\n >>> @interface.implementer(zope.component.interfaces.IComponentLookup)\n ... @component.adapter(interface.Interface)\n ... def siteManager(obj):\n ... return component.getGlobalSiteManager()\n ...\n >>> component.provideAdapter(siteManager)\n\n >>> from zope.locking import adapters\n >>> component.provideAdapter(adapters.TokenBroker)\n >>> broker = interfaces.ITokenBroker(demo)\n >>> broker.lock()\n Traceback (most recent call last):\n ...\n ValueError\n >>> broker.lock('joe')\n Traceback (most recent call last):\n ...\n zope.locking.interfaces.ParticipationError\n\nIf we set up an interaction with one participation, the lock will have a\nbetter chance.\n\n >>> import zope.security.interfaces\n >>> @interface.implementer(zope.security.interfaces.IPrincipal)\n ... class DemoPrincipal(object):\n ... def __init__(self, id, title=None, description=None):\n ... self.id = id\n ... self.title = title\n ... self.description = description\n ...\n >>> joe = DemoPrincipal('joe')\n >>> import zope.security.management\n >>> @interface.implementer(zope.security.interfaces.IParticipation)\n ... class DemoParticipation(object):\n ... def __init__(self, principal):\n ... self.principal = principal\n ... self.interaction = None\n ...\n >>> zope.security.management.endInteraction()\n >>> zope.security.management.newInteraction(DemoParticipation(joe))\n\n >>> token = broker.lock()\n >>> interfaces.IExclusiveLock.providedBy(token)\n True\n >>> token.context is demo\n True\n >>> token.__parent__ is demo\n True\n >>> sorted(token.principal_ids)\n ['joe']\n >>> token.started is not None\n True\n >>> util.get(demo) is token\n True\n >>> token.end()\n\nYou can only specify principals that are in the current interaction.\n\n >>> token = broker.lock('joe')\n >>> sorted(token.principal_ids)\n ['joe']\n >>> token.end()\n >>> broker.lock('mary')\n Traceback (most recent call last):\n ...\n zope.locking.interfaces.ParticipationError\n\nThe method can take a duration.\n\n >>> token = broker.lock(duration=two)\n >>> token.duration == two\n True\n >>> token.end()\n\nIf the interaction has more than one principal, a principal (in the\ninteraction) must be specified.\n\n >>> mary = DemoPrincipal('mary')\n >>> participation = DemoParticipation(mary)\n >>> zope.security.management.getInteraction().add(participation)\n >>> broker.lock()\n Traceback (most recent call last):\n ...\n ValueError\n >>> broker.lock('susan')\n Traceback (most recent call last):\n ...\n zope.locking.interfaces.ParticipationError\n >>> token = broker.lock('joe')\n >>> sorted(token.principal_ids)\n ['joe']\n >>> token.end()\n >>> token = broker.lock('mary')\n >>> sorted(token.principal_ids)\n ['mary']\n >>> token.end()\n >>> zope.security.management.endInteraction()\n\nlockShared\n----------\n\nThe `lockShared` method has similar characteristics, except that it can handle\nmultiple principals.\n\nWithout an interaction, principals are either not found, or not part of the\ninteraction:\n\n >>> broker.lockShared()\n Traceback (most recent call last):\n ...\n ValueError\n >>> broker.lockShared(('joe',))\n Traceback (most recent call last):\n ...\n zope.locking.interfaces.ParticipationError\n\nWith an interaction, the principals get the lock by default.\n\n >>> zope.security.management.newInteraction(DemoParticipation(joe))\n\n >>> token = broker.lockShared()\n >>> interfaces.ISharedLock.providedBy(token)\n True\n >>> token.context is demo\n True\n >>> token.__parent__ is demo\n True\n >>> sorted(token.principal_ids)\n ['joe']\n >>> token.started is not None\n True\n >>> util.get(demo) is token\n True\n >>> token.end()\n\nYou can only specify principals that are in the current interaction.\n\n >>> token = broker.lockShared(('joe',))\n >>> sorted(token.principal_ids)\n ['joe']\n >>> token.end()\n >>> broker.lockShared(('mary',))\n Traceback (most recent call last):\n ...\n zope.locking.interfaces.ParticipationError\n\nThe method can take a duration.\n\n >>> token = broker.lockShared(duration=two)\n >>> token.duration == two\n True\n >>> token.end()\n\nIf the interaction has more than one principal, all are included, unless some\nare singled out.\n\n >>> participation = DemoParticipation(mary)\n >>> zope.security.management.getInteraction().add(participation)\n >>> token = broker.lockShared()\n >>> sorted(token.principal_ids)\n ['joe', 'mary']\n >>> token.end()\n >>> token = broker.lockShared(('joe',))\n >>> sorted(token.principal_ids)\n ['joe']\n >>> token.end()\n >>> token = broker.lockShared(('mary',))\n >>> sorted(token.principal_ids)\n ['mary']\n >>> token.end()\n >>> zope.security.management.endInteraction()\n\nfreeze\n------\n\nThe `freeze` method allows users to create an endable freeze. It has no\nrequirements on the interaction. It should be protected carefully, from a\nsecurity perspective.\n\n >>> token = broker.freeze()\n >>> interfaces.IEndableFreeze.providedBy(token)\n True\n >>> token.context is demo\n True\n >>> token.__parent__ is demo\n True\n >>> sorted(token.principal_ids)\n []\n >>> token.started is not None\n True\n >>> util.get(demo) is token\n True\n >>> token.end()\n\nThe method can take a duration.\n\n >>> token = broker.freeze(duration=two)\n >>> token.duration == two\n True\n >>> token.end()\n\nget\n---\n\nThe `get` method is exactly equivalent to the token utility's get method:\nit returns the current active token for the object, or None. It is useful\nfor protected code, since utilities typically do not get security assertions,\nand this method can get its security assertions from the object, which is\noften the right place.\n\nAgain, the TokenBroker does embody some policy; if it is not good policy for\nyour application, build your own interfaces and adapters that do.\n\n-------------\nTokenHandlers\n-------------\n\nTokenHandlers are useful for endable tokens with one or more principals--that\nis, locks, but not freezes. They are intended to be protected with a lower\nexternal security permission then the usual token methods and attributes, and\nthen impose their own checks on the basis of the current interaction. They are\nvery much policy, and other approaches may be useful. They are intended to be\nregistered as trusted adapters.\n\nFor exclusive locks and shared locks, then, we have token handlers.\nGenerally, token handlers give access to all of the same capabilities as their\ncorresponding tokens, with the following additional constraints and\ncapabilities:\n\n- `expiration`, `duration`, and `remaining_duration` all may be set only if\n all the principals in the current interaction are owners of the wrapped\n token; and\n\n- `release` removes some or all of the principals in the interaction if all\n the principals in the current interaction are owners of the wrapped token.\n\nNote that `end` is unaffected: this is effectively \"break lock\", while\n`release` is effectively \"unlock\". Permissions should be set accordingly.\n\nShared lock handlers have two additional methods that are discussed in their\nsection.\n\nExclusiveLockHandlers\n---------------------\n\nGiven the general constraints described above, exclusive lock handlers will\ngenerally only allow access to their special capabilities if the operation\nis in an interaction with only the lock owner.\n\n >>> zope.security.management.newInteraction(DemoParticipation(joe))\n >>> component.provideAdapter(adapters.ExclusiveLockHandler)\n >>> lock = broker.lock()\n >>> handler = interfaces.IExclusiveLockHandler(lock)\n >>> verifyObject(interfaces.IExclusiveLockHandler, handler)\n True\n >>> handler.__parent__ is lock\n True\n >>> handler.expiration is None\n True\n >>> handler.duration = two\n >>> lock.duration == two\n True\n >>> handler.expiration = handler.started + three\n >>> lock.expiration == handler.started + three\n True\n >>> handler.remaining_duration = two\n >>> lock.remaining_duration <= two\n True\n >>> handler.release()\n >>> handler.ended >= handler.started\n True\n >>> lock.ended >= lock.started\n True\n >>> lock = util.register(tokens.ExclusiveLock(demo, 'mary'))\n >>> handler = interfaces.ITokenHandler(lock) # for joe's interaction still\n >>> handler.duration = two # doctest: +ELLIPSIS\n Traceback (most recent call last):\n ...\n zope.locking.interfaces.ParticipationError: ...\n >>> handler.expiration = handler.started + three # doctest: +ELLIPSIS\n Traceback (most recent call last):\n ...\n zope.locking.interfaces.ParticipationError: ...\n >>> handler.remaining_duration = two # doctest: +ELLIPSIS\n Traceback (most recent call last):\n ...\n zope.locking.interfaces.ParticipationError: ...\n >>> handler.release() # doctest: +ELLIPSIS\n Traceback (most recent call last):\n ...\n zope.locking.interfaces.ParticipationError: ...\n >>> lock.end()\n\nSharedLockHandlers\n------------------\n\nShared lock handlers let anyone who is an owner of a token set the expiration,\nduration, and remaining_duration values. This is a 'get out of the way' policy\nthat relies on social interactions to make sure all the participants are\nrepresented as they want. Other policies could be written in other adapters.\n\n >>> component.provideAdapter(adapters.SharedLockHandler)\n >>> lock = util.register(tokens.SharedLock(demo, ('joe', 'mary')))\n >>> handler = interfaces.ITokenHandler(lock) # for joe's interaction still\n >>> verifyObject(interfaces.ISharedLockHandler, handler)\n True\n >>> handler.__parent__ is lock\n True\n >>> handler.expiration is None\n True\n >>> handler.duration = two\n >>> lock.duration == two\n True\n >>> handler.expiration = handler.started + three\n >>> lock.expiration == handler.started + three\n True\n >>> handler.remaining_duration = two\n >>> lock.remaining_duration <= two\n True\n >>> sorted(handler.principal_ids)\n ['joe', 'mary']\n >>> handler.release()\n >>> sorted(handler.principal_ids)\n ['mary']\n >>> handler.duration = two # doctest: +ELLIPSIS\n Traceback (most recent call last):\n ...\n zope.locking.interfaces.ParticipationError: ...\n >>> handler.expiration = handler.started + three # doctest: +ELLIPSIS\n Traceback (most recent call last):\n ...\n zope.locking.interfaces.ParticipationError: ...\n >>> handler.remaining_duration = two # doctest: +ELLIPSIS\n Traceback (most recent call last):\n ...\n zope.locking.interfaces.ParticipationError: ...\n >>> handler.release() # doctest: +ELLIPSIS\n Traceback (most recent call last):\n ...\n zope.locking.interfaces.ParticipationError: ...\n\nThe shared lock handler adds two additional methods to a standard handler:\n`join` and `add`. They do similar jobs, but are separate to allow separate\nsecurity settings for each. The `join` method lets some or all of the\nprincipals in the current interaction join.\n\n >>> handler.join()\n >>> sorted(handler.principal_ids)\n ['joe', 'mary']\n >>> handler.join(('susan',))\n Traceback (most recent call last):\n ...\n zope.locking.interfaces.ParticipationError\n\nThe `add` method lets any principal ids be added to the lock, but all\nprincipals in the current interaction must be a part of the lock.\n\n >>> handler.add(('susan',))\n >>> sorted(handler.principal_ids)\n ['joe', 'mary', 'susan']\n >>> handler.release()\n >>> handler.add('jake') # doctest: +ELLIPSIS\n Traceback (most recent call last):\n ...\n zope.locking.interfaces.ParticipationError: ...\n >>> lock.end()\n >>> zope.security.management.endInteraction()\n\n--------\nWarnings\n--------\n\n* The token utility will register a token for an object if it can. It does not\n check to see if it is actually the local token utility for the given object.\n This should be arranged by clients of the token utility, and verified\n externally if desired.\n\n* Tokens are stored as keys in BTrees, and therefore must be orderable\n (i.e., they must implement __cmp__).\n\n-------------------------------\nIntended Security Configuration\n-------------------------------\n\nUtilities are typically unprotected in Zope 3--or more accurately, have\nno security assertions and are used with no security proxy--and the token\nutility expects to be so. As such, the broker and handler objects are\nexpected to be the objects used by view code, and so associated with security\nproxies. All should have appropriate __parent__ attribute values. The\nability to mutate the tokens--`end`, `add` and `remove` methods, for\ninstance--should be protected with an administrator-type permission such as\n'zope.Security'. Setting the timeout properties on the token should be\nprotected in the same way. Setting the handlers attributes can have a less\nrestrictive setting, since they calculate security themselves on the basis of\nlock membership.\n\nOn the adapter, the `end` method should be protected with the same or\nsimilar permission. Calling methods such as lock and lockShared should be\nprotected with something like 'zope.ManageContent'. Getting attributes should\nbe 'zope.View' or 'zope.Public', and unlocking and setting the timeouts, since\nthey are already protected to make sure the principal is a member of the lock,\ncan probably be 'zope.Public'.\n\nThese settings can be abused relatively easily to create an insecure\nsystem--for instance, if a user can get an adapter to IPrincipalLockable for\nanother principal--but are a reasonable start.\n\n >>> broker.__parent__ is demo\n True\n >>> handler.__parent__ is lock\n True\n\n---------------\nRandom Thoughts\n---------------\n\nAs a side effect of the design, it is conceivable that multiple lock utilities\ncould be in use at once, governing different aspects of an object; however,\nthis may never itself be of use.\n\n\n=======\nChanges\n=======\n\n------------------\n2.0.0 (2018-01-23)\n------------------\n\n- Python 3 compatibility.\n\n- Note: The browser views and related code where removed. You need to provide\n those in application-level code now.\n\n- Package the zcml files.\n\n- Updated dependencies.\n\n- Revived from svn.zope.org\n\n------------------\n1.2.2 (2011-01-31)\n------------------\n\n- Consolidate duplicate evolution code.\n\n- Split generations config into its own zcml file.\n\n------------------\n1.2.1 (2010-01-20)\n------------------\n\n- Bug fix: the generation added in 1.2 did not properly clean up\n expired tokens, and could leave the token utility in an inconsistent\n state.\n\n----------------\n1.2 (2009-11-23)\n----------------\n\n- Bug fix: tokens were stored in a manner that prevented them from\n being cleaned up properly in the utility's _principal_ids mapping.\n Make zope.locking.tokens.Token orderable to fix this, as tokens\n are stored as keys in BTrees.\n\n- Add a zope.app.generations Schema Manager to clean up any lingering\n tokens due to this bug. Token utilities not accessible through the\n component registry can be cleaned up manually with\n zope.locking.generations.fix_token_utility.\n\n- TokenUtility's register method will now add the token to the utility's\n database connection if the token provides IPersistent.\n\n- Clean up the tests and docs and move some common code to testing.py.\n\n- Fix some missing imports.\n\n---\n1.1\n---\n\n(series for Zope 3.4; eggs)\n\n1.1b\n----\n\n- converted to use eggs\n\n---\n1.0\n---\n\n(series for Zope 3.3; no dependencies on Zope eggs)\n\n1.0b\n----\n\nInitial non-dev release", "description_content_type": null, "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/zopefoundation/zope.locking", "keywords": "", "license": "ZPL 2.1", "maintainer": "", "maintainer_email": "", "name": "zope.locking", "package_url": "https://pypi.org/project/zope.locking/", "platform": "", "project_url": "https://pypi.org/project/zope.locking/", "project_urls": { "Homepage": "https://github.com/zopefoundation/zope.locking" }, "release_url": "https://pypi.org/project/zope.locking/2.0.0/", "requires_dist": null, "requires_python": "", "summary": "Advisory exclusive locks, shared locks, and freezes (locked to no-one).", "version": "2.0.0" }, "last_serial": 3514368, "releases": { "1.0b": [ { "comment_text": "", "digests": { "md5": "2afecccb918df93f7b46aa7e733ad2de", "sha256": "61d22db4d6b67d3fa0bd5c8058fa629e6bf57bd7b5fa25e9a50268ec5de44775" }, "downloads": -1, "filename": "zope.locking-1.0b-py2.4.egg", "has_sig": false, "md5_digest": "2afecccb918df93f7b46aa7e733ad2de", "packagetype": "bdist_egg", "python_version": "2.4", "requires_python": null, "size": 59721, "upload_time": "2007-07-04T20:18:31", "url": "https://files.pythonhosted.org/packages/74/7a/b4fade2b2db84a9362d89206c0defaf3594fda350124063d56c849b6daf7/zope.locking-1.0b-py2.4.egg" }, { "comment_text": "", "digests": { "md5": "0a95b7c0f8b6983d25b766f4f74c34e6", "sha256": "b61838bf25a3d5687aea23f302c1e505c7e36891bdc02164493d1516b0043422" }, "downloads": -1, "filename": "zope.locking-1.0b.tar.gz", "has_sig": false, "md5_digest": "0a95b7c0f8b6983d25b766f4f74c34e6", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 45426, "upload_time": "2007-07-04T20:18:28", "url": "https://files.pythonhosted.org/packages/14/ea/e48ecb826edb9f803bb61a63744696c844569e2e4330335051dab5432a49/zope.locking-1.0b.tar.gz" } ], "1.1b": [ { "comment_text": "", "digests": { "md5": "8622c628d3f18b50959995216dd47ed7", "sha256": "b65cf58fe3b05788a908e9b1c728d27f65cad8b0cf0ac01417e257db7d2d44d2" }, "downloads": -1, "filename": "zope.locking-1.1b-py2.4.egg", "has_sig": false, "md5_digest": "8622c628d3f18b50959995216dd47ed7", "packagetype": "bdist_egg", "python_version": "2.4", "requires_python": null, "size": 59917, "upload_time": "2007-07-04T20:54:53", "url": "https://files.pythonhosted.org/packages/87/f3/f1f14e7568c2545a0810294818ed17928c7be473002d2aa731373b2dc8b2/zope.locking-1.1b-py2.4.egg" }, { "comment_text": "", "digests": { "md5": "8e1aa00402d5b2e04b1eca4cffdb8c3e", "sha256": "01314dac7306ba227c8cc4def03c90778b9de334b510e76a7770aa49d19f1277" }, "downloads": -1, "filename": "zope.locking-1.1b.tar.gz", "has_sig": false, "md5_digest": "8e1aa00402d5b2e04b1eca4cffdb8c3e", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 45760, "upload_time": "2007-07-04T20:54:51", "url": "https://files.pythonhosted.org/packages/ec/fa/10a08e1c81a861a8d15bb07b8f7c9e33b52f58d78fddf870e0157024be9b/zope.locking-1.1b.tar.gz" } ], "1.2": [ { "comment_text": "", "digests": { "md5": "ad8a9a52902bf663193b0b6811581837", "sha256": "922b187044a797f2826b4941f9bec1fa5e8c9b32a7c0ce9dde4cc482477a34be" }, "downloads": -1, "filename": "zope.locking-1.2.tar.gz", "has_sig": false, "md5_digest": "ad8a9a52902bf663193b0b6811581837", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 46716, "upload_time": "2009-11-23T22:13:39", "url": "https://files.pythonhosted.org/packages/2b/69/052d72681ccc314fa3fe079e08000dcef7bf24bd27700e53aa88acd6daa8/zope.locking-1.2.tar.gz" } ], "1.2.1": [ { "comment_text": "", "digests": { "md5": "5216ef45151520ea99573d3c327b18ed", "sha256": "5a31e72be936dd9b2bde98e6fa0211cbdc135bd89a0707ae64bf746a9f342645" }, "downloads": -1, "filename": "zope.locking-1.2.1.tar.gz", "has_sig": false, "md5_digest": "5216ef45151520ea99573d3c327b18ed", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 47176, "upload_time": "2010-01-20T16:34:04", "url": "https://files.pythonhosted.org/packages/77/30/0d383feac1f84a159b5c53f393bcf3eddff2298def58d2371821c1b49ef1/zope.locking-1.2.1.tar.gz" } ], "1.2.2": [ { "comment_text": "", "digests": { "md5": "b415fbadf21b3d151cd1848d9cdcace3", "sha256": "66a94fcda859832a28eb8442f3962008d3023a207bacf3a00015638ce2347f92" }, "downloads": -1, "filename": "zope.locking-1.2.2.tar.gz", "has_sig": false, "md5_digest": "b415fbadf21b3d151cd1848d9cdcace3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 47657, "upload_time": "2011-01-31T17:40:36", "url": "https://files.pythonhosted.org/packages/e6/e8/4330c768e23759c2620a9323093ec578ba770fd4a38920e1ba8498e96587/zope.locking-1.2.2.tar.gz" } ], "2.0.0": [ { "comment_text": "", "digests": { "md5": "f77aba00a02895aa7531cc5aba38a255", "sha256": "592c4ec5a69a36b3a5a9af42585d620e7868759d5c841a5260871bc589c3627b" }, "downloads": -1, "filename": "zope.locking-2.0.0.tar.gz", "has_sig": false, "md5_digest": "f77aba00a02895aa7531cc5aba38a255", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 44905, "upload_time": "2018-01-23T15:54:56", "url": "https://files.pythonhosted.org/packages/eb/9e/abaa46fe66008ec1549b52d82de4d09439625d1ca296c8b81eccca073139/zope.locking-2.0.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "f77aba00a02895aa7531cc5aba38a255", "sha256": "592c4ec5a69a36b3a5a9af42585d620e7868759d5c841a5260871bc589c3627b" }, "downloads": -1, "filename": "zope.locking-2.0.0.tar.gz", "has_sig": false, "md5_digest": "f77aba00a02895aa7531cc5aba38a255", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 44905, "upload_time": "2018-01-23T15:54:56", "url": "https://files.pythonhosted.org/packages/eb/9e/abaa46fe66008ec1549b52d82de4d09439625d1ca296c8b81eccca073139/zope.locking-2.0.0.tar.gz" } ] }