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
moat/kv/server.py CHANGED
@@ -1,15 +1,16 @@
1
1
  # Local server
2
2
  from __future__ import annotations
3
3
 
4
- import io
5
4
  import os
6
5
  import signal
7
6
  import time
7
+ from collections.abc import Mapping
8
8
 
9
9
  import anyio
10
10
  from anyio.abc import SocketAttribute
11
11
  from asyncscope import scope
12
12
  from moat.util import DelayedRead, DelayedWrite, create_queue, ensure_cfg
13
+ from moat.lib.codec import get_codec
13
14
 
14
15
  try:
15
16
  from contextlib import asynccontextmanager
@@ -17,10 +18,9 @@ except ImportError:
17
18
  from async_generator import asynccontextmanager
18
19
 
19
20
  import logging
20
- from collections.abc import Mapping
21
21
  from functools import partial
22
22
  from pprint import pformat
23
- from typing import Any
23
+ from typing import Any, TYPE_CHECKING
24
24
 
25
25
  from asyncactor import (
26
26
  Actor,
@@ -55,7 +55,6 @@ from . import _version_tuple
55
55
  from . import client as moat_kv_client # needs to be mock-able
56
56
  from .actor.deletor import DeleteActor
57
57
  from .backend import get_backend
58
- from .codec import packer, stream_unpacker, unpacker
59
58
  from .exceptions import (
60
59
  ACLError,
61
60
  CancelledError,
@@ -68,6 +67,10 @@ from .exceptions import (
68
67
  )
69
68
  from .model import Node, NodeEvent, NodeSet, UpdateEvent, Watcher
70
69
  from .types import ACLFinder, ACLStepper, ConvNull, NullACL, RootEntry
70
+ import contextlib
71
+
72
+ if TYPE_CHECKING:
73
+ import io
71
74
 
72
75
  Stream = anyio.abc.ByteStream
73
76
 
@@ -75,9 +78,6 @@ ClosedResourceError = anyio.ClosedResourceError
75
78
 
76
79
  _client_nr = 0
77
80
 
78
- SERF_MAXLEN = 450
79
- SERF_LEN_DELTA = 15
80
-
81
81
 
82
82
  def max_n(a, b):
83
83
  if a is None:
@@ -228,10 +228,8 @@ class StreamCommand:
228
228
  await self.send(error=repr(exc))
229
229
  finally:
230
230
  with anyio.move_on_after(2, shield=True):
231
- try:
231
+ with contextlib.suppress(anyio.BrokenResourceError):
232
232
  await self.send(state="end")
233
- except anyio.BrokenResourceError:
234
- pass
235
233
 
236
234
  else:
237
235
  res = await self.run(**kw)
@@ -553,62 +551,64 @@ class SCmd_watch(StreamCommand):
553
551
  min_depth = msg.get("min_depth", 0)
554
552
  empty = msg.get("empty", False)
555
553
 
556
- async with Watcher(entry) as watcher:
557
- async with anyio.create_task_group() as tg:
558
- tock = client.server.tock
559
- shorter = PathShortener(entry.path)
560
- if msg.get("fetch", False):
561
-
562
- async def orig_state():
563
- kv = {"max_depth": max_depth, "min_depth": min_depth}
564
-
565
- async def worker(entry, acl):
566
- if entry.data is NotGiven and not empty:
567
- return
568
- if entry.tock < tock:
569
- res = entry.serialize(
570
- chop_path=client._chop_path,
571
- nchain=nchain,
572
- conv=conv,
573
- )
574
- shorter(res)
575
- if not acl.allows("r"):
576
- res.pop("value", None)
577
- await self.send(**res)
578
-
579
- if not acl.allows("e"):
580
- raise StopAsyncIteration
581
- if not acl.allows("x"):
582
- acl.block("r")
583
-
584
- await entry.walk(worker, acl=acl, **kv)
585
- await self.send(state="uptodate")
586
-
587
- tg.start_soon(orig_state)
588
-
589
- async for m in watcher:
590
- ml = len(m.entry.path) - len(msg.path)
591
- if ml < min_depth:
592
- continue
593
- if max_depth >= 0 and ml > max_depth:
594
- continue
595
- a = acl
596
- for p in getattr(m, "path", [])[shorter.depth :]:
597
- if not a.allows("e"):
598
- break
554
+ async with (
555
+ Watcher(entry) as watcher,
556
+ anyio.create_task_group() as tg,
557
+ ):
558
+ tock = client.server.tock
559
+ shorter = PathShortener(entry.path)
560
+ if msg.get("fetch", False):
561
+
562
+ async def orig_state():
563
+ kv = {"max_depth": max_depth, "min_depth": min_depth}
564
+
565
+ async def worker(entry, acl):
566
+ if entry.data is NotGiven and not empty:
567
+ return
568
+ if entry.tock < tock:
569
+ res = entry.serialize(
570
+ chop_path=client._chop_path,
571
+ nchain=nchain,
572
+ conv=conv,
573
+ )
574
+ shorter(res)
575
+ if not acl.allows("r"):
576
+ res.pop("value", None)
577
+ await self.send(**res)
578
+
579
+ if not acl.allows("e"):
580
+ raise StopAsyncIteration
599
581
  if not acl.allows("x"):
600
- a.block("r")
601
- a = a.step(p)
602
- else:
603
- res = m.entry.serialize(
604
- chop_path=client._chop_path,
605
- nchain=nchain,
606
- conv=conv,
607
- )
608
- shorter(res)
609
- if not a.allows("r"):
610
- res.pop("value", None)
611
- await self.send(**res)
582
+ acl.block("r")
583
+
584
+ await entry.walk(worker, acl=acl, **kv)
585
+ await self.send(state="uptodate")
586
+
587
+ tg.start_soon(orig_state)
588
+
589
+ async for m in watcher:
590
+ ml = len(m.entry.path) - len(msg.path)
591
+ if ml < min_depth:
592
+ continue
593
+ if max_depth >= 0 and ml > max_depth:
594
+ continue
595
+ a = acl
596
+ for p in getattr(m, "path", [])[shorter.depth :]:
597
+ if not a.allows("e"):
598
+ break
599
+ if not acl.allows("x"):
600
+ a.block("r")
601
+ a = a.step(p)
602
+ else:
603
+ res = m.entry.serialize(
604
+ chop_path=client._chop_path,
605
+ nchain=nchain,
606
+ conv=conv,
607
+ )
608
+ shorter(res)
609
+ if not a.allows("r"):
610
+ res.pop("value", None)
611
+ await self.send(**res)
612
612
 
613
613
 
614
614
  class SCmd_msg_monitor(StreamCommand):
@@ -621,6 +621,7 @@ class SCmd_msg_monitor(StreamCommand):
621
621
  multiline = True
622
622
 
623
623
  async def run(self):
624
+ codec = get_codec("std-msgpack")
624
625
  msg = self.msg
625
626
  raw = msg.get("raw", False)
626
627
  topic = msg.topic
@@ -642,7 +643,7 @@ class SCmd_msg_monitor(StreamCommand):
642
643
  res["raw"] = resp.payload
643
644
  else:
644
645
  try:
645
- res["data"] = unpacker(resp.payload)
646
+ res["data"] = codec.decode(resp.payload)
646
647
  except Exception as exc:
647
648
  res["raw"] = resp.payload
648
649
  res["error"] = repr(exc)
@@ -716,10 +717,10 @@ class ServerClient:
716
717
  if fn is None:
717
718
  fn = StreamCommand(self, msg)
718
719
  if needAuth and not getattr(fn, "noAuth", False):
719
- raise NoAuthError()
720
+ raise NoAuthError
720
721
  else:
721
722
  if needAuth and not getattr(fn, "noAuth", False):
722
- raise NoAuthError()
723
+ raise NoAuthError
723
724
  fn = partial(self._process, fn, msg)
724
725
  if evt is not None:
725
726
  evt.set()
@@ -1069,7 +1070,7 @@ class ServerClient:
1069
1070
  assert "data" not in msg
1070
1071
  data = msg.raw
1071
1072
  else:
1072
- data = packer(msg.data)
1073
+ data = self.codec.encode(msg.data)
1073
1074
  await self.server.backend.send(*topic, payload=data)
1074
1075
 
1075
1076
  async def cmd_delete_tree(self, msg):
@@ -1173,7 +1174,7 @@ class ServerClient:
1173
1174
  if "tock" not in msg:
1174
1175
  msg["tock"] = self.server.tock
1175
1176
  try:
1176
- await self.stream.send(packer(msg))
1177
+ await self.stream.send(self.codec.encode(msg))
1177
1178
  except ClosedResourceError:
1178
1179
  self.logger.info("ERO%d %r", self._client_nr, msg)
1179
1180
  self._send_lock = None
@@ -1189,7 +1190,7 @@ class ServerClient:
1189
1190
 
1190
1191
  async def run(self):
1191
1192
  """Main loop for this client connection."""
1192
- unpacker_ = stream_unpacker() # pylint: disable=redefined-outer-name
1193
+ self.codec = get_codec("std-msgpack") # pylint: disable=redefined-outer-name
1193
1194
 
1194
1195
  async with anyio.create_task_group() as tg:
1195
1196
  self.tg = tg
@@ -1225,7 +1226,7 @@ class ServerClient:
1225
1226
  await self.send(msg)
1226
1227
 
1227
1228
  while True:
1228
- for msg in unpacker_:
1229
+ for msg in self.codec:
1229
1230
  seq = None
1230
1231
  try:
1231
1232
  seq = msg.seq
@@ -1263,7 +1264,7 @@ class ServerClient:
1263
1264
  if len(buf) == 0: # Connection was closed.
1264
1265
  self.logger.debug("EOF %d", self._client_nr)
1265
1266
  break
1266
- unpacker_.feed(buf)
1267
+ self.codec.feed(buf)
1267
1268
 
1268
1269
  tg.cancel_scope.cancel()
1269
1270
 
@@ -1350,7 +1351,7 @@ class _RecoverControl:
1350
1351
 
1351
1352
  class Server:
1352
1353
  """
1353
- This is the MoaT-KV server. It manages connections to the Serf/MQTT server,
1354
+ This is the MoaT-KV server. It manages connections to the MQTT server,
1354
1355
  the MoaT-KV clients, and (optionally) logs all changes to a file.
1355
1356
 
1356
1357
  Args:
@@ -1379,7 +1380,8 @@ class Server:
1379
1380
  ports = None
1380
1381
  _tock = 0
1381
1382
 
1382
- def __init__(self, name: str, cfg: dict = None, init: Any = NotGiven):
1383
+ def __init__(self, name: str, cfg: dict | None = None, init: Any = NotGiven):
1384
+ self.codec = get_codec("std-msgpack")
1383
1385
  self.root = RootEntry(self, tock=self.tock)
1384
1386
  from moat.util import CFG
1385
1387
 
@@ -1408,16 +1410,16 @@ class Server:
1408
1410
  # connected clients
1409
1411
  self._clients = set()
1410
1412
 
1411
- # cache for partial messages
1412
- self._part_len = SERF_MAXLEN - SERF_LEN_DELTA - len(self.node.name)
1413
- self._part_seq = 0
1414
- self._part_cache = dict()
1415
-
1413
+ # running saver tasks
1416
1414
  self._savers = []
1417
1415
 
1418
1416
  # This is here, not in _run_del, because _del_actor needs to be accessible early
1419
1417
  self._del_actor = DeleteActor(self)
1420
1418
 
1419
+ # backwards compat
1420
+ self._part_cache = dict()
1421
+ self._part_unpacker = get_codec("std-msgpack").decode
1422
+
1421
1423
  @property
1422
1424
  def node_cache(self):
1423
1425
  """
@@ -1514,7 +1516,7 @@ class Server:
1514
1516
 
1515
1517
  async def _set_tock(self):
1516
1518
  if self._actor is not None and self._ready.is_set():
1517
- await self._actor.set_value((self._tock, self.node.tick))
1519
+ await self._actor.set_value([self._tock, self.node.tick])
1518
1520
 
1519
1521
  async def del_check(self, value):
1520
1522
  """
@@ -1568,8 +1570,7 @@ class Server:
1568
1570
  if "tick" not in msg:
1569
1571
  msg["tick"] = self.node.tick
1570
1572
  self.logger.debug("Send %s: %r", action, msg)
1571
- for m in self._pack_multiple(msg):
1572
- await self.backend.send(*self.cfg.server.root, action, payload=m)
1573
+ await self.backend.send(*self.cfg.server.root, action, payload=msg)
1573
1574
 
1574
1575
  async def watcher(self):
1575
1576
  """
@@ -1832,37 +1833,6 @@ class Server:
1832
1833
  self._del_actor.add_deleted(self._delete_also_nodes)
1833
1834
  self._delete_also_nodes = NodeSet()
1834
1835
 
1835
- def _pack_multiple(self, msg):
1836
- """"""
1837
- # protect against mistakenly encoded multi-part messages
1838
- # TODO use a msgpack extension instead
1839
- if isinstance(msg, Mapping):
1840
- i = 0
1841
- while (f"_p{i}") in msg:
1842
- i += 1
1843
- j = i
1844
- while i:
1845
- i -= 1
1846
- msg[f"_p{i + 1}"] = msg[f"_p{i}"]
1847
- if j:
1848
- msg["_p0"] = ""
1849
-
1850
- p = packer(msg)
1851
- pl = self._part_len
1852
- if len(p) > SERF_MAXLEN:
1853
- # Owch. We need to split this thing.
1854
- self._part_seq = seq = self._part_seq + 1
1855
- i = 0
1856
- while i >= 0:
1857
- i += 1
1858
- px, p = p[:pl], p[pl:]
1859
- if not p:
1860
- i = -i
1861
- px = {"_p0": (self.node.name, seq, i, px)}
1862
- yield packer(px)
1863
- return
1864
- yield p
1865
-
1866
1836
  def _unpack_multiple(self, msg):
1867
1837
  """
1868
1838
  Undo the effects of _pack_multiple.
@@ -1885,7 +1855,7 @@ class Server:
1885
1855
  return None
1886
1856
  p = b"".join(s)
1887
1857
  del self._part_cache[(nn, seq)]
1888
- msg = unpacker(p)
1858
+ msg = self._part_unpacker(p)
1889
1859
  msg["_p0"] = ""
1890
1860
 
1891
1861
  i = 0
@@ -1907,15 +1877,21 @@ class Server:
1907
1877
  """
1908
1878
  cmd = getattr(self, "user_" + action)
1909
1879
  try:
1910
- async with self.backend.monitor(*self.cfg.server.root, action) as stream:
1880
+ async with self.backend.monitor(
1881
+ *self.cfg.server.root,
1882
+ action,
1883
+ codec=self.codec,
1884
+ ) as stream:
1911
1885
  if delay is not None:
1912
1886
  await delay.wait()
1913
1887
 
1914
1888
  async for resp in stream:
1915
- msg = unpacker(resp.payload)
1889
+ msg = resp.payload
1890
+
1916
1891
  msg = self._unpack_multiple(msg)
1917
1892
  if not msg: # None, empty, whatever
1918
1893
  continue
1894
+
1919
1895
  self.logger.debug("Recv %s: %r", action, msg)
1920
1896
  try:
1921
1897
  with anyio.fail_after(15):
@@ -2406,8 +2382,8 @@ class Server:
2406
2382
 
2407
2383
  async def load(
2408
2384
  self,
2409
- path: str = None,
2410
- stream: io.IOBase = None,
2385
+ path: str | None = None,
2386
+ stream: io.IOBase | None = None,
2411
2387
  local: bool = False,
2412
2388
  authoritative: bool = False,
2413
2389
  ):
@@ -2425,8 +2401,10 @@ class Server:
2425
2401
  raise RuntimeError("This server already has data.")
2426
2402
  elif not local and self.node.tick is None:
2427
2403
  raise RuntimeError("This server is not yet operational.")
2428
- async with MsgReader(path=path, stream=stream) as rdr:
2404
+ async with MsgReader(path=path, stream=stream, codec="std-msgpack") as rdr:
2429
2405
  async for m in rdr:
2406
+ if m is None:
2407
+ continue
2430
2408
  if "value" in m:
2431
2409
  longer(m)
2432
2410
  if "tock" in m:
@@ -2472,15 +2450,15 @@ class Server:
2472
2450
  await writer(msg) # XXX legacy
2473
2451
  await self.root.walk(saver, full=full)
2474
2452
 
2475
- async def save(self, path: str = None, stream=None, full=True):
2453
+ async def save(self, path: str | None = None, stream=None, full=True):
2476
2454
  """Save the current state to ``path`` or ``stream``."""
2477
2455
  shorter = PathShortener([])
2478
- async with MsgWriter(path=path, stream=stream) as mw:
2456
+ async with MsgWriter(path=path, stream=stream, codec="std-msgpack") as mw:
2479
2457
  await self._save(mw, shorter, full=full)
2480
2458
 
2481
2459
  async def save_stream(
2482
2460
  self,
2483
- path: str = None,
2461
+ path: str | None = None,
2484
2462
  stream: anyio.abc.Stream = None,
2485
2463
  save_state: bool = False,
2486
2464
  done: ValueEvent = None,
@@ -2504,7 +2482,7 @@ class Server:
2504
2482
  """
2505
2483
  shorter = PathShortener([])
2506
2484
 
2507
- async with MsgWriter(path=path, stream=stream) as mw:
2485
+ async with MsgWriter(path=path, stream=stream, codec="std-msgpack") as mw:
2508
2486
  msg = await self.get_state(nodes=True, known=True, deleted=True)
2509
2487
  # await mw({"info": msg})
2510
2488
  await mw(msg) # XXX legacy
@@ -2557,7 +2535,7 @@ class Server:
2557
2535
 
2558
2536
  async def _saver(
2559
2537
  self,
2560
- path: str = None,
2538
+ path: str | None = None,
2561
2539
  stream=None,
2562
2540
  done: ValueEvent = None,
2563
2541
  save_state=False,
@@ -2582,7 +2560,9 @@ class Server:
2582
2560
  with anyio.CancelScope(shield=True):
2583
2561
  sd.set()
2584
2562
 
2585
- async def run_saver(self, path: str = None, stream=None, save_state=False, wait: bool = True):
2563
+ async def run_saver(
2564
+ self, path: str | None = None, stream=None, save_state=False, wait: bool = True
2565
+ ):
2586
2566
  """
2587
2567
  Start a task that continually saves to disk.
2588
2568
 
moat/kv/types.py CHANGED
@@ -110,7 +110,7 @@ class MatchEntry(MetaEntry):
110
110
  elif isinstance(value.type, str):
111
111
  value.type = P(value.type)
112
112
  elif not isinstance(value.type, (Path, list, tuple)):
113
- raise ValueError("Type of %r is not a list" % (value.type,))
113
+ raise ValueError(f"Type of {value.type!r} is not a list")
114
114
  try:
115
115
  self.metaroot["type"].follow(value.type, create=False)
116
116
  except KeyError:
@@ -306,8 +306,9 @@ class CodecEntry(Entry):
306
306
  f"failed decoder at {self.path} on {v!r} with {exc!r}",
307
307
  ) from exc
308
308
  else:
309
- if r != w:
310
- raise ValueError(f"Decoding at {self.path}: {v!r} got {r!r}, not {w!r}")
309
+ pass # float, list/tuple, and similar nonsense
310
+ # if r != w:
311
+ # raise ValueError(f"Decoding at {self.path}: {v!r} got {r!r}, not {w!r}")
311
312
 
312
313
  if value is not None and value.encode is not None:
313
314
  if not value["out"]:
@@ -321,8 +322,9 @@ class CodecEntry(Entry):
321
322
  f"failed encoder at {self.path} on {v!r} with {exc!r}",
322
323
  ) from exc
323
324
  else:
324
- if r != w:
325
- raise ValueError(f"Encoding at {self.path}: {v!r} got {r!r}, not {w!r}")
325
+ pass # float, list/tuple, and similar nonsense
326
+ # if r != w:
327
+ # raise ValueError(f"Encoding at {self.path}: {v!r} got {r!r}, not {w!r}")
326
328
 
327
329
  await super().set(value)
328
330
  self._enc = enc
@@ -364,7 +366,7 @@ class ConvEntry(MetaEntry):
364
366
  if isinstance(value.codec, str):
365
367
  value.codec = P(value.codec)
366
368
  elif not isinstance(value.codec, (Path, list, tuple)):
367
- raise ValueError("Codec %r is not a list" % (value.codec,))
369
+ raise ValueError(f"Codec {value.codec!r} is not a list")
368
370
  try:
369
371
  self.metaroot["codec"].follow(value.codec, create=False)
370
372
  except KeyError:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: moat-kv
3
- Version: 0.70.24
3
+ Version: 0.71.6
4
4
  Summary: A distributed no-master key-value store
5
5
  Author-email: Matthias Urlichs <matthias@urlichs.de>
6
6
  Project-URL: homepage, https://m-o-a-t.org
@@ -19,26 +19,27 @@ Classifier: Topic :: Home Automation
19
19
  Classifier: Topic :: System :: Distributed Computing
20
20
  Requires-Python: >=3.8
21
21
  Description-Content-Type: text/x-rst
22
+ License-File: LICENSE
23
+ License-File: LICENSE.APACHE2
24
+ License-File: LICENSE.MIT
22
25
  License-File: LICENSE.txt
23
26
  Requires-Dist: asyncclick>7.99
24
27
  Requires-Dist: trio>=0.22
25
28
  Requires-Dist: anyio>=4
26
29
  Requires-Dist: range_set>=0.2
27
30
  Requires-Dist: attrs>=22
28
- Requires-Dist: asyncserf>=0.16
29
- Requires-Dist: asyncactor>=0.24
31
+ Requires-Dist: asyncactor~=0.26.3
30
32
  Requires-Dist: asyncscope>=0.10.4
31
33
  Requires-Dist: jsonschema>=2.5
32
34
  Requires-Dist: ruyaml>=0.89
33
35
  Requires-Dist: PyNaCl>=1.3
34
36
  Requires-Dist: moat-lib-diffiehellman~=0.13.4
37
+ Requires-Dist: moat-link
35
38
  Requires-Dist: psutil
36
39
  Requires-Dist: simpleeval>=0.9.10
37
- Requires-Dist: moat-mqtt~=0.42.3
40
+ Requires-Dist: moat-mqtt~=0.42.4
38
41
  Requires-Dist: moat-util~=0.56.4
39
42
  Requires-Dist: exceptiongroup; python_version < "3.11"
40
- Provides-Extra: dev
41
- Requires-Dist: moat-src>=0.5.0; extra == "dev"
42
43
  Dynamic: license-file
43
44
 
44
45
  =======
@@ -0,0 +1,47 @@
1
+ moat/kv/__init__.py,sha256=an570v034lNp7TNb23T501SM7WNmfeXl2ztGuodG_hg,444
2
+ moat/kv/_cfg.yaml,sha256=j_rLFRiK1xbj_nzlI1AbzLDhq2pXWrM4M_lr6BH-rSc,2174
3
+ moat/kv/_main.py,sha256=SgI4ef8AQDOC4N4pLRiyaiSiFIzN-J337WIcuC7DPOY,2234
4
+ moat/kv/client.py,sha256=qxhhGtfxbcR0MN2dcQzcTga9UBAr9-zRRrMscqeTpVE,34207
5
+ moat/kv/code.py,sha256=QtXHx09Hh9mxSA8m0z1KAFUEDqR0gpTII5g8MlJ8mbI,6427
6
+ moat/kv/config.py,sha256=cWJ1m8PRwzzapsin1XSDjRtYJCgNN6g7gA9q48luDV8,1093
7
+ moat/kv/data.py,sha256=85N2W_pDVRgBn7l9AGdDZHvuHrIPGlbxzgXi_3S0DSI,5924
8
+ moat/kv/errors.py,sha256=cjX0e184EC10HvQ7UOkWdWmc1A8jPr983IFR8UkaI8U,17089
9
+ moat/kv/exceptions.py,sha256=4gRsYa6kuBy1qFYiBUYElup_cvOe7QgPQtryHAYphzo,1849
10
+ moat/kv/model.py,sha256=K2GVQlaFXbwf2JCl-bZi55MTCHFSK7jgomjI7APRIJw,33282
11
+ moat/kv/runner.py,sha256=6vpChgCjiWHAjc2u0bbOOQ8cT5KB-f4uBnQb8MgH9wk,41826
12
+ moat/kv/server.py,sha256=fwQZNzyJAhqR01d__3hUmEoAD3kgmRfSG0KdESezqpQ,94108
13
+ moat/kv/types.py,sha256=UgUOrF-NUZq2lgKWgUwMdrfPkLIFxdxe-jyYdxeRyVk,14186
14
+ moat/kv/actor/__init__.py,sha256=i560uy9WLE4SmOXWcXKjFKNKZdRR51jOwWg7cSASVRo,2131
15
+ moat/kv/actor/deletor.py,sha256=F-pb0jHddhmqLp65G7vbZ_QIxXu5ZF0UBzHWKRgN45U,4537
16
+ moat/kv/auth/__init__.py,sha256=m1x3tyAIxhaF5jSVC-wEJkntBAG2uERvgN0wRTLnp2w,12572
17
+ moat/kv/auth/_test.py,sha256=k-cHh4m22XuVdXjBN3XAqL5zkH8nKDriEbxH25Tuj10,4404
18
+ moat/kv/auth/password.py,sha256=wwKrynFPKcJept3IPCWCDLSyTF298NdSBABgCn0htqY,6474
19
+ moat/kv/auth/root.py,sha256=lW-_hgQp3ZIzAAe4iBl0rdMU-pFuHIC6ObA3rdFXxkk,1303
20
+ moat/kv/backend/__init__.py,sha256=f5nIOWD2zml2YiaBNXtEOzC7SEGgwDFJMB8YzRISJ0A,1689
21
+ moat/kv/backend/mqtt.py,sha256=KSerkVN4MHhzjFprZMraTk56Hj0Hx1vev9xfYoKST2A,2009
22
+ moat/kv/command/__init__.py,sha256=9_8wL9Scv8_Cs8HJyJHGvx1vwXErsuvlsAqNZLcJQR0,8
23
+ moat/kv/command/acl.py,sha256=wSdlzKBjWIFX3IxCdLcG3ZqjvtX8KPtqe38ibyU2VDo,4665
24
+ moat/kv/command/auth.py,sha256=vP22ZIBMjRdUxN4_ELKiDgIE5ZqdMxs1ky1WDVUx50M,7149
25
+ moat/kv/command/code.py,sha256=ycGBoKBB0Gse3Tt9h9WvgiIZVe3Yg-x92RRjfCCCXVQ,8066
26
+ moat/kv/command/codec.py,sha256=8GqOl-oxWFM2D1Fm9Iheot2AmS_SsOYfqfbbde3qvQY,6028
27
+ moat/kv/command/data.py,sha256=HcLMMcAnyMBH4iH8X0hZiBgmsnV1yppOlg26zB9R72A,8400
28
+ moat/kv/command/error.py,sha256=XqHmVJgBGjr6XOozi23wEjiTqPopD1CzkIeR1a3Z_vY,4350
29
+ moat/kv/command/internal.py,sha256=KnteLPA9SIYk1e2AaN9yWt9r52QvlJCpDEwYcH6hJVI,7404
30
+ moat/kv/command/job.py,sha256=QIiaK0c08CyB-SMjgoWLqa6N3cXQvds0i_w01Q6Jm5I,13590
31
+ moat/kv/command/log.py,sha256=ycdtMgiBw2DaCu0yu6tpWKPLlQ33ZZemXsfwZrCzRGg,1350
32
+ moat/kv/command/server.py,sha256=Hl_6FFq322c5KjJCKxH1nCrxo-h0VEwH4IOMbVeUBN4,3392
33
+ moat/kv/command/type.py,sha256=bQrbJzVo-KWFC69ywA4EsuE_Gp0IT_YpYrgtU6yaW1g,6214
34
+ moat/kv/command/dump/__init__.py,sha256=VLSN8MNHwAe44dmOwieUhzdaPuWCJf5p3XVkMh6PibI,3918
35
+ moat/kv/mock/__init__.py,sha256=XWqHDWWWbXL61GgIFxxz0ciu2nW2Ba1uKm1J5ZTUuBw,2595
36
+ moat/kv/mock/mqtt.py,sha256=b9pzbwQBmTX2eSiSgNO7nh2-7l8-0TT8n2GxinQ-1l0,5213
37
+ moat/kv/mock/tracer.py,sha256=qLEIn9gdlYUypyRYD8O8SbM8ye8XR4xtBl3otP6uNLs,2058
38
+ moat/kv/obj/__init__.py,sha256=OaQ7PsvvhRhd_H7KUM15nGeApckkCyOv7Xf4llqkzi0,19792
39
+ moat/kv/obj/command.py,sha256=PzMQOEsXRKjKU3WvFtq1QaPJYBk6oWScKIrA-hAD5vg,7426
40
+ moat_kv-0.71.6.dist-info/licenses/LICENSE,sha256=ZSyHhIjRRWNh4Iw_hgf9e6WYkqFBA9Fczk_5PIW1zIs,185
41
+ moat_kv-0.71.6.dist-info/licenses/LICENSE.APACHE2,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
42
+ moat_kv-0.71.6.dist-info/licenses/LICENSE.MIT,sha256=Pm2uVV65J4f8gtHUg1Vnf0VMf2Wus40_nnK_mj2vA0s,1046
43
+ moat_kv-0.71.6.dist-info/licenses/LICENSE.txt,sha256=L5vKJLVOg5t0CEEPpW9-O_0vzbP0PEjEF06tLvnIDuk,541
44
+ moat_kv-0.71.6.dist-info/METADATA,sha256=OzfZkMKKGMmZtkvfnziwUBR6bbN9VJo7cdmVhXcDMxQ,3376
45
+ moat_kv-0.71.6.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
46
+ moat_kv-0.71.6.dist-info/top_level.txt,sha256=pcs9fl5w5AB5GVi4SvBqIVmFrkRwQkVw_dEvW0Q0cSA,5
47
+ moat_kv-0.71.6.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (77.0.3.post20250321)
2
+ Generator: setuptools (78.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -0,0 +1,3 @@
1
+ This software is made available under the terms of *either* of the
2
+ licenses found in LICENSE.APACHE2 or LICENSE.MIT. Contributions to are
3
+ made under the terms of *both* these licenses.