建议性独占锁、共享锁和冻结(锁定为无人)。
项目描述
系统API
该包的核心方法是将锁和冻结令牌创建并由令牌实用工具注册。令牌只有在注册后才能工作。这可以确切地知道,从而操纵系统中的所有活动令牌。
然后,我们将介绍第一个对象,即TokenUtility:负责注册和检索令牌的实用工具。
>>> from zope import component, interface >>> from zope.locking import interfaces, utility, tokens >>> util = utility.TokenUtility() >>> from zope.interface.verify import verifyObject >>> verifyObject(interfaces.ITokenUtility, util) True
该实用工具只有几个方法–get、iterForPrincipalId、__iter__和register,我们将在下面查看。它应该持久化,并且包含的实现实际上是持久的。持久化并期望作为本地实用工具安装。实用工具在注册持久令牌之前需要与数据库建立连接。
>>> from zope.locking.testing import Demo >>> lock = tokens.ExclusiveLock(Demo(), 'Fantomas') >>> util.register(lock) Traceback (most recent call last): ... AttributeError: 'NoneType' object has no attribute 'add'>>> conn = get_connection() >>> conn.add(util)
如果令牌提供了IPersistent,实用工具将把它添加到它的连接中。
>>> lock._p_jar is None True>>> lock = util.register(lock) >>> lock._p_jar is util._p_jar True>>> lock.end() >>> lock = util.register(lock)
标准令牌实用工具可以接受任何可适配到IKeyReference的对象的令牌。
>>> import datetime >>> import pytz >>> before_creation = datetime.datetime.now(pytz.utc) >>> demo = Demo()
现在,使用demo类的实例,可以使用令牌实用工具为demo实例注册锁和冻结令牌。
如上所述,创建锁或冻结令牌的一般模式是先创建它–此时大多数方法和属性都不可用–然后将其注册到令牌实用工具。注册后,锁就有效并就位。
TokenUtility实际上可以与实现zope.locking.interfaces.IAbstractToken的任何东西一起使用,但我们将查看zope.locking包附带的前四个令牌:排他锁、共享锁、永久冻结和可结束冻结。
排他锁
排他锁是只由单个主体拥有的令牌。不能添加或删除任何主体:锁令牌必须结束并启动另一个,以便另一个主体能够获得锁的好处(无论它们被配置为什么)。
以下是一个创建和注册排他锁的示例:id为‘john’的主体锁定demo对象。
>>> lock = tokens.ExclusiveLock(demo, 'john') >>> res = util.register(lock) >>> res is lock True
锁令牌现在生效。注册令牌(锁)触发了一个ITokenStartedEvent,我们现在将探讨它。
(注意,此示例使用事件列表来查看已触发的事件。这是一个简单的列表,其append方法已添加为zope.event.subscribers列表的订阅者。当此文件作为测试运行时,它包含为全局。)
>>> from zope.component.eventtesting import events >>> ev = events[-1] >>> verifyObject(interfaces.ITokenStartedEvent, ev) True >>> ev.object is lock True
现在锁令牌已创建并注册,令牌实用工具知道它。实用工具的get方法只是简单地返回对象的活动令牌或None——它永远不会返回已结束的令牌,实际上,所有实用工具方法都不这样做。
>>> util.get(demo) is lock True >>> util.get(Demo()) is None True
注意,get接受交替的默认值,类似于dictionary.get
>>> util.get(Demo(), util) is util True
iterForPrincipalId方法返回给定主体ID的活动锁的迭代器。
>>> list(util.iterForPrincipalId('john')) == [lock] True >>> list(util.iterForPrincipalId('mary')) == [] True
实用工具的__iter__方法只是遍历所有活动(非结束)令牌。
>>> list(util) == [lock] True
令牌实用工具不允许为同一对象注册多个活动令牌。
>>> util.register(tokens.ExclusiveLock(demo, 'mary')) ... # doctest: +ELLIPSIS Traceback (most recent call last): ... zope.locking.interfaces.RegistrationError: ... >>> util.register(tokens.SharedLock(demo, ('mary', 'jane'))) ... # doctest: +ELLIPSIS Traceback (most recent call last): ... zope.locking.interfaces.RegistrationError: ... >>> util.register(tokens.Freeze(demo)) ... # doctest: +ELLIPSIS Traceback (most recent call last): ... zope.locking.interfaces.RegistrationError: ...
还值得看一下锁令牌本身。已注册的锁令牌实现了IExclusiveLock。
>>> verifyObject(interfaces.IExclusiveLock, lock) True
它提供了一系列功能。或许最重要的属性是令牌是否生效:ended。此令牌是活动的,因此它尚未结束
>>> lock.ended is None True
当它结束时,ended属性是UTC时间戳,表示令牌何时结束。我们将在下面演示。
稍后,creation、expiration、duration和remaining_duration将很重要;现在我们只注意它们的存在。
>>> before_creation <= lock.started <= datetime.datetime.now(pytz.utc) True >>> lock.expiration is None # == forever True >>> lock.duration is None # == forever True >>> lock.remaining_duration is None # == forever True
end方法和相关的结束和过期属性都是IEndable接口的一部分——不是所有令牌都必须实现的接口,我们稍后也将讨论。
>>> interfaces.IEndable.providedBy(lock) True
context和__parent__属性指向被锁定的对象——在我们的示例中是demo。《context》是获取对象的预期标准API,但《__parent__》对于Zope 3安全设置很重要,如本文档末尾所述。
>>> lock.context is demo True >>> lock.__parent__ is demo # important for security True
将锁注册到令牌实用工具时设置了实用工具属性,并将开始属性初始化为锁开始的日期时间。除了令牌实用工具之外,任何代码都不应设置实用工具属性。
>>> lock.utility is util True
令牌始终提供一个principal_ids属性,该属性提供了一个令牌中包含的主体的可迭代对象。在我们的例子中,这是对‘john’的排他锁,因此值很简单。
>>> sorted(lock.principal_ids) ['john']
基本令牌(如排他锁)上的唯一方法是end。不带参数调用它将永久且明确地结束令牌的生命。
>>> lock.end()
像注册令牌一样,结束令牌会触发一个事件。
>>> ev = events[-1] >>> verifyObject(interfaces.ITokenEndedEvent, ev) True >>> ev.object is lock True
它影响令牌上的属性。同样,其中最重要的是ended,现在是结束的日期时间。
>>> lock.ended >= lock.started True >>> lock.remaining_duration == datetime.timedelta() True
它还会影响对令牌实用工具的查询。
>>> util.get(demo) is None True >>> list(util.iterForPrincipalId('john')) == [] True >>> list(util) == [] True
不要尝试结束已结束的令牌。
>>> lock.end() Traceback (most recent call last): ... zope.locking.interfaces.EndedError
结束令牌的另一种方式是使用过期日期时间。我们将看到,处理超时时最重要的一点是,由于超时而过期的令牌不会触发任何过期事件。它只是开始为ended属性提供expiration值。
>>> one = datetime.timedelta(hours=1) >>> two = datetime.timedelta(hours=2) >>> three = datetime.timedelta(hours=3) >>> four = datetime.timedelta(hours=4) >>> lock = util.register(tokens.ExclusiveLock(demo, 'john', three)) >>> lock.duration datetime.timedelta(seconds=10800) >>> three >= lock.remaining_duration >= two True >>> lock.ended is None True >>> util.get(demo) is lock True >>> list(util.iterForPrincipalId('john')) == [lock] True >>> list(util) == [lock] True
可结束令牌的过期时间始终是创建日期加上超时时间。
>>> lock.expiration == lock.started + lock.duration True >>> ((before_creation + three) <= ... (lock.expiration) <= # this value is the expiration date ... (before_creation + four)) True
可以在锁仍然活动时更改过期时间,使用任何expiration、remaining_duration或duration属性。所有更改都会触发事件。首先,我们将更改过期属性。
>>> lock.expiration = lock.started + one >>> lock.expiration == lock.started + one True >>> lock.duration == one True >>> ev = events[-1] >>> verifyObject(interfaces.IExpirationChangedEvent, ev) True >>> ev.object is lock True >>> ev.old == lock.started + three True
然后,我们将更改持续时间属性。
>>> lock.duration = four >>> lock.duration datetime.timedelta(seconds=14400) >>> four >= lock.remaining_duration >= three True >>> ev = events[-1] >>> verifyObject(interfaces.IExpirationChangedEvent, ev) True >>> ev.object is lock True >>> ev.old == lock.started + one True
现在,我们将修改我们的代码,使其认为它是两小时后,然后检查并修改剩余持续时间属性。
>>> def hackNow(): ... return (datetime.datetime.now(pytz.utc) + ... datetime.timedelta(hours=2)) ... >>> import zope.locking.utils >>> oldNow = zope.locking.utils.now >>> zope.locking.utils.now = hackNow # make code think it's 2 hours later >>> lock.duration datetime.timedelta(seconds=14400) >>> two >= lock.remaining_duration >= one True >>> lock.remaining_duration -= one >>> one >= lock.remaining_duration >= datetime.timedelta() True >>> three + datetime.timedelta(minutes=1) >= lock.duration >= three True >>> ev = events[-1] >>> verifyObject(interfaces.IExpirationChangedEvent, ev) True >>> ev.object is lock True >>> ev.old == lock.started + four True
现在,我们将修改我们的代码,使其认为它是一天后。非常重要的一点是记住,带有超时的锁无声结束——也就是说,不会触发事件。
>>> def hackNow(): ... return ( ... datetime.datetime.now(pytz.utc) + datetime.timedelta(days=1)) ... >>> zope.locking.utils.now = hackNow # make code think it is a day later >>> lock.ended == lock.expiration True >>> util.get(demo) is None True >>> util.get(demo, util) is util # alternate default works True >>> lock.remaining_duration == datetime.timedelta() True >>> lock.end() Traceback (most recent call last): ... zope.locking.interfaces.EndedError
一旦锁已结束,就无法再更改超时时间。
>>> lock.duration = datetime.timedelta(days=2) Traceback (most recent call last): ... zope.locking.interfaces.EndedError
我们将撤销这些修改,并结束锁(在修改完成后,锁将不再结束)。
>>> zope.locking.utils.now = oldNow # undo the hack >>> lock.end()
请确保注册令牌。创建锁但未注册会使它处于未完全初始化的状态。
>>> lock = tokens.ExclusiveLock(demo, 'john') >>> lock.started # doctest: +ELLIPSIS Traceback (most recent call last): ... zope.locking.interfaces.UnregisteredError: ... >>> lock.ended # doctest: +ELLIPSIS Traceback (most recent call last): ... zope.locking.interfaces.UnregisteredError: ...
可结束的冻结
可结束的冻结令牌与锁令牌类似,但不会授予任何人的“锁”。
>>> token = util.register(tokens.EndableFreeze(demo)) >>> verifyObject(interfaces.IEndableFreeze, token) True >>> ev = events[-1] >>> verifyObject(interfaces.ITokenStartedEvent, ev) True >>> ev.object is token True >>> sorted(token.principal_ids) [] >>> token.end()
除了可结束的冻结之外,它们与独占锁完全相同。请参阅annoying.txt,以获取复制粘贴测试的综合副本,这些测试重复了独占锁测试。请注意,EndableFreeze永远不会成为主体令牌的可迭代部分:根据定义,冻结与没有任何主体相关联。
冻结
冻结与可结束的冻结类似,但它们不可结束。它们旨在用于系统级操作,这些操作应永久禁用某些更改,例如更改存档对象版本的 内容。
创建它们的方式是相同的...
>>> token = util.register(tokens.Freeze(demo)) >>> verifyObject(interfaces.IFreeze, token) True >>> ev = events[-1] >>> verifyObject(interfaces.ITokenStartedEvent, ev) True >>> ev.object is token True >>> sorted(token.principal_ids) []
但它们不会消失...
>>> token.end() Traceback (most recent call last): ... AttributeError: 'Freeze' object has no attribute 'end'
它们也没有过期、持续时间、剩余持续时间或结束日期。它们是永久的,除非您进入数据库来篡改特定于实现的 数据结构。
没有API方法可以结束冻结。我们需要为剩余的演示创建一个新对象,并且此令牌将存在于剩余的示例中。
>>> old_demo = demo >>> demo = Demo()
用户API,适配器和安全
到目前为止讨论的API对一些常见的锁定用例做出了很少的妥协。以下是迄今为止讨论中尚未满足的一些特定需求。
应该允许或拒绝对象级别的用户创建和注册令牌。
通常,注册可结束令牌比注册永久令牌更容易。
所有用户都应能够解锁或修改他们自己的令牌的一些方面,或从共享令牌中移除他们的参与;但是,应可能限制对用户不拥有的结束令牌的访问(通常称为“打破锁”)。
在Zope 3安全模型中,前两个需求旨在通过ITokenBroker接口及其关联的适配器来解决,最后一个需求旨在通过ITokenHandler及其关联的适配器来解决。
令牌经纪商
令牌经纪人适配器适配一个对象,该对象是代理令牌的对象,并使用该对象作为安全上下文。它们提供了一些有用的方法:lock、lockShared、freeze 和 get。TokenBroker期望成为一个受信任的适配器。
lock
lock方法创建并注册一个独占锁。如果没有参数,它将尝试为当前交互中的用户创建它。
当然,没有交互是无法工作的。请注意,我们通过注册实用工具来开始示例。通常,我们需要将实用工具放在站点包中,以便它持久存在,但为了这个演示,我们简化了注册过程。
>>> component.provideUtility(util, provides=interfaces.ITokenUtility)>>> import zope.interface.interfaces >>> @interface.implementer(zope.interface.interfaces.IComponentLookup) ... @component.adapter(interface.Interface) ... def siteManager(obj): ... return component.getGlobalSiteManager() ... >>> component.provideAdapter(siteManager)>>> from zope.locking import adapters >>> component.provideAdapter(adapters.TokenBroker) >>> broker = interfaces.ITokenBroker(demo) >>> broker.lock() Traceback (most recent call last): ... ValueError >>> broker.lock('joe') Traceback (most recent call last): ... zope.locking.interfaces.ParticipationError
如果我们与一个参与者建立交互,锁的成功率会更高。
>>> import zope.security.interfaces >>> @interface.implementer(zope.security.interfaces.IPrincipal) ... class DemoPrincipal(object): ... def __init__(self, id, title=None, description=None): ... self.id = id ... self.title = title ... self.description = description ... >>> joe = DemoPrincipal('joe') >>> import zope.security.management >>> @interface.implementer(zope.security.interfaces.IParticipation) ... class DemoParticipation(object): ... def __init__(self, principal): ... self.principal = principal ... self.interaction = None ... >>> zope.security.management.endInteraction() >>> zope.security.management.newInteraction(DemoParticipation(joe))>>> token = broker.lock() >>> interfaces.IExclusiveLock.providedBy(token) True >>> token.context is demo True >>> token.__parent__ is demo True >>> sorted(token.principal_ids) ['joe'] >>> token.started is not None True >>> util.get(demo) is token True >>> token.end()
您只能指定当前交互中的主体。
>>> token = broker.lock('joe') >>> sorted(token.principal_ids) ['joe'] >>> token.end() >>> broker.lock('mary') Traceback (most recent call last): ... zope.locking.interfaces.ParticipationError
此方法可以接受一个持续时间。
>>> token = broker.lock(duration=two) >>> token.duration == two True >>> token.end()
如果交互中有多个主体,则必须指定一个主体(在交互中)。
>>> mary = DemoPrincipal('mary') >>> participation = DemoParticipation(mary) >>> zope.security.management.getInteraction().add(participation) >>> broker.lock() Traceback (most recent call last): ... ValueError >>> broker.lock('susan') Traceback (most recent call last): ... zope.locking.interfaces.ParticipationError >>> token = broker.lock('joe') >>> sorted(token.principal_ids) ['joe'] >>> token.end() >>> token = broker.lock('mary') >>> sorted(token.principal_ids) ['mary'] >>> token.end() >>> zope.security.management.endInteraction()
freeze
freeze 方法允许用户创建一个可终止的冻结。它对交互没有要求。应从安全角度小心保护。
>>> token = broker.freeze() >>> interfaces.IEndableFreeze.providedBy(token) True >>> token.context is demo True >>> token.__parent__ is demo True >>> sorted(token.principal_ids) [] >>> token.started is not None True >>> util.get(demo) is token True >>> token.end()
此方法可以接受一个持续时间。
>>> token = broker.freeze(duration=two) >>> token.duration == two True >>> token.end()
get
get 方法与令牌实用工具的 get 方法完全等价:它返回对象的当前活动令牌,或 None。这对于受保护的代码很有用,因为实用工具通常不获取安全断言,而此方法可以从对象获取其安全断言,这通常是正确的位置。
再次强调,TokenBroker 确实体现了某些策略;如果这不是您应用程序的好策略,请构建自己的接口和适配器。
令牌处理器
TokenHandlers 对于具有一个或多个主体的可终止令牌很有用——也就是说,锁,但不包括冻结。它们旨在以比通常的令牌方法和属性更低的外部安全权限进行保护,然后根据当前交互执行自己的检查。它们在很大程度上是策略,其他方法可能也很有用。它们旨在作为可信适配器进行注册。
因此,对于排他性锁和共享锁,我们有了令牌处理器。通常,令牌处理器提供了与其对应的令牌相同的所有功能,以下是一些额外的约束和能力:
过期时间、持续时间 和 剩余持续时间 只能在当前交互中的所有主体都是包装令牌的所有者的情况下设置;
释放 如果当前交互中的所有主体都是包装令牌的所有者,则从交互中删除一些或所有主体。
请注意,end 不受影响:这实际上是“打破锁”,而 release 实际上是“解锁”。权限应相应设置。
共享锁处理器在其部分中讨论了两个额外的方法。
排他锁处理器
基于上述一般约束,排他性锁处理器通常仅在操作与仅锁所有者的交互中时才允许访问其特殊功能。
>>> zope.security.management.newInteraction(DemoParticipation(joe)) >>> component.provideAdapter(adapters.ExclusiveLockHandler) >>> lock = broker.lock() >>> handler = interfaces.IExclusiveLockHandler(lock) >>> verifyObject(interfaces.IExclusiveLockHandler, handler) True >>> handler.__parent__ is lock True >>> handler.expiration is None True >>> handler.duration = two >>> lock.duration == two True >>> handler.expiration = handler.started + three >>> lock.expiration == handler.started + three True >>> handler.remaining_duration = two >>> lock.remaining_duration <= two True >>> handler.release() >>> handler.ended >= handler.started True >>> lock.ended >= lock.started True >>> lock = util.register(tokens.ExclusiveLock(demo, 'mary')) >>> handler = interfaces.ITokenHandler(lock) # for joe's interaction still >>> handler.duration = two # doctest: +ELLIPSIS Traceback (most recent call last): ... zope.locking.interfaces.ParticipationError: ... >>> handler.expiration = handler.started + three # doctest: +ELLIPSIS Traceback (most recent call last): ... zope.locking.interfaces.ParticipationError: ... >>> handler.remaining_duration = two # doctest: +ELLIPSIS Traceback (most recent call last): ... zope.locking.interfaces.ParticipationError: ... >>> handler.release() # doctest: +ELLIPSIS Traceback (most recent call last): ... zope.locking.interfaces.ParticipationError: ... >>> lock.end()
警告
令牌实用工具会在可能的情况下为对象注册令牌。它不会检查它是否确实是给定对象的本地令牌实用工具。这应由令牌实用工具的客户端来安排,如果需要,可以通过外部验证。
令牌作为BTree中的键存储,因此必须是可排序的(即,它们必须实现 __cmp__)。
预期安全配置
在Zope 3中,实用工具通常是未受保护的——或者更准确地说,没有安全断言,并且没有安全代理的使用——令牌实用工具期望如此。因此,经纪人对象和处理对象应预期为视图代码使用的对象,并且与安全代理相关联。所有这些都应该具有适当的 __parent__ 属性值。修改令牌的能力——例如,end、add 和 remove 方法——应通过类似于“zope.Security”的管理员类型权限进行保护。设置令牌的超时属性应以相同的方式进行保护。设置处理器的属性可以具有更宽松的设置,因为它们根据锁成员资格自己计算安全性。
在适配器上,end 方法应使用相同或类似的权限进行保护。调用锁定和锁定共享等方法应使用类似“zope.ManageContent”的权限。获取属性应为“zope.View”或“zope.Public”,解锁和设置超时,因为它们已经受到保护以确保主体是锁的成员,可能可以“zope.Public”。
这些设置可以通过相对容易的方式滥用以创建不安全的系统——例如,如果用户可以获取对另一个主体的 IPrincipalLockable 的适配器——但这是一个合理的起点。
>>> broker.__parent__ is demo True >>> handler.__parent__ is lock True
随想
由于设计的影响,可能会同时使用多个锁定实用工具,以管理对象的不同方面;然而,这本身可能永远不会有用。
更改
2.1.0 (2020-04-15)
修复 ObjectEvent 的弃用警告。
添加对 Python 3.7 和 3.8 的支持。
放弃对 Python 3.3 和 3.4 的支持。
2.0.0 (2018-01-23)
Python 3 兼容性。
注意:浏览器视图和相关代码已删除。现在需要在应用程序级代码中提供这些。
打包 zcml 文件。
更新依赖关系。
从 svn.zope.org 恢复。
1.2.2 (2011-01-31)
合并重复的进化代码。
将生成配置拆分为自己的 zcml 文件。
1.2.1 (2010-01-20)
错误修复:1.2 中添加的生成没有正确清理过期的令牌,并且可能使令牌实用工具处于不一致的状态。
1.2 (2009-11-23)
错误修复:令牌以阻止在实用工具的 _principal_ids 映射中正确清理的方式存储。将 zope.locking.tokens.Token 制造为可排序的以修复此问题,因为令牌作为 BTree 中的键存储。
添加 zope.app.generations Schema Manager 以清理由于此错误而留下的任何残留令牌。无法通过组件注册访问的令牌实用工具可以手动使用 zope.locking.generations.fix_token_utility 进行清理。
TokenUtility 的 register 方法现在将在令牌提供 IPersistent 时将其添加到实用工具的数据库连接中。
清理测试和文档,并将一些常用代码移动到 testing.py。
修复一些缺失的导入。
1.1
(Zope 3.4 的系列;egg)
1.1b
转换为使用 eggs
1.0
(Zope 3.3 的系列;不依赖于 Zope eggs)
1.0b
初始非开发版本
项目详情
zope.locking-2.1.0.tar.gz 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | af4cfac7eeccf3e36d67d8276f47344e87c9ba1d9c6d5ff5a0fa9023eaf6d60d |
|
MD5 | 7e781f242652ad6d13bb223c559b54b1 |
|
BLAKE2b-256 | 4518fef69d76f103044ac961ea3d5a6ec2de9d1ef0674835339e683b84e41be9 |