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.
Files changed (127) hide show
  1. moat/kv/__init__.py +6 -7
  2. moat/kv/_cfg.yaml +5 -8
  3. moat/kv/actor/__init__.py +2 -1
  4. moat/kv/actor/deletor.py +4 -1
  5. moat/kv/auth/__init__.py +12 -13
  6. moat/kv/auth/_test.py +4 -1
  7. moat/kv/auth/password.py +11 -7
  8. moat/kv/backend/mqtt.py +4 -8
  9. moat/kv/client.py +20 -39
  10. moat/kv/code.py +3 -3
  11. moat/kv/command/data.py +4 -3
  12. moat/kv/command/dump/__init__.py +29 -29
  13. moat/kv/command/internal.py +2 -3
  14. moat/kv/command/job.py +1 -2
  15. moat/kv/command/type.py +3 -6
  16. moat/kv/data.py +9 -8
  17. moat/kv/errors.py +16 -8
  18. moat/kv/mock/__init__.py +2 -12
  19. moat/kv/model.py +28 -32
  20. moat/kv/obj/__init__.py +3 -3
  21. moat/kv/obj/command.py +3 -3
  22. moat/kv/runner.py +4 -5
  23. moat/kv/server.py +106 -126
  24. moat/kv/types.py +8 -6
  25. {moat_kv-0.70.24.dist-info → moat_kv-0.71.6.dist-info}/METADATA +7 -6
  26. moat_kv-0.71.6.dist-info/RECORD +47 -0
  27. {moat_kv-0.70.24.dist-info → moat_kv-0.71.6.dist-info}/WHEEL +1 -1
  28. moat_kv-0.71.6.dist-info/licenses/LICENSE +3 -0
  29. moat_kv-0.71.6.dist-info/licenses/LICENSE.APACHE2 +202 -0
  30. moat_kv-0.71.6.dist-info/licenses/LICENSE.MIT +20 -0
  31. moat_kv-0.71.6.dist-info/top_level.txt +1 -0
  32. build/lib/docs/source/conf.py +0 -201
  33. build/lib/examples/pathify.py +0 -45
  34. build/lib/moat/kv/__init__.py +0 -19
  35. build/lib/moat/kv/_cfg.yaml +0 -97
  36. build/lib/moat/kv/_main.py +0 -91
  37. build/lib/moat/kv/actor/__init__.py +0 -98
  38. build/lib/moat/kv/actor/deletor.py +0 -139
  39. build/lib/moat/kv/auth/__init__.py +0 -444
  40. build/lib/moat/kv/auth/_test.py +0 -166
  41. build/lib/moat/kv/auth/password.py +0 -234
  42. build/lib/moat/kv/auth/root.py +0 -58
  43. build/lib/moat/kv/backend/__init__.py +0 -67
  44. build/lib/moat/kv/backend/mqtt.py +0 -74
  45. build/lib/moat/kv/backend/serf.py +0 -45
  46. build/lib/moat/kv/client.py +0 -1025
  47. build/lib/moat/kv/code.py +0 -236
  48. build/lib/moat/kv/codec.py +0 -11
  49. build/lib/moat/kv/command/__init__.py +0 -1
  50. build/lib/moat/kv/command/acl.py +0 -180
  51. build/lib/moat/kv/command/auth.py +0 -261
  52. build/lib/moat/kv/command/code.py +0 -293
  53. build/lib/moat/kv/command/codec.py +0 -186
  54. build/lib/moat/kv/command/data.py +0 -265
  55. build/lib/moat/kv/command/dump/__init__.py +0 -143
  56. build/lib/moat/kv/command/error.py +0 -149
  57. build/lib/moat/kv/command/internal.py +0 -248
  58. build/lib/moat/kv/command/job.py +0 -433
  59. build/lib/moat/kv/command/log.py +0 -53
  60. build/lib/moat/kv/command/server.py +0 -114
  61. build/lib/moat/kv/command/type.py +0 -201
  62. build/lib/moat/kv/config.py +0 -46
  63. build/lib/moat/kv/data.py +0 -216
  64. build/lib/moat/kv/errors.py +0 -561
  65. build/lib/moat/kv/exceptions.py +0 -126
  66. build/lib/moat/kv/mock/__init__.py +0 -101
  67. build/lib/moat/kv/mock/mqtt.py +0 -159
  68. build/lib/moat/kv/mock/serf.py +0 -250
  69. build/lib/moat/kv/mock/tracer.py +0 -63
  70. build/lib/moat/kv/model.py +0 -1069
  71. build/lib/moat/kv/obj/__init__.py +0 -646
  72. build/lib/moat/kv/obj/command.py +0 -241
  73. build/lib/moat/kv/runner.py +0 -1347
  74. build/lib/moat/kv/server.py +0 -2809
  75. build/lib/moat/kv/types.py +0 -513
  76. debian/moat-kv/usr/lib/python3/dist-packages/docs/source/conf.py +0 -201
  77. debian/moat-kv/usr/lib/python3/dist-packages/examples/pathify.py +0 -45
  78. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/__init__.py +0 -19
  79. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/_cfg.yaml +0 -97
  80. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/_main.py +0 -91
  81. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/actor/__init__.py +0 -98
  82. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/actor/deletor.py +0 -139
  83. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/__init__.py +0 -444
  84. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/_test.py +0 -166
  85. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/password.py +0 -234
  86. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/root.py +0 -58
  87. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/__init__.py +0 -67
  88. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/mqtt.py +0 -74
  89. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/serf.py +0 -45
  90. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/client.py +0 -1025
  91. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/code.py +0 -236
  92. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/codec.py +0 -11
  93. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/__init__.py +0 -1
  94. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/acl.py +0 -180
  95. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/auth.py +0 -261
  96. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/code.py +0 -293
  97. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/codec.py +0 -186
  98. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/data.py +0 -265
  99. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/dump/__init__.py +0 -143
  100. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/error.py +0 -149
  101. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/internal.py +0 -248
  102. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/job.py +0 -433
  103. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/log.py +0 -53
  104. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/server.py +0 -114
  105. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/type.py +0 -201
  106. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/config.py +0 -46
  107. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/data.py +0 -216
  108. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/errors.py +0 -561
  109. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/exceptions.py +0 -126
  110. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/__init__.py +0 -101
  111. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/mqtt.py +0 -159
  112. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/serf.py +0 -250
  113. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/tracer.py +0 -63
  114. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/model.py +0 -1069
  115. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/obj/__init__.py +0 -646
  116. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/obj/command.py +0 -241
  117. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/runner.py +0 -1347
  118. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/server.py +0 -2809
  119. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/types.py +0 -513
  120. docs/source/conf.py +0 -201
  121. examples/pathify.py +0 -45
  122. moat/kv/backend/serf.py +0 -45
  123. moat/kv/codec.py +0 -11
  124. moat/kv/mock/serf.py +0 -250
  125. moat_kv-0.70.24.dist-info/RECORD +0 -137
  126. moat_kv-0.70.24.dist-info/top_level.txt +0 -9
  127. {moat_kv-0.70.24.dist-info → moat_kv-0.71.6.dist-info}/licenses/LICENSE.txt +0 -0
@@ -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