moat-kv 0.70.14__tar.gz → 0.70.18__tar.gz
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-0.70.18/.gitmodules +21 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/PKG-INFO +1 -1
- {moat-kv-0.70.14 → moat-kv-0.70.18}/docs/source/TODO.rst +4 -7
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/__init__.py +4 -1
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/_main.py +12 -4
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/actor/deletor.py +4 -1
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/auth/__init__.py +16 -10
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/auth/_test.py +12 -4
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/auth/password.py +1 -3
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/client.py +44 -15
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/code.py +3 -1
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/command/acl.py +17 -4
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/command/auth.py +14 -3
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/command/code.py +41 -10
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/command/codec.py +33 -14
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/command/data.py +51 -16
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/command/dump/__init__.py +10 -1
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/command/error.py +12 -4
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/command/internal.py +25 -7
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/command/job.py +31 -9
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/command/server.py +13 -3
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/command/type.py +24 -8
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/data.py +3 -1
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/errors.py +23 -5
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/mock/__init__.py +5 -2
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/mock/mqtt.py +9 -3
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/mock/serf.py +6 -2
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/mock/tracer.py +3 -1
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/model.py +21 -7
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/obj/__init__.py +26 -5
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/obj/command.py +10 -6
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/runner.py +41 -12
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/server.py +113 -32
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/types.py +6 -2
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat_kv.egg-info/PKG-INFO +1 -1
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat_kv.egg-info/requires.txt +4 -4
- {moat-kv-0.70.14 → moat-kv-0.70.18}/pyproject.toml +31 -4
- {moat-kv-0.70.14 → moat-kv-0.70.18}/scripts/init +3 -1
- {moat-kv-0.70.14 → moat-kv-0.70.18}/tests/__init__.py +2 -2
- {moat-kv-0.70.14 → moat-kv-0.70.18}/tests/test_basic.py +7 -3
- {moat-kv-0.70.14 → moat-kv-0.70.18}/tests/test_basic_serf.py +7 -3
- {moat-kv-0.70.14 → moat-kv-0.70.18}/tests/test_feature_config.py +3 -1
- {moat-kv-0.70.14 → moat-kv-0.70.18}/tests/test_feature_convert.py +4 -2
- {moat-kv-0.70.14 → moat-kv-0.70.18}/tests/test_feature_error.py +4 -1
- {moat-kv-0.70.14 → moat-kv-0.70.18}/tests/test_feature_ssl.py +4 -2
- {moat-kv-0.70.14 → moat-kv-0.70.18}/tests/test_feature_typecheck.py +7 -3
- {moat-kv-0.70.14 → moat-kv-0.70.18}/tests/test_load_save.py +29 -7
- {moat-kv-0.70.14 → moat-kv-0.70.18}/tests/test_multi.py +4 -1
- {moat-kv-0.70.14 → moat-kv-0.70.18}/tests/test_recover.py +9 -3
- moat-kv-0.70.14/.gitmodules +0 -9
- {moat-kv-0.70.14 → moat-kv-0.70.18}/.appveyor.yml +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/.coveragerc +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/.gitignore +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/.pylintrc +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/.readthedocs.yml +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/.travis.yml +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/LICENSE +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/LICENSE.APACHE2 +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/LICENSE.MIT +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/MANIFEST.in +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/Makefile +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/README.rst +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/ci/rtd-requirements.txt +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/ci/test-requirements.txt +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/ci/travis.sh +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/docs/Makefile +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/docs/make.bat +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/docs/source/_static/.gitkeep +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/docs/source/acls.rst +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/docs/source/auth.rst +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/docs/source/client_protocol.rst +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/docs/source/code.rst +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/docs/source/command_line.rst +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/docs/source/common_protocol.rst +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/docs/source/conf.py +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/docs/source/debugging.rst +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/docs/source/extend.rst +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/docs/source/history.rst +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/docs/source/index.rst +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/docs/source/model.rst +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/docs/source/overview.rst +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/docs/source/related.rst +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/docs/source/server_protocol.rst +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/docs/source/startup.rst +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/docs/source/translator.rst +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/docs/source/tutorial.rst +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/examples/code/transform.scale.yml +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/examples/code/transform.switch.yml +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/examples/code/transform.timeslot.yml +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/examples/pathify.py +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/mktag +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/__init__.py +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/_config.yaml +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/actor/__init__.py +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/auth/root.py +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/backend/__init__.py +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/backend/mqtt.py +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/backend/serf.py +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/codec.py +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/command/__init__.py +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/command/log.py +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/config.py +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat/kv/exceptions.py +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat_kv.egg-info/SOURCES.txt +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat_kv.egg-info/dependency_links.txt +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/moat_kv.egg-info/top_level.txt +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/scripts/current +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/scripts/env +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/scripts/recover +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/scripts/rotate +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/scripts/run +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/scripts/run-all +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/scripts/run-any +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/scripts/run-single +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/scripts/success +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/setup.cfg +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/systemd/moat-kv-recover.service +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/systemd/moat-kv-rotate.service +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/systemd/moat-kv-rotate.timer +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/systemd/moat-kv-run-all.service +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/systemd/moat-kv-run-all@.service +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/systemd/moat-kv-run-any.service +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/systemd/moat-kv-run-any@.service +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/systemd/moat-kv-run-single.service +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/systemd/moat-kv-run-single@.service +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/systemd/moat-kv.service +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/systemd/postinst +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/systemd/sysusers +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/tests/conftest.py +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/tests/logging.cfg +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/tests/test_feature_acl.py +1 -1
- {moat-kv-0.70.14 → moat-kv-0.70.18}/tests/test_feature_allrunner.py +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/tests/test_feature_auth.py +1 -1
- {moat-kv-0.70.14 → moat-kv-0.70.18}/tests/test_feature_code.py +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/tests/test_feature_dh.py +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/tests/test_feature_mirror.py +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/tests/test_feature_runner.py +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/tests/test_feature_singlerunner.py +0 -0
- {moat-kv-0.70.14 → moat-kv-0.70.18}/tests/test_kill.py +1 -1
- {moat-kv-0.70.14 → moat-kv-0.70.18}/tests/test_passthru.py +0 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
[submodule "ext/inv"]
|
2
|
+
path = ext/inv
|
3
|
+
url = git://git.smurf.noris.de/moat-kv-inv.git
|
4
|
+
[submodule "ext/ow"]
|
5
|
+
path = ext/ow
|
6
|
+
url = git://git.smurf.noris.de/moat-kv-ow.git
|
7
|
+
[submodule "ext/ha"]
|
8
|
+
path = ext/ha
|
9
|
+
url = git://git.smurf.noris.de/moat-kv-ha.git
|
10
|
+
[submodule "ext/moat-knx"]
|
11
|
+
path = ext/knx
|
12
|
+
url = git://git.smurf.noris.de/moat-kv-knx.git
|
13
|
+
[submodule "ext/gpio"]
|
14
|
+
path = ext/gpio
|
15
|
+
url = git://git.smurf.noris.de/moat-kv-gpio.git
|
16
|
+
[submodule "moat-kv-akumuli"]
|
17
|
+
path = ext/akumuli
|
18
|
+
url = git://git.smurf.noris.de/moat-kv-akumuli.git
|
19
|
+
[submodule "ext/wago"]
|
20
|
+
path = ext/wago
|
21
|
+
url = git://git.smurf.noris.de/moat-kv-wago.git
|
@@ -1,6 +1,10 @@
|
|
1
1
|
Open issues
|
2
2
|
===========
|
3
3
|
|
4
|
+
* Exchange a version code on startup
|
5
|
+
|
6
|
+
* CBOR
|
7
|
+
|
4
8
|
* Ping: ignore messages with decreasing tock (per node)
|
5
9
|
|
6
10
|
* chroot operation: add and test proper sub-roots, including auth and
|
@@ -22,14 +26,9 @@ Open issues
|
|
22
26
|
|
23
27
|
* Rather than mangling split messages, use a MsgPack extension type.
|
24
28
|
|
25
|
-
* Rate limiting. Currently the server can flood the client with data and no
|
26
|
-
other call can get a word in edgewise.
|
27
|
-
|
28
29
|
* AnyRunner: Do proper load balancing; the leader should be able to tell
|
29
30
|
some other node to run a job if it's busy.
|
30
31
|
|
31
|
-
* Add an EveryRunner (with per-node status of course).
|
32
|
-
|
33
32
|
* Keep an error index on the server? Something more general?
|
34
33
|
|
35
34
|
* Restart code that's been changed (without waiting for restart/retry).
|
@@ -39,8 +38,6 @@ Open issues
|
|
39
38
|
|
40
39
|
* Implement a shared secret to sign server-to-server messages.
|
41
40
|
|
42
|
-
* Re-implement Serf (or something like it) in Python, it's large and adds latency
|
43
|
-
|
44
41
|
* Runner: switch to monotonic time (except for target time!)
|
45
42
|
|
46
43
|
* Error consolidation: if a conflict doesn't get resolved on its own, do it
|
@@ -3,10 +3,13 @@
|
|
3
3
|
__path__ = __import__("pkgutil").extend_path(__path__, __name__)
|
4
4
|
|
5
5
|
try:
|
6
|
+
import warning
|
6
7
|
import pkg_resources # part of setuptools
|
7
8
|
|
8
|
-
|
9
|
+
with warnings.filterwarnings("ignore"):
|
10
|
+
_version = pkg_resources.require("moat.kv")[0].version
|
9
11
|
del pkg_resources
|
12
|
+
del warnings
|
10
13
|
|
11
14
|
_version_tuple = tuple(int(x) for x in _version.split("."))
|
12
15
|
|
@@ -35,15 +35,23 @@ class NullObj:
|
|
35
35
|
raise self._exc
|
36
36
|
|
37
37
|
def __getattr__(self, k):
|
38
|
-
if k[0] == "_" and k not in ("_request","_cfg"):
|
38
|
+
if k[0] == "_" and k not in ("_request", "_cfg"):
|
39
39
|
return object.__getattribute__(self, k)
|
40
40
|
raise self._exc
|
41
41
|
|
42
42
|
|
43
|
-
@load_subgroup(
|
44
|
-
|
43
|
+
@load_subgroup(
|
44
|
+
sub_pre="moat.kv.command", sub_post="cli", ext_pre="moat.kv", ext_post="_main.cli"
|
45
|
+
)
|
46
|
+
@click.option(
|
47
|
+
"-h", "--host", default=None, help=f"Host to use. Default: {CFG.kv.conn.host}"
|
48
|
+
)
|
45
49
|
@click.option(
|
46
|
-
"-p",
|
50
|
+
"-p",
|
51
|
+
"--port",
|
52
|
+
type=int,
|
53
|
+
default=None,
|
54
|
+
help=f"Port to use. Default: {CFG.kv.conn.port}",
|
47
55
|
)
|
48
56
|
@click.option(
|
49
57
|
"-a",
|
@@ -117,7 +117,10 @@ class DeleteActor:
|
|
117
117
|
continue
|
118
118
|
self.n_pings += 1
|
119
119
|
if self.n_pings > self.n_nodes:
|
120
|
-
mx, self.max_seen = (
|
120
|
+
mx, self.max_seen = (
|
121
|
+
self.max_seen,
|
122
|
+
max(self.max_seen, val[1]),
|
123
|
+
)
|
121
124
|
if val[0] > mx > 0:
|
122
125
|
await self.server.resync_deleted(evt.msg.history)
|
123
126
|
continue
|
@@ -279,7 +279,11 @@ class BaseClientAuthMaker(_AuthLoaded):
|
|
279
279
|
"""
|
280
280
|
# pragma: no cover
|
281
281
|
res = await client._request(
|
282
|
-
"auth_get",
|
282
|
+
"auth_get",
|
283
|
+
typ=cls._auth_method,
|
284
|
+
kind=_kind,
|
285
|
+
ident=ident,
|
286
|
+
nchain=0 if _initial else 2,
|
283
287
|
)
|
284
288
|
self = cls(_initial=_initial)
|
285
289
|
self._chain = res.chain
|
@@ -338,7 +342,9 @@ class BaseServerAuth(_AuthLoaded):
|
|
338
342
|
|
339
343
|
try:
|
340
344
|
data = data["conv"].data["key"]
|
341
|
-
res, _ = root.follow_acl(
|
345
|
+
res, _ = root.follow_acl(
|
346
|
+
Path(None, "conv", data), create=False, nulls_ok=True
|
347
|
+
)
|
342
348
|
return res
|
343
349
|
except (KeyError, AttributeError):
|
344
350
|
return ConvNull
|
@@ -348,7 +354,9 @@ class BaseServerAuth(_AuthLoaded):
|
|
348
354
|
data = data["acl"].data["key"]
|
349
355
|
if data == "*":
|
350
356
|
return NullACL
|
351
|
-
acl, _ = root.follow_acl(
|
357
|
+
acl, _ = root.follow_acl(
|
358
|
+
Path(None, "acl", data), create=False, nulls_ok=True
|
359
|
+
)
|
352
360
|
return ACLFinder(acl)
|
353
361
|
except (KeyError, AttributeError):
|
354
362
|
return NullACL
|
@@ -362,17 +370,13 @@ class BaseServerAuth(_AuthLoaded):
|
|
362
370
|
"""
|
363
371
|
return {}
|
364
372
|
|
365
|
-
async def check_read(
|
366
|
-
self, *path, client: ServerClient, data=None
|
367
|
-
): # pylint: disable=unused-argument
|
373
|
+
async def check_read(self, *path, client: ServerClient, data=None): # pylint: disable=unused-argument
|
368
374
|
"""Check that this user may read the element at this location.
|
369
375
|
This method may modify the data.
|
370
376
|
"""
|
371
377
|
return data
|
372
378
|
|
373
|
-
async def check_write(
|
374
|
-
self, *path, client: ServerClient, data=None
|
375
|
-
): # pylint: disable=unused-argument
|
379
|
+
async def check_write(self, *path, client: ServerClient, data=None): # pylint: disable=unused-argument
|
376
380
|
"""Check that this user may write the element at this location.
|
377
381
|
This method may modify the data.
|
378
382
|
"""
|
@@ -415,7 +419,9 @@ class BaseServerAuthMaker(_AuthLoaded):
|
|
415
419
|
|
416
420
|
@classmethod
|
417
421
|
async def recv(
|
418
|
-
cls,
|
422
|
+
cls,
|
423
|
+
cmd: StreamCommand,
|
424
|
+
data: attrdict, # pylint: disable=unused-argument
|
419
425
|
) -> "BaseServerAuthMaker":
|
420
426
|
"""Create/update a new user by reading the record from the client"""
|
421
427
|
dt = data.get("data", None) or {}
|
@@ -62,7 +62,9 @@ class ServerUserMaker(BaseServerAuthMaker):
|
|
62
62
|
await cmd.send(step="SendWant")
|
63
63
|
msg = await cmd.recv()
|
64
64
|
assert msg.step == "WantName"
|
65
|
-
await cmd.send(
|
65
|
+
await cmd.send(
|
66
|
+
step="SendName", name=self.name, chain=self._chain.serialize(nchain=3)
|
67
|
+
)
|
66
68
|
msg = await cmd.recv()
|
67
69
|
|
68
70
|
# Annoying methods to read+save the user name from/to KV
|
@@ -82,13 +84,17 @@ class ClientUserMaker(BaseClientAuthMaker):
|
|
82
84
|
gen_schema = dict(
|
83
85
|
type="object",
|
84
86
|
additionalProperties=False,
|
85
|
-
properties=dict(
|
87
|
+
properties=dict(
|
88
|
+
name=dict(type="string", minLength=1, pattern="^[a-zA-Z][a-zA-Z0-9_]*$")
|
89
|
+
),
|
86
90
|
required=["name"],
|
87
91
|
)
|
88
92
|
mod_schema = dict(
|
89
93
|
type="object",
|
90
94
|
additionalProperties=False,
|
91
|
-
properties=dict(
|
95
|
+
properties=dict(
|
96
|
+
name=dict(type="string", minLength=1, pattern="^[a-zA-Z][a-zA-Z0-9_]*$")
|
97
|
+
),
|
92
98
|
# required=[],
|
93
99
|
)
|
94
100
|
name = None
|
@@ -148,7 +154,9 @@ class ClientUser(BaseClientAuth):
|
|
148
154
|
schema = dict(
|
149
155
|
type="object",
|
150
156
|
additionalProperties=False,
|
151
|
-
properties=dict(
|
157
|
+
properties=dict(
|
158
|
+
name=dict(type="string", minLength=1, pattern="^[a-zA-Z][a-zA-Z0-9_]*$")
|
159
|
+
),
|
152
160
|
required=["name"],
|
153
161
|
)
|
154
162
|
_name = None
|
@@ -168,9 +168,7 @@ class ClientUserMaker(BaseClientAuthMaker):
|
|
168
168
|
pass
|
169
169
|
return self
|
170
170
|
|
171
|
-
async def send(
|
172
|
-
self, client: Client, _kind="user", **msg
|
173
|
-
): # pylint: disable=unused-argument,arguments-differ
|
171
|
+
async def send(self, client: Client, _kind="user", **msg): # pylint: disable=unused-argument,arguments-differ
|
174
172
|
"""Send a record representing this user to the server."""
|
175
173
|
if self._pass is not None:
|
176
174
|
msg["password"] = await pack_pwd(client, self._pass, self._length)
|
@@ -110,14 +110,16 @@ async def client_scope(_name=None, **cfg):
|
|
110
110
|
"""
|
111
111
|
|
112
112
|
if _name is None:
|
113
|
-
_name = cfg.get("conn",{}).get("name", "conn")
|
113
|
+
_name = cfg.get("conn", {}).get("name", "conn")
|
114
114
|
if _name is None:
|
115
115
|
global _cid
|
116
116
|
_cid += 1
|
117
117
|
_name = f"_{_cid}"
|
118
118
|
# uniqueness required for testing.
|
119
119
|
# TODO replace with a dependency on the test server.
|
120
|
-
return await scope.service(
|
120
|
+
return await scope.service(
|
121
|
+
f"moat.kv.client.{_name}", _scoped_client, _name=_name, **cfg
|
122
|
+
)
|
121
123
|
|
122
124
|
|
123
125
|
class StreamedRequest:
|
@@ -153,7 +155,9 @@ class StreamedRequest:
|
|
153
155
|
self._path_long = lambda x: x
|
154
156
|
if client.qlen > 0:
|
155
157
|
self.dw = DelayedWrite(client.qlen)
|
156
|
-
self.qr = DelayedRead(
|
158
|
+
self.qr = DelayedRead(
|
159
|
+
client.qlen, get_seq=self._get_seq, send_ack=self._send_ack
|
160
|
+
)
|
157
161
|
else:
|
158
162
|
self.qr = create_queue(client.config.server.buffer)
|
159
163
|
|
@@ -480,7 +484,9 @@ class Client:
|
|
480
484
|
res = await self._request(
|
481
485
|
"diffie_hellman", pubkey=num2byte(k.public_key), length=length
|
482
486
|
) # length=k.key_length
|
483
|
-
await anyio.to_thread.run_sync(
|
487
|
+
await anyio.to_thread.run_sync(
|
488
|
+
k.generate_shared_secret, byte2num(res.pubkey)
|
489
|
+
)
|
484
490
|
self._dh_key = num2byte(k.shared_secret)[0:32]
|
485
491
|
return self._dh_key
|
486
492
|
|
@@ -529,6 +535,7 @@ class Client:
|
|
529
535
|
|
530
536
|
except BaseException as exc:
|
531
537
|
logger.warning("Reader died: %r", exc, exc_info=exc)
|
538
|
+
raise
|
532
539
|
finally:
|
533
540
|
with anyio.fail_after(2, shield=True):
|
534
541
|
hdl, self._handlers = self._handlers, None
|
@@ -540,9 +547,7 @@ class Client:
|
|
540
547
|
except ClosedResourceError:
|
541
548
|
pass
|
542
549
|
|
543
|
-
async def _request(
|
544
|
-
self, action, iter=None, seq=None, _async=False, **params
|
545
|
-
): # pylint: disable=redefined-builtin # iter
|
550
|
+
async def _request(self, action, iter=None, seq=None, _async=False, **params): # pylint: disable=redefined-builtin # iter
|
546
551
|
"""Send a request. Wait for a reply.
|
547
552
|
|
548
553
|
Args:
|
@@ -676,12 +681,16 @@ class Client:
|
|
676
681
|
if not sa or not sa[0]:
|
677
682
|
# no auth required
|
678
683
|
if auth:
|
679
|
-
logger.info(
|
684
|
+
logger.info(
|
685
|
+
"Tried to use auth=%s, but not required.", auth._auth_method
|
686
|
+
)
|
680
687
|
return
|
681
688
|
if not auth:
|
682
689
|
raise ClientAuthRequiredError("You need to log in using:", sa[0])
|
683
690
|
if auth._auth_method != sa[0]:
|
684
|
-
raise ClientAuthMethodError(
|
691
|
+
raise ClientAuthMethodError(
|
692
|
+
f"You cannot use {auth._auth_method!r} auth", sa
|
693
|
+
)
|
685
694
|
if getattr(auth, "_DEBUG", False):
|
686
695
|
auth._length = 16
|
687
696
|
await auth.auth(self)
|
@@ -727,7 +736,9 @@ class Client:
|
|
727
736
|
self._server_init = msg = await hello.get()
|
728
737
|
self.logger.debug("Hello %s", msg)
|
729
738
|
self.server_name = msg.node
|
730
|
-
self.client_name =
|
739
|
+
self.client_name = (
|
740
|
+
cfg["name"] or socket.gethostname() or self.server_name
|
741
|
+
)
|
731
742
|
if "qlen" in msg:
|
732
743
|
self.qlen = min(msg.qlen, 99) # self.config.server.buffer
|
733
744
|
await self._send(seq=0, qlen=self.qlen)
|
@@ -735,7 +746,9 @@ class Client:
|
|
735
746
|
|
736
747
|
from .config import ConfigRoot
|
737
748
|
|
738
|
-
self._config = await ConfigRoot.as_handler(
|
749
|
+
self._config = await ConfigRoot.as_handler(
|
750
|
+
self, require_client=False
|
751
|
+
)
|
739
752
|
|
740
753
|
except TimeoutError:
|
741
754
|
raise
|
@@ -779,7 +792,16 @@ class Client:
|
|
779
792
|
raise RuntimeError("You need a path, not a string")
|
780
793
|
return self._request(action="get_value", path=path, iter=False, nchain=nchain)
|
781
794
|
|
782
|
-
def set(
|
795
|
+
def set(
|
796
|
+
self,
|
797
|
+
path,
|
798
|
+
value=NotGiven,
|
799
|
+
*,
|
800
|
+
chain=NotGiven,
|
801
|
+
prev=NotGiven,
|
802
|
+
nchain=0,
|
803
|
+
idem=None,
|
804
|
+
):
|
783
805
|
"""
|
784
806
|
Set or update a value.
|
785
807
|
|
@@ -856,8 +878,13 @@ class Client:
|
|
856
878
|
raise RuntimeError("You need a path, not a string")
|
857
879
|
if empty is None:
|
858
880
|
empty = not with_data
|
859
|
-
res = await self._request(
|
860
|
-
|
881
|
+
res = await self._request(
|
882
|
+
action="enum", path=path, with_data=with_data, empty=empty, **kw
|
883
|
+
)
|
884
|
+
try:
|
885
|
+
return res.result
|
886
|
+
except AttributeError:
|
887
|
+
raise res.q.value.error from None # XXX fix this
|
861
888
|
|
862
889
|
async def get_tree(self, path, *, long_path=True, **kw):
|
863
890
|
"""
|
@@ -936,7 +963,9 @@ class Client:
|
|
936
963
|
"""
|
937
964
|
if isinstance(path, str):
|
938
965
|
raise RuntimeError("You need a path, not a string")
|
939
|
-
return self._stream(
|
966
|
+
return self._stream(
|
967
|
+
action="watch", path=path, iter=True, long_path=long_path, **kw
|
968
|
+
)
|
940
969
|
|
941
970
|
def mirror(self, path, *, root_type=None, **kw):
|
942
971
|
"""An async context manager that affords an update-able mirror
|
@@ -159,7 +159,9 @@ class CodeRoot(ClientRoot):
|
|
159
159
|
make_proc(code, variables, path, use_async=is_async)
|
160
160
|
|
161
161
|
r = await self.client.set(
|
162
|
-
self._path + path,
|
162
|
+
self._path + path,
|
163
|
+
value=dict(code=code, is_async=is_async, vars=variables),
|
164
|
+
nchain=2,
|
163
165
|
)
|
164
166
|
await self.wait_chain(r.chain)
|
165
167
|
|
@@ -94,10 +94,15 @@ async def set_(obj, acl, name, path):
|
|
94
94
|
acl = set(acl)
|
95
95
|
|
96
96
|
if acl - ACL:
|
97
|
-
raise click.UsageError(
|
97
|
+
raise click.UsageError(
|
98
|
+
f"You're trying to set an unknown ACL flag: {acl - ACL !r}"
|
99
|
+
)
|
98
100
|
|
99
101
|
res = await obj.client._request(
|
100
|
-
action="get_internal",
|
102
|
+
action="get_internal",
|
103
|
+
path=("acl", name) + path,
|
104
|
+
iter=False,
|
105
|
+
nchain=3 if obj.meta else 1,
|
101
106
|
)
|
102
107
|
ov = set(res.get("value", ""))
|
103
108
|
if ov - ACL:
|
@@ -105,7 +110,10 @@ async def set_(obj, acl, name, path):
|
|
105
110
|
|
106
111
|
if mode == "-" and not acl:
|
107
112
|
res = await obj.client._request(
|
108
|
-
action="delete_internal",
|
113
|
+
action="delete_internal",
|
114
|
+
path=("acl", name) + path,
|
115
|
+
iter=False,
|
116
|
+
chain=res.chain,
|
109
117
|
)
|
110
118
|
v = "-"
|
111
119
|
|
@@ -125,7 +133,12 @@ async def set_(obj, acl, name, path):
|
|
125
133
|
)
|
126
134
|
|
127
135
|
if obj.meta:
|
128
|
-
res = {
|
136
|
+
res = {
|
137
|
+
"old": "".join(ov),
|
138
|
+
"new": "".join(v),
|
139
|
+
"chain": res.chain,
|
140
|
+
"tock": res.tock,
|
141
|
+
}
|
129
142
|
yprint(res, stream=obj.stdout)
|
130
143
|
else:
|
131
144
|
res = {"old": "".join(ov), "new": "".join(v)}
|
@@ -63,7 +63,12 @@ async def enum_typ(obj, kind="user", ident=None, nchain=0):
|
|
63
63
|
async for auth in enum_auth(obj):
|
64
64
|
if ident is not None:
|
65
65
|
res = await obj.client._request(
|
66
|
-
action="auth_list",
|
66
|
+
action="auth_list",
|
67
|
+
typ=auth,
|
68
|
+
kind=kind,
|
69
|
+
ident=ident,
|
70
|
+
iter=False,
|
71
|
+
nchain=nchain,
|
67
72
|
)
|
68
73
|
yield res
|
69
74
|
else:
|
@@ -106,7 +111,10 @@ async def user():
|
|
106
111
|
|
107
112
|
@user.command("list")
|
108
113
|
@click.option(
|
109
|
-
"-v",
|
114
|
+
"-v",
|
115
|
+
"--verbose",
|
116
|
+
is_flag=True,
|
117
|
+
help="Print complete results. Default: just the names",
|
110
118
|
)
|
111
119
|
@click.pass_obj # pylint: disable=function-redefined
|
112
120
|
async def list_user(obj, verbose):
|
@@ -157,7 +165,10 @@ async def param(obj, new, ident, type, key, args): # pylint: disable=redefined-
|
|
157
165
|
u._length = 16
|
158
166
|
# ou = await u.recv(obj.client, ident, _initial=False) # unused
|
159
167
|
res = await obj.client._request(
|
160
|
-
action="get_internal",
|
168
|
+
action="get_internal",
|
169
|
+
path=("auth", auth, "user", ident, type),
|
170
|
+
iter=False,
|
171
|
+
nchain=3,
|
161
172
|
)
|
162
173
|
|
163
174
|
kw = res.get("value", NotGiven)
|
@@ -3,7 +3,16 @@
|
|
3
3
|
import sys
|
4
4
|
|
5
5
|
import asyncclick as click
|
6
|
-
from moat.util import
|
6
|
+
from moat.util import (
|
7
|
+
NotGiven,
|
8
|
+
P,
|
9
|
+
Path,
|
10
|
+
PathLongener,
|
11
|
+
attr_args,
|
12
|
+
process_args,
|
13
|
+
yload,
|
14
|
+
yprint,
|
15
|
+
)
|
7
16
|
|
8
17
|
|
9
18
|
@click.group(invoke_without_command=True) # pylint: disable=undefined-variable
|
@@ -23,14 +32,18 @@ async def cli(ctx, path):
|
|
23
32
|
|
24
33
|
|
25
34
|
@cli.command()
|
26
|
-
@click.option(
|
35
|
+
@click.option(
|
36
|
+
"-s", "--script", type=click.File(mode="w", lazy=True), help="Save the code here"
|
37
|
+
)
|
27
38
|
@click.pass_obj
|
28
39
|
async def get(obj, script):
|
29
40
|
"""Read a code entry"""
|
30
41
|
if not len(obj.codepath):
|
31
42
|
raise click.UsageError("You need a non-empty path.")
|
32
43
|
|
33
|
-
res = await obj.client._request(
|
44
|
+
res = await obj.client._request(
|
45
|
+
action="get_value", path=obj.path, iter=False, nchain=obj.meta
|
46
|
+
)
|
34
47
|
if "value" not in res:
|
35
48
|
if obj.debug:
|
36
49
|
print("No entry here.", file=sys.stderr)
|
@@ -53,10 +66,14 @@ async def get(obj, script):
|
|
53
66
|
help="The code is async / sync (default: async)",
|
54
67
|
default=True,
|
55
68
|
)
|
56
|
-
@click.option(
|
69
|
+
@click.option(
|
70
|
+
"-t", "--thread", is_flag=True, help="The code should run in a worker thread"
|
71
|
+
)
|
57
72
|
@click.option("-s", "--script", type=click.File(mode="r"), help="File with the code")
|
58
73
|
@click.option("-i", "--info", type=str, help="one-liner info about the code")
|
59
|
-
@click.option(
|
74
|
+
@click.option(
|
75
|
+
"-d", "--data", type=click.File(mode="r"), help="load the metadata (YAML)"
|
76
|
+
)
|
60
77
|
@attr_args
|
61
78
|
@click.pass_obj
|
62
79
|
async def set_(obj, thread, script, data, vars_, eval_, path_, async_, info):
|
@@ -124,7 +141,9 @@ async def mod():
|
|
124
141
|
|
125
142
|
|
126
143
|
@mod.command("get")
|
127
|
-
@click.option(
|
144
|
+
@click.option(
|
145
|
+
"-s", "--script", type=click.File(mode="w", lazy=True), help="Save the code here"
|
146
|
+
)
|
128
147
|
@click.argument("path", nargs=1)
|
129
148
|
@click.pass_obj # pylint: disable=function-redefined
|
130
149
|
async def get_mod(obj, path, script):
|
@@ -153,8 +172,12 @@ async def get_mod(obj, path, script):
|
|
153
172
|
|
154
173
|
|
155
174
|
@mod.command("set")
|
156
|
-
@click.option(
|
157
|
-
|
175
|
+
@click.option(
|
176
|
+
"-s", "--script", type=click.File(mode="r"), help="File with the module's code"
|
177
|
+
)
|
178
|
+
@click.option(
|
179
|
+
"-d", "--data", type=click.File(mode="r"), help="load the metadata (YAML)"
|
180
|
+
)
|
158
181
|
@click.argument("path", nargs=1) # pylint: disable=function-redefined
|
159
182
|
@click.pass_obj
|
160
183
|
async def set_mod(obj, path, script, data):
|
@@ -201,10 +224,18 @@ async def set_mod(obj, path, script, data):
|
|
201
224
|
"for values. Default: return as list",
|
202
225
|
)
|
203
226
|
@click.option(
|
204
|
-
"-m",
|
227
|
+
"-m",
|
228
|
+
"--maxdepth",
|
229
|
+
type=int,
|
230
|
+
default=None,
|
231
|
+
help="Limit recursion depth. Default: whole tree",
|
205
232
|
)
|
206
233
|
@click.option(
|
207
|
-
"-M",
|
234
|
+
"-M",
|
235
|
+
"--mindepth",
|
236
|
+
type=int,
|
237
|
+
default=None,
|
238
|
+
help="Starting depth. Default: whole tree",
|
208
239
|
)
|
209
240
|
@click.option("-f", "--full", is_flag=True, help="print complete entries.")
|
210
241
|
@click.option("-s", "--short", is_flag=True, help="print shortened entries.")
|