moat-kv 0.70.22__py3-none-any.whl → 0.70.24__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.
- build/lib/docs/source/conf.py +201 -0
- build/lib/examples/pathify.py +45 -0
- build/lib/moat/kv/__init__.py +19 -0
- build/lib/moat/kv/_cfg.yaml +97 -0
- build/lib/moat/kv/_main.py +91 -0
- build/lib/moat/kv/actor/__init__.py +98 -0
- build/lib/moat/kv/actor/deletor.py +139 -0
- build/lib/moat/kv/auth/__init__.py +444 -0
- build/lib/moat/kv/auth/_test.py +166 -0
- build/lib/moat/kv/auth/password.py +234 -0
- build/lib/moat/kv/auth/root.py +58 -0
- build/lib/moat/kv/backend/__init__.py +67 -0
- build/lib/moat/kv/backend/mqtt.py +74 -0
- build/lib/moat/kv/backend/serf.py +45 -0
- build/lib/moat/kv/client.py +1025 -0
- build/lib/moat/kv/code.py +236 -0
- build/lib/moat/kv/codec.py +11 -0
- build/lib/moat/kv/command/__init__.py +1 -0
- build/lib/moat/kv/command/acl.py +180 -0
- build/lib/moat/kv/command/auth.py +261 -0
- build/lib/moat/kv/command/code.py +293 -0
- build/lib/moat/kv/command/codec.py +186 -0
- build/lib/moat/kv/command/data.py +265 -0
- build/lib/moat/kv/command/dump/__init__.py +143 -0
- build/lib/moat/kv/command/error.py +149 -0
- build/lib/moat/kv/command/internal.py +248 -0
- build/lib/moat/kv/command/job.py +433 -0
- build/lib/moat/kv/command/log.py +53 -0
- build/lib/moat/kv/command/server.py +114 -0
- build/lib/moat/kv/command/type.py +201 -0
- build/lib/moat/kv/config.py +46 -0
- build/lib/moat/kv/data.py +216 -0
- build/lib/moat/kv/errors.py +561 -0
- build/lib/moat/kv/exceptions.py +126 -0
- build/lib/moat/kv/mock/__init__.py +101 -0
- build/lib/moat/kv/mock/mqtt.py +159 -0
- build/lib/moat/kv/mock/serf.py +250 -0
- build/lib/moat/kv/mock/tracer.py +63 -0
- build/lib/moat/kv/model.py +1069 -0
- build/lib/moat/kv/obj/__init__.py +646 -0
- build/lib/moat/kv/obj/command.py +241 -0
- build/lib/moat/kv/runner.py +1347 -0
- build/lib/moat/kv/server.py +2809 -0
- build/lib/moat/kv/types.py +513 -0
- debian/moat-kv/usr/lib/python3/dist-packages/docs/source/conf.py +201 -0
- debian/moat-kv/usr/lib/python3/dist-packages/examples/pathify.py +45 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/__init__.py +19 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/_cfg.yaml +97 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/_main.py +91 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/actor/__init__.py +98 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/actor/deletor.py +139 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/__init__.py +444 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/_test.py +166 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/password.py +234 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/root.py +58 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/__init__.py +67 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/mqtt.py +74 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/serf.py +45 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/client.py +1025 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/code.py +236 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/codec.py +11 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/__init__.py +1 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/acl.py +180 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/auth.py +261 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/code.py +293 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/codec.py +186 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/data.py +265 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/dump/__init__.py +143 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/error.py +149 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/internal.py +248 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/job.py +433 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/log.py +53 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/server.py +114 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/type.py +201 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/config.py +46 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/data.py +216 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/errors.py +561 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/exceptions.py +126 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/__init__.py +101 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/mqtt.py +159 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/serf.py +250 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/tracer.py +63 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/model.py +1069 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/obj/__init__.py +646 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/obj/command.py +241 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/runner.py +1347 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/server.py +2809 -0
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/types.py +513 -0
- docs/source/conf.py +201 -0
- examples/pathify.py +45 -0
- moat/kv/__init__.py +1 -0
- moat/kv/_cfg.yaml +97 -0
- moat/kv/_main.py +6 -9
- moat/kv/actor/__init__.py +2 -1
- moat/kv/actor/deletor.py +3 -1
- moat/kv/auth/__init__.py +8 -10
- moat/kv/auth/_test.py +6 -12
- moat/kv/auth/password.py +2 -0
- moat/kv/auth/root.py +2 -0
- moat/kv/backend/__init__.py +1 -0
- moat/kv/backend/mqtt.py +3 -3
- moat/kv/backend/serf.py +1 -0
- moat/kv/client.py +34 -50
- moat/kv/code.py +10 -3
- moat/kv/codec.py +1 -0
- moat/kv/command/acl.py +12 -6
- moat/kv/command/auth.py +5 -2
- moat/kv/command/code.py +10 -23
- moat/kv/command/codec.py +10 -14
- moat/kv/command/data.py +12 -21
- moat/kv/command/dump/__init__.py +4 -2
- moat/kv/command/error.py +5 -12
- moat/kv/command/internal.py +6 -15
- moat/kv/command/job.py +26 -31
- moat/kv/command/log.py +1 -0
- moat/kv/command/server.py +2 -3
- moat/kv/command/type.py +26 -28
- 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/mock/__init__.py +9 -5
- moat/kv/mock/mqtt.py +7 -12
- moat/kv/mock/serf.py +6 -9
- moat/kv/mock/tracer.py +2 -4
- moat/kv/model.py +16 -24
- moat/kv/obj/__init__.py +30 -20
- moat/kv/obj/command.py +7 -12
- moat/kv/runner.py +38 -35
- moat/kv/server.py +86 -90
- moat/kv/types.py +5 -8
- {moat_kv-0.70.22.dist-info → moat_kv-0.70.24.dist-info}/METADATA +15 -18
- moat_kv-0.70.24.dist-info/RECORD +137 -0
- {moat_kv-0.70.22.dist-info → moat_kv-0.70.24.dist-info}/WHEEL +1 -1
- moat_kv-0.70.24.dist-info/licenses/LICENSE.txt +14 -0
- moat_kv-0.70.24.dist-info/top_level.txt +9 -0
- moat/kv/_config.yaml +0 -98
- moat_kv-0.70.22.dist-info/LICENSE +0 -3
- moat_kv-0.70.22.dist-info/LICENSE.APACHE2 +0 -202
- moat_kv-0.70.22.dist-info/LICENSE.MIT +0 -20
- moat_kv-0.70.22.dist-info/RECORD +0 -49
- moat_kv-0.70.22.dist-info/top_level.txt +0 -1
examples/pathify.py
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
#!/usr/bin/python3
|
2
|
+
|
3
|
+
# Batch-convert data. In this case I had some entries which were stored as
|
4
|
+
# a list, but using Path made much more sense (esp when you need to
|
5
|
+
# view/edit the yaml export).
|
6
|
+
|
7
|
+
import anyio
|
8
|
+
from moat.kv.client import open_client
|
9
|
+
from moat.util import P, yload, Path
|
10
|
+
import asyncclick as click
|
11
|
+
|
12
|
+
|
13
|
+
def conv(m, s: str) -> bool:
|
14
|
+
try:
|
15
|
+
d = m.value[s]
|
16
|
+
except KeyError:
|
17
|
+
return 0
|
18
|
+
if isinstance(d, Path):
|
19
|
+
return 0
|
20
|
+
if not isinstance(d, Sequence):
|
21
|
+
return 0
|
22
|
+
d = Path.build(d)
|
23
|
+
m.value[s] = d
|
24
|
+
return 1
|
25
|
+
|
26
|
+
|
27
|
+
@click.command()
|
28
|
+
@click.argument("path", type=P)
|
29
|
+
@click.argument("keys", type=str, nargs=-1)
|
30
|
+
async def main(path, keys):
|
31
|
+
if not keys:
|
32
|
+
keys = "src dest dst state".split()
|
33
|
+
with open("/etc/moat.kv.cfg") as cff:
|
34
|
+
cfg = yload(cff)
|
35
|
+
async with open_client(**cfg) as client:
|
36
|
+
async for m in client.get_tree(path, nchain=2):
|
37
|
+
n = 0
|
38
|
+
for k in keys:
|
39
|
+
n += conv(m, k)
|
40
|
+
if n:
|
41
|
+
await client.set(ORIG + m.path, value=m.value, chain=m.chain)
|
42
|
+
|
43
|
+
|
44
|
+
if __name__ == "__main__":
|
45
|
+
main()
|
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/actor/__init__.py
CHANGED
@@ -2,8 +2,9 @@
|
|
2
2
|
This module implements a :class:`asyncactor.Actor` which works on top of
|
3
3
|
a MoaT-KV client.
|
4
4
|
"""
|
5
|
+
from __future__ import annotations
|
5
6
|
|
6
|
-
from asyncactor import Actor
|
7
|
+
from asyncactor import Actor
|
7
8
|
from asyncactor.abc import MonitorStream, Transport
|
8
9
|
|
9
10
|
__all__ = [
|
moat/kv/actor/deletor.py
CHANGED
@@ -3,6 +3,8 @@ This module implements additional code for the server-side DeleteActor,
|
|
3
3
|
which is used to clean up the list of deleted nodes.
|
4
4
|
"""
|
5
5
|
|
6
|
+
from __future__ import annotations
|
7
|
+
|
6
8
|
import weakref
|
7
9
|
from collections import deque
|
8
10
|
|
@@ -41,7 +43,7 @@ class DeleteActor:
|
|
41
43
|
self.tags = self.tags[-TAGS:]
|
42
44
|
await self.actor.set_value((self.tags[0], self.tags[-1]))
|
43
45
|
|
44
|
-
def add_deleted(self, nodes:
|
46
|
+
def add_deleted(self, nodes: NodeSet): # noqa: F821
|
45
47
|
"""
|
46
48
|
These nodes are deleted. Remember them for some time.
|
47
49
|
"""
|
moat/kv/auth/__init__.py
CHANGED
@@ -59,6 +59,8 @@ The server process is:
|
|
59
59
|
|
60
60
|
"""
|
61
61
|
|
62
|
+
from __future__ import annotations
|
63
|
+
|
62
64
|
import io
|
63
65
|
from importlib import import_module
|
64
66
|
|
@@ -87,7 +89,7 @@ add_schema = {
|
|
87
89
|
"additionalProperties": False,
|
88
90
|
"properties": {"key": {type: "string", "minLength": 1}},
|
89
91
|
},
|
90
|
-
}
|
92
|
+
},
|
91
93
|
}
|
92
94
|
|
93
95
|
|
@@ -111,7 +113,7 @@ def gen_auth(s: str):
|
|
111
113
|
|
112
114
|
m, *p = s.split()
|
113
115
|
if len(p) == 0 and m[0] == "=":
|
114
|
-
with
|
116
|
+
with open(m[1:], encoding="utf-8") as f:
|
115
117
|
kw = yload(f)
|
116
118
|
m = kw.pop("type")
|
117
119
|
else:
|
@@ -147,7 +149,7 @@ async def null_server_login(stream):
|
|
147
149
|
return stream
|
148
150
|
|
149
151
|
|
150
|
-
async def null_client_login(stream, user:
|
152
|
+
async def null_client_login(stream, user: BaseClientAuth): # pylint: disable=unused-argument
|
151
153
|
return stream
|
152
154
|
|
153
155
|
|
@@ -342,9 +344,7 @@ class BaseServerAuth(_AuthLoaded):
|
|
342
344
|
|
343
345
|
try:
|
344
346
|
data = data["conv"].data["key"]
|
345
|
-
res, _ = root.follow_acl(
|
346
|
-
Path(None, "conv", data), create=False, nulls_ok=True
|
347
|
-
)
|
347
|
+
res, _ = root.follow_acl(Path(None, "conv", data), create=False, nulls_ok=True)
|
348
348
|
return res
|
349
349
|
except (KeyError, AttributeError):
|
350
350
|
return ConvNull
|
@@ -354,9 +354,7 @@ class BaseServerAuth(_AuthLoaded):
|
|
354
354
|
data = data["acl"].data["key"]
|
355
355
|
if data == "*":
|
356
356
|
return NullACL
|
357
|
-
acl, _ = root.follow_acl(
|
358
|
-
Path(None, "acl", data), create=False, nulls_ok=True
|
359
|
-
)
|
357
|
+
acl, _ = root.follow_acl(Path(None, "acl", data), create=False, nulls_ok=True)
|
360
358
|
return ACLFinder(acl)
|
361
359
|
except (KeyError, AttributeError):
|
362
360
|
return NullACL
|
@@ -422,7 +420,7 @@ class BaseServerAuthMaker(_AuthLoaded):
|
|
422
420
|
cls,
|
423
421
|
cmd: StreamCommand,
|
424
422
|
data: attrdict, # pylint: disable=unused-argument
|
425
|
-
) ->
|
423
|
+
) -> BaseServerAuthMaker:
|
426
424
|
"""Create/update a new user by reading the record from the client"""
|
427
425
|
dt = data.get("data", None) or {}
|
428
426
|
jsonschema.validate(instance=dt, schema=cls.schema)
|
moat/kv/auth/_test.py
CHANGED
@@ -5,6 +5,8 @@ Test auth method.
|
|
5
5
|
Does not limit anything, allows everything.
|
6
6
|
"""
|
7
7
|
|
8
|
+
from __future__ import annotations
|
9
|
+
|
8
10
|
import logging
|
9
11
|
|
10
12
|
log = logging.getLogger(__name__)
|
@@ -62,9 +64,7 @@ class ServerUserMaker(BaseServerAuthMaker):
|
|
62
64
|
await cmd.send(step="SendWant")
|
63
65
|
msg = await cmd.recv()
|
64
66
|
assert msg.step == "WantName"
|
65
|
-
await cmd.send(
|
66
|
-
step="SendName", name=self.name, chain=self._chain.serialize(nchain=3)
|
67
|
-
)
|
67
|
+
await cmd.send(step="SendName", name=self.name, chain=self._chain.serialize(nchain=3))
|
68
68
|
msg = await cmd.recv()
|
69
69
|
|
70
70
|
# Annoying methods to read+save the user name from/to KV
|
@@ -84,17 +84,13 @@ class ClientUserMaker(BaseClientAuthMaker):
|
|
84
84
|
gen_schema = dict(
|
85
85
|
type="object",
|
86
86
|
additionalProperties=False,
|
87
|
-
properties=dict(
|
88
|
-
name=dict(type="string", minLength=1, pattern="^[a-zA-Z][a-zA-Z0-9_]*$")
|
89
|
-
),
|
87
|
+
properties=dict(name=dict(type="string", minLength=1, pattern="^[a-zA-Z][a-zA-Z0-9_]*$")),
|
90
88
|
required=["name"],
|
91
89
|
)
|
92
90
|
mod_schema = dict(
|
93
91
|
type="object",
|
94
92
|
additionalProperties=False,
|
95
|
-
properties=dict(
|
96
|
-
name=dict(type="string", minLength=1, pattern="^[a-zA-Z][a-zA-Z0-9_]*$")
|
97
|
-
),
|
93
|
+
properties=dict(name=dict(type="string", minLength=1, pattern="^[a-zA-Z][a-zA-Z0-9_]*$")),
|
98
94
|
# required=[],
|
99
95
|
)
|
100
96
|
name = None
|
@@ -154,9 +150,7 @@ class ClientUser(BaseClientAuth):
|
|
154
150
|
schema = dict(
|
155
151
|
type="object",
|
156
152
|
additionalProperties=False,
|
157
|
-
properties=dict(
|
158
|
-
name=dict(type="string", minLength=1, pattern="^[a-zA-Z][a-zA-Z0-9_]*$")
|
159
|
-
),
|
153
|
+
properties=dict(name=dict(type="string", minLength=1, pattern="^[a-zA-Z][a-zA-Z0-9_]*$")),
|
160
154
|
required=["name"],
|
161
155
|
)
|
162
156
|
_name = None
|
moat/kv/auth/password.py
CHANGED
moat/kv/auth/root.py
CHANGED
moat/kv/backend/__init__.py
CHANGED
moat/kv/backend/mqtt.py
CHANGED
@@ -1,9 +1,11 @@
|
|
1
|
+
from __future__ import annotations
|
1
2
|
import logging
|
2
3
|
from contextlib import asynccontextmanager
|
3
4
|
|
4
5
|
import anyio
|
5
6
|
from moat.mqtt.client import MQTTClient
|
6
7
|
from moat.mqtt.codecs import NoopCodec
|
8
|
+
from moat.util import NotGiven
|
7
9
|
|
8
10
|
from . import Backend
|
9
11
|
|
@@ -24,9 +26,7 @@ class MqttBackend(Backend):
|
|
24
26
|
|
25
27
|
@asynccontextmanager
|
26
28
|
async def connect(self, *a, **kw):
|
27
|
-
codec = kw.pop("codec",
|
28
|
-
if codec is None:
|
29
|
-
codec = NoopCodec()
|
29
|
+
codec = kw.pop("codec", NotGiven)
|
30
30
|
C = MQTTClient(self._tg, codec=codec)
|
31
31
|
try:
|
32
32
|
await C.connect(*a, **kw)
|
moat/kv/backend/serf.py
CHANGED
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()
|
@@ -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:
|