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/tokens.py ADDED
@@ -0,0 +1,256 @@
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 datetime
16
+ import functools
17
+
18
+ import persistent
19
+ from BTrees.OOBTree import OOBTree
20
+
21
+ from zope import event
22
+ from zope import interface
23
+ from zope.locking import interfaces
24
+ from zope.locking import utils
25
+
26
+
27
+ NO_DURATION = datetime.timedelta()
28
+
29
+
30
+ class AnnotationsMapping(OOBTree):
31
+ """a class on which security settings may be hung."""
32
+
33
+
34
+ @functools.total_ordering
35
+ class Token(persistent.Persistent):
36
+
37
+ def __init__(self, target):
38
+ self.context = self.__parent__ = target
39
+ self.annotations = AnnotationsMapping()
40
+ self.annotations.__parent__ = self # for security.
41
+
42
+ _principal_ids = frozenset()
43
+
44
+ @property
45
+ def principal_ids(self):
46
+ return self._principal_ids
47
+
48
+ _started = None
49
+
50
+ @property
51
+ def started(self):
52
+ if self._utility is None:
53
+ raise interfaces.UnregisteredError(self)
54
+ return self._started
55
+
56
+ _utility = None
57
+
58
+ @property
59
+ def utility(self):
60
+ return self._utility
61
+
62
+ @utility.setter
63
+ def utility(self, value):
64
+ if self._utility is not None:
65
+ if value is not self._utility:
66
+ raise ValueError('cannot reset utility')
67
+ else:
68
+ assert interfaces.ITokenUtility.providedBy(value)
69
+ self._utility = value
70
+ assert self._started is None
71
+ self._started = utils.now()
72
+
73
+ def __eq__(self, other):
74
+ return (
75
+ (self._p_jar.db().database_name, self._p_oid) ==
76
+ (other._p_jar.db().database_name, other._p_oid))
77
+
78
+ def __lt__(self, other):
79
+ return (
80
+ (self._p_jar.db().database_name, self._p_oid) <
81
+ (other._p_jar.db().database_name, other._p_oid))
82
+
83
+
84
+ class EndableToken(Token):
85
+
86
+ def __init__(self, target, duration=None):
87
+ super().__init__(target)
88
+ self._duration = duration
89
+
90
+ @property
91
+ def utility(self):
92
+ return self._utility
93
+
94
+ @utility.setter
95
+ def utility(self, value):
96
+ if self._utility is not None:
97
+ if value is not self._utility:
98
+ raise ValueError('cannot reset utility')
99
+ else:
100
+ assert interfaces.ITokenUtility.providedBy(value)
101
+ self._utility = value
102
+ assert self._started is None
103
+ self._started = utils.now()
104
+ if self._duration is not None:
105
+ self._expiration = self._started + self._duration
106
+ del self._duration # to catch bugs.
107
+
108
+ _expiration = _duration = None
109
+
110
+ @property
111
+ def expiration(self):
112
+ if self._started is None:
113
+ raise interfaces.UnregisteredError(self)
114
+ return self._expiration
115
+
116
+ @expiration.setter
117
+ def expiration(self, value):
118
+ if self._started is None:
119
+ raise interfaces.UnregisteredError(self)
120
+ if self.ended:
121
+ raise interfaces.EndedError
122
+ if value is not None:
123
+ if not isinstance(value, datetime.datetime):
124
+ raise ValueError('expiration must be datetime.datetime')
125
+ elif value.tzinfo is None:
126
+ raise ValueError('expiration must be timezone-aware')
127
+ old = self._expiration
128
+ self._expiration = value
129
+ if old != self._expiration:
130
+ self.utility.register(self)
131
+ event.notify(interfaces.ExpirationChangedEvent(self, old))
132
+
133
+ @property
134
+ def duration(self):
135
+ if self._started is None:
136
+ return self._duration
137
+ if self._expiration is None:
138
+ return None
139
+ return self._expiration - self._started
140
+
141
+ @duration.setter
142
+ def duration(self, value):
143
+ if self._started is None:
144
+ self._duration = value
145
+ else:
146
+ if self.ended:
147
+ raise interfaces.EndedError
148
+ old = self._expiration
149
+ if value is None:
150
+ self._expiration = value
151
+ elif not isinstance(value, datetime.timedelta):
152
+ raise ValueError('duration must be datetime.timedelta')
153
+ else:
154
+ if value < NO_DURATION:
155
+ raise ValueError('duration may not be negative')
156
+ self._expiration = self._started + value
157
+ if old != self._expiration:
158
+ self.utility.register(self)
159
+ event.notify(interfaces.ExpirationChangedEvent(self, old))
160
+
161
+ @property
162
+ def remaining_duration(self):
163
+ if self._started is None:
164
+ raise interfaces.UnregisteredError(self)
165
+ if self.ended is not None:
166
+ return NO_DURATION
167
+ if self._expiration is None:
168
+ return None
169
+ return self._expiration - utils.now()
170
+
171
+ @remaining_duration.setter
172
+ def remaining_duration(self, value):
173
+ if self._started is None:
174
+ raise interfaces.UnregisteredError(self)
175
+ if self.ended:
176
+ raise interfaces.EndedError
177
+ old = self._expiration
178
+ if value is None:
179
+ self._expiration = value
180
+ elif not isinstance(value, datetime.timedelta):
181
+ raise ValueError('duration must be datetime.timedelta')
182
+ else:
183
+ if value < NO_DURATION:
184
+ raise ValueError('duration may not be negative')
185
+ self._expiration = utils.now() + value
186
+ if old != self._expiration:
187
+ self.utility.register(self)
188
+ event.notify(interfaces.ExpirationChangedEvent(self, old))
189
+
190
+ _ended = None
191
+
192
+ @property
193
+ def ended(self):
194
+ if self._utility is None:
195
+ raise interfaces.UnregisteredError(self)
196
+ if self._ended is not None:
197
+ return self._ended
198
+ if (self._expiration is not None and
199
+ self._expiration <= utils.now()):
200
+ return self._expiration
201
+
202
+ def end(self):
203
+ if self.ended:
204
+ raise interfaces.EndedError
205
+ self._ended = utils.now()
206
+ self.utility.register(self)
207
+ event.notify(interfaces.TokenEndedEvent(self))
208
+
209
+
210
+ @interface.implementer(interfaces.IExclusiveLock)
211
+ class ExclusiveLock(EndableToken):
212
+
213
+ def __init__(self, target, principal_id, duration=None):
214
+ self._principal_ids = frozenset((principal_id,))
215
+ super().__init__(target, duration)
216
+
217
+
218
+ @interface.implementer(interfaces.ISharedLock)
219
+ class SharedLock(EndableToken):
220
+
221
+ def __init__(self, target, principal_ids, duration=None):
222
+ self._principal_ids = frozenset(principal_ids)
223
+ super().__init__(target, duration)
224
+
225
+ def add(self, principal_ids):
226
+ if self.ended:
227
+ raise interfaces.EndedError
228
+ old = self._principal_ids
229
+ self._principal_ids = self._principal_ids.union(principal_ids)
230
+ if old != self._principal_ids:
231
+ self.utility.register(self)
232
+ event.notify(interfaces.PrincipalsChangedEvent(self, old))
233
+
234
+ def remove(self, principal_ids):
235
+ if self.ended:
236
+ raise interfaces.EndedError
237
+ old = self._principal_ids
238
+ self._principal_ids = self._principal_ids.difference(principal_ids)
239
+ if not self._principal_ids:
240
+ self.end()
241
+ elif old != self._principal_ids:
242
+ self.utility.register(self)
243
+ else:
244
+ return
245
+ # principals changed if you got here
246
+ event.notify(interfaces.PrincipalsChangedEvent(self, old))
247
+
248
+
249
+ @interface.implementer(interfaces.IEndableFreeze)
250
+ class EndableFreeze(EndableToken):
251
+ pass
252
+
253
+
254
+ @interface.implementer(interfaces.IFreeze)
255
+ class Freeze(Token):
256
+ pass
@@ -0,0 +1,149 @@
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 persistent
16
+ import persistent.interfaces
17
+ from BTrees.OOBTree import OOBTree
18
+ from BTrees.OOBTree import OOTreeSet
19
+ from zope.keyreference.interfaces import IKeyReference
20
+ from zope.location import Location
21
+
22
+ from zope import event
23
+ from zope import interface
24
+ from zope.locking import interfaces
25
+ from zope.locking import utils
26
+
27
+
28
+ @interface.implementer(interfaces.ITokenUtility)
29
+ class TokenUtility(persistent.Persistent, Location):
30
+
31
+ def __init__(self):
32
+ self._locks = OOBTree()
33
+ self._expirations = OOBTree()
34
+ self._principal_ids = OOBTree()
35
+
36
+ def _del(self, tree, token, value):
37
+ """remove a token for a value within either of the two index trees"""
38
+ reg = tree[value]
39
+ reg.remove(token)
40
+ if not reg:
41
+ del tree[value]
42
+
43
+ def _add(self, tree, token, value):
44
+ """add a token for a value within either of the two index trees"""
45
+ reg = tree.get(value)
46
+ if reg is None:
47
+ reg = tree[value] = OOTreeSet()
48
+ reg.insert(token)
49
+
50
+ def _cleanup(self):
51
+ "clean out expired keys"
52
+ expiredkeys = []
53
+ for k in self._expirations.keys(max=utils.now()):
54
+ for token in self._expirations[k]:
55
+ assert token.ended
56
+ for p in token.principal_ids:
57
+ self._del(self._principal_ids, token, p)
58
+ key_ref = IKeyReference(token.context)
59
+ del self._locks[key_ref]
60
+ expiredkeys.append(k)
61
+ for k in expiredkeys:
62
+ del self._expirations[k]
63
+
64
+ def register(self, token):
65
+ assert interfaces.IToken.providedBy(token)
66
+ if token.utility is None:
67
+ token.utility = self
68
+ elif token.utility is not self:
69
+ raise ValueError('Lock is already registered with another utility')
70
+ if persistent.interfaces.IPersistent.providedBy(token):
71
+ self._p_jar.add(token)
72
+ key_ref = IKeyReference(token.context)
73
+ current = self._locks.get(key_ref)
74
+ if current is not None:
75
+ current, principal_ids, expiration = current
76
+ current_endable = interfaces.IEndable.providedBy(current)
77
+ if current is not token:
78
+ if current_endable and not current.ended:
79
+ raise interfaces.RegistrationError(token)
80
+ # expired token: clean up indexes and fall through
81
+ if current_endable and expiration is not None:
82
+ self._del(self._expirations, current, expiration)
83
+ for p in principal_ids:
84
+ self._del(self._principal_ids, current, p)
85
+ else:
86
+ # current is token; reindex and return
87
+ if current_endable and token.ended:
88
+ if expiration is not None:
89
+ self._del(self._expirations, token, expiration)
90
+ for p in principal_ids:
91
+ self._del(self._principal_ids, token, p)
92
+ del self._locks[key_ref]
93
+ else:
94
+ if current_endable and token.expiration != expiration:
95
+ # reindex timeout
96
+ if expiration is not None:
97
+ self._del(self._expirations, token, expiration)
98
+ if token.expiration is not None:
99
+ self._add(
100
+ self._expirations, token, token.expiration)
101
+ orig = frozenset(principal_ids)
102
+ new = frozenset(token.principal_ids)
103
+ removed = orig.difference(new)
104
+ added = new.difference(orig)
105
+ for p in removed:
106
+ self._del(self._principal_ids, token, p)
107
+ for p in added:
108
+ self._add(self._principal_ids, token, p)
109
+ self._locks[key_ref] = (
110
+ token,
111
+ frozenset(token.principal_ids),
112
+ current_endable and token.expiration or None)
113
+ self._cleanup()
114
+ return token
115
+ # expired current token or no current token; this is new
116
+ endable = interfaces.IEndable.providedBy(token)
117
+ self._locks[key_ref] = (
118
+ token,
119
+ frozenset(token.principal_ids),
120
+ endable and token.expiration or None)
121
+ if (endable and
122
+ token.expiration is not None):
123
+ self._add(self._expirations, token, token.expiration)
124
+ for p in token.principal_ids:
125
+ self._add(self._principal_ids, token, p)
126
+ self._cleanup()
127
+ event.notify(interfaces.TokenStartedEvent(token))
128
+ return token
129
+
130
+ def get(self, obj, default=None):
131
+ res = self._locks.get(IKeyReference(obj))
132
+ if res is not None and (
133
+ not interfaces.IEndable.providedBy(res[0])
134
+ or not res[0].ended):
135
+ return res[0]
136
+ return default
137
+
138
+ def iterForPrincipalId(self, principal_id):
139
+ locks = self._principal_ids.get(principal_id, ())
140
+ for lock in locks:
141
+ assert principal_id in frozenset(lock.principal_ids)
142
+ if not lock.ended:
143
+ yield lock
144
+
145
+ def __iter__(self):
146
+ for lock in self._locks.values():
147
+ if (not interfaces.IEndable.providedBy(lock[0])
148
+ or not lock[0].ended):
149
+ yield lock[0]
zope/locking/utils.py ADDED
@@ -0,0 +1,23 @@
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 datetime
16
+
17
+ import pytz
18
+
19
+
20
+ # this is a small convenience, but is more important as a convenient monkey-
21
+ # patch opportunity for the package's README.txt doctest.
22
+ def now():
23
+ return datetime.datetime.now(pytz.utc)
@@ -0,0 +1 @@
1
+ import sys, types, os;p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('zope',));importlib = __import__('importlib.util');__import__('importlib.machinery');m = sys.modules.setdefault('zope', importlib.util.module_from_spec(importlib.machinery.PathFinder.find_spec('zope', [os.path.dirname(p)])));m = m or sys.modules.setdefault('zope', types.ModuleType('zope'));mp = (m or []) and m.__dict__.setdefault('__path__',[]);(p not in mp) and mp.append(p)
@@ -0,0 +1,44 @@
1
+ Zope Public License (ZPL) Version 2.1
2
+
3
+ A copyright notice accompanies this license document that identifies the
4
+ copyright holders.
5
+
6
+ This license has been certified as open source. It has also been designated as
7
+ GPL compatible by the Free Software Foundation (FSF).
8
+
9
+ Redistribution and use in source and binary forms, with or without
10
+ modification, are permitted provided that the following conditions are met:
11
+
12
+ 1. Redistributions in source code must retain the accompanying copyright
13
+ notice, this list of conditions, and the following disclaimer.
14
+
15
+ 2. Redistributions in binary form must reproduce the accompanying copyright
16
+ notice, this list of conditions, and the following disclaimer in the
17
+ documentation and/or other materials provided with the distribution.
18
+
19
+ 3. Names of the copyright holders must not be used to endorse or promote
20
+ products derived from this software without prior written permission from the
21
+ copyright holders.
22
+
23
+ 4. The right to distribute this software or to use it for any purpose does not
24
+ give you the right to use Servicemarks (sm) or Trademarks (tm) of the
25
+ copyright
26
+ holders. Use of them is covered by separate agreement with the copyright
27
+ holders.
28
+
29
+ 5. If any files are modified, you must cause the modified files to carry
30
+ prominent notices stating that you changed the files and the date of any
31
+ change.
32
+
33
+ Disclaimer
34
+
35
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED
36
+ OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
38
+ EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
39
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
40
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
41
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
42
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
43
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
44
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.