moat-kv 0.70.20__py3-none-any.whl → 0.70.23__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 (52) hide show
  1. moat/kv/__init__.py +1 -0
  2. moat/kv/_cfg.yaml +97 -0
  3. moat/kv/_main.py +6 -9
  4. moat/kv/client.py +36 -52
  5. moat/kv/code.py +10 -3
  6. moat/kv/codec.py +1 -0
  7. moat/kv/config.py +2 -0
  8. moat/kv/data.py +8 -7
  9. moat/kv/errors.py +17 -9
  10. moat/kv/exceptions.py +1 -7
  11. moat/kv/model.py +16 -24
  12. moat/kv/runner.py +39 -36
  13. moat/kv/server.py +86 -90
  14. moat/kv/types.py +5 -8
  15. {moat_kv-0.70.20.dist-info → moat_kv-0.70.23.dist-info}/METADATA +22 -25
  16. moat_kv-0.70.23.dist-info/RECORD +19 -0
  17. {moat_kv-0.70.20.dist-info → moat_kv-0.70.23.dist-info}/WHEEL +1 -1
  18. moat_kv-0.70.23.dist-info/licenses/LICENSE.txt +14 -0
  19. moat/kv/_config.yaml +0 -98
  20. moat/kv/actor/__init__.py +0 -97
  21. moat/kv/actor/deletor.py +0 -137
  22. moat/kv/auth/__init__.py +0 -446
  23. moat/kv/auth/_test.py +0 -172
  24. moat/kv/auth/password.py +0 -232
  25. moat/kv/auth/root.py +0 -56
  26. moat/kv/backend/__init__.py +0 -66
  27. moat/kv/backend/mqtt.py +0 -74
  28. moat/kv/backend/serf.py +0 -44
  29. moat/kv/command/__init__.py +0 -1
  30. moat/kv/command/acl.py +0 -174
  31. moat/kv/command/auth.py +0 -258
  32. moat/kv/command/code.py +0 -306
  33. moat/kv/command/codec.py +0 -190
  34. moat/kv/command/data.py +0 -274
  35. moat/kv/command/dump/__init__.py +0 -141
  36. moat/kv/command/error.py +0 -156
  37. moat/kv/command/internal.py +0 -257
  38. moat/kv/command/job.py +0 -438
  39. moat/kv/command/log.py +0 -52
  40. moat/kv/command/server.py +0 -115
  41. moat/kv/command/type.py +0 -203
  42. moat/kv/mock/__init__.py +0 -97
  43. moat/kv/mock/mqtt.py +0 -164
  44. moat/kv/mock/serf.py +0 -253
  45. moat/kv/mock/tracer.py +0 -65
  46. moat/kv/obj/__init__.py +0 -636
  47. moat/kv/obj/command.py +0 -246
  48. moat_kv-0.70.20.dist-info/LICENSE +0 -3
  49. moat_kv-0.70.20.dist-info/LICENSE.APACHE2 +0 -202
  50. moat_kv-0.70.20.dist-info/LICENSE.MIT +0 -20
  51. moat_kv-0.70.20.dist-info/RECORD +0 -49
  52. {moat_kv-0.70.20.dist-info → moat_kv-0.70.23.dist-info}/top_level.txt +0 -0
moat/kv/obj/__init__.py DELETED
@@ -1,636 +0,0 @@
1
- """
2
- Object interface to moat.kv data
3
-
4
- """
5
-
6
- import heapq
7
- import weakref
8
- from collections.abc import Mapping
9
- from functools import partial
10
-
11
- import anyio
12
- from asyncscope import scope
13
-
14
- try:
15
- from contextlib import asynccontextmanager
16
- except ImportError:
17
- from async_generator import asynccontextmanager
18
-
19
- from moat.util import NoLock, NotGiven, Path, PathLongener, combine_dict, yload
20
-
21
- __all__ = ["ClientEntry", "AttrClientEntry", "ClientRoot"]
22
-
23
-
24
- class NamedRoot:
25
- """
26
- This is a mix-on class for the root of a subhierarchy that caches named
27
- sub-entries.
28
-
29
- Named children should call `_add_name` on this entry.
30
- """
31
-
32
- def __init__(self, *a, **k):
33
- self.__named = {}
34
- super().__init__(*a, **k)
35
-
36
- def by_name(self, name):
37
- if name is None:
38
- return None
39
- if not isinstance(name, str):
40
- raise ValueError("No string: " + repr(name))
41
- return self.__named.get(name)
42
-
43
- def _add_name(self, obj):
44
- n = obj.name
45
- if n is None:
46
- return
47
-
48
- self.__named[n] = obj
49
- obj.reg_del(self, "_del__name", obj, n)
50
-
51
- def _del__name(self, obj, n):
52
- old = self.__named.pop(n)
53
- if old is None or old is obj:
54
- return
55
- # Oops, that has been superseded. Put it back.
56
- self.__named[n] = old
57
-
58
-
59
- class ClientEntry:
60
- """A helper class that represents a node on the server, as returned by
61
- :meth:`Client.mirror`.
62
- """
63
-
64
- value = NotGiven
65
- chain = None
66
-
67
- def __init__(self, parent, name=None):
68
- self._init()
69
- self._path = parent._path + (name,)
70
- self._name = name
71
- self._parent = weakref.ref(parent)
72
- self._root = weakref.ref(parent.root)
73
- self.client = parent.client
74
-
75
- def _init(self):
76
- self._lock = anyio.Lock() # for saving etc.
77
- self.chain = None
78
- self.value = NotGiven
79
- self._children = dict()
80
-
81
- @classmethod
82
- def child_type(cls, name): # pylint: disable=unused-argument
83
- """Given a node, return the type which the child with that name should have.
84
- The default is "same as this class".
85
- """
86
- return cls
87
-
88
- def value_or(self, default, typ=None):
89
- """
90
- Shortcut to coerce the value to some type
91
- """
92
- val = self.value
93
- if val is NotGiven:
94
- return default
95
- if typ is not None and not isinstance(val, typ):
96
- return default
97
- return val
98
-
99
- def val(self, *attr):
100
- """
101
- Shortcut to get an attribute value
102
- """
103
- return self.val_d(NotGiven, *attr)
104
-
105
- def val_d(self, default, *attr):
106
- """
107
- Shortcut to get an attribute value, or a default
108
- """
109
- val = self.value
110
- if val is NotGiven:
111
- if default is NotGiven:
112
- raise ValueError("no value set")
113
- return default
114
- for a in attr:
115
- try:
116
- val = val[a]
117
- except KeyError:
118
- if default is NotGiven:
119
- raise
120
- return default
121
- return val
122
-
123
- def find_cfg(self, *k, default=NotGiven):
124
- """
125
- Convenience method to get a config value
126
-
127
- It is retrieved first from this node's value, then from the parent.
128
- """
129
- val = self.value_or({}, Mapping)
130
- try:
131
- for kk in k:
132
- try:
133
- val = val[kk]
134
- except TypeError:
135
- raise TypeError(self.value, k, val, kk)
136
- return val
137
- except KeyError:
138
- return self.parent.find_cfg(*k, default=default)
139
-
140
- @property
141
- def parent(self):
142
- return self._parent()
143
-
144
- @property
145
- def root(self):
146
- return self._root()
147
-
148
- @property
149
- def subpath(self):
150
- """Return the path to this entry, starting with its :class:`ClientRoot` base."""
151
- return self._path[len(self.root._path) :] # noqa: E203
152
-
153
- @property
154
- def all_children(self):
155
- """Iterate all child nodes with data.
156
-
157
- You can send ``True`` to the iterator if you want to skip a subtree.
158
- """
159
- for k in self:
160
- if k.value is not NotGiven:
161
- res = yield k
162
- if res is True:
163
- continue
164
- yield from k.all_children
165
-
166
- def allocate(self, name: str, exists: bool = False):
167
- """
168
- Create the child named "name". It is created (locally) if it doesn't exist.
169
-
170
- Arguments:
171
- name: The child node's name.
172
- exists: return the existing value? otherwise error
173
-
174
- If this returns ``None``, the subtree shall not be tracked.
175
-
176
- """
177
- c = self._children.get(name, None)
178
- if c is not None:
179
- if exists:
180
- return c
181
- raise RuntimeError("Duplicate child", self, name, c)
182
- c = self.child_type(name)
183
- if c is None:
184
- raise KeyError(name)
185
- self._children[name] = c = c(self, name)
186
- return c
187
-
188
- def follow(self, path, *, create=None, empty_ok=False):
189
- """Look up a sub-entry.
190
-
191
- Arguments:
192
- path (Path): the path elements to follow.
193
- create (bool): Create the entries. Default ``False``.
194
- Otherwise return ``None`` if not found.
195
-
196
- The path may not be empty. It also must not be a string,
197
- because that indicates that you called ``.follow(*path)`` instead of
198
- ``.follow(path)``.
199
- """
200
- if isinstance(path, str):
201
- raise RuntimeError("You seem to have used '*path' instead of 'path'.")
202
- if not empty_ok and not len(path):
203
- raise RuntimeError("Empty path")
204
-
205
- node = self
206
- for n, elem in enumerate(path, start=1):
207
- next_node = node.get(elem)
208
- if next_node is None:
209
- if create is False:
210
- return None
211
- next_node = node.allocate(elem)
212
- elif create and n == len(path) and next_node.value is not NotGiven:
213
- raise RuntimeError("Duplicate child", self, path, n)
214
-
215
- node = next_node
216
-
217
- return node
218
-
219
- def __getitem__(self, name):
220
- return self._children[name]
221
-
222
- def by_name(self, name):
223
- """
224
- Lookup by a human-readable name?
225
- """
226
- return self._children[name]
227
-
228
- def __delitem__(self, name):
229
- del self._children[name]
230
-
231
- def get(self, name):
232
- return self._children.get(name, None)
233
-
234
- def __iter__(self):
235
- """Iterating an entry returns its children."""
236
- return iter(list(self._children.values()))
237
-
238
- def __bool__(self):
239
- return self.value is not NotGiven or bool(self._children)
240
-
241
- def __len__(self):
242
- return len(self._children)
243
-
244
- def __contains__(self, k):
245
- if isinstance(k, type(self)):
246
- k = k._name
247
- return k in self._children
248
-
249
- async def update(self, value, _locked=False, wait=False):
250
- """Update (or simply set) this node's value.
251
-
252
- This is a coroutine.
253
- """
254
- async with NoLock if _locked else self._lock:
255
- r = await self.root.client.set(
256
- self._path, chain=self.chain, value=value, nchain=3, idem=True
257
- )
258
- if wait:
259
- await self.root.wait_chain(r.chain)
260
- self.value = value
261
- self.chain = r.chain
262
- return r
263
-
264
- async def delete(
265
- self, _locked=False, nchain=0, chain=True, wait=False, recursive=False
266
- ):
267
- """Delete this node's value.
268
-
269
- This is a coroutine.
270
- """
271
- async with NoLock if _locked else self._lock:
272
- r = await self.root.client.delete(
273
- self._path,
274
- nchain=nchain,
275
- recursive=recursive,
276
- **({"chain": self.chain} if chain else {}),
277
- )
278
- if wait:
279
- await self.root.wait_chain(r.chain)
280
- self.chain = None
281
- return r
282
-
283
- async def set_value(self, value):
284
- """Callback to set the value when data has arrived.
285
-
286
- This method is strictly for overriding.
287
- Don't call me, I'll call you.
288
-
289
- This is a coroutine, for ease of integration.
290
- """
291
- self.value = value
292
-
293
- async def seen_value(self):
294
- """The current value was seen (again).
295
-
296
- Useful for syncing.
297
-
298
- The default action is to do nothing.
299
- """
300
- pass
301
-
302
- def mark_inconsistent(self, r):
303
- """There has been an inconsistent update.
304
-
305
- This call will immediately be followed by a call to
306
- :meth:`set_value`, thus it is not async.
307
-
308
- The default action is to do nothing.
309
- """
310
- pass
311
-
312
-
313
- def _node_gt(self, other):
314
- if other is None and self is not None:
315
- return True
316
- if self is None or self == other:
317
- return False
318
- while self["node"] != other["node"]:
319
- self = self["prev"]
320
- if self is None:
321
- return False
322
- return self["tick"] >= other["tick"]
323
-
324
-
325
- class AttrClientEntry(ClientEntry):
326
- """A ClientEntry which expects a dict as value and sets (some of) the clients'
327
- attributes appropriately.
328
-
329
- Set the classvar ``ATTRS`` to a list of the attrs you want saved. Note
330
- that these are not inherited: when you subclass, copy and extend the
331
- ``ATTRS`` of your superclass.
332
-
333
- If the entry is deleted (value set to ``None``, the attributes listed in
334
- ``ATTRS`` will be deleted too, or revert to the class values.
335
- """
336
-
337
- ATTRS = ()
338
-
339
- async def update(self, value, **kw): # pylint: disable=arguments-differ
340
- raise RuntimeError("Nope. Set attributes and call '.save()'.")
341
-
342
- async def set_value(self, value):
343
- """Callback to set the value when data has arrived.
344
-
345
- This method sets the actual attributes.
346
-
347
- This method is strictly for overriding.
348
- Don't call me, I'll call you.
349
- """
350
- await super().set_value(value)
351
- for k in self.ATTRS:
352
- if value is not NotGiven and k in value:
353
- setattr(self, k, value[k])
354
- else:
355
- try:
356
- delattr(self, k)
357
- except AttributeError:
358
- pass
359
-
360
- def get_value(self, skip_none=False, skip_empty=False):
361
- """
362
- Extract value from attrs
363
- """
364
- res = {}
365
- for attr in type(self).ATTRS:
366
- try:
367
- v = getattr(self, attr)
368
- except AttributeError:
369
- pass
370
- else:
371
- if v is NotGiven:
372
- continue
373
- if skip_none and v is None:
374
- continue
375
- if skip_empty and v == ():
376
- continue
377
- res[attr] = v
378
- return res
379
-
380
- async def save(self, wait=False):
381
- """
382
- Save myself to storage, by copying ATTRS to a new value.
383
- """
384
- async with self._lock:
385
- r = await super().update(value=self.get_value(), _locked=True)
386
- if wait:
387
- await self.root.wait_chain(r.chain)
388
- return r
389
-
390
-
391
- class MirrorRoot(ClientEntry):
392
- """
393
- This class represents the root of a subsystem's storage, used for
394
- object-agnostic data mirroring.
395
-
396
- Used internally.
397
- """
398
-
399
- _tg = None
400
-
401
- CFG = None # You need to override this with a dict(prefix=('where','ever'))
402
-
403
- def __init__(self, client, path, *, need_wait=False, cfg=None, require_client=True):
404
- # pylint: disable=super-init-not-called
405
- self._init()
406
- self.client = client
407
- self._path = path
408
- self._need_wait = need_wait
409
- self._loaded = anyio.Event()
410
- self._require_client = require_client
411
-
412
- if cfg is None:
413
- cfg = {}
414
- self._cfg = cfg
415
- self._name = self.client.name
416
-
417
- if need_wait:
418
- self._waiters = dict()
419
- self._seen = dict()
420
-
421
- @classmethod
422
- async def as_handler(
423
- cls, client, cfg=None, key="prefix", subpath=(), name=None, **kw
424
- ):
425
- """Return an (or "the") instance of this class.
426
-
427
- The handler is created if it doesn't exist.
428
-
429
- Instances are distinguished by a key (from config), which
430
- must contain their path, and an optional subpath.
431
- """
432
- d = []
433
- if cfg is not None:
434
- d.append(cfg)
435
-
436
- defcfg = client._cfg.get(cls.CFG)
437
- if not defcfg:
438
- # seems we didn't load the class' default config yet.
439
- import inspect
440
- from pathlib import Path as _Path
441
-
442
- md = inspect.getmodule(cls)
443
- try:
444
- f = (_Path(md.__file__).parent / "_config.yaml").open("r")
445
- except EnvironmentError:
446
- pass
447
- else:
448
- with f:
449
- defcfg = yload(f, attr=True).get("kv",{}).get(cls.CFG)
450
- if cfg:
451
- if defcfg:
452
- cfg = combine_dict(cfg, defcfg)
453
- else:
454
- if not defcfg:
455
- raise RuntimeError("no config for " + repr(cls))
456
- cfg = defcfg
457
-
458
- if name is None:
459
- # if key != "prefix":
460
- # subpath = Path(key) + subpath
461
- name = str(Path("_moat.kv", client.name, cls.CFG, *subpath))
462
-
463
- def make():
464
- return client.mirror(
465
- cfg[key] + subpath, root_type=cls, need_wait=True, cfg=cfg, **kw
466
- )
467
-
468
- return await client.unique_helper(name, factory=make)
469
-
470
- @classmethod
471
- def child_type(cls, name):
472
- """Given a node, return the type which the child with that name should have.
473
- The default is :class:`ClientEntry`.
474
-
475
- This may return ``None``. In that case the subtree with this name
476
- shall not be tracked further.
477
- """
478
- return ClientEntry
479
-
480
- @property
481
- def root(self):
482
- """Returns this instance."""
483
- return self
484
-
485
- def find_cfg(self, *k, default=NotGiven):
486
- """
487
- Convenience method to get a config value.
488
-
489
- It is retrieved first from this node's value, then from the configuration read via CFG.
490
- """
491
- val = self.value_or({}, Mapping)
492
- try:
493
- for kk in k:
494
- val = val[kk]
495
- return val
496
- except KeyError:
497
- try:
498
- val = self._cfg
499
- for kk in k:
500
- val = val[kk]
501
- return val
502
- except KeyError:
503
- if default is NotGiven:
504
- raise
505
- return default
506
-
507
- async def run_starting(self):
508
- """Hook for 'about to start reading'"""
509
- pass
510
-
511
- async def running(self):
512
- """Hook for 'done reading current state'"""
513
- self._loaded.set()
514
-
515
- @asynccontextmanager
516
- async def run(self):
517
- """A coroutine that fetches, and continually updates, a subtree."""
518
- if self._require_client:
519
- scope.requires(self.client.scope)
520
-
521
- async with anyio.create_task_group() as tg:
522
- self._tg = tg
523
-
524
- async def monitor(*, task_status):
525
- pl = PathLongener(())
526
- await self.run_starting()
527
- async with self.client._stream(
528
- "watch", nchain=3, path=self._path, fetch=True
529
- ) as w:
530
- async for r in w:
531
- if "path" not in r:
532
- if r.get("state", "") == "uptodate":
533
- await self.running()
534
- task_status.started()
535
- continue
536
- pl(r)
537
- val = r.get("value", NotGiven)
538
- entry = self.follow(r.path, create=None, empty_ok=True)
539
- if entry is not None:
540
- # Test for consistency
541
- try:
542
- if entry.chain == r.chain:
543
- # entry.update() has set this
544
- await entry.seen_value()
545
- elif _node_gt(entry.chain, r.chain):
546
- # stale data
547
- pass
548
- elif not _node_gt(r.chain, entry.chain):
549
- entry.mark_inconsistent(r)
550
- except AttributeError:
551
- pass
552
-
553
- # update entry
554
- entry.chain = (
555
- None if val is NotGiven else r.get("chain", None)
556
- )
557
- await entry.set_value(val)
558
-
559
- if val is NotGiven and not entry:
560
- # the entry has no value and no children,
561
- # so we delete it (and possibly its
562
- # parents) from our tree.
563
- n = list(entry.subpath)
564
- while n:
565
- # no-op except for class-specific side effects
566
- # like setting an event
567
- await entry.set_value(NotGiven)
568
-
569
- entry = entry.parent
570
- del entry[n.pop()]
571
- if entry:
572
- break
573
-
574
- if not self._need_wait or "chain" not in r:
575
- continue
576
- c = r.chain
577
- while c is not None:
578
- if self._seen.get(c.node, 0) < c.tick:
579
- self._seen[c.node] = c.tick
580
- try:
581
- w = self._waiters[c.node]
582
- except KeyError:
583
- pass
584
- else:
585
- while w and w[0][0] <= c.tick:
586
- heapq.heappop(w)[1].set()
587
- c = c.get("prev", None)
588
-
589
- await tg.start(monitor)
590
- try:
591
- yield self
592
- finally:
593
- with anyio.fail_after(2, shield=True):
594
- tg.cancel_scope.cancel()
595
- pass # end of 'run', closing taskgroup
596
-
597
- async def cancel(self):
598
- """Stop the monitor"""
599
- await self._tg.cancel_scope.cancel()
600
-
601
- async def wait_loaded(self):
602
- """Wait for the tree to be loaded completely."""
603
- await self._loaded.wait()
604
-
605
- async def wait_chain(self, chain):
606
- """Wait for a tree update containing this tick."""
607
- try:
608
- if chain.tick <= self._seen[chain.node]:
609
- return
610
- except KeyError:
611
- pass
612
- w = self._waiters.setdefault(chain.node, [])
613
- e = anyio.Event()
614
- heapq.heappush(w, (chain.tick, e))
615
- await e.wait()
616
-
617
- async def spawn(self, p, *a, **kw):
618
- p = partial(p, *a, **kw)
619
- self._tg.start_soon(p)
620
-
621
-
622
- class ClientRoot(MirrorRoot):
623
-
624
- """
625
- This class represents the root of a subsystem's storage.
626
-
627
- To use this class, create a subclass that, at minimum, overrides
628
- ``CFG`` and ``child_type``. ``CFG`` must be a dict with at least a
629
- ``prefix`` tuple. You instantiate the entry using :meth:`as_handler`.
630
-
631
- """
632
-
633
- def __init__(self, *a, **kw):
634
- if self.CFG is None:
635
- raise TypeError(f"You need to override .CFG in {type(self).__name__}")
636
- super().__init__(*a, **kw)