moat-kv 0.70.22__py3-none-any.whl → 0.70.23__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. moat/kv/__init__.py +1 -0
  2. moat/kv/_cfg.yaml +97 -0
  3. moat/kv/_main.py +6 -9
  4. moat/kv/client.py +34 -50
  5. moat/kv/code.py +10 -3
  6. moat/kv/codec.py +1 -0
  7. moat/kv/config.py +2 -0
  8. moat/kv/data.py +8 -7
  9. moat/kv/errors.py +17 -9
  10. moat/kv/exceptions.py +1 -7
  11. moat/kv/model.py +16 -24
  12. moat/kv/runner.py +38 -35
  13. moat/kv/server.py +86 -90
  14. moat/kv/types.py +5 -8
  15. {moat_kv-0.70.22.dist-info → moat_kv-0.70.23.dist-info}/METADATA +15 -18
  16. moat_kv-0.70.23.dist-info/RECORD +19 -0
  17. {moat_kv-0.70.22.dist-info → moat_kv-0.70.23.dist-info}/WHEEL +1 -1
  18. moat_kv-0.70.23.dist-info/licenses/LICENSE.txt +14 -0
  19. moat/kv/_config.yaml +0 -98
  20. moat/kv/actor/__init__.py +0 -97
  21. moat/kv/actor/deletor.py +0 -137
  22. moat/kv/auth/__init__.py +0 -446
  23. moat/kv/auth/_test.py +0 -172
  24. moat/kv/auth/password.py +0 -232
  25. moat/kv/auth/root.py +0 -56
  26. moat/kv/backend/__init__.py +0 -66
  27. moat/kv/backend/mqtt.py +0 -74
  28. moat/kv/backend/serf.py +0 -44
  29. moat/kv/command/__init__.py +0 -1
  30. moat/kv/command/acl.py +0 -174
  31. moat/kv/command/auth.py +0 -258
  32. moat/kv/command/code.py +0 -306
  33. moat/kv/command/codec.py +0 -190
  34. moat/kv/command/data.py +0 -274
  35. moat/kv/command/dump/__init__.py +0 -141
  36. moat/kv/command/error.py +0 -156
  37. moat/kv/command/internal.py +0 -257
  38. moat/kv/command/job.py +0 -438
  39. moat/kv/command/log.py +0 -52
  40. moat/kv/command/server.py +0 -115
  41. moat/kv/command/type.py +0 -203
  42. moat/kv/mock/__init__.py +0 -97
  43. moat/kv/mock/mqtt.py +0 -164
  44. moat/kv/mock/serf.py +0 -253
  45. moat/kv/mock/tracer.py +0 -65
  46. moat/kv/obj/__init__.py +0 -636
  47. moat/kv/obj/command.py +0 -246
  48. moat_kv-0.70.22.dist-info/LICENSE +0 -3
  49. moat_kv-0.70.22.dist-info/LICENSE.APACHE2 +0 -202
  50. moat_kv-0.70.22.dist-info/LICENSE.MIT +0 -20
  51. moat_kv-0.70.22.dist-info/RECORD +0 -49
  52. {moat_kv-0.70.22.dist-info → moat_kv-0.70.23.dist-info}/top_level.txt +0 -0
moat/kv/command/type.py DELETED
@@ -1,203 +0,0 @@
1
- # command line interface
2
-
3
- import json
4
-
5
- import asyncclick as click
6
- from moat.util import NotGiven, P, Path, PathLongener, yload, yprint
7
-
8
-
9
- @click.group() # pylint: disable=undefined-variable
10
- async def cli():
11
- """Manage types and type matches. Usage: … type …"""
12
- pass
13
-
14
-
15
- @cli.command()
16
- @click.option(
17
- "-s", "--script", type=click.File(mode="w", lazy=True), help="Save the script here"
18
- )
19
- @click.option(
20
- "-S", "--schema", type=click.File(mode="w", lazy=True), help="Save the schema here"
21
- )
22
- @click.option(
23
- "-y", "--yaml", "yaml_", is_flag=True, help="Write schema as YAML. Default: JSON."
24
- )
25
- @click.argument("path", type=P, nargs=1)
26
- @click.pass_obj
27
- async def get(obj, path, script, schema, yaml_):
28
- """Read type checker information"""
29
- if not len(path):
30
- raise click.UsageError("You need a non-empty path.")
31
- res = await obj.client._request(
32
- action="get_internal", path=Path("type") + path, iter=False, nchain=obj.meta
33
- )
34
- try:
35
- r = res.value
36
- except AttributeError:
37
- raise click.UsageError(f"No data at {Path('type') + path}") from None
38
-
39
- if not obj.meta:
40
- res = res.value
41
- if script:
42
- script.write(r.pop("code"))
43
- if schema:
44
- if yaml_:
45
- yprint(r.pop("schema"), stream=schema)
46
- else:
47
- json.dump(r.pop("schema"), schema)
48
- yprint(res, stream=obj.stdout)
49
-
50
-
51
- @cli.command("set")
52
- @click.option("-g", "--good", multiple=True, help="Example for passing values")
53
- @click.option("-b", "--bad", multiple=True, help="Example for failing values")
54
- @click.option(
55
- "-d", "--data", type=click.File(mode="r"), help="Load metadata from this YAML file."
56
- )
57
- @click.option(
58
- "-s", "--script", type=click.File(mode="r"), help="File with the checking script"
59
- )
60
- @click.option(
61
- "-S", "--schema", type=click.File(mode="r"), help="File with the JSON schema"
62
- )
63
- @click.option(
64
- "-y", "--yaml", "yaml_", is_flag=True, help="load the schema as YAML. Default: JSON"
65
- )
66
- @click.argument("path", type=P, nargs=1)
67
- @click.pass_obj
68
- async def set_(obj, path, good, bad, script, schema, yaml_, data):
69
- """Write type checker information."""
70
- if not len(path):
71
- raise click.UsageError("You need a non-empty path.")
72
-
73
- if data:
74
- msg = yload(data)
75
- else:
76
- msg = {}
77
- chain = NotGiven
78
- if "value" in msg:
79
- chain = msg.get("chain", NotGiven)
80
- msg = msg["value"]
81
-
82
- msg.setdefault("good", [])
83
- msg.setdefault("bad", [])
84
- for x in good:
85
- msg["good"].append(eval(x)) # pylint: disable=eval-used
86
- for x in bad:
87
- msg["bad"].append(eval(x)) # pylint: disable=eval-used
88
-
89
- if "code" in msg:
90
- if script:
91
- raise click.UsageError("Duplicate script")
92
- elif script:
93
- msg["code"] = script.read()
94
-
95
- if "schema" in msg:
96
- raise click.UsageError("Missing schema")
97
- elif schema:
98
- if yaml_:
99
- msg["schema"] = yload(schema)
100
- else:
101
- msg["schema"] = json.load(schema)
102
-
103
- if "schema" not in msg and "code" not in msg:
104
- raise click.UsageError("I need a schema, Python code, or both.")
105
-
106
- if len(msg["good"]) < 2:
107
- raise click.UsageError("Missing known-good test values (at least two)")
108
- if not msg["bad"]:
109
- raise click.UsageError("Missing known-bad test values")
110
-
111
- res = await obj.client._request(
112
- action="set_internal",
113
- value=msg,
114
- path=Path("type") + path,
115
- iter=False,
116
- nchain=obj.meta,
117
- **({} if chain is NotGiven else {"chain": chain}),
118
- )
119
- if obj.meta:
120
- yprint(res, stream=obj.stdout)
121
-
122
-
123
- @cli.command()
124
- @click.option("-R", "--raw", is_flag=True, help="Print just the path.")
125
- @click.option("-t", "--type", "type_", help="Type path to link to.")
126
- @click.option("-d", "--delete", help="Use to delete this mapping.")
127
- @click.argument("path", type=P, nargs=1)
128
- @click.pass_obj
129
- async def match(obj, path, type_, delete, raw): # pylint: disable=redefined-builtin
130
- """Match a type to a path (read, if no type given; list if empty path)"""
131
- if not len(path):
132
- if raw or type_ or delete:
133
- raise click.UsageError("No options allowed when dumping the match tree..")
134
- y = {}
135
- pl = PathLongener()
136
- async for r in await obj.client._request(
137
- "get_tree_internal", path=Path("match") + path, iter=True, nchain=0
138
- ):
139
- pl(r)
140
- path = r["path"]
141
- yy = y
142
- for p in path:
143
- yy = yy.setdefault(p, {})
144
- try:
145
- yy["_"] = r["value"]
146
- except KeyError:
147
- pass
148
- yprint(y, stream=obj.stdout)
149
- return
150
-
151
- if type_ and delete:
152
- raise click.UsageError("You can't both set and delete a path.")
153
- if raw and (type_ or delete):
154
- raise click.UsageError("You can only print the raw path when reading a match.")
155
-
156
- if delete:
157
- res = await obj.client._request(
158
- action="delete_internal", path=Path("type") + path
159
- )
160
- if obj.meta:
161
- yprint(res, stream=obj.stdout)
162
- return
163
-
164
- msg = {}
165
- if type_:
166
- msg["type"] = P(type_)
167
- act = "set_internal"
168
- elif delete:
169
- act = "delete_internal"
170
- else:
171
- act = "get_internal"
172
- res = await obj.client._request(
173
- action=act, value=msg, path=Path("match") + path, iter=False, nchain=obj.meta
174
- )
175
- if obj.meta:
176
- yprint(res, stream=obj.stdout)
177
- elif type_ or delete:
178
- pass
179
- else:
180
- print(" ".join(str(x) for x in res.type), file=obj.stdout)
181
-
182
-
183
- @cli.command()
184
- @click.argument("path", type=P, nargs=1)
185
- @click.pass_obj
186
- async def list(obj, path): # pylint: disable=redefined-builtin
187
- """Dump type data"""
188
-
189
- y = {}
190
- pl = PathLongener()
191
- async for r in await obj.client._request(
192
- "get_tree_internal", path=Path("type") + path, iter=True, nchain=0
193
- ):
194
- pl(r)
195
- path = r["path"]
196
- yy = y
197
- for p in path:
198
- yy = yy.setdefault(p, {})
199
- try:
200
- yy["_"] = r["value"]
201
- except KeyError:
202
- pass
203
- yprint(y, stream=obj.stdout)
moat/kv/mock/__init__.py DELETED
@@ -1,97 +0,0 @@
1
- # from asyncclick.testing import CliRunner
2
- import io
3
- import logging
4
- import shlex
5
- import socket
6
- import sys
7
- from pathlib import Path
8
-
9
- import attr
10
- from asyncscope import main_scope, scope
11
- from moat.src.test import run # pylint:disable=import-error,no-name-in-module
12
- from moat.util import ( # pylint:disable=no-name-in-module
13
- OptCtx,
14
- attrdict,
15
- combine_dict,
16
- list_ext,
17
- load_ext,
18
- wrap_main,
19
- yload,
20
- )
21
-
22
- from moat.kv.client import _scoped_client, client_scope
23
-
24
- logger = logging.getLogger(__name__)
25
- try:
26
- from contextlib import asynccontextmanager
27
- except ImportError:
28
- from async_generator import asynccontextmanager
29
-
30
- CFG = yload(Path(__file__).parent.parent / "_config.yaml", attr=True)
31
-
32
-
33
- @attr.s
34
- class S:
35
- tg = attr.ib()
36
- client_ctx = attr.ib()
37
- s = attr.ib(factory=list) # servers
38
- c = attr.ib(factory=list) # clients
39
- _seq = 1
40
-
41
- async def ready(self, i=None):
42
- if i is not None:
43
- await self.s[i].is_ready
44
- return self.s[i]
45
- for s in self.s:
46
- if s is not None:
47
- await s.is_ready
48
- return self.s
49
-
50
- def __iter__(self):
51
- return iter(self.s)
52
-
53
- @asynccontextmanager
54
- async def client(self, i: int = 0, **kv):
55
- """Get a (new) client for the i'th server."""
56
- await self.s[i].is_serving
57
- self._seq += 1
58
- for host, port, *_ in self.s[i].ports:
59
- if host != "::" and host[0] == ":":
60
- continue
61
- try:
62
- cfg = combine_dict(
63
- dict(conn=dict(host=host, port=port, ssl=self.client_ctx, **kv)),
64
- CFG["kv"],
65
- )
66
-
67
- async def scc(s, **cfg):
68
- scope.requires(s._scope)
69
- return await _scoped_client(scope.name, **cfg)
70
-
71
- async with scope.using_scope():
72
- c = await scope.service(
73
- f"moat.kv.client.{i}.{self._seq}", scc, self.s[i], **cfg
74
- )
75
- yield c
76
- return
77
- except socket.gaierror:
78
- pass
79
- raise RuntimeError("Duh? no connection")
80
-
81
- async def run(self, *args, do_stdout=True):
82
- h = p = None
83
- for s in self.s:
84
- for h, p, *_ in s.ports:
85
- if h[0] != ":":
86
- break
87
- else:
88
- continue
89
- break
90
- if len(args) == 1:
91
- args = args[0]
92
- if isinstance(args, str):
93
- args = args.split(" ")
94
- async with scope.using_scope():
95
- return await run(
96
- "-VV", "kv", "-h", h, "-p", p, *args, do_stdout=do_stdout
97
- )
moat/kv/mock/mqtt.py DELETED
@@ -1,164 +0,0 @@
1
- import copy
2
- import logging
3
- import os
4
- import time
5
- from contextlib import AsyncExitStack, asynccontextmanager
6
- from functools import partial
7
-
8
- import anyio
9
- import mock
10
- import trio
11
- from asyncscope import main_scope, scope
12
- from moat.mqtt.broker import create_broker
13
- from moat.util import NotGiven, attrdict, combine_dict
14
-
15
- from moat.kv.mock import S
16
- from moat.kv.server import Server
17
-
18
- from . import CFG
19
-
20
- logger = logging.getLogger(__name__)
21
-
22
- otm = time.time
23
-
24
- PORT = 40000 + (os.getpid() + 10) % 10000
25
-
26
- broker_cfg = {
27
- "listeners": {"default": {"type": "tcp", "bind": f"127.0.0.1:{PORT}"}},
28
- "timeout-disconnect-delay": 2,
29
- "auth": {"allow-anonymous": True, "password-file": None},
30
- }
31
-
32
- URI = f"mqtt://127.0.0.1:{PORT}/"
33
-
34
-
35
- @asynccontextmanager
36
- async def stdtest(n=1, run=True, ssl=False, tocks=20, **kw):
37
- C_OUT = CFG.get("_stdout", NotGiven)
38
- if C_OUT is not NotGiven:
39
- del CFG["_stdout"]
40
- TESTCFG = copy.deepcopy(CFG["kv"])
41
- TESTCFG.server.port = None
42
- TESTCFG.root = "test"
43
- if C_OUT is not NotGiven:
44
- CFG["_stdout"] = C_OUT
45
- TESTCFG["_stdout"] = C_OUT
46
-
47
- if ssl:
48
- import ssl
49
-
50
- import trustme
51
-
52
- ca = trustme.CA()
53
- cert = ca.issue_server_cert("127.0.0.1")
54
- server_ctx = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH)
55
- client_ctx = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH)
56
- ca.configure_trust(client_ctx)
57
- cert.configure_cert(server_ctx)
58
- else:
59
- server_ctx = client_ctx = False
60
-
61
- clock = trio.lowlevel.current_clock()
62
- try:
63
- clock.autojump_threshold = 0.02 # networking
64
- except Exception:
65
- pass # test doesn't have autojump_clock fixture
66
-
67
- async def mock_get_host_port(st, host):
68
- i = int(host[host.rindex("_") + 1 :]) # noqa: E203
69
- s = st.s[i]
70
- await s.is_serving
71
- for host, port, *_ in s.ports:
72
- if host == "::" or host[0] != ":":
73
- return host, port
74
-
75
- def tm():
76
- try:
77
- return trio.current_time()
78
- except RuntimeError:
79
- return otm()
80
-
81
- async def mock_set_tock(self, old):
82
- assert self._tock < tocks, "Test didn't terminate. Limit:" + str(tocks)
83
- await old()
84
-
85
- done = False
86
- async with main_scope("moat.kv.test.mqtt") as scp:
87
- tg = scp._tg
88
- st = S(tg, client_ctx)
89
- async with AsyncExitStack() as ex:
90
- st.ex = ex # pylint: disable=attribute-defined-outside-init
91
- ex.enter_context(mock.patch("time.time", new=tm))
92
- ex.enter_context(mock.patch("time.monotonic", new=tm))
93
- logging._startTime = tm()
94
-
95
- async def run_broker(cfg):
96
- async with create_broker(config=cfg) as srv:
97
- # NB: some services use "async with await …"
98
- scope.register(srv)
99
- await scope.no_more_dependents()
100
-
101
- async def with_broker(s, *a, **k):
102
- await scope.service("moat.mqtt.broker", run_broker, broker_cfg)
103
- s._scope = scope.get()
104
- return await s._scoped_serve(*a, **k)
105
-
106
- args_def = kw.get("args", attrdict())
107
- for i in range(n):
108
- name = "test_" + str(i)
109
- args = kw.get(name, args_def)
110
- args = combine_dict(
111
- args,
112
- args_def,
113
- {
114
- "cfg": {
115
- "conn": {"ssl": client_ctx},
116
- "server": {
117
- "bind_default": {
118
- "host": "127.0.0.1",
119
- "port": i + PORT + 1,
120
- "ssl": server_ctx,
121
- },
122
- "backend": "mqtt",
123
- "mqtt": {"uri": URI},
124
- },
125
- }
126
- },
127
- {"cfg": TESTCFG},
128
- )
129
- args_def.pop("init", None)
130
- s = Server(name, **args)
131
- ex.enter_context(
132
- mock.patch.object(
133
- s, "_set_tock", new=partial(mock_set_tock, s, s._set_tock)
134
- )
135
- )
136
- ex.enter_context(
137
- mock.patch.object(
138
- s, "_get_host_port", new=partial(mock_get_host_port, st)
139
- )
140
- )
141
- st.s.append(s)
142
-
143
- evts = []
144
- for i in range(n):
145
- if kw.get(f"run_{i}", run):
146
- evt = anyio.Event()
147
- await scp.spawn_service(with_broker, st.s[i], ready_evt=evt)
148
- evts.append(evt)
149
- else:
150
- setattr(
151
- st, f"run_{i}", partial(scp.spawn_service, with_broker, st.s[i])
152
- )
153
-
154
- for e in evts:
155
- await e.wait()
156
- try:
157
- done = True
158
- yield st
159
- finally:
160
- with anyio.fail_after(2, shield=True):
161
- logger.info("Runtime: %s", clock.current_time())
162
- tg.cancel_scope.cancel()
163
- if not done:
164
- yield None