moat-kv 0.70.24__py3-none-any.whl → 0.71.6__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.
- moat/kv/__init__.py +6 -7
- moat/kv/_cfg.yaml +5 -8
- moat/kv/actor/__init__.py +2 -1
- moat/kv/actor/deletor.py +4 -1
- moat/kv/auth/__init__.py +12 -13
- moat/kv/auth/_test.py +4 -1
- moat/kv/auth/password.py +11 -7
- moat/kv/backend/mqtt.py +4 -8
- moat/kv/client.py +20 -39
- moat/kv/code.py +3 -3
- moat/kv/command/data.py +4 -3
- moat/kv/command/dump/__init__.py +29 -29
- moat/kv/command/internal.py +2 -3
- moat/kv/command/job.py +1 -2
- moat/kv/command/type.py +3 -6
- moat/kv/data.py +9 -8
- moat/kv/errors.py +16 -8
- moat/kv/mock/__init__.py +2 -12
- moat/kv/model.py +28 -32
- moat/kv/obj/__init__.py +3 -3
- moat/kv/obj/command.py +3 -3
- moat/kv/runner.py +4 -5
- moat/kv/server.py +106 -126
- moat/kv/types.py +8 -6
- {moat_kv-0.70.24.dist-info → moat_kv-0.71.6.dist-info}/METADATA +7 -6
- moat_kv-0.71.6.dist-info/RECORD +47 -0
- {moat_kv-0.70.24.dist-info → moat_kv-0.71.6.dist-info}/WHEEL +1 -1
- moat_kv-0.71.6.dist-info/licenses/LICENSE +3 -0
- moat_kv-0.71.6.dist-info/licenses/LICENSE.APACHE2 +202 -0
- moat_kv-0.71.6.dist-info/licenses/LICENSE.MIT +20 -0
- moat_kv-0.71.6.dist-info/top_level.txt +1 -0
- build/lib/docs/source/conf.py +0 -201
- build/lib/examples/pathify.py +0 -45
- build/lib/moat/kv/__init__.py +0 -19
- build/lib/moat/kv/_cfg.yaml +0 -97
- build/lib/moat/kv/_main.py +0 -91
- build/lib/moat/kv/actor/__init__.py +0 -98
- build/lib/moat/kv/actor/deletor.py +0 -139
- build/lib/moat/kv/auth/__init__.py +0 -444
- build/lib/moat/kv/auth/_test.py +0 -166
- build/lib/moat/kv/auth/password.py +0 -234
- build/lib/moat/kv/auth/root.py +0 -58
- build/lib/moat/kv/backend/__init__.py +0 -67
- build/lib/moat/kv/backend/mqtt.py +0 -74
- build/lib/moat/kv/backend/serf.py +0 -45
- build/lib/moat/kv/client.py +0 -1025
- build/lib/moat/kv/code.py +0 -236
- build/lib/moat/kv/codec.py +0 -11
- build/lib/moat/kv/command/__init__.py +0 -1
- build/lib/moat/kv/command/acl.py +0 -180
- build/lib/moat/kv/command/auth.py +0 -261
- build/lib/moat/kv/command/code.py +0 -293
- build/lib/moat/kv/command/codec.py +0 -186
- build/lib/moat/kv/command/data.py +0 -265
- build/lib/moat/kv/command/dump/__init__.py +0 -143
- build/lib/moat/kv/command/error.py +0 -149
- build/lib/moat/kv/command/internal.py +0 -248
- build/lib/moat/kv/command/job.py +0 -433
- build/lib/moat/kv/command/log.py +0 -53
- build/lib/moat/kv/command/server.py +0 -114
- build/lib/moat/kv/command/type.py +0 -201
- build/lib/moat/kv/config.py +0 -46
- build/lib/moat/kv/data.py +0 -216
- build/lib/moat/kv/errors.py +0 -561
- build/lib/moat/kv/exceptions.py +0 -126
- build/lib/moat/kv/mock/__init__.py +0 -101
- build/lib/moat/kv/mock/mqtt.py +0 -159
- build/lib/moat/kv/mock/serf.py +0 -250
- build/lib/moat/kv/mock/tracer.py +0 -63
- build/lib/moat/kv/model.py +0 -1069
- build/lib/moat/kv/obj/__init__.py +0 -646
- build/lib/moat/kv/obj/command.py +0 -241
- build/lib/moat/kv/runner.py +0 -1347
- build/lib/moat/kv/server.py +0 -2809
- build/lib/moat/kv/types.py +0 -513
- debian/moat-kv/usr/lib/python3/dist-packages/docs/source/conf.py +0 -201
- debian/moat-kv/usr/lib/python3/dist-packages/examples/pathify.py +0 -45
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/__init__.py +0 -19
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/_cfg.yaml +0 -97
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/_main.py +0 -91
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/actor/__init__.py +0 -98
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/actor/deletor.py +0 -139
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/__init__.py +0 -444
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/_test.py +0 -166
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/password.py +0 -234
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/root.py +0 -58
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/__init__.py +0 -67
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/mqtt.py +0 -74
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/serf.py +0 -45
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/client.py +0 -1025
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/code.py +0 -236
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/codec.py +0 -11
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/__init__.py +0 -1
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/acl.py +0 -180
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/auth.py +0 -261
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/code.py +0 -293
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/codec.py +0 -186
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/data.py +0 -265
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/dump/__init__.py +0 -143
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/error.py +0 -149
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/internal.py +0 -248
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/job.py +0 -433
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/log.py +0 -53
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/server.py +0 -114
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/type.py +0 -201
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/config.py +0 -46
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/data.py +0 -216
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/errors.py +0 -561
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/exceptions.py +0 -126
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/__init__.py +0 -101
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/mqtt.py +0 -159
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/serf.py +0 -250
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/tracer.py +0 -63
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/model.py +0 -1069
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/obj/__init__.py +0 -646
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/obj/command.py +0 -241
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/runner.py +0 -1347
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/server.py +0 -2809
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/types.py +0 -513
- docs/source/conf.py +0 -201
- examples/pathify.py +0 -45
- moat/kv/backend/serf.py +0 -45
- moat/kv/codec.py +0 -11
- moat/kv/mock/serf.py +0 -250
- moat_kv-0.70.24.dist-info/RECORD +0 -137
- moat_kv-0.70.24.dist-info/top_level.txt +0 -9
- {moat_kv-0.70.24.dist-info → moat_kv-0.71.6.dist-info}/licenses/LICENSE.txt +0 -0
build/lib/moat/kv/model.py
DELETED
@@ -1,1069 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
This module contains MoaT-KV's basic data model.
|
3
|
-
|
4
|
-
TODO: message chains should be refactored to arrays: much lower overhead.
|
5
|
-
"""
|
6
|
-
|
7
|
-
from __future__ import annotations
|
8
|
-
|
9
|
-
import weakref
|
10
|
-
from collections import defaultdict
|
11
|
-
from logging import getLogger
|
12
|
-
from typing import Any
|
13
|
-
|
14
|
-
from moat.util import NotGiven, Path, attrdict, create_queue
|
15
|
-
from range_set import RangeSet
|
16
|
-
|
17
|
-
from .exceptions import ACLError
|
18
|
-
|
19
|
-
logger = getLogger(__name__)
|
20
|
-
|
21
|
-
|
22
|
-
class NodeDataSkipped(Exception):
|
23
|
-
def __init__(self, node):
|
24
|
-
super().__init__()
|
25
|
-
self.node = node
|
26
|
-
|
27
|
-
def __repr__(self):
|
28
|
-
return "<%s:%s>" % (self.__class__.__name__, self.node)
|
29
|
-
|
30
|
-
|
31
|
-
ConvNull = None # imported later, if/when needed
|
32
|
-
NullACL = None # imported later, if/when needed
|
33
|
-
|
34
|
-
|
35
|
-
class Node:
|
36
|
-
"""Represents one MoaT-KV participant."""
|
37
|
-
|
38
|
-
name: str = None
|
39
|
-
tick: int = None
|
40
|
-
_present: RangeSet = None # I have these as valid data. Superset of ``._deleted``.
|
41
|
-
_deleted: RangeSet = None # I have these as no-longer-valid data
|
42
|
-
_reported: RangeSet = None # somebody else reported these missing data for this node
|
43
|
-
_superseded: RangeSet = None # I know these once existed, but no more.
|
44
|
-
entries: dict = None
|
45
|
-
tock: int = 0 # tock when node was last observed
|
46
|
-
|
47
|
-
def __new__(cls, name, tick=None, cache=None, create=True):
|
48
|
-
try:
|
49
|
-
self = cache[name]
|
50
|
-
except KeyError:
|
51
|
-
if not create:
|
52
|
-
raise
|
53
|
-
self = object.__new__(cls)
|
54
|
-
self.name = name
|
55
|
-
self.tick = tick
|
56
|
-
self._present = RangeSet()
|
57
|
-
self._deleted = RangeSet()
|
58
|
-
self._reported = RangeSet()
|
59
|
-
self._superseded = RangeSet()
|
60
|
-
self.entries = {}
|
61
|
-
cache[name] = self
|
62
|
-
else:
|
63
|
-
if tick is not None:
|
64
|
-
if self.tick is None or self.tick < tick:
|
65
|
-
self.tick = tick
|
66
|
-
return self
|
67
|
-
|
68
|
-
# pylint: disable=unused-argument
|
69
|
-
def __init__(self, name, tick=None, cache=None, create=True):
|
70
|
-
return
|
71
|
-
|
72
|
-
def __hash__(self):
|
73
|
-
return hash(self.name)
|
74
|
-
|
75
|
-
def __eq__(self, other):
|
76
|
-
if isinstance(other, Node):
|
77
|
-
other = other.name
|
78
|
-
return self.name == other
|
79
|
-
|
80
|
-
def __getitem__(self, item):
|
81
|
-
return self.entries[item]
|
82
|
-
|
83
|
-
def get(self, item, default=None):
|
84
|
-
return self.entries.get(item, default)
|
85
|
-
|
86
|
-
def enumerate(self, n: int = 0, current: bool = False):
|
87
|
-
"""
|
88
|
-
Return a list of valid keys for that node.
|
89
|
-
|
90
|
-
Used to find data from no-longer-used nodes so they can be deleted.
|
91
|
-
"""
|
92
|
-
for k, v in self.entries.items():
|
93
|
-
if current and v.chain is not None and v.chain.node is not self:
|
94
|
-
continue
|
95
|
-
yield k
|
96
|
-
if n:
|
97
|
-
n -= 1
|
98
|
-
if not n:
|
99
|
-
return
|
100
|
-
|
101
|
-
def __contains__(self, item):
|
102
|
-
return item in self.entries
|
103
|
-
|
104
|
-
def __repr__(self):
|
105
|
-
return "<%s: %s @%s>" % (self.__class__.__name__, self.name, self.tick)
|
106
|
-
|
107
|
-
def seen(self, tick, entry=None, local=False):
|
108
|
-
"""An event with this tick was in the entry's chain.
|
109
|
-
|
110
|
-
Args:
|
111
|
-
``tick``: The event affecting the given entry.
|
112
|
-
``entry``: The entry affected by this event.
|
113
|
-
``local``: The message was not broadcast, thus do not assume that
|
114
|
-
other nodes saw this.
|
115
|
-
|
116
|
-
"""
|
117
|
-
if not local:
|
118
|
-
self._reported.discard(tick)
|
119
|
-
if entry is not None:
|
120
|
-
self._present.add(tick)
|
121
|
-
self.entries[tick] = entry
|
122
|
-
|
123
|
-
# this might happen when loading old state.
|
124
|
-
if tick in self._superseded:
|
125
|
-
self._superseded.discard(tick)
|
126
|
-
logger.info("%s was marked as superseded", entry)
|
127
|
-
|
128
|
-
def is_deleted(self, tick):
|
129
|
-
"""
|
130
|
-
Check whether this tick has been marked as deleted.
|
131
|
-
"""
|
132
|
-
return tick in self._deleted
|
133
|
-
|
134
|
-
def mark_deleted(self, tick):
|
135
|
-
"""
|
136
|
-
The data for this tick will be deleted.
|
137
|
-
|
138
|
-
Args:
|
139
|
-
tick: The event that caused the deletion.
|
140
|
-
|
141
|
-
Returns: the entry, if still present
|
142
|
-
"""
|
143
|
-
self._deleted.add(tick)
|
144
|
-
return self.entries.get(tick, None)
|
145
|
-
|
146
|
-
def clear_deleted(self, tick):
|
147
|
-
"""
|
148
|
-
The data for this tick are definitely gone (deleted).
|
149
|
-
"""
|
150
|
-
self._present.discard(tick)
|
151
|
-
self._deleted.discard(tick)
|
152
|
-
self._reported.discard(tick)
|
153
|
-
self._superseded.add(tick)
|
154
|
-
|
155
|
-
e = self.entries.pop(tick, None)
|
156
|
-
if e is not None:
|
157
|
-
e.purge_deleted()
|
158
|
-
|
159
|
-
def purge_deleted(self, r: RangeSet):
|
160
|
-
"""
|
161
|
-
All entries in this rangeset are deleted.
|
162
|
-
|
163
|
-
This is a shortcut for calling :meth:`clear_deleted` on each item.
|
164
|
-
"""
|
165
|
-
self._present -= r
|
166
|
-
self._deleted -= r
|
167
|
-
self._reported -= r
|
168
|
-
self._superseded += r
|
169
|
-
|
170
|
-
# Mark that these as really gone.
|
171
|
-
for a, b in r:
|
172
|
-
for t in range(a, b):
|
173
|
-
e = self.entries.pop(t, None)
|
174
|
-
if e is not None:
|
175
|
-
e.purge_deleted()
|
176
|
-
|
177
|
-
def supersede(self, tick):
|
178
|
-
"""
|
179
|
-
The event with this tick is no longer in the referred entry's chain.
|
180
|
-
This happens when an entry is updated.
|
181
|
-
|
182
|
-
Args:
|
183
|
-
``tick``: The event that once affected the given entry.
|
184
|
-
"""
|
185
|
-
self._present.discard(tick)
|
186
|
-
self._superseded.add(tick)
|
187
|
-
self.entries.pop(tick, None)
|
188
|
-
|
189
|
-
def report_superseded(self, r: RangeSet, local=False):
|
190
|
-
"""
|
191
|
-
Some node said that these entries may have been superseded.
|
192
|
-
|
193
|
-
Args:
|
194
|
-
``range``: The RangeSet thus marked.
|
195
|
-
``local``: The message was not broadcast, thus do not assume that
|
196
|
-
other nodes saw this.
|
197
|
-
"""
|
198
|
-
coll = r & self._present
|
199
|
-
r -= coll
|
200
|
-
self._superseded += r
|
201
|
-
if not local:
|
202
|
-
self._reported -= r
|
203
|
-
|
204
|
-
# Are any of these present?
|
205
|
-
for a, b in coll:
|
206
|
-
for t in range(a, b):
|
207
|
-
e = self.entries[t]
|
208
|
-
if e.chain.node is self and e.chain.tick == t:
|
209
|
-
logger.info("%s present but marked as superseded", e)
|
210
|
-
|
211
|
-
def report_missing(self, r: RangeSet):
|
212
|
-
"""
|
213
|
-
Some node doesn't know about these ticks.
|
214
|
-
|
215
|
-
We may need to broadcast either their content,
|
216
|
-
or the fact that these ticks have been superseded.
|
217
|
-
"""
|
218
|
-
self._reported += r
|
219
|
-
|
220
|
-
def report_deleted(self, r: RangeSet, server):
|
221
|
-
"""
|
222
|
-
This range has been reported as deleted.
|
223
|
-
|
224
|
-
Args:
|
225
|
-
range (RangeSet): the range that's gone.
|
226
|
-
add (dict): store additional vanished items. Nodename -> RangeSet
|
227
|
-
"""
|
228
|
-
# Ignore those which we know have been deleted
|
229
|
-
n = r - self._deleted
|
230
|
-
|
231
|
-
for a, b in n:
|
232
|
-
for t in range(a, b):
|
233
|
-
entry = self.mark_deleted(t)
|
234
|
-
if entry is None:
|
235
|
-
continue
|
236
|
-
chain = entry.mark_deleted(server)
|
237
|
-
if chain is None:
|
238
|
-
continue
|
239
|
-
for node, tick in chain:
|
240
|
-
server.mark_deleted(node, tick)
|
241
|
-
|
242
|
-
# Mark as deleted. The system will flush them later.
|
243
|
-
self._deleted += r
|
244
|
-
self._reported -= r
|
245
|
-
self._superseded += r
|
246
|
-
|
247
|
-
r &= self._present
|
248
|
-
for a, b in r:
|
249
|
-
for t in range(a, b):
|
250
|
-
e = self.entries.get(t, None)
|
251
|
-
if e is not None:
|
252
|
-
if e.data is not NotGiven:
|
253
|
-
logger.info("%s present but marked as deleted", e)
|
254
|
-
e.purge_deleted()
|
255
|
-
|
256
|
-
@property
|
257
|
-
def local_present(self):
|
258
|
-
"""Values I know about"""
|
259
|
-
return self._present
|
260
|
-
|
261
|
-
@property
|
262
|
-
def local_superseded(self):
|
263
|
-
"""Values I knew about"""
|
264
|
-
return self._superseded
|
265
|
-
|
266
|
-
@property
|
267
|
-
def local_deleted(self):
|
268
|
-
"""Values I know to have vanished"""
|
269
|
-
return self._deleted
|
270
|
-
|
271
|
-
@property
|
272
|
-
def local_missing(self):
|
273
|
-
"""Values I have not seen, the inverse of :meth:`local_present`
|
274
|
-
plus :meth:`local_superseded`"""
|
275
|
-
assert self.tick
|
276
|
-
r = RangeSet(((1, self.tick + 1),))
|
277
|
-
r -= self._present
|
278
|
-
r -= self._superseded
|
279
|
-
return r
|
280
|
-
|
281
|
-
@property
|
282
|
-
def remote_missing(self):
|
283
|
-
"""Values from this node which somebody else has not seen"""
|
284
|
-
return self._reported
|
285
|
-
|
286
|
-
def kill_this_node(self, cache=None):
|
287
|
-
"""
|
288
|
-
Remove this node from the system.
|
289
|
-
No chain's first link may point to this node.
|
290
|
-
"""
|
291
|
-
for e in self.entries.values():
|
292
|
-
if e.chain.node is self:
|
293
|
-
raise RuntimeError(f"Still main node at {e!r}")
|
294
|
-
c = e.chain.filter(self)
|
295
|
-
if c is None:
|
296
|
-
raise RuntimeError(f"Empty chain after filter for {self.name} at {e!r}")
|
297
|
-
e.chain = c
|
298
|
-
|
299
|
-
if cache is not None:
|
300
|
-
cache.pop(self.name, None)
|
301
|
-
|
302
|
-
|
303
|
-
class NodeSet(defaultdict):
|
304
|
-
"""
|
305
|
-
Represents a dict (nodename > RangeSet).
|
306
|
-
"""
|
307
|
-
|
308
|
-
def __init__(self, encoded=None, cache=None):
|
309
|
-
super().__init__(RangeSet)
|
310
|
-
if encoded is not None:
|
311
|
-
assert cache is not None
|
312
|
-
for n, v in encoded.items():
|
313
|
-
n = Node(n, cache=cache)
|
314
|
-
r = RangeSet()
|
315
|
-
r.__setstate__(v)
|
316
|
-
self[n] = r
|
317
|
-
|
318
|
-
def __repr__(self):
|
319
|
-
return "%s(%s)" % (type(self).__name__, dict.__repr__(self))
|
320
|
-
|
321
|
-
def __bool__(self):
|
322
|
-
for v in self.values():
|
323
|
-
if v:
|
324
|
-
return True
|
325
|
-
return False
|
326
|
-
|
327
|
-
def serialize(self):
|
328
|
-
d = dict()
|
329
|
-
for k, v in self.items():
|
330
|
-
assert not hasattr(k, "name")
|
331
|
-
d[k] = v.__getstate__()
|
332
|
-
return d
|
333
|
-
|
334
|
-
@classmethod
|
335
|
-
def deserialize(cls, state):
|
336
|
-
self = cls()
|
337
|
-
for k, v in state.items():
|
338
|
-
r = RangeSet()
|
339
|
-
r.__setstate__(v)
|
340
|
-
self[k] = v
|
341
|
-
return self
|
342
|
-
|
343
|
-
def copy(self):
|
344
|
-
res = type(self)()
|
345
|
-
for k, v in self.items():
|
346
|
-
if v:
|
347
|
-
res[k] = v.copy()
|
348
|
-
return res
|
349
|
-
|
350
|
-
def add(self, node, tick):
|
351
|
-
assert not hasattr(node, "name")
|
352
|
-
self[node].add(tick)
|
353
|
-
|
354
|
-
def __isub__(self, other):
|
355
|
-
for k, v in other.items():
|
356
|
-
r = self.get(k, None)
|
357
|
-
if r is None:
|
358
|
-
continue
|
359
|
-
r -= v
|
360
|
-
|
361
|
-
|
362
|
-
class NodeEvent:
|
363
|
-
"""Represents any event originating at a node.
|
364
|
-
|
365
|
-
Args:
|
366
|
-
``node``: The node thus affected
|
367
|
-
``tick``: Counter, timestamp, whatever
|
368
|
-
``prev``: The previous event, if any
|
369
|
-
|
370
|
-
"""
|
371
|
-
|
372
|
-
def __init__(self, node: Node, tick: int = None, prev: NodeEvent = None):
|
373
|
-
self.node = node
|
374
|
-
if tick is None:
|
375
|
-
tick = node.tick
|
376
|
-
self.tick = tick
|
377
|
-
if tick is not None and tick > 0:
|
378
|
-
node.seen(tick)
|
379
|
-
self.prev = None
|
380
|
-
if prev is not None:
|
381
|
-
self.prev = prev
|
382
|
-
|
383
|
-
def __len__(self):
|
384
|
-
"""Length of this chain"""
|
385
|
-
if self.prev is None:
|
386
|
-
return 1
|
387
|
-
return 1 + len(self.prev)
|
388
|
-
|
389
|
-
def _repr_chain(self):
|
390
|
-
tk = self.tick or "-"
|
391
|
-
pr = f"{self.node.name}:{tk}"
|
392
|
-
if self.prev is not None:
|
393
|
-
pr += " " + self.prev._repr_chain()
|
394
|
-
return pr
|
395
|
-
|
396
|
-
def __repr__(self):
|
397
|
-
pr = self._repr_chain()
|
398
|
-
tk = ("-" if self.tick is None else self.tick,)
|
399
|
-
return f"<{self.__class__.__name__} {pr} {tk}>"
|
400
|
-
|
401
|
-
def __iter__(self):
|
402
|
-
c = self
|
403
|
-
while c is not None:
|
404
|
-
yield c.node, c.tick
|
405
|
-
c = c.prev
|
406
|
-
|
407
|
-
def __eq__(self, other):
|
408
|
-
if other is None:
|
409
|
-
return False
|
410
|
-
return self.node == other.node and self.tick == other.tick
|
411
|
-
|
412
|
-
def equals(self, other):
|
413
|
-
"""Check whether these chains are equal. Used for ping comparisons.
|
414
|
-
|
415
|
-
The last two items may be missing from either chain.
|
416
|
-
"""
|
417
|
-
if other is None:
|
418
|
-
return self.prev is None or len(self.prev) <= 1
|
419
|
-
if self != other:
|
420
|
-
return False
|
421
|
-
if self.prev is None:
|
422
|
-
return other.prev is None or len(other.prev) <= 2
|
423
|
-
elif other.prev is None:
|
424
|
-
return self.prev is None or len(self.prev) <= 2
|
425
|
-
else:
|
426
|
-
return self.prev.equals(other.prev)
|
427
|
-
|
428
|
-
def __lt__(self, other):
|
429
|
-
"""Check whether this node precedes ``other``, i.e. "other" is an
|
430
|
-
event that is based on this.
|
431
|
-
"""
|
432
|
-
if other is None:
|
433
|
-
return False
|
434
|
-
if self == other:
|
435
|
-
return False
|
436
|
-
while self.node != other.node:
|
437
|
-
other = other.prev
|
438
|
-
if other is None:
|
439
|
-
return False
|
440
|
-
return self.tick <= other.tick
|
441
|
-
|
442
|
-
def __gt__(self, other):
|
443
|
-
"""Check whether this node succedes ``other``, i.e. this event is
|
444
|
-
based on it.
|
445
|
-
"""
|
446
|
-
if other is None:
|
447
|
-
return True
|
448
|
-
if self == other:
|
449
|
-
return False
|
450
|
-
while self.node != other.node:
|
451
|
-
self = self.prev # pylint: disable=self-cls-assignment
|
452
|
-
if self is None:
|
453
|
-
return False
|
454
|
-
return self.tick >= other.tick
|
455
|
-
|
456
|
-
def __lte__(self, other):
|
457
|
-
return self.__eq__(other) or self.__lt__(other)
|
458
|
-
|
459
|
-
def __gte__(self, other):
|
460
|
-
return self.__eq__(other) or self.__gt__(other)
|
461
|
-
|
462
|
-
def __contains__(self, node):
|
463
|
-
return self.find(node) is not None
|
464
|
-
|
465
|
-
def find(self, node):
|
466
|
-
"""Return the position of a node in this chain.
|
467
|
-
Zero if the first entry matches.
|
468
|
-
|
469
|
-
Returns ``None`` if not present.
|
470
|
-
"""
|
471
|
-
res = 0
|
472
|
-
while self is not None:
|
473
|
-
if self.node == node:
|
474
|
-
return res
|
475
|
-
res += 1
|
476
|
-
self = self.prev # pylint: disable=self-cls-assignment
|
477
|
-
return None
|
478
|
-
|
479
|
-
def filter(self, node, server=None):
|
480
|
-
"""Return an event chain without the given node.
|
481
|
-
|
482
|
-
If the node is not in the chain, the result is *not* a copy.
|
483
|
-
"""
|
484
|
-
if self.node == node:
|
485
|
-
if server is not None:
|
486
|
-
server.drop_old_event(self)
|
487
|
-
return self.prev
|
488
|
-
# Invariant: a node can only be in the chain once
|
489
|
-
# Thus we can stop filtering after we encounter it.
|
490
|
-
if self.prev is None:
|
491
|
-
return self
|
492
|
-
prev = self.prev.filter(node, server=server)
|
493
|
-
if prev is self.prev:
|
494
|
-
# No change, so return unmodified
|
495
|
-
return self
|
496
|
-
return NodeEvent(node=self.node, tick=self.tick, prev=prev)
|
497
|
-
|
498
|
-
def serialize(self, nchain=-1) -> dict:
|
499
|
-
if not nchain:
|
500
|
-
raise RuntimeError("A chopped-off NodeEvent must not be sent")
|
501
|
-
res = attrdict(node=self.node.name)
|
502
|
-
if self.tick is not None:
|
503
|
-
res.tick = self.tick
|
504
|
-
if self.prev is None:
|
505
|
-
res.prev = None
|
506
|
-
elif nchain != 1:
|
507
|
-
res.prev = self.prev.serialize(nchain - 1)
|
508
|
-
return res
|
509
|
-
|
510
|
-
@classmethod
|
511
|
-
def deserialize(cls, msg, cache):
|
512
|
-
if msg is None:
|
513
|
-
return None
|
514
|
-
msg = msg.get("chain", msg)
|
515
|
-
tick = msg.get("tick", None)
|
516
|
-
if "node" not in msg:
|
517
|
-
assert "prev" not in msg
|
518
|
-
assert tick is None
|
519
|
-
return None
|
520
|
-
self = cls(node=Node(msg["node"], tick=tick, cache=cache), tick=tick)
|
521
|
-
if "prev" in msg:
|
522
|
-
self.prev = cls.deserialize(msg["prev"], cache=cache)
|
523
|
-
return self
|
524
|
-
|
525
|
-
def attach(self, prev: NodeEvent = None, server=None):
|
526
|
-
"""Copy this node, if necessary, and attach a filtered `prev` chain to it"""
|
527
|
-
if prev is not None:
|
528
|
-
prev = prev.filter(self.node, server=server)
|
529
|
-
if self.prev is not None or prev is not None:
|
530
|
-
self = NodeEvent( # pylint: disable=self-cls-assignment
|
531
|
-
node=self.node,
|
532
|
-
tick=self.tick,
|
533
|
-
prev=prev,
|
534
|
-
)
|
535
|
-
return self
|
536
|
-
|
537
|
-
|
538
|
-
class UpdateEvent:
|
539
|
-
"""Represents an event which updates something."""
|
540
|
-
|
541
|
-
def __init__(self, event: NodeEvent, entry: Entry, new_value, old_value=NotGiven, tock=None):
|
542
|
-
self.event = event
|
543
|
-
self.entry = entry
|
544
|
-
self.new_value = new_value
|
545
|
-
if old_value is not NotGiven:
|
546
|
-
self.old_value = old_value
|
547
|
-
if new_value is NotGiven:
|
548
|
-
event.node.mark_deleted(event.tick)
|
549
|
-
self.tock = tock
|
550
|
-
|
551
|
-
def __repr__(self):
|
552
|
-
if self.entry.chain == self.event:
|
553
|
-
res = ""
|
554
|
-
else:
|
555
|
-
res = repr(self.event) + ": "
|
556
|
-
return "<%s:%s%s: %s→%s>" % (
|
557
|
-
self.__class__.__name__,
|
558
|
-
res,
|
559
|
-
repr(self.entry),
|
560
|
-
"-"
|
561
|
-
if not hasattr(self, "old_value")
|
562
|
-
else ""
|
563
|
-
if self.new_value == self.entry.data
|
564
|
-
else repr(self.old_value),
|
565
|
-
repr(self.new_value),
|
566
|
-
)
|
567
|
-
|
568
|
-
def serialize(self, chop_path=0, nchain=-1, with_old=False, conv=None):
|
569
|
-
if conv is None:
|
570
|
-
# pylint: disable=redefined-outer-name
|
571
|
-
global ConvNull
|
572
|
-
if ConvNull is None:
|
573
|
-
from .types import ConvNull
|
574
|
-
conv = ConvNull
|
575
|
-
res = self.event.serialize(nchain=nchain)
|
576
|
-
res.path = self.entry.path[chop_path:]
|
577
|
-
if with_old:
|
578
|
-
if self.old_value is not NotGiven:
|
579
|
-
res.old_value = conv.enc_value(self.old_value, entry=self.entry)
|
580
|
-
if self.new_value is not NotGiven:
|
581
|
-
res.new_value = conv.enc_value(self.new_value, entry=self.entry)
|
582
|
-
elif self.new_value is not NotGiven:
|
583
|
-
res.value = conv.enc_value(self.new_value, entry=self.entry)
|
584
|
-
res.tock = self.entry.tock
|
585
|
-
return res
|
586
|
-
|
587
|
-
@classmethod
|
588
|
-
def deserialize(cls, root, msg, cache, nulls_ok=False, conv=None):
|
589
|
-
if conv is None:
|
590
|
-
# pylint: disable=redefined-outer-name
|
591
|
-
global ConvNull
|
592
|
-
if ConvNull is None:
|
593
|
-
from .types import ConvNull
|
594
|
-
conv = ConvNull
|
595
|
-
entry = root.follow(msg.path, create=True, nulls_ok=nulls_ok)
|
596
|
-
event = NodeEvent.deserialize(msg, cache=cache)
|
597
|
-
old_value = NotGiven
|
598
|
-
if "value" in msg:
|
599
|
-
value = conv.dec_value(msg.value, entry=entry)
|
600
|
-
elif "new_value" in msg:
|
601
|
-
value = conv.dec_value(msg.new_value, entry=entry)
|
602
|
-
old_value = conv.dec_value(msg.old_value, entry=entry)
|
603
|
-
else:
|
604
|
-
value = NotGiven
|
605
|
-
|
606
|
-
return cls(event, entry, value, old_value=old_value, tock=msg.tock)
|
607
|
-
|
608
|
-
|
609
|
-
class Entry:
|
610
|
-
"""This class represents one key/value pair"""
|
611
|
-
|
612
|
-
_parent: Entry = None
|
613
|
-
name: str = None
|
614
|
-
_path: list[str] = None
|
615
|
-
_root: Entry = None
|
616
|
-
chain: NodeEvent = None
|
617
|
-
SUBTYPE = None
|
618
|
-
SUBTYPES = {}
|
619
|
-
_data: Any = NotGiven
|
620
|
-
|
621
|
-
monitors = None
|
622
|
-
|
623
|
-
def __init__(self, name: str, parent: Entry, tock=None):
|
624
|
-
self.name = name
|
625
|
-
self._sub = {}
|
626
|
-
self.monitors = set()
|
627
|
-
self.tock = tock
|
628
|
-
|
629
|
-
if parent is not None:
|
630
|
-
parent._add_subnode(self)
|
631
|
-
self._parent = weakref.ref(parent)
|
632
|
-
|
633
|
-
def _add_subnode(self, child: Entry):
|
634
|
-
self._sub[child.name] = child
|
635
|
-
|
636
|
-
def __hash__(self):
|
637
|
-
return hash(self.name)
|
638
|
-
|
639
|
-
def __eq__(self, other):
|
640
|
-
if other is None:
|
641
|
-
return False
|
642
|
-
if isinstance(other, Entry):
|
643
|
-
other = other.name
|
644
|
-
return self.name == other
|
645
|
-
|
646
|
-
def chain_links(self):
|
647
|
-
c = self.chain
|
648
|
-
if c is not None:
|
649
|
-
yield from iter(c)
|
650
|
-
|
651
|
-
def keys(self):
|
652
|
-
return self._sub.keys()
|
653
|
-
|
654
|
-
def values(self):
|
655
|
-
return self._sub.values()
|
656
|
-
|
657
|
-
def items(self):
|
658
|
-
return self._sub.items()
|
659
|
-
|
660
|
-
def __len__(self):
|
661
|
-
return len(self._sub)
|
662
|
-
|
663
|
-
def __bool__(self):
|
664
|
-
return self._data is not None or len(self._sub) > 0
|
665
|
-
|
666
|
-
def __contains__(self, key):
|
667
|
-
return key in self._sub
|
668
|
-
|
669
|
-
@property
|
670
|
-
def path(self):
|
671
|
-
if self._path is None:
|
672
|
-
parent = self.parent
|
673
|
-
if parent is None:
|
674
|
-
self._path = Path()
|
675
|
-
else:
|
676
|
-
self._path = parent.path + [self.name]
|
677
|
-
return self._path
|
678
|
-
|
679
|
-
def follow_acl(self, path, *, create=True, nulls_ok=False, acl=None, acl_key=None):
|
680
|
-
"""Follow this path.
|
681
|
-
|
682
|
-
If ``create`` is True (default), unknown nodes are silently created.
|
683
|
-
Otherwise they cause a `KeyError`. If ``None``, assume
|
684
|
-
``create=True`` but only check the ACLs.
|
685
|
-
|
686
|
-
If ``nulls_ok`` is False (default), `None` is not allowed as a path
|
687
|
-
element. If 2, it is allowed anywhere; if True, only as the first
|
688
|
-
element.
|
689
|
-
|
690
|
-
If ``acl`` is not ``None``, then ``acl_key`` is the ACL letter to
|
691
|
-
check for. ``acl`` must be an :class:`~moat.kv.types.ACLFinder`
|
692
|
-
created from the root of the ACL in question.
|
693
|
-
|
694
|
-
The ACL key 'W' is special: it checks 'c' if the node is new, else
|
695
|
-
'w'.
|
696
|
-
|
697
|
-
Returns a (node, acl) tuple.
|
698
|
-
"""
|
699
|
-
|
700
|
-
# KEEP IN SYNC with `follow`, below!
|
701
|
-
if acl is None:
|
702
|
-
global NullACL
|
703
|
-
if NullACL is None:
|
704
|
-
from .types import NullACL # pylint: disable=redefined-outer-name
|
705
|
-
acl = NullACL
|
706
|
-
|
707
|
-
first = True
|
708
|
-
for name in path:
|
709
|
-
if name is None and not nulls_ok:
|
710
|
-
raise ValueError("Null path element")
|
711
|
-
if nulls_ok == 1: # root only
|
712
|
-
nulls_ok = False
|
713
|
-
child = self._sub.get(name, None) if self is not None else None
|
714
|
-
if child is None:
|
715
|
-
if create is False:
|
716
|
-
raise KeyError(path)
|
717
|
-
acl.check("n")
|
718
|
-
if create is not None:
|
719
|
-
child = self.SUBTYPES.get(name, self.SUBTYPE)
|
720
|
-
if child is None:
|
721
|
-
raise ValueError(f"Cannot add {name} to {self}")
|
722
|
-
child = child(name, self, tock=self.tock)
|
723
|
-
else:
|
724
|
-
acl.check("x")
|
725
|
-
try:
|
726
|
-
acl = acl.step(name, new=first)
|
727
|
-
except KeyError:
|
728
|
-
raise ACLError(acl.result, name) from None
|
729
|
-
first = False
|
730
|
-
self = child # pylint: disable=self-cls-assignment
|
731
|
-
|
732
|
-
# If the caller doesn't know if the node exists, help them out.
|
733
|
-
if acl_key == "W":
|
734
|
-
acl_key = "w" if self is not None and self._data is not NotGiven else "c"
|
735
|
-
acl.check(acl_key)
|
736
|
-
return (self, acl)
|
737
|
-
|
738
|
-
def follow(self, path, *, create=True, nulls_ok=False):
|
739
|
-
"""
|
740
|
-
As :meth:`follow_acl`, but isn't interested in ACLs and only returns the node.
|
741
|
-
"""
|
742
|
-
# KEEP IN SYNC with `follow_acl`, above!
|
743
|
-
|
744
|
-
for name in path:
|
745
|
-
if name is None and not nulls_ok:
|
746
|
-
raise ValueError("Null path element")
|
747
|
-
if nulls_ok == 1: # root only
|
748
|
-
nulls_ok = False
|
749
|
-
child = self._sub.get(name, None) if self is not None else None
|
750
|
-
if child is None:
|
751
|
-
if create is False:
|
752
|
-
raise KeyError(path)
|
753
|
-
if create is not None:
|
754
|
-
child = self.SUBTYPES.get(name, self.SUBTYPE)
|
755
|
-
if child is None:
|
756
|
-
raise ValueError(f"Cannot add {name} to {self}")
|
757
|
-
child = child(name, self, tock=self.tock)
|
758
|
-
self = child # pylint: disable=self-cls-assignment
|
759
|
-
return self
|
760
|
-
|
761
|
-
def __getitem__(self, name):
|
762
|
-
return self._sub[name]
|
763
|
-
|
764
|
-
@property
|
765
|
-
def root(self):
|
766
|
-
root = self._root
|
767
|
-
if root is not None:
|
768
|
-
root = root()
|
769
|
-
if root is None:
|
770
|
-
raise RuntimeError("Root node is gone")
|
771
|
-
return root
|
772
|
-
|
773
|
-
parent = self.parent
|
774
|
-
if parent is None:
|
775
|
-
return self
|
776
|
-
root = parent.root
|
777
|
-
self._root = weakref.ref(root)
|
778
|
-
return root
|
779
|
-
|
780
|
-
async def set(self, value):
|
781
|
-
self._data = value
|
782
|
-
|
783
|
-
@property
|
784
|
-
def parent(self):
|
785
|
-
parent = self._parent
|
786
|
-
if parent is None:
|
787
|
-
return None
|
788
|
-
parent = parent()
|
789
|
-
if parent is None:
|
790
|
-
raise RuntimeError("Parent node is gone")
|
791
|
-
return parent
|
792
|
-
|
793
|
-
def __repr__(self):
|
794
|
-
try:
|
795
|
-
res = "<%s:%s" % (self.__class__.__name__, self.path)
|
796
|
-
if self.chain is not None:
|
797
|
-
res += "@%s" % (repr(self.chain),)
|
798
|
-
if self.data is not None:
|
799
|
-
res += " =%s" % (repr(self.data),)
|
800
|
-
res += ">"
|
801
|
-
except Exception as exc:
|
802
|
-
res = "<%s:%s" % (self.__class__.__name__, str(exc))
|
803
|
-
return res
|
804
|
-
|
805
|
-
@property
|
806
|
-
def data(self):
|
807
|
-
return self._data
|
808
|
-
|
809
|
-
def mark_deleted(self, server):
|
810
|
-
"""
|
811
|
-
This entry has been deleted.
|
812
|
-
|
813
|
-
Returns:
|
814
|
-
the entry's chain.
|
815
|
-
"""
|
816
|
-
c = self.chain
|
817
|
-
for node, tick in self.chain_links():
|
818
|
-
e = node.mark_deleted(tick)
|
819
|
-
assert e is None or e is self
|
820
|
-
server.mark_deleted(node, tick)
|
821
|
-
self._data = NotGiven
|
822
|
-
return c
|
823
|
-
|
824
|
-
def purge_deleted(self):
|
825
|
-
"""
|
826
|
-
Call :meth:`Node.clear_deleted` on each link in this entry's chain.
|
827
|
-
"""
|
828
|
-
c, self.chain = self.chain, None
|
829
|
-
if c is None:
|
830
|
-
return
|
831
|
-
for node, tick in c:
|
832
|
-
node.clear_deleted(tick)
|
833
|
-
self._chop()
|
834
|
-
|
835
|
-
def _chop(self):
|
836
|
-
"""
|
837
|
-
Remove a deleted entry (and possibly its parent).
|
838
|
-
"""
|
839
|
-
logger.debug("CHOP %r", self)
|
840
|
-
this, p = self, self._parent
|
841
|
-
while p is not None:
|
842
|
-
p = p()
|
843
|
-
if p is None:
|
844
|
-
return
|
845
|
-
p._sub.pop(this.name, None)
|
846
|
-
if p._sub:
|
847
|
-
return
|
848
|
-
this, p = p, p._parent
|
849
|
-
|
850
|
-
async def set_data(self, event: NodeEvent, data: Any, server=None, tock=None):
|
851
|
-
"""This entry is updated by that event.
|
852
|
-
|
853
|
-
Args:
|
854
|
-
event: The :class:`NodeEvent` to base the update on.
|
855
|
-
data (Any): whatever the node should contains. Use :any:`moat.kv.util.NotGiven`
|
856
|
-
to delete.
|
857
|
-
|
858
|
-
Returns:
|
859
|
-
The :class:`UpdateEvent` that has been generated and applied.
|
860
|
-
"""
|
861
|
-
event = event.attach(self.chain, server=server)
|
862
|
-
evt = UpdateEvent(event, self, data, self._data, tock=tock)
|
863
|
-
await self.apply(evt, server=server)
|
864
|
-
return evt
|
865
|
-
|
866
|
-
async def apply(self, evt: UpdateEvent, server=None, root=None, loading=False):
|
867
|
-
"""Apply this :cls`UpdateEvent` to me.
|
868
|
-
|
869
|
-
Also, forward to watchers.
|
870
|
-
"""
|
871
|
-
chk = None
|
872
|
-
if root is not None and None in root:
|
873
|
-
chk = root[None].get("match", None)
|
874
|
-
|
875
|
-
if evt.event is None:
|
876
|
-
raise RuntimeError("huh?")
|
877
|
-
|
878
|
-
if evt.event == self.chain:
|
879
|
-
if self._data != evt.new_value:
|
880
|
-
logger.error(
|
881
|
-
"Diff %r: has\n%r\nbut should have\n%r\n",
|
882
|
-
evt.event,
|
883
|
-
self._data,
|
884
|
-
evt.new_value,
|
885
|
-
)
|
886
|
-
return
|
887
|
-
|
888
|
-
if hasattr(evt, "new_value"):
|
889
|
-
evt_val = evt.new_value
|
890
|
-
else:
|
891
|
-
evt_val = evt.value
|
892
|
-
|
893
|
-
if self.chain > evt.event: # already superseded.
|
894
|
-
logger.info("*** superseded ***")
|
895
|
-
logger.info("Node: %s", self.path)
|
896
|
-
logger.info("Current: %s :%s: %r", self.chain, self.tock, self._data)
|
897
|
-
logger.info("New: %s :%s: %r", evt.event, evt.tock, evt_val)
|
898
|
-
return
|
899
|
-
|
900
|
-
if self._data is not NotGiven:
|
901
|
-
if not (self.chain < evt.event):
|
902
|
-
if not loading:
|
903
|
-
logger.warning("*** inconsistency ***")
|
904
|
-
logger.warning("Node: %s", self.path)
|
905
|
-
logger.warning("Current: %s :%s: %r", self.chain, self.tock, self._data)
|
906
|
-
logger.warning("New: %s :%s: %r", evt.event, evt.tock, evt_val)
|
907
|
-
if evt.tock < self.tock:
|
908
|
-
if not loading:
|
909
|
-
logger.warning("New value ignored")
|
910
|
-
# also mark the new event's chain as superseded
|
911
|
-
server.drop_old_event(self.chain, evt.event)
|
912
|
-
return
|
913
|
-
if not loading:
|
914
|
-
logger.warning("New value used")
|
915
|
-
|
916
|
-
if chk is not None and evt_val is not NotGiven:
|
917
|
-
chk.check_value(evt_val, self)
|
918
|
-
if not hasattr(evt, "old_value"):
|
919
|
-
evt.old_value = self._data
|
920
|
-
await self.set(evt_val)
|
921
|
-
self.tock = evt.tock
|
922
|
-
|
923
|
-
server.drop_old_event(evt.event, self.chain)
|
924
|
-
self.chain = evt.event
|
925
|
-
|
926
|
-
if evt_val is NotGiven:
|
927
|
-
self.mark_deleted(server)
|
928
|
-
|
929
|
-
for n, t in self.chain_links():
|
930
|
-
n.seen(t, self)
|
931
|
-
await self.updated(evt)
|
932
|
-
|
933
|
-
async def walk(self, proc, acl=None, max_depth=-1, min_depth=0, _depth=0, full=False):
|
934
|
-
"""
|
935
|
-
Call coroutine ``proc`` on this node and all its children).
|
936
|
-
|
937
|
-
If `acl` (must be an ACLStepper) is given, `proc` is called with
|
938
|
-
the acl as second argument.
|
939
|
-
|
940
|
-
If `proc` raises `StopAsyncIteration`, chop this subtree.
|
941
|
-
"""
|
942
|
-
if min_depth <= _depth:
|
943
|
-
try:
|
944
|
-
if acl is not None:
|
945
|
-
await proc(self, acl)
|
946
|
-
else:
|
947
|
-
await proc(self)
|
948
|
-
except StopAsyncIteration:
|
949
|
-
return
|
950
|
-
if max_depth == _depth:
|
951
|
-
return
|
952
|
-
_depth += 1
|
953
|
-
for k, v in list(self._sub.items()):
|
954
|
-
if k is None and not full:
|
955
|
-
continue
|
956
|
-
a = acl.step(k) if acl is not None else None
|
957
|
-
await v.walk(proc, acl=a, max_depth=max_depth, min_depth=min_depth, _depth=_depth)
|
958
|
-
|
959
|
-
def serialize(self, chop_path=0, nchain=2, conv=None):
|
960
|
-
"""Serialize this entry for msgpack.
|
961
|
-
|
962
|
-
Args:
|
963
|
-
``chop_path``: If <0, do not return the entry's path.
|
964
|
-
Otherwise, do, but remove the first N entries.
|
965
|
-
``nchain``: how many change events to include.
|
966
|
-
"""
|
967
|
-
if conv is None:
|
968
|
-
global ConvNull
|
969
|
-
if ConvNull is None:
|
970
|
-
from .types import ConvNull # pylint: disable=redefined-outer-name
|
971
|
-
conv = ConvNull
|
972
|
-
res = attrdict()
|
973
|
-
if self._data is not NotGiven:
|
974
|
-
res.value = conv.enc_value(self._data, entry=self)
|
975
|
-
if self.chain is not None and nchain != 0:
|
976
|
-
res.chain = self.chain.serialize(nchain=nchain)
|
977
|
-
res.tock = self.tock
|
978
|
-
if chop_path >= 0:
|
979
|
-
path = self.path
|
980
|
-
if chop_path > 0:
|
981
|
-
path = path[chop_path:]
|
982
|
-
res.path = path
|
983
|
-
return res
|
984
|
-
|
985
|
-
async def updated(self, event: UpdateEvent):
|
986
|
-
"""Send an event to this node (and all its parents)'s watchers."""
|
987
|
-
node = self
|
988
|
-
while True:
|
989
|
-
bad = set()
|
990
|
-
for q in list(node.monitors):
|
991
|
-
if q._moat.kv__free is None or q._moat.kv__free > 1:
|
992
|
-
if q._moat.kv__free is not None:
|
993
|
-
q._moat.kv__free -= 1
|
994
|
-
await q.put(event)
|
995
|
-
else:
|
996
|
-
bad.add(q)
|
997
|
-
for q in bad:
|
998
|
-
try:
|
999
|
-
if q._moat.kv__free > 0:
|
1000
|
-
await q.put(None)
|
1001
|
-
node.monitors.remove(q)
|
1002
|
-
except KeyError:
|
1003
|
-
pass
|
1004
|
-
else:
|
1005
|
-
pass
|
1006
|
-
# await q.aclose()
|
1007
|
-
node = node.parent
|
1008
|
-
if node is None:
|
1009
|
-
break
|
1010
|
-
|
1011
|
-
_counter = 0
|
1012
|
-
|
1013
|
-
@property
|
1014
|
-
def counter(self):
|
1015
|
-
self._counter += 1
|
1016
|
-
return self._counter
|
1017
|
-
|
1018
|
-
|
1019
|
-
Entry.SUBTYPE = Entry
|
1020
|
-
|
1021
|
-
|
1022
|
-
class Watcher:
|
1023
|
-
"""
|
1024
|
-
This helper class is used as an async context manager plus async
|
1025
|
-
iterator. It reports all updates to an entry (or its children).
|
1026
|
-
|
1027
|
-
If a watcher terminates, sending to its channel has blocked.
|
1028
|
-
The receiver needs to take appropriate re-syncing action.
|
1029
|
-
"""
|
1030
|
-
|
1031
|
-
root: Entry = None
|
1032
|
-
q = None
|
1033
|
-
q_len = 100
|
1034
|
-
|
1035
|
-
def __init__(self, root: Entry, full: bool = False, q_len: int = None):
|
1036
|
-
self.root = root
|
1037
|
-
self.full = full
|
1038
|
-
if q_len is not None:
|
1039
|
-
self.q_len = q_len
|
1040
|
-
|
1041
|
-
async def __aenter__(self):
|
1042
|
-
if self.q is not None:
|
1043
|
-
raise RuntimeError("You cannot enter this context more than once")
|
1044
|
-
self.q = create_queue(self.q_len)
|
1045
|
-
self.q._moat.kv__free = self.q_len or None # pylint:disable=no-member
|
1046
|
-
self.root.monitors.add(self.q)
|
1047
|
-
return self
|
1048
|
-
|
1049
|
-
async def __aexit__(self, *tb):
|
1050
|
-
self.root.monitors.remove(self.q)
|
1051
|
-
self.q = None
|
1052
|
-
|
1053
|
-
def __aiter__(self):
|
1054
|
-
if self.q is None:
|
1055
|
-
raise RuntimeError("You need to enclose this with 'async with'")
|
1056
|
-
return self
|
1057
|
-
|
1058
|
-
async def __anext__(self):
|
1059
|
-
if self.q is None:
|
1060
|
-
raise RuntimeError("Aborted. Queue filled?")
|
1061
|
-
while True:
|
1062
|
-
res = await self.q.get()
|
1063
|
-
if self.q._moat.kv__free is not None: # pylint:disable=no-member
|
1064
|
-
self.q._moat.kv__free += 1 # pylint:disable=no-member
|
1065
|
-
if res is None:
|
1066
|
-
raise RuntimeError("Aborted. Queue filled?")
|
1067
|
-
if len(res.entry.path) and res.entry.path[0] is None and not self.full:
|
1068
|
-
continue
|
1069
|
-
return res
|