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.
- moat/kv/__init__.py +1 -0
- moat/kv/_cfg.yaml +97 -0
- moat/kv/_main.py +6 -9
- moat/kv/client.py +36 -52
- moat/kv/code.py +10 -3
- moat/kv/codec.py +1 -0
- moat/kv/config.py +2 -0
- moat/kv/data.py +8 -7
- moat/kv/errors.py +17 -9
- moat/kv/exceptions.py +1 -7
- moat/kv/model.py +16 -24
- moat/kv/runner.py +39 -36
- moat/kv/server.py +86 -90
- moat/kv/types.py +5 -8
- {moat_kv-0.70.20.dist-info → moat_kv-0.70.23.dist-info}/METADATA +22 -25
- moat_kv-0.70.23.dist-info/RECORD +19 -0
- {moat_kv-0.70.20.dist-info → moat_kv-0.70.23.dist-info}/WHEEL +1 -1
- moat_kv-0.70.23.dist-info/licenses/LICENSE.txt +14 -0
- moat/kv/_config.yaml +0 -98
- moat/kv/actor/__init__.py +0 -97
- moat/kv/actor/deletor.py +0 -137
- moat/kv/auth/__init__.py +0 -446
- moat/kv/auth/_test.py +0 -172
- moat/kv/auth/password.py +0 -232
- moat/kv/auth/root.py +0 -56
- moat/kv/backend/__init__.py +0 -66
- moat/kv/backend/mqtt.py +0 -74
- moat/kv/backend/serf.py +0 -44
- moat/kv/command/__init__.py +0 -1
- moat/kv/command/acl.py +0 -174
- moat/kv/command/auth.py +0 -258
- moat/kv/command/code.py +0 -306
- moat/kv/command/codec.py +0 -190
- moat/kv/command/data.py +0 -274
- moat/kv/command/dump/__init__.py +0 -141
- moat/kv/command/error.py +0 -156
- moat/kv/command/internal.py +0 -257
- moat/kv/command/job.py +0 -438
- moat/kv/command/log.py +0 -52
- moat/kv/command/server.py +0 -115
- moat/kv/command/type.py +0 -203
- moat/kv/mock/__init__.py +0 -97
- moat/kv/mock/mqtt.py +0 -164
- moat/kv/mock/serf.py +0 -253
- moat/kv/mock/tracer.py +0 -65
- moat/kv/obj/__init__.py +0 -636
- moat/kv/obj/command.py +0 -246
- moat_kv-0.70.20.dist-info/LICENSE +0 -3
- moat_kv-0.70.20.dist-info/LICENSE.APACHE2 +0 -202
- moat_kv-0.70.20.dist-info/LICENSE.MIT +0 -20
- moat_kv-0.70.20.dist-info/RECORD +0 -49
- {moat_kv-0.70.20.dist-info → moat_kv-0.70.23.dist-info}/top_level.txt +0 -0
moat/kv/__init__.py
CHANGED
moat/kv/_cfg.yaml
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
conn:
|
2
|
+
# client: controls how to talk to the MoaT-KV server
|
3
|
+
host: localhost
|
4
|
+
port: 27586
|
5
|
+
ssl: false
|
6
|
+
# ssl:
|
7
|
+
# cert: '/path/to/cert.pem',key='/path/to/cert.key'
|
8
|
+
init_timeout: 5
|
9
|
+
# time to wait for connection plus greeting
|
10
|
+
auth: null
|
11
|
+
# no auth used by default
|
12
|
+
name: null
|
13
|
+
# defaults to a seqnum
|
14
|
+
config:
|
15
|
+
prefix: !P :.moat.kv.config
|
16
|
+
errors:
|
17
|
+
prefix: !P :.moat.kv.error
|
18
|
+
codes:
|
19
|
+
prefix: !P :.moat.kv.code.proc
|
20
|
+
modules:
|
21
|
+
prefix: !P :.moat.kv.code.module
|
22
|
+
runner: # for moat.kv.runner.RunnerRoot
|
23
|
+
# storage for runnable commands
|
24
|
+
prefix: !P :.moat.kv.run"
|
25
|
+
# storage for runner states
|
26
|
+
state: !P :.moat.kv.state"
|
27
|
+
|
28
|
+
name: "run"
|
29
|
+
# Serf event name, suffixed by subpath
|
30
|
+
|
31
|
+
start_delay: 1
|
32
|
+
# time to wait between job starts. Not optional.
|
33
|
+
|
34
|
+
ping: -15
|
35
|
+
# set an I-am-running message every those-many seconds
|
36
|
+
# positive: set in moat.kv, negative: broadcast to :moat.kv.run tag
|
37
|
+
|
38
|
+
actor:
|
39
|
+
# Actor config, required for Runner
|
40
|
+
cycle: 20
|
41
|
+
nodes: -1
|
42
|
+
splits: 5
|
43
|
+
n_hosts: 3
|
44
|
+
version: 1
|
45
|
+
sub:
|
46
|
+
# tags for various runner modes
|
47
|
+
group: "any"
|
48
|
+
single: "at"
|
49
|
+
all: "all"
|
50
|
+
server:
|
51
|
+
# server-side configuration
|
52
|
+
buffer: 10
|
53
|
+
# per-stream buffer
|
54
|
+
|
55
|
+
backend: "mqtt"
|
56
|
+
# default
|
57
|
+
mqtt:
|
58
|
+
uri: "mqtt://localhost:1883"
|
59
|
+
serf:
|
60
|
+
host: "localhost"
|
61
|
+
port: 7373
|
62
|
+
|
63
|
+
# event message path/topic prefix
|
64
|
+
root: !P moat.kv
|
65
|
+
|
66
|
+
paranoia: False
|
67
|
+
# typecheck server-to-server updates?
|
68
|
+
#
|
69
|
+
# which addresses/ports to accept MoaT-KV connections on
|
70
|
+
bind: [{}]
|
71
|
+
bind_default:
|
72
|
+
# default values for all elements of "bind"
|
73
|
+
host: "localhost"
|
74
|
+
port: PORT
|
75
|
+
ssl: False
|
76
|
+
change:
|
77
|
+
length: 5
|
78
|
+
# chain length: use max nr of network sections +1
|
79
|
+
ping:
|
80
|
+
cycle: 10
|
81
|
+
gap: 2
|
82
|
+
# asyncserf.Actor config timing for server sync
|
83
|
+
# ping also controls minimum server startup time
|
84
|
+
delete:
|
85
|
+
# asyncserf.Actor config timing for deletion
|
86
|
+
cycle: 100
|
87
|
+
gap: 10
|
88
|
+
version: 1
|
89
|
+
paranoia: false
|
90
|
+
# typecheck server>server updates?
|
91
|
+
|
92
|
+
# how does a new server reach existing nodes, to download state?
|
93
|
+
domain: null
|
94
|
+
# domain in which to look up node names, if not in hostmap
|
95
|
+
hostmap: # map MoaT-KV server names to connect destinations
|
96
|
+
test1: ["localhost", 27586]
|
97
|
+
test2: ["does-not-exist.invalid", 27586]
|
moat/kv/_main.py
CHANGED
@@ -4,11 +4,12 @@ Basic DistKV support
|
|
4
4
|
|
5
5
|
"""
|
6
6
|
|
7
|
+
from __future__ import annotations
|
8
|
+
|
7
9
|
import logging
|
8
|
-
from pathlib import Path
|
9
10
|
|
10
11
|
import asyncclick as click
|
11
|
-
from moat.util import attrdict, combine_dict, load_subgroup,
|
12
|
+
from moat.util import attrdict, combine_dict, load_subgroup, CFG, ensure_cfg
|
12
13
|
|
13
14
|
from moat.kv.auth import gen_auth
|
14
15
|
from moat.kv.client import client_scope
|
@@ -16,7 +17,7 @@ from moat.kv.client import client_scope
|
|
16
17
|
logger = logging.getLogger(__name__)
|
17
18
|
|
18
19
|
|
19
|
-
|
20
|
+
ensure_cfg("moat.kv")
|
20
21
|
|
21
22
|
|
22
23
|
class NullObj:
|
@@ -40,12 +41,8 @@ class NullObj:
|
|
40
41
|
raise self._exc
|
41
42
|
|
42
43
|
|
43
|
-
@load_subgroup(
|
44
|
-
|
45
|
-
)
|
46
|
-
@click.option(
|
47
|
-
"-h", "--host", default=None, help=f"Host to use. Default: {CFG.kv.conn.host}"
|
48
|
-
)
|
44
|
+
@load_subgroup(sub_pre="moat.kv.command", sub_post="cli", ext_pre="moat.kv", ext_post="_main.cli")
|
45
|
+
@click.option("-h", "--host", default=None, help=f"Host to use. Default: {CFG.kv.conn.host}")
|
49
46
|
@click.option(
|
50
47
|
"-p",
|
51
48
|
"--port",
|
moat/kv/client.py
CHANGED
@@ -4,17 +4,18 @@ Client code.
|
|
4
4
|
Main entry point: :func:`open_client`.
|
5
5
|
"""
|
6
6
|
|
7
|
+
from __future__ import annotations
|
8
|
+
|
7
9
|
import logging
|
8
10
|
import os
|
9
11
|
import socket
|
10
12
|
from contextlib import AsyncExitStack, asynccontextmanager
|
11
13
|
from inspect import iscoroutine
|
12
|
-
from pathlib import Path
|
13
|
-
from typing import Tuple
|
14
14
|
|
15
15
|
import anyio
|
16
16
|
from asyncscope import Scope, main_scope, scope
|
17
17
|
from moat.util import ( # pylint: disable=no-name-in-module
|
18
|
+
CFG,
|
18
19
|
DelayedRead,
|
19
20
|
DelayedWrite,
|
20
21
|
NotGiven,
|
@@ -25,9 +26,10 @@ from moat.util import ( # pylint: disable=no-name-in-module
|
|
25
26
|
byte2num,
|
26
27
|
combine_dict,
|
27
28
|
create_queue,
|
29
|
+
ensure_cfg,
|
28
30
|
gen_ssl,
|
31
|
+
gen_ident,al_lower,
|
29
32
|
num2byte,
|
30
|
-
yload,
|
31
33
|
)
|
32
34
|
|
33
35
|
from .codec import packer, stream_unpacker
|
@@ -45,17 +47,6 @@ logger = logging.getLogger(__name__)
|
|
45
47
|
|
46
48
|
ClosedResourceError = anyio.ClosedResourceError
|
47
49
|
|
48
|
-
rs = os.environ.get("PYTHONHASHSEED", None)
|
49
|
-
if rs is None:
|
50
|
-
import random
|
51
|
-
else: # pragma: no cover
|
52
|
-
try:
|
53
|
-
import trio._core._run as tcr
|
54
|
-
except ImportError:
|
55
|
-
import random
|
56
|
-
else:
|
57
|
-
random = tcr._r
|
58
|
-
|
59
50
|
__all__ = ["NoData", "ManyData", "open_client", "client_scope", "StreamedRequest"]
|
60
51
|
|
61
52
|
|
@@ -117,9 +108,7 @@ async def client_scope(_name=None, **cfg):
|
|
117
108
|
_name = f"_{_cid}"
|
118
109
|
# uniqueness required for testing.
|
119
110
|
# TODO replace with a dependency on the test server.
|
120
|
-
return await scope.service(
|
121
|
-
f"moat.kv.client.{_name}", _scoped_client, _name=_name, **cfg
|
122
|
-
)
|
111
|
+
return await scope.service(f"moat.kv.client.{_name}", _scoped_client, _name=_name, **cfg)
|
123
112
|
|
124
113
|
|
125
114
|
class StreamedRequest:
|
@@ -155,9 +144,7 @@ class StreamedRequest:
|
|
155
144
|
self._path_long = lambda x: x
|
156
145
|
if client.qlen > 0:
|
157
146
|
self.dw = DelayedWrite(client.qlen)
|
158
|
-
self.qr = DelayedRead(
|
159
|
-
client.qlen, get_seq=self._get_seq, send_ack=self._send_ack
|
160
|
-
)
|
147
|
+
self.qr = DelayedRead(client.qlen, get_seq=self._get_seq, send_ack=self._send_ack)
|
161
148
|
else:
|
162
149
|
self.qr = create_queue(client.config.server.buffer)
|
163
150
|
|
@@ -227,7 +214,7 @@ class StreamedRequest:
|
|
227
214
|
|
228
215
|
async def get(self):
|
229
216
|
"""Receive a single reply"""
|
230
|
-
|
217
|
+
# receive reply
|
231
218
|
if self._reply_stream:
|
232
219
|
raise RuntimeError("Unexpected multi stream msg")
|
233
220
|
msg = await self.recv()
|
@@ -246,7 +233,7 @@ class StreamedRequest:
|
|
246
233
|
async def __anext__(self):
|
247
234
|
try:
|
248
235
|
res = await self.qr.get()
|
249
|
-
except (anyio.EndOfStream, anyio.ClosedResourceError):
|
236
|
+
except (anyio.EndOfStream, anyio.ClosedResourceError, EOFError):
|
250
237
|
raise StopAsyncIteration
|
251
238
|
except CancelledError:
|
252
239
|
raise StopAsyncIteration # just terminate
|
@@ -274,7 +261,7 @@ class StreamedRequest:
|
|
274
261
|
async def cancel(self):
|
275
262
|
try:
|
276
263
|
await self.qr.put_error(CancelledError())
|
277
|
-
except (anyio.BrokenResourceError, anyio.ClosedResourceError):
|
264
|
+
except (anyio.BrokenResourceError, anyio.ClosedResourceError, EOFError):
|
278
265
|
pass
|
279
266
|
else:
|
280
267
|
try:
|
@@ -411,7 +398,7 @@ class Client:
|
|
411
398
|
qlen: int = 0
|
412
399
|
|
413
400
|
def __init__(self, cfg: dict):
|
414
|
-
|
401
|
+
ensure_cfg("moat.kv")
|
415
402
|
self._cfg = combine_dict(cfg, CFG["kv"], cls=attrdict)
|
416
403
|
self.config = ClientConfig(self)
|
417
404
|
|
@@ -419,7 +406,7 @@ class Client:
|
|
419
406
|
self._handlers = {}
|
420
407
|
self._send_lock = anyio.Lock()
|
421
408
|
self._helpers = {}
|
422
|
-
self._name =
|
409
|
+
self._name = gen_ident(9, alphabet=al_lower)
|
423
410
|
self.logger = logging.getLogger(f"moat.kv.client.{self._name}")
|
424
411
|
|
425
412
|
@property
|
@@ -482,11 +469,11 @@ class Client:
|
|
482
469
|
|
483
470
|
k = await anyio.to_thread.run_sync(gen_key)
|
484
471
|
res = await self._request(
|
485
|
-
"diffie_hellman",
|
472
|
+
"diffie_hellman",
|
473
|
+
pubkey=num2byte(k.public_key),
|
474
|
+
length=length,
|
486
475
|
) # length=k.key_length
|
487
|
-
await anyio.to_thread.run_sync(
|
488
|
-
k.generate_shared_secret, byte2num(res.pubkey)
|
489
|
-
)
|
476
|
+
await anyio.to_thread.run_sync(k.generate_shared_secret, byte2num(res.pubkey))
|
490
477
|
self._dh_key = num2byte(k.shared_secret)[0:32]
|
491
478
|
return self._dh_key
|
492
479
|
|
@@ -681,16 +668,12 @@ class Client:
|
|
681
668
|
if not sa or not sa[0]:
|
682
669
|
# no auth required
|
683
670
|
if auth:
|
684
|
-
logger.info(
|
685
|
-
"Tried to use auth=%s, but not required.", auth._auth_method
|
686
|
-
)
|
671
|
+
logger.info("Tried to use auth=%s, but not required.", auth._auth_method)
|
687
672
|
return
|
688
673
|
if not auth:
|
689
674
|
raise ClientAuthRequiredError("You need to log in using:", sa[0])
|
690
675
|
if auth._auth_method != sa[0]:
|
691
|
-
raise ClientAuthMethodError(
|
692
|
-
f"You cannot use {auth._auth_method!r} auth", sa
|
693
|
-
)
|
676
|
+
raise ClientAuthMethodError(f"You cannot use {auth._auth_method!r} auth", sa)
|
694
677
|
if getattr(auth, "_DEBUG", False):
|
695
678
|
auth._length = 16
|
696
679
|
await auth.auth(self)
|
@@ -736,9 +719,7 @@ class Client:
|
|
736
719
|
self._server_init = msg = await hello.get()
|
737
720
|
self.logger.debug("Hello %s", msg)
|
738
721
|
self.server_name = msg.node
|
739
|
-
self.client_name = (
|
740
|
-
cfg["name"] or socket.gethostname() or self.server_name
|
741
|
-
)
|
722
|
+
self.client_name = cfg["name"] or socket.gethostname() or self.server_name
|
742
723
|
if "qlen" in msg:
|
743
724
|
self.qlen = min(msg.qlen, 99) # self.config.server.buffer
|
744
725
|
await self._send(seq=0, qlen=self.qlen)
|
@@ -746,13 +727,11 @@ class Client:
|
|
746
727
|
|
747
728
|
from .config import ConfigRoot
|
748
729
|
|
749
|
-
self._config = await ConfigRoot.as_handler(
|
750
|
-
self, require_client=False
|
751
|
-
)
|
730
|
+
self._config = await ConfigRoot.as_handler(self, require_client=False)
|
752
731
|
|
753
732
|
except TimeoutError:
|
754
733
|
raise
|
755
|
-
except
|
734
|
+
except OSError as e:
|
756
735
|
raise ServerConnectionError(host, port) from e
|
757
736
|
else:
|
758
737
|
yield self
|
@@ -830,7 +809,12 @@ class Client:
|
|
830
809
|
kw["idem"] = idem
|
831
810
|
|
832
811
|
return self._request(
|
833
|
-
action="set_value",
|
812
|
+
action="set_value",
|
813
|
+
path=path,
|
814
|
+
value=value,
|
815
|
+
iter=False,
|
816
|
+
nchain=nchain,
|
817
|
+
**kw,
|
834
818
|
)
|
835
819
|
|
836
820
|
def delete(self, path, *, chain=NotGiven, prev=NotGiven, nchain=0, recursive=False):
|
@@ -878,9 +862,7 @@ class Client:
|
|
878
862
|
raise RuntimeError("You need a path, not a string")
|
879
863
|
if empty is None:
|
880
864
|
empty = not with_data
|
881
|
-
res = await self._request(
|
882
|
-
action="enum", path=path, with_data=with_data, empty=empty, **kw
|
883
|
-
)
|
865
|
+
res = await self._request(action="enum", path=path, with_data=with_data, empty=empty, **kw)
|
884
866
|
try:
|
885
867
|
return res.result
|
886
868
|
except AttributeError:
|
@@ -910,7 +892,11 @@ class Client:
|
|
910
892
|
if long_path:
|
911
893
|
lp = PathLongener()
|
912
894
|
async for r in await self._request(
|
913
|
-
action="get_tree",
|
895
|
+
action="get_tree",
|
896
|
+
path=path,
|
897
|
+
iter=True,
|
898
|
+
long_path=True,
|
899
|
+
**kw,
|
914
900
|
):
|
915
901
|
if long_path:
|
916
902
|
lp(r)
|
@@ -963,9 +949,7 @@ class Client:
|
|
963
949
|
"""
|
964
950
|
if isinstance(path, str):
|
965
951
|
raise RuntimeError("You need a path, not a string")
|
966
|
-
return self._stream(
|
967
|
-
action="watch", path=path, iter=True, long_path=long_path, **kw
|
968
|
-
)
|
952
|
+
return self._stream(action="watch", path=path, iter=True, long_path=long_path, **kw)
|
969
953
|
|
970
954
|
def mirror(self, path, *, root_type=None, **kw):
|
971
955
|
"""An async context manager that affords an update-able mirror
|
@@ -997,7 +981,7 @@ class Client:
|
|
997
981
|
root = root_type(self, path, **kw)
|
998
982
|
return root.run()
|
999
983
|
|
1000
|
-
def msg_monitor(self, topic:
|
984
|
+
def msg_monitor(self, topic: tuple[str], raw: bool = False):
|
1001
985
|
"""
|
1002
986
|
Return an async iterator of tunneled messages. This receives
|
1003
987
|
all messages sent using :meth:`msg_send` with the same topic.
|
@@ -1023,7 +1007,7 @@ class Client:
|
|
1023
1007
|
"""
|
1024
1008
|
return self._stream(action="msg_monitor", topic=topic, raw=raw)
|
1025
1009
|
|
1026
|
-
def msg_send(self, topic:
|
1010
|
+
def msg_send(self, topic: tuple[str], data=None, raw: bytes = None):
|
1027
1011
|
"""
|
1028
1012
|
Tunnel a user-tagged message. This sends the message
|
1029
1013
|
to all active callers of :meth:`msg_monitor` which use the same topic.
|
moat/kv/code.py
CHANGED
@@ -5,6 +5,7 @@ so that it can be called easily.
|
|
5
5
|
"Code" consists of either Python modules or single procedures.
|
6
6
|
|
7
7
|
"""
|
8
|
+
from __future__ import annotations
|
8
9
|
|
9
10
|
import logging
|
10
11
|
import sys
|
@@ -36,7 +37,7 @@ class ModuleRoot(ClientRoot):
|
|
36
37
|
|
37
38
|
CFG = "modules"
|
38
39
|
|
39
|
-
err:
|
40
|
+
err: ErrorRoot = None # noqa: F821
|
40
41
|
|
41
42
|
@classmethod
|
42
43
|
def child_type(cls, name):
|
@@ -95,7 +96,10 @@ class ModuleEntry(ClientEntry):
|
|
95
96
|
self._module = None
|
96
97
|
logger.warning("Could not compile @%r", self.subpath)
|
97
98
|
await self.root.err.record_error(
|
98
|
-
"compile",
|
99
|
+
"compile",
|
100
|
+
self.subpath,
|
101
|
+
exc=exc,
|
102
|
+
message="compiler error",
|
99
103
|
)
|
100
104
|
else:
|
101
105
|
await self.root.err.record_working("compile", self.subpath)
|
@@ -207,7 +211,10 @@ class CodeEntry(ClientEntry):
|
|
207
211
|
except Exception as exc:
|
208
212
|
logger.warning("Could not compile @%s", self.subpath)
|
209
213
|
await self.root.err.record_error(
|
210
|
-
"compile",
|
214
|
+
"compile",
|
215
|
+
self.subpath,
|
216
|
+
exc=exc,
|
217
|
+
message="compiler error",
|
211
218
|
)
|
212
219
|
self._code = None
|
213
220
|
else:
|
moat/kv/codec.py
CHANGED
moat/kv/config.py
CHANGED
moat/kv/data.py
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
"""
|
2
2
|
Data access
|
3
3
|
"""
|
4
|
+
from __future__ import annotations
|
5
|
+
|
4
6
|
import datetime
|
5
7
|
import os
|
6
8
|
import sys
|
@@ -35,7 +37,8 @@ def add_dates(d):
|
|
35
37
|
continue
|
36
38
|
if start <= v <= stop:
|
37
39
|
d[f"_{k}"] = datetime.datetime.fromtimestamp(v).isoformat(
|
38
|
-
sep=" ",
|
40
|
+
sep=" ",
|
41
|
+
timespec="milliseconds",
|
39
42
|
)
|
40
43
|
|
41
44
|
_add(d)
|
@@ -81,9 +84,7 @@ async def data_get(
|
|
81
84
|
kw.setdefault("nchain", obj.meta)
|
82
85
|
y = {}
|
83
86
|
if internal:
|
84
|
-
res = await obj.client._request(
|
85
|
-
action="get_tree_internal", path=path, iter=True, **kw
|
86
|
-
)
|
87
|
+
res = await obj.client._request(action="get_tree_internal", path=path, iter=True, **kw)
|
87
88
|
else:
|
88
89
|
res = obj.client.get_tree(path, **kw)
|
89
90
|
async for r in res:
|
@@ -182,16 +183,16 @@ def res_update(res, attr: Path, value=None, **kw): # pylint: disable=redefined-
|
|
182
183
|
return val._update(attr, value=value, **kw)
|
183
184
|
|
184
185
|
|
185
|
-
async def node_attr(obj, path,
|
186
|
+
async def node_attr(obj, path, res=None, chain=None, **kw):
|
186
187
|
"""
|
187
188
|
Sub-attr setter.
|
188
189
|
|
189
190
|
Args:
|
190
191
|
obj: command object
|
191
192
|
path: address of the node to change
|
192
|
-
vars_, eval_, path_: the results of `attr_args`
|
193
193
|
res: old node, if it has been read already
|
194
194
|
chain: change chain of node, copied from res if clear
|
195
|
+
**kw: the results of `attr_args`
|
195
196
|
|
196
197
|
Returns the result of setting the attribute.
|
197
198
|
"""
|
@@ -207,7 +208,7 @@ async def node_attr(obj, path, vars_, eval_, path_, res=None, chain=None):
|
|
207
208
|
except AttributeError:
|
208
209
|
chain = None
|
209
210
|
val = NotGiven
|
210
|
-
val = process_args(val,
|
211
|
+
val = process_args(val, **kw)
|
211
212
|
if val is NotGiven:
|
212
213
|
res = await obj.client.delete(path, nchain=obj.meta, chain=chain)
|
213
214
|
else:
|
moat/kv/errors.py
CHANGED
@@ -79,6 +79,8 @@ of this record.
|
|
79
79
|
|
80
80
|
"""
|
81
81
|
|
82
|
+
from __future__ import annotations
|
83
|
+
|
82
84
|
import logging
|
83
85
|
import traceback
|
84
86
|
from collections import defaultdict
|
@@ -212,7 +214,7 @@ class ErrorEntry(AttrClientEntry):
|
|
212
214
|
try:
|
213
215
|
m = message.format(exc=exc, **data)
|
214
216
|
except Exception as exc: # pylint: disable=unused-argument # OH COME ON
|
215
|
-
m = message + f" (FORMAT {exc
|
217
|
+
m = message + f" (FORMAT {exc!r})"
|
216
218
|
else:
|
217
219
|
m = message
|
218
220
|
if m:
|
@@ -304,9 +306,7 @@ class ErrorEntry(AttrClientEntry):
|
|
304
306
|
if value is NotGiven:
|
305
307
|
if self.value is NotGiven:
|
306
308
|
return
|
307
|
-
keep = await self.root.get_error_record(
|
308
|
-
self.subsystem, self.path, create=False
|
309
|
-
)
|
309
|
+
keep = await self.root.get_error_record(self.subsystem, self.path, create=False)
|
310
310
|
if keep is not None:
|
311
311
|
self._real_entry = keep.real_entry
|
312
312
|
await self.move_to_real()
|
@@ -449,7 +449,13 @@ class ErrorRoot(ClientRoot):
|
|
449
449
|
raise RuntimeError(f"This cannot happen: {entry.node} {entry.tock}")
|
450
450
|
|
451
451
|
async def record_working( # pylint: disable=dangerous-default-value
|
452
|
-
self,
|
452
|
+
self,
|
453
|
+
subsystem,
|
454
|
+
path,
|
455
|
+
*,
|
456
|
+
comment=None,
|
457
|
+
data={},
|
458
|
+
force=False,
|
453
459
|
):
|
454
460
|
"""This exception has been fixed.
|
455
461
|
|
@@ -522,7 +528,11 @@ class ErrorRoot(ClientRoot):
|
|
522
528
|
return # owch, but can't be helped
|
523
529
|
|
524
530
|
r = await rec.real_entry.add_exc(
|
525
|
-
self.name,
|
531
|
+
self.name,
|
532
|
+
exc=exc,
|
533
|
+
data=data,
|
534
|
+
comment=comment,
|
535
|
+
message=message,
|
526
536
|
)
|
527
537
|
return r
|
528
538
|
|
@@ -535,9 +545,7 @@ class ErrorRoot(ClientRoot):
|
|
535
545
|
return
|
536
546
|
|
537
547
|
try:
|
538
|
-
del (self._done if entry.resolved else self._active)[entry.subsystem][
|
539
|
-
entry.path
|
540
|
-
]
|
548
|
+
del (self._done if entry.resolved else self._active)[entry.subsystem][entry.path]
|
541
549
|
except KeyError:
|
542
550
|
pass
|
543
551
|
|
moat/kv/exceptions.py
CHANGED
@@ -3,7 +3,7 @@ This module affords all MoaT-KV exceptions.
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
# pylint: disable=unnecessary-pass
|
6
|
-
|
6
|
+
from __future__ import annotations
|
7
7
|
|
8
8
|
error_types = {}
|
9
9
|
|
@@ -39,8 +39,6 @@ class ClientError(MoaTKVError):
|
|
39
39
|
|
40
40
|
etype: str = None
|
41
41
|
|
42
|
-
pass
|
43
|
-
|
44
42
|
|
45
43
|
@_typed
|
46
44
|
class ClientChainError(ClientError):
|
@@ -48,8 +46,6 @@ class ClientChainError(ClientError):
|
|
48
46
|
|
49
47
|
etype = "chain"
|
50
48
|
|
51
|
-
pass
|
52
|
-
|
53
49
|
|
54
50
|
@_typed
|
55
51
|
class ClientConnectionError(ClientError):
|
@@ -57,8 +53,6 @@ class ClientConnectionError(ClientError):
|
|
57
53
|
|
58
54
|
etype = "conn"
|
59
55
|
|
60
|
-
pass
|
61
|
-
|
62
56
|
|
63
57
|
class ServerClosedError(ServerError):
|
64
58
|
"""The server closed our connection."""
|