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.
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 +36 -52
  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 +39 -36
  13. moat/kv/server.py +86 -90
  14. moat/kv/types.py +5 -8
  15. {moat_kv-0.70.20.dist-info → moat_kv-0.70.23.dist-info}/METADATA +22 -25
  16. moat_kv-0.70.23.dist-info/RECORD +19 -0
  17. {moat_kv-0.70.20.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.20.dist-info/LICENSE +0 -3
  49. moat_kv-0.70.20.dist-info/LICENSE.APACHE2 +0 -202
  50. moat_kv-0.70.20.dist-info/LICENSE.MIT +0 -20
  51. moat_kv-0.70.20.dist-info/RECORD +0 -49
  52. {moat_kv-0.70.20.dist-info → moat_kv-0.70.23.dist-info}/top_level.txt +0 -0
moat/kv/command/codec.py DELETED
@@ -1,190 +0,0 @@
1
- # command line interface
2
-
3
- import asyncclick as click
4
- from moat.util import NotGiven, P, Path, PathLongener, yload, yprint
5
-
6
-
7
- @click.group() # pylint: disable=undefined-variable
8
- async def cli():
9
- """Manage codecs and converters. Usage: … codec …"""
10
- pass
11
-
12
-
13
- @cli.command()
14
- @click.option(
15
- "-e", "--encode", type=click.File(mode="w", lazy=True), help="Save the encoder here"
16
- )
17
- @click.option(
18
- "-d", "--decode", type=click.File(mode="w", lazy=True), help="Save the decoder here"
19
- )
20
- @click.option(
21
- "-s", "--script", type=click.File(mode="w", lazy=True), help="Save the data here"
22
- )
23
- @click.argument("path", nargs=1)
24
- @click.pass_obj
25
- async def get(obj, path, script, encode, decode):
26
- """Read type information"""
27
- path = P(path)
28
- if not len(path):
29
- raise click.UsageError("You need a non-empty path.")
30
- res = await obj.client._request(
31
- action="get_internal", path=Path("codec") + path, iter=False, nchain=obj.meta
32
- )
33
- if encode and res.get("encode", None) is not None:
34
- encode.write(res.pop("encode"))
35
- if decode and res.get("decode", None) is not None:
36
- decode.write(res.pop("decode"))
37
-
38
- if not obj.meta:
39
- res = res.value
40
- yprint(res, stream=script or obj.stdout)
41
-
42
-
43
- @cli.command(name="list")
44
- @click.pass_obj
45
- @click.argument("path", nargs=1)
46
- async def list_(obj, path):
47
- """List type information entries"""
48
- res = await obj.client._request(
49
- action="get_tree_internal",
50
- path=Path("codec") + P(path),
51
- iter=True,
52
- nchain=obj.meta,
53
- )
54
- pl = PathLongener(())
55
- async for r in res:
56
- pl(r)
57
- print(" ".join(str(x) for x in r.path), file=obj.stdout)
58
-
59
-
60
- @cli.command("set")
61
- @click.option("-e", "--encode", type=click.File(mode="r"), help="File with the encoder")
62
- @click.option("-d", "--decode", type=click.File(mode="r"), help="File with the decoder")
63
- @click.option("-D", "--data", type=click.File(mode="r"), help="File with the rest")
64
- @click.option("-i", "--in", "in_", nargs=2, multiple=True, help="Decoding sample")
65
- @click.option("-o", "--out", nargs=2, multiple=True, help="Encoding sample")
66
- @click.argument("path", nargs=1)
67
- @click.pass_obj
68
- async def set_(obj, path, encode, decode, data, in_, out):
69
- """Save codec information"""
70
- path = P(path)
71
- if not len(path):
72
- raise click.UsageError("You need a non-empty path.")
73
-
74
- if data:
75
- msg = yload(data)
76
- else:
77
- msg = {}
78
- chain = NotGiven
79
- if "value" in msg:
80
- chain = msg.get("chain", NotGiven)
81
- msg = msg["value"]
82
-
83
- if "encode" in msg:
84
- if encode:
85
- raise click.UsageError("Duplicate encode script")
86
- else:
87
- if not encode:
88
- raise click.UsageError("Missing encode script")
89
- msg["encode"] = encode.read()
90
- if "decode" in msg:
91
- if decode:
92
- raise click.UsageError("Duplicate decode script")
93
- else:
94
- if not decode:
95
- raise click.UsageError("Missing decode script")
96
- msg["decode"] = decode.read()
97
- if in_:
98
- msg["in"] = [(eval(a), eval(b)) for a, b in in_] # pylint: disable=eval-used
99
- if out:
100
- msg["out"] = [(eval(a), eval(b)) for a, b in out] # pylint: disable=eval-used
101
-
102
- if not msg["in"]:
103
- raise click.UsageError("Missing decode tests")
104
- if not msg["out"]:
105
- raise click.UsageError("Missing encode tests")
106
-
107
- res = await obj.client._request(
108
- action="set_internal",
109
- value=msg,
110
- path=Path("codec") + path,
111
- iter=False,
112
- nchain=obj.meta,
113
- **({} if chain is NotGiven else {"chain": chain}),
114
- )
115
- if obj.meta:
116
- yprint(res, stream=obj.stdout)
117
-
118
-
119
- @cli.command()
120
- @click.option("-c", "--codec", type=P, help="Codec to link to.")
121
- @click.option("-d", "--delete", is_flag=True, help="Use to delete this converter.")
122
- @click.option(
123
- "-l",
124
- "--list",
125
- "list_",
126
- is_flag=True,
127
- help="Use to list this converter; '-' to list all.",
128
- )
129
- @click.argument("name", nargs=1)
130
- @click.argument("path", type=P, nargs=1)
131
- @click.pass_obj
132
- async def convert(obj, path, codec, name, delete, list_):
133
- """Match a codec to a path (read, if no codec given)"""
134
- path = P(path)
135
- if delete and list_:
136
- raise click.UsageError("You can't both list and delete a path.")
137
- if not len(path) and not list_:
138
- raise click.UsageError("You need a non-empty path.")
139
- if codec and delete:
140
- raise click.UsageError("You can't both set and delete a path.")
141
-
142
- if list_:
143
- if name == "-":
144
- if len(path):
145
- raise click.UsageError("You can't use a path here.")
146
- res = await obj.client._request(
147
- action="enum_internal",
148
- path=Path("conv"),
149
- iter=False,
150
- nchain=0,
151
- empty=True,
152
- )
153
- for r in res.result:
154
- print(r, file=obj.stdout)
155
-
156
- else:
157
- res = await obj.client._request(
158
- action="get_tree_internal",
159
- path=Path("conv", name) + path,
160
- iter=True,
161
- nchain=obj.meta,
162
- )
163
- pl = PathLongener(())
164
- async for r in res:
165
- pl(r)
166
- try:
167
- print(f"{r.path} : {Path.build(r.value['codec'])}", file=obj.stdout)
168
- except Exception as e:
169
- print(f"{Path(r.path)} {e !r}", file=obj.stdout)
170
-
171
- return
172
- if delete:
173
- res = await obj.client._request(
174
- action="delete_internal", path=Path("conv", name) + path
175
- )
176
- else:
177
- msg = {"codec": codec}
178
- res = await obj.client._request(
179
- action="set_internal",
180
- value=msg,
181
- path=Path("conv", name) + path,
182
- iter=False,
183
- nchain=obj.meta,
184
- )
185
- if obj.meta:
186
- yprint(res, stream=obj.stdout)
187
- elif type or delete:
188
- print(res.tock, file=obj.stdout)
189
- else:
190
- print(" ".join(res.type), file=obj.stdout)
moat/kv/command/data.py DELETED
@@ -1,274 +0,0 @@
1
- # command line interface
2
-
3
- import datetime
4
- import time
5
-
6
- import asyncclick as click
7
- from moat.util import MsgReader, NotGiven, P, PathLongener, attr_args, yprint
8
-
9
- from moat.kv.client import StreamedRequest
10
- from moat.kv.data import add_dates, data_get, node_attr
11
-
12
-
13
- @click.group(short_help="Manage data.", invoke_without_command=True) # pylint: disable=undefined-variable
14
- @click.argument("path", type=P, nargs=1)
15
- @click.pass_context
16
- async def cli(ctx, path):
17
- """
18
- This subcommand accesses the actual user data stored in your MoaT-KV tree.
19
- """
20
- if ctx.invoked_subcommand is None:
21
- await data_get(ctx.obj, path, recursive=False)
22
- else:
23
- ctx.obj.path = path
24
-
25
-
26
- @cli.command()
27
- @click.option(
28
- "-d",
29
- "--as-dict",
30
- default=None,
31
- help="Structure as dictionary. The argument is the key to use "
32
- "for values. Default: return as list",
33
- )
34
- @click.option(
35
- "-m",
36
- "--maxdepth",
37
- type=int,
38
- default=None,
39
- help="Limit recursion depth. Default: whole tree",
40
- )
41
- @click.option(
42
- "-M",
43
- "--mindepth",
44
- type=int,
45
- default=None,
46
- help="Starting depth. Default: whole tree",
47
- )
48
- @click.option("-r", "--recursive", is_flag=True, help="Read a complete subtree")
49
- @click.option("-e", "--empty", is_flag=True, help="Include empty nodes")
50
- @click.option(
51
- "-R", "--raw", is_flag=True, help="Print string values without quotes etc."
52
- )
53
- @click.option("-D", "--add-date", is_flag=True, help="Add *_date entries")
54
- @click.pass_obj
55
- async def get(obj, **k):
56
- """
57
- Read a MoaT-KV value.
58
-
59
- If you read a sub-tree recursively, be aware that the whole subtree
60
- will be read before anything is printed. Use the "watch --state" subcommand
61
- for incremental output.
62
- """
63
-
64
- await data_get(obj, obj.path, **k)
65
-
66
-
67
- @cli.command("list")
68
- @click.option(
69
- "-d",
70
- "--as-dict",
71
- default=None,
72
- help="Structure as dictionary. The argument is the key to use "
73
- "for values. Default: return as list",
74
- )
75
- @click.option(
76
- "-m",
77
- "--maxdepth",
78
- type=int,
79
- default=1,
80
- help="Limit recursion depth. Default: 1 (single layer).",
81
- )
82
- @click.option(
83
- "-M",
84
- "--mindepth",
85
- type=int,
86
- default=1,
87
- help="Starting depth. Default: 1 (single layer).",
88
- )
89
- @click.pass_obj
90
- async def list_(obj, **k):
91
- """
92
- List MoaT-KV values.
93
-
94
- This is like "get" but with "--mindepth=1 --maxdepth=1 --recursive --empty"
95
-
96
- If you read a sub-tree recursively, be aware that the whole subtree
97
- will be read before anything is printed. Use the "watch --state" subcommand
98
- for incremental output.
99
- """
100
-
101
- k["recursive"] = True
102
- k["raw"] = True
103
- k["empty"] = True
104
- await data_get(obj, obj.path, **k)
105
-
106
-
107
- @cli.command("set", short_help="Add or update an entry")
108
- @attr_args
109
- @click.option("-l", "--last", nargs=2, help="Previous change entry (node serial)")
110
- @click.option("-n", "--new", is_flag=True, help="This is a new entry.")
111
- @click.pass_obj
112
- async def set_(obj, vars_, eval_, path_, last, new):
113
- """
114
- Store a value at some MoaT-KV position.
115
-
116
- If you update a value you can use "--last" to ensure that no other
117
- change arrived between reading and writing the entry. (This is not
118
- foolproof but reduces the window to a fraction of a second.)
119
-
120
- When adding a new entry use "--new" to ensure that you don't
121
- accidentally overwrite something.
122
-
123
- MoaT-KV entries typically are mappings. Use a colon as the path if you
124
- want to replace the top level.
125
- """
126
- args = {}
127
- if new:
128
- if last:
129
- raise click.UsageError("'new' and 'last' are mutually exclusive")
130
- args["chain"] = None
131
- else:
132
- if last:
133
- args["chain"] = {"node": last[0], "tick": int(last[1])}
134
-
135
- res = await node_attr(obj, obj.path, vars_, eval_, path_, **args)
136
-
137
- if obj.meta:
138
- yprint(res, stream=obj.stdout)
139
-
140
-
141
- class nstr:
142
- def __new__(cls, val):
143
- if val is NotGiven:
144
- return val
145
- return str(val)
146
-
147
-
148
- @cli.command(short_help="Delete an entry / subtree")
149
- @click.option(
150
- "-p",
151
- "--prev",
152
- type=nstr,
153
- default=NotGiven,
154
- help="Previous value. Deprecated; use 'last'",
155
- )
156
- @click.option("-l", "--last", nargs=2, help="Previous change entry (node serial)")
157
- @click.option("-r", "--recursive", is_flag=True, help="Delete a complete subtree")
158
- @click.option("--internal", is_flag=True, help="Affect the internal tree. DANGER.")
159
- @click.option(
160
- "-e", "--eval", "eval_", is_flag=True, help="The previous value shall be evaluated."
161
- )
162
- @click.pass_obj
163
- async def delete(obj, prev, last, recursive, eval_, internal):
164
- """
165
- Delete an entry, or a whole subtree.
166
-
167
- You really should use "--last" (preferred) or "--prev" (if you must) to
168
- ensure that no other change arrived (but note that this doesn't work
169
- when deleting a subtree).
170
-
171
- Non-recursively deleting an entry with children works and does *not*
172
- affect the child entries.
173
-
174
- The root entry cannot be deleted.
175
- """
176
- args = {}
177
- if eval_ and prev is NotGiven:
178
- raise click.UsageError("You need to add a value that can be evaluated")
179
- if recursive:
180
- if prev is not NotGiven or last:
181
- raise click.UsageError(
182
- "You can't use a prev value when deleting recursively."
183
- )
184
- if internal:
185
- raise click.UsageError("'internal' and 'recursive' are mutually exclusive")
186
- else:
187
- if prev is not NotGiven:
188
- if eval_:
189
- prev = eval(prev) # pylint: disable=eval-used
190
- args["prev"] = prev
191
- if last:
192
- args["chain"] = {"node": last[0], "tick": int(last[1])}
193
-
194
- res = await obj.client.delete(
195
- path=obj.path, nchain=obj.meta, recursive=recursive, **args
196
- )
197
- if isinstance(res, StreamedRequest):
198
- pl = PathLongener(obj.path)
199
- async for r in res:
200
- pl(r)
201
- if obj.meta:
202
- yprint(r, stream=obj.stdout)
203
- else:
204
- if obj.meta:
205
- yprint(res, stream=obj.stdout)
206
-
207
-
208
- @cli.command()
209
- @click.option("-s", "--state", is_flag=True, help="Also get the current state.")
210
- @click.option("-o", "--only", is_flag=True, help="Value only, nothing fancy.")
211
- @click.option("-p", "--path-only", is_flag=True, help="Value only, nothing fancy.")
212
- @click.option("-D", "--add-date", is_flag=True, help="Add *_date entries")
213
- @click.option("-i", "--ignore", multiple=True, type=P, help="Skip this (sub)tree")
214
- @click.pass_obj
215
- async def monitor(obj, state, only, path_only, add_date, ignore):
216
- """Monitor a MoaT-KV subtree"""
217
-
218
- flushing = not state
219
- seen = False
220
-
221
- async with obj.client.watch(
222
- obj.path,
223
- nchain=obj.meta,
224
- fetch=state, max_depth=0 if only else -1,
225
- long_path=False,
226
- ) as res:
227
- pl = PathLongener(() if path_only else obj.path)
228
- async for r in res:
229
- if add_date and "value" in r:
230
- add_dates(r.value)
231
- if any(p == r.path[: len(p)] for p in ignore):
232
- continue
233
- pl(r)
234
- if r.get("state", "") == "uptodate":
235
- if only and not seen:
236
- # value doesn't exist
237
- return
238
- flushing = True
239
- if only or path_only:
240
- continue
241
- else:
242
- del r["seq"]
243
- if only or path_only:
244
- try:
245
- if path_only:
246
- print(f"{r.path} {r.value}", file=obj.stdout)
247
- else:
248
- print(r.value, file=obj.stdout)
249
- continue
250
- except AttributeError:
251
- # value has been deleted
252
- continue
253
- if flushing:
254
- r["time"] = time.time()
255
- r["_time"] = datetime.datetime.now().isoformat(
256
- sep=" ", timespec="milliseconds"
257
- )
258
- yprint(r, stream=obj.stdout)
259
- print("---", file=obj.stdout)
260
- if flushing:
261
- obj.stdout.flush()
262
- seen = True
263
-
264
-
265
- @cli.command()
266
- @click.option("-i", "--infile", type=click.Path(), help="File to read (msgpack).")
267
- @click.pass_obj
268
- async def update(obj, infile):
269
- """Send a list of updates to a MoaT-KV subtree"""
270
- async with MsgReader(path=infile) as reader:
271
- async for msg in reader:
272
- if not hasattr(msg, "path"):
273
- continue
274
- await obj.client.set(obj.path + msg.path, value=msg.value)
@@ -1,141 +0,0 @@
1
- # command line interface
2
-
3
- import datetime
4
- import sys
5
- from collections.abc import Mapping
6
-
7
- import asyncclick as click
8
- from moat.mqtt.codecs import MsgPackCodec
9
- from moat.util import (
10
- MsgReader,
11
- MsgWriter,
12
- P,
13
- Path,
14
- PathLongener,
15
- load_subgroup,
16
- yload,
17
- yprint,
18
- )
19
-
20
- from moat.kv.codec import unpacker
21
-
22
-
23
- @load_subgroup(short_help="Local data mangling", sub_pre="dump")
24
- async def cli():
25
- """
26
- Low-level tools that don't depend on a running MoaT-KV server.
27
- """
28
- pass
29
-
30
-
31
- @cli.command()
32
- @click.argument("node", nargs=1)
33
- @click.argument("file", type=click.Path(), nargs=1)
34
- async def init(node, file):
35
- """Write an initial preload file.
36
-
37
- Usage: moat kv dump init <node> <outfile>
38
-
39
- Writes an initial MoaT-KV file that behaves as if it was generated by <node>.
40
-
41
- Using this command, followed by "moat kv server -l <outfile> <node>", is
42
- equivalent to running "moat kv server -i 'Initial data' <node>.
43
- """
44
- async with MsgWriter(path=file) as f:
45
- await f(
46
- dict(
47
- chain=dict(node=node, tick=1, prev=None),
48
- depth=0,
49
- path=[],
50
- tock=1,
51
- value="Initial data",
52
- )
53
- )
54
-
55
-
56
- @cli.command("msg")
57
- @click.argument("path", nargs=1)
58
- @click.pass_obj
59
- async def msg_(obj, path):
60
- """
61
- Monitor the server-to-sever message stream.
62
-
63
- Use ':' for the main server's "update" stream.
64
- Use '+NAME' to monitor a different stream instead.
65
- Use '+' to monitor all streams.
66
- Otherwise use the given name as-is; Mosquitto wildcard rules apply.
67
-
68
- \b
69
- Common streams (prefix with '+'):
70
- * ping all servers
71
- * update data changes
72
- * del nodes responsible for cleaning up deleted records
73
- """
74
- from moat.kv.backend import get_backend
75
-
76
- class _Unpack:
77
- def __init__(self):
78
- self._part_cache = dict()
79
-
80
- import moat.kv.server
81
-
82
- _Unpack._unpack_multiple = moat.kv.server.Server._unpack_multiple
83
- _unpacker = _Unpack()._unpack_multiple
84
-
85
- path = P(path)
86
- if len(path) == 0:
87
- path = P(obj.cfg.server["root"]) | "update"
88
- elif len(path) == 1 and path[0].startswith("+"): # pylint: disable=no-member # owch
89
- p = path[0][1:]
90
- path = P(obj.cfg.server["root"])
91
- path |= p or "#"
92
- be = obj.cfg.server.backend
93
- kw = obj.cfg.server[be]
94
-
95
- async with get_backend(be)(**kw) as conn:
96
- async with conn.monitor(*path) as stream:
97
- async for msg in stream:
98
- v = vars(msg)
99
- if isinstance(v.get("payload"), (bytearray, bytes)):
100
- t = msg.topic
101
- v = unpacker(v["payload"])
102
- v = _unpacker(v)
103
- if v is None:
104
- continue
105
- if not isinstance(v, Mapping):
106
- v = {"_data": v}
107
- v["_topic"] = Path.build(t)
108
- else:
109
- v["_type"] = type(msg).__name__
110
-
111
- v["_timestamp"] = datetime.datetime.now().isoformat(
112
- sep=" ", timespec="milliseconds"
113
- )
114
-
115
- yprint(v, stream=obj.stdout)
116
- print("---", file=obj.stdout)
117
-
118
-
119
- @cli.command("post")
120
- @click.argument("path", nargs=1)
121
- @click.pass_obj
122
- async def post_(obj, path):
123
- """
124
- Send a msgpack-encoded message (or several) to a specific MQTT topic.
125
-
126
- Messages are read from YAML.
127
- Common streams:
128
- * ping: sync: all servers (default)
129
- * update: data changes
130
- * del: sync: nodes responsible for cleaning up deleted records
131
- """
132
- from moat.kv.backend import get_backend
133
-
134
- path = P(path)
135
- be = obj.cfg.server.backend
136
- kw = obj.cfg.server[be]
137
-
138
- async with get_backend(be)(codec=MsgPackCodec, **kw) as conn:
139
- for d in yload(sys.stdin, multi=True):
140
- topic = d.pop("_topic", path)
141
- await conn.send(*topic, payload=d)