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
@@ -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"""
|
zope/locking/testing.py
ADDED
@@ -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
|