zope.locking 3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- zope/locking/README.rst +1070 -0
- zope/locking/__init__.py +1 -0
- zope/locking/adapters.py +174 -0
- zope/locking/annoying.rst +353 -0
- zope/locking/cleanup.rst +457 -0
- zope/locking/configure.zcml +9 -0
- zope/locking/ftesting.zcml +12 -0
- zope/locking/generations.py +97 -0
- zope/locking/generations.zcml +11 -0
- zope/locking/interfaces.py +462 -0
- zope/locking/testing.py +64 -0
- zope/locking/tests.py +67 -0
- zope/locking/tokens.py +256 -0
- zope/locking/utility.py +149 -0
- zope/locking/utils.py +23 -0
- zope.locking-3.0-py3.12-nspkg.pth +1 -0
- zope_locking-3.0.dist-info/LICENSE.rst +44 -0
- zope_locking-3.0.dist-info/METADATA +1246 -0
- zope_locking-3.0.dist-info/RECORD +22 -0
- zope_locking-3.0.dist-info/WHEEL +5 -0
- zope_locking-3.0.dist-info/namespace_packages.txt +1 -0
- zope_locking-3.0.dist-info/top_level.txt +1 -0
zope/locking/__init__.py
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# package
|
zope/locking/adapters.py
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
##############################################################################
|
2
|
+
#
|
3
|
+
# Copyright (c) 2018 Zope Foundation and Contributors.
|
4
|
+
# All Rights Reserved.
|
5
|
+
#
|
6
|
+
# This software is subject to the provisions of the Zope Public License,
|
7
|
+
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
|
8
|
+
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
|
9
|
+
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
10
|
+
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
|
11
|
+
# FOR A PARTICULAR PURPOSE.
|
12
|
+
#
|
13
|
+
##############################################################################
|
14
|
+
|
15
|
+
import zope.security.management
|
16
|
+
|
17
|
+
from zope import component
|
18
|
+
from zope import interface
|
19
|
+
from zope.locking import interfaces
|
20
|
+
from zope.locking import tokens
|
21
|
+
|
22
|
+
|
23
|
+
@component.adapter(interface.Interface)
|
24
|
+
@interface.implementer(interfaces.ITokenBroker)
|
25
|
+
class TokenBroker:
|
26
|
+
|
27
|
+
def __init__(self, context):
|
28
|
+
self.context = self.__parent__ = context
|
29
|
+
self.utility = component.getUtility(
|
30
|
+
interfaces.ITokenUtility, context=context)
|
31
|
+
|
32
|
+
# for subclasses to call, to avoid duplicating code
|
33
|
+
def _getLockPrincipalId(self, principal_id):
|
34
|
+
interaction_principals = getInteractionPrincipals()
|
35
|
+
if principal_id is None:
|
36
|
+
if (interaction_principals is None
|
37
|
+
or len(interaction_principals) != 1):
|
38
|
+
raise ValueError
|
39
|
+
principal_id = next(iter(interaction_principals))
|
40
|
+
elif (interaction_principals is None or
|
41
|
+
principal_id not in interaction_principals):
|
42
|
+
raise interfaces.ParticipationError
|
43
|
+
return principal_id
|
44
|
+
|
45
|
+
def lock(self, principal_id=None, duration=None):
|
46
|
+
principal_id = self._getLockPrincipalId(principal_id)
|
47
|
+
return self.utility.register(
|
48
|
+
tokens.ExclusiveLock(self.context, principal_id, duration))
|
49
|
+
|
50
|
+
# for subclasses to call, to avoid duplicating code
|
51
|
+
def _getSharedLockPrincipalIds(self, principal_ids):
|
52
|
+
interaction_principals = getInteractionPrincipals()
|
53
|
+
if principal_ids is None:
|
54
|
+
if (interaction_principals is None
|
55
|
+
or len(interaction_principals) < 1):
|
56
|
+
raise ValueError
|
57
|
+
principal_ids = interaction_principals
|
58
|
+
elif (interaction_principals is None or
|
59
|
+
set(principal_ids).difference(interaction_principals)):
|
60
|
+
raise interfaces.ParticipationError
|
61
|
+
return principal_ids
|
62
|
+
|
63
|
+
def lockShared(self, principal_ids=None, duration=None):
|
64
|
+
principal_ids = self._getSharedLockPrincipalIds(principal_ids)
|
65
|
+
return self.utility.register(
|
66
|
+
tokens.SharedLock(self.context, principal_ids, duration))
|
67
|
+
|
68
|
+
def freeze(self, duration=None):
|
69
|
+
return self.utility.register(
|
70
|
+
tokens.EndableFreeze(self.context, duration))
|
71
|
+
|
72
|
+
def get(self):
|
73
|
+
return self.utility.get(self.context)
|
74
|
+
|
75
|
+
|
76
|
+
def getInteractionPrincipals():
|
77
|
+
interaction = zope.security.management.queryInteraction()
|
78
|
+
if interaction is not None:
|
79
|
+
return {p.principal.id for p in interaction.participations}
|
80
|
+
|
81
|
+
|
82
|
+
class TokenHandler:
|
83
|
+
def __init__(self, token):
|
84
|
+
self.__parent__ = self.token = token
|
85
|
+
|
86
|
+
def __getattr__(self, name):
|
87
|
+
return getattr(self.token, name)
|
88
|
+
|
89
|
+
def _checkInteraction(self):
|
90
|
+
if self.token.ended is not None:
|
91
|
+
raise interfaces.ExpirationChangedEvent
|
92
|
+
interaction_principals = getInteractionPrincipals()
|
93
|
+
token_principals = frozenset(self.token.principal_ids)
|
94
|
+
if interaction_principals is not None:
|
95
|
+
omitted = interaction_principals.difference(token_principals)
|
96
|
+
if omitted:
|
97
|
+
raise interfaces.ParticipationError(omitted)
|
98
|
+
return interaction_principals, token_principals
|
99
|
+
|
100
|
+
def _getPrincipalIds(self, principal_ids):
|
101
|
+
interaction_principals, token_principals = self._checkInteraction()
|
102
|
+
if principal_ids is None:
|
103
|
+
principal_ids = interaction_principals or ()
|
104
|
+
else:
|
105
|
+
for p in principal_ids:
|
106
|
+
if p not in interaction_principals:
|
107
|
+
raise ValueError(p)
|
108
|
+
return principal_ids, interaction_principals, token_principals
|
109
|
+
|
110
|
+
@property
|
111
|
+
def expiration(self):
|
112
|
+
return self.token.expiration
|
113
|
+
|
114
|
+
@expiration.setter
|
115
|
+
def expiration(self, value):
|
116
|
+
self._checkInteraction()
|
117
|
+
self.token.expiration = value
|
118
|
+
|
119
|
+
@property
|
120
|
+
def duration(self):
|
121
|
+
return self.token.duration
|
122
|
+
|
123
|
+
@duration.setter
|
124
|
+
def duration(self, value):
|
125
|
+
self._checkInteraction()
|
126
|
+
self.token.duration = value
|
127
|
+
|
128
|
+
@property
|
129
|
+
def remaining_duration(self):
|
130
|
+
return self.token.remaining_duration
|
131
|
+
|
132
|
+
@remaining_duration.setter
|
133
|
+
def remaining_duration(self, value):
|
134
|
+
self._checkInteraction()
|
135
|
+
self.token.remaining_duration = value
|
136
|
+
|
137
|
+
def release(self, principal_ids=None):
|
138
|
+
raise NotImplementedError
|
139
|
+
|
140
|
+
|
141
|
+
@component.adapter(interfaces.IExclusiveLock)
|
142
|
+
@interface.implementer(interfaces.IExclusiveLockHandler)
|
143
|
+
class ExclusiveLockHandler(TokenHandler):
|
144
|
+
|
145
|
+
def release(self, principal_ids=None):
|
146
|
+
pids, interaction_pids, token_pids = self._getPrincipalIds(
|
147
|
+
principal_ids)
|
148
|
+
remaining = token_pids.difference(pids)
|
149
|
+
if not remaining:
|
150
|
+
self.token.end()
|
151
|
+
|
152
|
+
|
153
|
+
@component.adapter(interfaces.ISharedLock)
|
154
|
+
@interface.implementer(interfaces.ISharedLockHandler)
|
155
|
+
class SharedLockHandler(TokenHandler):
|
156
|
+
|
157
|
+
def release(self, principal_ids=None):
|
158
|
+
pids, interaction_pids, token_pids = self._getPrincipalIds(
|
159
|
+
principal_ids)
|
160
|
+
self.token.remove(pids)
|
161
|
+
|
162
|
+
def join(self, principal_ids=None):
|
163
|
+
interaction_principals = getInteractionPrincipals()
|
164
|
+
if principal_ids is None:
|
165
|
+
if interaction_principals is None:
|
166
|
+
raise ValueError
|
167
|
+
principal_ids = interaction_principals
|
168
|
+
elif set(principal_ids).difference(interaction_principals):
|
169
|
+
raise interfaces.ParticipationError
|
170
|
+
self.token.add(principal_ids)
|
171
|
+
|
172
|
+
def add(self, principal_ids):
|
173
|
+
self._checkInteraction()
|
174
|
+
self.token.add(principal_ids)
|
@@ -0,0 +1,353 @@
|
|
1
|
+
This file is for annoying tests that were not appropriate for the README but
|
2
|
+
should be included for completeness.
|
3
|
+
|
4
|
+
This is some setup that the tests need.
|
5
|
+
|
6
|
+
>>> from zope.locking import utility, interfaces, tokens
|
7
|
+
>>> util = utility.TokenUtility()
|
8
|
+
>>> from zope.interface.verify import verifyObject
|
9
|
+
>>> verifyObject(interfaces.ITokenUtility, util)
|
10
|
+
True
|
11
|
+
|
12
|
+
>>> from zope import interface, component
|
13
|
+
|
14
|
+
>>> conn = get_connection()
|
15
|
+
>>> conn.add(util)
|
16
|
+
|
17
|
+
>>> import datetime
|
18
|
+
>>> import pytz
|
19
|
+
>>> before_creation = datetime.datetime.now(pytz.utc)
|
20
|
+
>>> from zope.locking.testing import Demo
|
21
|
+
>>> demo = Demo()
|
22
|
+
|
23
|
+
----------------------------------
|
24
|
+
Timed Expirations for Shared Locks
|
25
|
+
----------------------------------
|
26
|
+
|
27
|
+
Timed expirations work the same as with exclusive locks.
|
28
|
+
|
29
|
+
>>> one = datetime.timedelta(hours=1)
|
30
|
+
>>> two = datetime.timedelta(hours=2)
|
31
|
+
>>> three = datetime.timedelta(hours=3)
|
32
|
+
>>> four = datetime.timedelta(hours=4)
|
33
|
+
|
34
|
+
>>> lock = util.register(
|
35
|
+
... tokens.SharedLock(demo, ('john', 'mary'), duration=three))
|
36
|
+
>>> lock.duration
|
37
|
+
datetime.timedelta(seconds=10800)
|
38
|
+
>>> three >= lock.remaining_duration >= two
|
39
|
+
True
|
40
|
+
>>> lock.expiration == lock.started + lock.duration
|
41
|
+
True
|
42
|
+
>>> ((before_creation + three) <=
|
43
|
+
... (lock.expiration) <=
|
44
|
+
... (before_creation + four))
|
45
|
+
True
|
46
|
+
>>> lock.ended is None
|
47
|
+
True
|
48
|
+
>>> util.get(demo) is lock
|
49
|
+
True
|
50
|
+
>>> list(util.iterForPrincipalId('john')) == [lock]
|
51
|
+
True
|
52
|
+
>>> list(util.iterForPrincipalId('mary')) == [lock]
|
53
|
+
True
|
54
|
+
>>> list(util) == [lock]
|
55
|
+
True
|
56
|
+
|
57
|
+
Again, expirations can be changed while a lock is still active, using any of
|
58
|
+
the `expiration`, `remaining_duration` or `duration` attributes. All changes
|
59
|
+
fire events. First we'll change the expiration attribute.
|
60
|
+
|
61
|
+
>>> lock.expiration = lock.started + one
|
62
|
+
>>> lock.expiration == lock.started + one
|
63
|
+
True
|
64
|
+
>>> lock.duration == one
|
65
|
+
True
|
66
|
+
>>> from zope.component.eventtesting import events
|
67
|
+
>>> ev = events[-1]
|
68
|
+
>>> verifyObject(interfaces.IExpirationChangedEvent, ev)
|
69
|
+
True
|
70
|
+
>>> ev.object is lock
|
71
|
+
True
|
72
|
+
>>> ev.old == lock.started + three
|
73
|
+
True
|
74
|
+
|
75
|
+
Next we'll change the duration attribute.
|
76
|
+
|
77
|
+
>>> lock.duration = four
|
78
|
+
>>> lock.duration
|
79
|
+
datetime.timedelta(seconds=14400)
|
80
|
+
>>> four >= lock.remaining_duration >= three
|
81
|
+
True
|
82
|
+
>>> ev = events[-1]
|
83
|
+
>>> verifyObject(interfaces.IExpirationChangedEvent, ev)
|
84
|
+
True
|
85
|
+
>>> ev.object is lock
|
86
|
+
True
|
87
|
+
>>> ev.old == lock.started + one
|
88
|
+
True
|
89
|
+
|
90
|
+
Now we'll hack our code to make it think that it is two hours later, and then
|
91
|
+
check and modify the remaining_duration attribute.
|
92
|
+
|
93
|
+
>>> def hackNow():
|
94
|
+
... return (datetime.datetime.now(pytz.utc) +
|
95
|
+
... datetime.timedelta(hours=2))
|
96
|
+
...
|
97
|
+
>>> import zope.locking.utils
|
98
|
+
>>> oldNow = zope.locking.utils.now
|
99
|
+
>>> zope.locking.utils.now = hackNow # make code think it's 2 hours later
|
100
|
+
>>> lock.duration
|
101
|
+
datetime.timedelta(seconds=14400)
|
102
|
+
>>> two >= lock.remaining_duration >= one
|
103
|
+
True
|
104
|
+
>>> lock.remaining_duration -= datetime.timedelta(hours=1)
|
105
|
+
>>> one >= lock.remaining_duration >= datetime.timedelta()
|
106
|
+
True
|
107
|
+
>>> three + datetime.timedelta(minutes=1) >= lock.duration >= three
|
108
|
+
True
|
109
|
+
>>> ev = events[-1]
|
110
|
+
>>> verifyObject(interfaces.IExpirationChangedEvent, ev)
|
111
|
+
True
|
112
|
+
>>> ev.object is lock
|
113
|
+
True
|
114
|
+
>>> ev.old == lock.started + four
|
115
|
+
True
|
116
|
+
|
117
|
+
Now, we'll hack our code to make it think that it's a day later. It is very
|
118
|
+
important to remember that a lock ending with a timeout ends silently--that
|
119
|
+
is, no event is fired.
|
120
|
+
|
121
|
+
>>> def hackNow():
|
122
|
+
... return (
|
123
|
+
... datetime.datetime.now(pytz.utc) + datetime.timedelta(days=1))
|
124
|
+
...
|
125
|
+
>>> zope.locking.utils.now = hackNow # make code think it is a day later
|
126
|
+
>>> lock.ended >= lock.started
|
127
|
+
True
|
128
|
+
>>> util.get(demo) is None
|
129
|
+
True
|
130
|
+
>>> lock.remaining_duration == datetime.timedelta()
|
131
|
+
True
|
132
|
+
>>> list(util.iterForPrincipalId('john')) == []
|
133
|
+
True
|
134
|
+
>>> list(util.iterForPrincipalId('mary')) == []
|
135
|
+
True
|
136
|
+
>>> list(util) == []
|
137
|
+
True
|
138
|
+
>>> lock.end()
|
139
|
+
Traceback (most recent call last):
|
140
|
+
...
|
141
|
+
zope.locking.interfaces.EndedError
|
142
|
+
|
143
|
+
Once a lock has ended, the timeout can no longer be changed.
|
144
|
+
|
145
|
+
>>> lock.duration = datetime.timedelta(days=2)
|
146
|
+
Traceback (most recent call last):
|
147
|
+
...
|
148
|
+
zope.locking.interfaces.EndedError
|
149
|
+
|
150
|
+
We'll undo the hacks, and also end the lock (that is no longer ended once
|
151
|
+
the hack is finished).
|
152
|
+
|
153
|
+
>>> zope.locking.utils.now = oldNow # undo the hack
|
154
|
+
>>> lock.end()
|
155
|
+
|
156
|
+
--------------
|
157
|
+
EndableFreezes
|
158
|
+
--------------
|
159
|
+
|
160
|
+
An endable freeze token is similar to a lock token except that it grants the
|
161
|
+
'lock' to no one.
|
162
|
+
|
163
|
+
>>> token = util.register(tokens.EndableFreeze(demo))
|
164
|
+
>>> ev = events[-1]
|
165
|
+
>>> verifyObject(interfaces.ITokenStartedEvent, ev)
|
166
|
+
True
|
167
|
+
>>> ev.object is token
|
168
|
+
True
|
169
|
+
>>> sorted(token.principal_ids)
|
170
|
+
[]
|
171
|
+
|
172
|
+
Freezes are otherwise identical to exclusive locks.
|
173
|
+
|
174
|
+
The returned token implements IEndableFreeze and provides the same
|
175
|
+
capabilities as IExclusiveLock.
|
176
|
+
|
177
|
+
>>> verifyObject(interfaces.IEndableFreeze, token)
|
178
|
+
True
|
179
|
+
>>> token.context is demo
|
180
|
+
True
|
181
|
+
>>> token.__parent__ is demo # important for security
|
182
|
+
True
|
183
|
+
>>> token.utility is util
|
184
|
+
True
|
185
|
+
>>> token.ended is None
|
186
|
+
True
|
187
|
+
>>> before_creation <= token.started <= datetime.datetime.now(pytz.utc)
|
188
|
+
True
|
189
|
+
>>> token.expiration is None
|
190
|
+
True
|
191
|
+
>>> token.duration is None
|
192
|
+
True
|
193
|
+
>>> token.remaining_duration is None
|
194
|
+
True
|
195
|
+
>>> token.end()
|
196
|
+
>>> token.ended >= token.started
|
197
|
+
True
|
198
|
+
>>> util.get(demo) is None
|
199
|
+
True
|
200
|
+
|
201
|
+
Once a token is created, the token utility knows about it. Notice that an
|
202
|
+
EndableFreeze will never be a part of an iterable of tokens by principal: by
|
203
|
+
definition, a freeze is associated with no principals.
|
204
|
+
|
205
|
+
>>> token = util.register(tokens.EndableFreeze(demo))
|
206
|
+
>>> util.get(demo) is token
|
207
|
+
True
|
208
|
+
>>> list(util) == [token]
|
209
|
+
True
|
210
|
+
|
211
|
+
As part of that knowledge, it disallows another lock or freeze on the same
|
212
|
+
object.
|
213
|
+
|
214
|
+
>>> util.register(tokens.ExclusiveLock(demo, 'mary'))
|
215
|
+
... # doctest: +ELLIPSIS
|
216
|
+
Traceback (most recent call last):
|
217
|
+
...
|
218
|
+
zope.locking.interfaces.RegistrationError: ...
|
219
|
+
>>> util.register(tokens.SharedLock(demo, ('mary', 'jane')))
|
220
|
+
... # doctest: +ELLIPSIS
|
221
|
+
Traceback (most recent call last):
|
222
|
+
...
|
223
|
+
zope.locking.interfaces.RegistrationError: ...
|
224
|
+
>>> util.register(tokens.EndableFreeze(demo))
|
225
|
+
... # doctest: +ELLIPSIS
|
226
|
+
Traceback (most recent call last):
|
227
|
+
...
|
228
|
+
zope.locking.interfaces.RegistrationError: ...
|
229
|
+
>>> token.end()
|
230
|
+
>>> util.get(demo) is None
|
231
|
+
True
|
232
|
+
|
233
|
+
The other way of ending a token is with an expiration datetime. As we'll see,
|
234
|
+
one of the most important caveats about working with timeouts is that a token
|
235
|
+
that expires because of a timeout does not fire any expiration event. It
|
236
|
+
simply starts answering `True` for the `ended` attribute.
|
237
|
+
|
238
|
+
>>> one = datetime.timedelta(hours=1)
|
239
|
+
>>> two = datetime.timedelta(hours=2)
|
240
|
+
>>> three = datetime.timedelta(hours=3)
|
241
|
+
>>> four = datetime.timedelta(hours=4)
|
242
|
+
>>> token = util.register(tokens.EndableFreeze(demo, three))
|
243
|
+
>>> token.duration
|
244
|
+
datetime.timedelta(seconds=10800)
|
245
|
+
>>> three >= token.remaining_duration >= two
|
246
|
+
True
|
247
|
+
>>> token.ended is None
|
248
|
+
True
|
249
|
+
>>> util.get(demo) is token
|
250
|
+
True
|
251
|
+
>>> list(util) == [token]
|
252
|
+
True
|
253
|
+
|
254
|
+
The expiration time of a token is always the creation date plus the timeout.
|
255
|
+
|
256
|
+
>>> token.expiration == token.started + token.duration
|
257
|
+
True
|
258
|
+
>>> ((before_creation + three) <=
|
259
|
+
... (token.expiration) <= # this value is the expiration date
|
260
|
+
... (before_creation + four))
|
261
|
+
True
|
262
|
+
|
263
|
+
Expirations can be changed while a token is still active, using any of
|
264
|
+
the `expiration`, `remaining_duration` or `duration` attributes. All changes
|
265
|
+
fire events. First we'll change the expiration attribute.
|
266
|
+
|
267
|
+
>>> token.expiration = token.started + one
|
268
|
+
>>> token.expiration == token.started + one
|
269
|
+
True
|
270
|
+
>>> token.duration == one
|
271
|
+
True
|
272
|
+
>>> ev = events[-1]
|
273
|
+
>>> verifyObject(interfaces.IExpirationChangedEvent, ev)
|
274
|
+
True
|
275
|
+
>>> ev.object is token
|
276
|
+
True
|
277
|
+
>>> ev.old == token.started + three
|
278
|
+
True
|
279
|
+
|
280
|
+
Next we'll change the duration attribute.
|
281
|
+
|
282
|
+
>>> token.duration = four
|
283
|
+
>>> token.duration
|
284
|
+
datetime.timedelta(seconds=14400)
|
285
|
+
>>> four >= token.remaining_duration >= three
|
286
|
+
True
|
287
|
+
>>> ev = events[-1]
|
288
|
+
>>> verifyObject(interfaces.IExpirationChangedEvent, ev)
|
289
|
+
True
|
290
|
+
>>> ev.object is token
|
291
|
+
True
|
292
|
+
>>> ev.old == token.started + one
|
293
|
+
True
|
294
|
+
|
295
|
+
Now we'll hack our code to make it think that it is two hours later, and then
|
296
|
+
check and modify the remaining_duration attribute.
|
297
|
+
|
298
|
+
>>> def hackNow():
|
299
|
+
... return (datetime.datetime.now(pytz.utc) +
|
300
|
+
... datetime.timedelta(hours=2))
|
301
|
+
...
|
302
|
+
>>> import zope.locking.utils
|
303
|
+
>>> oldNow = zope.locking.utils.now
|
304
|
+
>>> zope.locking.utils.now = hackNow # make code think it's 2 hours later
|
305
|
+
>>> token.duration
|
306
|
+
datetime.timedelta(seconds=14400)
|
307
|
+
>>> two >= token.remaining_duration >= one
|
308
|
+
True
|
309
|
+
>>> token.remaining_duration -= one
|
310
|
+
>>> one >= token.remaining_duration >= datetime.timedelta()
|
311
|
+
True
|
312
|
+
>>> three + datetime.timedelta(minutes=1) >= token.duration >= three
|
313
|
+
True
|
314
|
+
>>> ev = events[-1]
|
315
|
+
>>> verifyObject(interfaces.IExpirationChangedEvent, ev)
|
316
|
+
True
|
317
|
+
>>> ev.object is token
|
318
|
+
True
|
319
|
+
>>> ev.old == token.started + four
|
320
|
+
True
|
321
|
+
|
322
|
+
Now, we'll hack our code to make it think that it's a day later. It is very
|
323
|
+
important to remember that a token ending with a timeout ends silently--that
|
324
|
+
is, no event is fired.
|
325
|
+
|
326
|
+
>>> def hackNow():
|
327
|
+
... return (
|
328
|
+
... datetime.datetime.now(pytz.utc) + datetime.timedelta(days=1))
|
329
|
+
...
|
330
|
+
>>> zope.locking.utils.now = hackNow # make code think it is a day later
|
331
|
+
>>> token.ended >= token.started
|
332
|
+
True
|
333
|
+
>>> util.get(demo) is None
|
334
|
+
True
|
335
|
+
>>> token.remaining_duration == datetime.timedelta()
|
336
|
+
True
|
337
|
+
>>> token.end()
|
338
|
+
Traceback (most recent call last):
|
339
|
+
...
|
340
|
+
zope.locking.interfaces.EndedError
|
341
|
+
|
342
|
+
Once a token has ended, the timeout can no longer be changed.
|
343
|
+
|
344
|
+
>>> token.duration = datetime.timedelta(days=2)
|
345
|
+
Traceback (most recent call last):
|
346
|
+
...
|
347
|
+
zope.locking.interfaces.EndedError
|
348
|
+
|
349
|
+
We'll undo the hacks, and also end the token (that is no longer ended once
|
350
|
+
the hack is finished).
|
351
|
+
|
352
|
+
>>> zope.locking.utils.now = oldNow # undo the hack
|
353
|
+
>>> token.end()
|