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,462 @@
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
+ """Locking interfaces"""
15
+ from zope.interface.interfaces import IObjectEvent
16
+ from zope.interface.interfaces import ObjectEvent
17
+
18
+ from zope import interface
19
+ from zope import schema
20
+
21
+
22
+ ##############################################################################
23
+ # Token utility
24
+ ##############################################################################
25
+
26
+
27
+ class ITokenUtility(interface.Interface):
28
+ """Responsible for initializing, registering, and finding all active tokens
29
+ """
30
+
31
+ def get(obj, default=None):
32
+ """For obj, return active IToken or default.
33
+
34
+ Token must be active (not ended), or else return default.
35
+ """
36
+
37
+ def iterForPrincipalId(principal_id):
38
+ """Return an iterable of all active tokens held by the principal id.
39
+ """
40
+
41
+ def __iter__():
42
+ """Return iterable of active tokens managed by utility.
43
+ """
44
+
45
+ def register(token):
46
+ """register an IToken, or a change to a previously-registered token.
47
+
48
+ If the token has not yet been assigned a `utility` value, sets the
49
+ `utility` attribute of the token to self, to mark registration.
50
+ Raises ValueError if token has been registered to another utility.
51
+
52
+ If lock has never been registered before, fires TokenStartedEvent.
53
+ """
54
+
55
+
56
+ ##############################################################################
57
+ # General (abstract) token interfaces
58
+ ##############################################################################
59
+
60
+
61
+ class IAbstractToken(interface.Interface):
62
+ """A token. Must be registered with token utility to start.
63
+
64
+ This is the core token interface. This core interface is mostly readonly.
65
+ It is used as a base by both tokens and token handlers.
66
+ """
67
+
68
+ __parent__ = interface.Attribute(
69
+ """the security context for the token.""")
70
+
71
+ context = interface.Attribute(
72
+ """the actual locked object. readonly.""")
73
+
74
+ utility = interface.Attribute(
75
+ """The lock utility in charge of this lock.
76
+
77
+ Should *only* ever be set once by ILockUtility.register method.
78
+ When the utility sets this attribute, the `start` attribute should
79
+ be set and the token should be considered active (potentially; see
80
+ IEndable).""")
81
+
82
+ principal_ids = interface.Attribute(
83
+ """An immutable iterable of the principal ids that own the lock;
84
+ or None if the object is not locked. If object is frozen, returns
85
+ an iterable with no members. Readonly.""")
86
+
87
+ started = schema.Datetime(
88
+ description=("""the date and time, with utc timezone, that the token
89
+ was registered with the token utility and became effective. Required
90
+ after the token has been registered."""),
91
+ required=False, readonly=True)
92
+
93
+
94
+ class IEndable(interface.Interface):
95
+ """A mixin for tokens that may be ended explicitly or timed out.
96
+
97
+ Some tokens are endable; locks, for instance, are endable. Freezes may be
98
+ permanent, so some are not IEndable.
99
+ """
100
+
101
+ ended = schema.Datetime(
102
+ description=("""the date and time, with utc timezone, that the token
103
+ ended, explicitly or from expiration."""),
104
+ required=False, readonly=True)
105
+
106
+ expiration = schema.Datetime(
107
+ description=(
108
+ """the expiration time, with utc timezone.
109
+ None indicates no expiration.
110
+ Readonly (but see extending interfaces).
111
+ """),
112
+ required=False)
113
+
114
+ duration = schema.Timedelta(
115
+ description=(
116
+ """the duration of the token timeout from its start.
117
+ None indicates no expiration.
118
+ Readonly (but see extending interfaces).
119
+ """),
120
+ required=False)
121
+
122
+ remaining_duration = schema.Timedelta(
123
+ description=(
124
+ """the remaining effective duration for the token from "now".
125
+ None indicates no expiration. If the token has ended, return
126
+ a datetime.timedelta of no time.
127
+ Readonly (but see extending interfaces).
128
+ """),
129
+ required=False)
130
+
131
+ def end():
132
+ """explicitly expire the token.
133
+
134
+ fires TokenEndedEvent if successful, or raises EndedError
135
+ if the token has already ended."""
136
+
137
+
138
+ ##############################################################################
139
+ # Token interfaces: registered by token utility
140
+ ##############################################################################
141
+
142
+ # Abstract token interfaces
143
+
144
+
145
+ class IToken(IAbstractToken):
146
+ """a token that actually stores data.
147
+
148
+ This is the sort of token that should be used in the token utility."""
149
+
150
+ __parent__ = interface.Attribute(
151
+ """the locked object. readonly. Important for security.""")
152
+
153
+ annotations = interface.Attribute(
154
+ """Stores arbitrary application data under package-unique keys.
155
+
156
+ By "package-unique keys", we mean keys that are are unique by
157
+ virtue of including the dotted name of a package as a prefix. A
158
+ package name is used to limit the authority for picking names for
159
+ a package to the people using that package.
160
+ """)
161
+
162
+
163
+ class IEndableToken(IToken, IEndable):
164
+ """A standard endable token."""
165
+
166
+ expiration = schema.Datetime(
167
+ description=(
168
+ """the expiration time, with utc timezone.
169
+ None indicates no expiration.
170
+ When setting, if token has ended then raise EndedError.
171
+ Otherwise call utility.register, fire ExpirationChangedEvent.
172
+ """),
173
+ required=False)
174
+
175
+ duration = schema.Timedelta(
176
+ description=(
177
+ """the duration of the token timeout from its start.
178
+ None indicates no expiration.
179
+ When setting, if token has ended then raise EndedError.
180
+ Otherwise call utility.register, fire ExpirationChangedEvent.
181
+ """),
182
+ required=False)
183
+
184
+ remaining_duration = schema.Timedelta(
185
+ description=(
186
+ """the remaining effective duration for the token from "now".
187
+ None indicates no expiration. If the token has ended, return
188
+ a datetime.timedelta of no time.
189
+ When setting, if token has ended then raise EndedError.
190
+ Otherwise call utility.register, fire ExpirationChangedEvent.
191
+ """),
192
+ required=False)
193
+
194
+
195
+ # Concrete token interfaces
196
+
197
+
198
+ class IExclusiveLock(IEndableToken):
199
+ """a lock held to one and only one principal.
200
+
201
+ principal_ids must always have one and only one member."""
202
+
203
+
204
+ class ISharedLock(IEndableToken):
205
+ "a lock held by one or more principals"
206
+
207
+ def add(principal_ids):
208
+ """Share this lock with principal_ids.
209
+
210
+ Adding principals that already are part of the lock can be ignored.
211
+
212
+ If ended, raise EndedError.
213
+ """
214
+
215
+ def remove(principal_ids):
216
+ """Remove principal_ids from lock.
217
+
218
+ Removing all principals removes the lock: there may not be an effective
219
+ shared lock shared to no one.
220
+
221
+ Removing principals that are not part of the lock can be ignored.
222
+
223
+ If ended, raise EndedError."""
224
+
225
+
226
+ class IFreeze(IToken):
227
+ """principal_ids must always be empty.
228
+
229
+ May not be ended."""
230
+
231
+
232
+ class IEndableFreeze(IFreeze, IEndableToken):
233
+ """May be ended."""
234
+
235
+
236
+ ##############################################################################
237
+ # Token broker interface
238
+ ##############################################################################
239
+
240
+
241
+ class ITokenBroker(interface.Interface):
242
+ """for one object, create standard endable tokens and get active ITokens.
243
+
244
+ Convenient adapter model for security: broker is in context of affected
245
+ object, so security settings for the object can be obtained automatically.
246
+ """
247
+
248
+ context = interface.Attribute(
249
+ 'The object whose tokens are brokered. readonly.')
250
+
251
+ __parent__ = interface.Attribute(
252
+ """the context. readonly. Important for security.""")
253
+
254
+ def lock(principal_id=None, duration=None):
255
+ """lock context, and return token.
256
+
257
+ if principal_id is None, use interaction's principal; if interaction
258
+ does not have one and only one principal, raise ValueError.
259
+
260
+ if principal_id is not None, principal_id must be in interaction,
261
+ or else raise ParticipationError.
262
+
263
+ Same constraints as token utility's register method.
264
+ """
265
+
266
+ def lockShared(principal_ids=None, duration=None):
267
+ """lock context with a shared lock, and return token.
268
+
269
+ if principal_ids is None, use interaction's principals; if interaction
270
+ does not have any principals, raise ValueError.
271
+
272
+ if principal_ids is not None, principal_ids must be in interaction,
273
+ or else raise ParticipationError. Must be at least one id.
274
+
275
+ Same constraints as token utility's register method.
276
+ """
277
+
278
+ def freeze(duration=None):
279
+ """freeze context with an endable freeze, and return token.
280
+ """
281
+
282
+ def get():
283
+ """Get context's active IToken, or None.
284
+
285
+ """
286
+
287
+ ##############################################################################
288
+ # Token handler interfaces
289
+ ##############################################################################
290
+
291
+ # Abstract token handler interfaces.
292
+
293
+
294
+ class ITokenHandler(IAbstractToken, IEndable):
295
+ """give appropriate increased access in a security system.
296
+
297
+ Appropriate for endable tokens with one or more principals (for instance,
298
+ neither freezes nor endable freezes."""
299
+
300
+ __parent__ = interface.Attribute(
301
+ """the actual token. readonly. Important for security.""")
302
+
303
+ token = interface.Attribute(
304
+ """the registered IToken that this adapter uses for actual
305
+ data storage""")
306
+
307
+ expiration = schema.Datetime(
308
+ description=(
309
+ """the expiration time, with utc timezone.
310
+ None indicates no expiration.
311
+ When setting, if token has ended then raise EndedError.
312
+ If all of the principals in the current interaction are not owners
313
+ of the current token (in principal_ids), raise ParticipationError.
314
+ Otherwise call utility.register, fire ExpirationChangedEvent.
315
+ """),
316
+ required=False)
317
+
318
+ duration = schema.Timedelta(
319
+ description=(
320
+ """the duration of the token timeout from its start.
321
+ None indicates no expiration.
322
+ When setting, if token has ended then raise EndedError.
323
+ If all of the principals in the current interaction are not owners
324
+ of the current token (in principal_ids), raise ParticipationError.
325
+ Otherwise call utility.register, fire ExpirationChangedEvent.
326
+ """),
327
+ required=False)
328
+
329
+ remaining_duration = schema.Timedelta(
330
+ description=(
331
+ """the remaining effective duration for the token from "now".
332
+ None indicates no expiration. If the token has ended, return
333
+ a datetime.timedelta of no time.
334
+ When setting, if token has ended then raise EndedError.
335
+ If all of the principals in the current interaction are not owners
336
+ of the current token (in principal_ids), raise ParticipationError.
337
+ Otherwise call utility.register, fire ExpirationChangedEvent.
338
+ """),
339
+ required=False)
340
+
341
+ def release(principal_ids=None): # may only remove ids in interaction.
342
+ """Remove given principal_ids from the token, or all in interaction.
343
+
344
+ All explicitly given principal_ids must be in interaction. Silently
345
+ ignores requests to remove principals who are not currently part of
346
+ token.
347
+
348
+ Ends the lock if the removed principals were the only principals.
349
+
350
+ Raises EndedError if lock has already ended.
351
+ """
352
+
353
+
354
+ # Concrete principal token interfaces.
355
+
356
+
357
+ class IExclusiveLockHandler(ITokenHandler):
358
+ """an exclusive lock"""
359
+
360
+
361
+ class ISharedLockHandler(ITokenHandler):
362
+ """a shared lock"""
363
+
364
+ def join(principal_ids=None):
365
+ """add the given principal_ids to the token, or all in interaction.
366
+ All explicitly given principal_ids must be in interaction. Silently
367
+ ignores requests to add principal_ids that are already part of the
368
+ token.
369
+
370
+ Raises EndedError if lock has already ended.
371
+ """
372
+
373
+ def add(principal_ids):
374
+ """Share current shared lock with principal_ids.
375
+ If all of the principals in the current interaction are not owners
376
+ of the current token (in principal_ids), raise ParticipationError."""
377
+
378
+
379
+ ##############################################################################
380
+ # Events
381
+ ##############################################################################
382
+
383
+ # event interfaces
384
+
385
+
386
+ class ITokenEvent(IObjectEvent):
387
+ """a token event"""
388
+
389
+
390
+ class ITokenStartedEvent(ITokenEvent):
391
+ """An token has started"""
392
+
393
+
394
+ class ITokenEndedEvent(ITokenEvent):
395
+ """A token has been explicitly ended.
396
+
397
+ Note that this is not fired when a lock expires."""
398
+
399
+
400
+ class IPrincipalsChangedEvent(ITokenEvent):
401
+ """Principals have changed for a token"""
402
+
403
+ old = interface.Attribute('a frozenset of the old principals')
404
+
405
+
406
+ class IExpirationChangedEvent(ITokenEvent):
407
+ """Expiration value changed for a token"""
408
+
409
+ old = interface.Attribute('the old expiration value')
410
+
411
+
412
+ # events
413
+
414
+
415
+ @interface.implementer(ITokenStartedEvent)
416
+ class TokenStartedEvent(ObjectEvent):
417
+ pass
418
+
419
+
420
+ @interface.implementer(ITokenEndedEvent)
421
+ class TokenEndedEvent(ObjectEvent):
422
+ pass
423
+
424
+
425
+ @interface.implementer(IPrincipalsChangedEvent)
426
+ class PrincipalsChangedEvent(ObjectEvent):
427
+ def __init__(self, object, old):
428
+ super().__init__(object)
429
+ self.old = frozenset(old)
430
+
431
+
432
+ @interface.implementer(IExpirationChangedEvent)
433
+ class ExpirationChangedEvent(ObjectEvent):
434
+ def __init__(self, object, old):
435
+ super().__init__(object)
436
+ self.old = old
437
+
438
+
439
+ ##############################################################################
440
+ # Exceptions
441
+ ##############################################################################
442
+
443
+
444
+ class TokenRuntimeError(RuntimeError):
445
+ """A general runtime error in the token code."""
446
+
447
+
448
+ class EndedError(TokenRuntimeError):
449
+ """The token has ended"""
450
+
451
+
452
+ class UnregisteredError(TokenRuntimeError):
453
+ """The token has not yet been registered"""
454
+
455
+
456
+ class ParticipationError(TokenRuntimeError):
457
+ """Some or all of the principals in the current interaction do not
458
+ participate in the token"""
459
+
460
+
461
+ class RegistrationError(TokenRuntimeError):
462
+ """The token may not be registered"""
@@ -0,0 +1,64 @@
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 functools
16
+
17
+ import zope.app.appsetup.testlayer
18
+ import zope.component
19
+ import zope.interface
20
+ import zope.keyreference.interfaces
21
+
22
+ import zope.locking
23
+
24
+
25
+ class IDemo(zope.interface.Interface):
26
+ """a demonstration interface for a demonstration class"""
27
+
28
+
29
+ @zope.interface.implementer(IDemo)
30
+ class Demo:
31
+ pass
32
+
33
+
34
+ @functools.total_ordering
35
+ @zope.component.adapter(IDemo)
36
+ @zope.interface.implementer(zope.keyreference.interfaces.IKeyReference)
37
+ class DemoKeyReference:
38
+ _class_counter = 0
39
+
40
+ key_type_id = 'zope.locking.testing.DemoKeyReference'
41
+
42
+ def __init__(self, context):
43
+ self.context = context
44
+ class_ = type(self)
45
+ self._id = getattr(context, '__demo_key_reference__', None)
46
+ if self._id is None:
47
+ self._id = class_._class_counter
48
+ context.__demo_key_reference__ = self._id
49
+ class_._class_counter += 1
50
+
51
+ def __call__(self):
52
+ return self.context
53
+
54
+ def __hash__(self):
55
+ return (self.key_type_id, self._id)
56
+
57
+ def __eq__(self, other):
58
+ return (self.key_type_id, self._id) == (other.key_type_id, other._id)
59
+
60
+ def __lt__(self, other):
61
+ return (self.key_type_id, self._id) < (other.key_type_id, other._id)
62
+
63
+
64
+ layer = zope.app.appsetup.testlayer.ZODBLayer(zope.locking)
zope/locking/tests.py ADDED
@@ -0,0 +1,67 @@
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 doctest
16
+ import re
17
+ import unittest
18
+
19
+ import zope.testing.renormalizing
20
+
21
+ import zope.locking.testing
22
+
23
+
24
+ normalizer = zope.testing.renormalizing.RENormalizing([
25
+ (re.compile(r'datetime\.timedelta\(0, (.*)\)'),
26
+ r'datetime.timedelta(seconds=\1)'),
27
+ ])
28
+
29
+
30
+ def test_suite():
31
+
32
+ layer = zope.locking.testing.layer
33
+
34
+ def get_connection():
35
+ return layer.db.open()
36
+
37
+ def get_db():
38
+ return layer.db
39
+
40
+ suite = unittest.TestSuite((
41
+ doctest.DocFileSuite(
42
+ 'README.rst',
43
+ optionflags=doctest.IGNORE_EXCEPTION_DETAIL,
44
+ checker=normalizer,
45
+ globs=dict(
46
+ get_connection=get_connection,
47
+ get_db=get_db
48
+ )),
49
+ doctest.DocFileSuite(
50
+ 'annoying.rst',
51
+ optionflags=doctest.IGNORE_EXCEPTION_DETAIL,
52
+ checker=normalizer,
53
+ globs=dict(
54
+ get_connection=get_connection,
55
+ get_db=get_db
56
+ )),
57
+ doctest.DocFileSuite(
58
+ 'cleanup.rst',
59
+ optionflags=doctest.IGNORE_EXCEPTION_DETAIL,
60
+ checker=normalizer,
61
+ globs=dict(
62
+ get_connection=get_connection,
63
+ get_db=get_db
64
+ )),
65
+ ))
66
+ suite.layer = layer
67
+ return suite