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.
@@ -0,0 +1 @@
1
+ # package
@@ -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()