moat-kv 0.71.0__py3-none-any.whl → 0.71.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- moat/kv/__init__.py +6 -7
- moat/kv/_cfg.yaml +3 -2
- moat/kv/actor/__init__.py +2 -1
- moat/kv/actor/deletor.py +4 -1
- moat/kv/auth/__init__.py +12 -13
- moat/kv/auth/_test.py +4 -1
- moat/kv/auth/password.py +11 -7
- moat/kv/backend/mqtt.py +4 -5
- moat/kv/client.py +20 -39
- moat/kv/code.py +3 -3
- moat/kv/command/data.py +4 -3
- moat/kv/command/dump/__init__.py +36 -34
- moat/kv/command/internal.py +2 -3
- moat/kv/command/job.py +1 -2
- moat/kv/command/type.py +3 -6
- moat/kv/data.py +9 -8
- moat/kv/errors.py +16 -8
- moat/kv/mock/__init__.py +2 -12
- moat/kv/model.py +29 -33
- moat/kv/obj/__init__.py +3 -3
- moat/kv/obj/command.py +3 -3
- moat/kv/runner.py +4 -5
- moat/kv/server.py +106 -126
- moat/kv/types.py +10 -12
- {moat_kv-0.71.0.dist-info → moat_kv-0.71.7.dist-info}/METADATA +6 -2
- moat_kv-0.71.7.dist-info/RECORD +47 -0
- {moat_kv-0.71.0.dist-info → moat_kv-0.71.7.dist-info}/WHEEL +1 -1
- moat_kv-0.71.7.dist-info/licenses/LICENSE +3 -0
- moat_kv-0.71.7.dist-info/licenses/LICENSE.APACHE2 +202 -0
- moat_kv-0.71.7.dist-info/licenses/LICENSE.MIT +20 -0
- moat_kv-0.71.7.dist-info/top_level.txt +1 -0
- build/lib/docs/source/conf.py +0 -201
- build/lib/examples/pathify.py +0 -45
- build/lib/moat/kv/__init__.py +0 -19
- build/lib/moat/kv/_cfg.yaml +0 -93
- build/lib/moat/kv/_main.py +0 -91
- build/lib/moat/kv/actor/__init__.py +0 -98
- build/lib/moat/kv/actor/deletor.py +0 -139
- build/lib/moat/kv/auth/__init__.py +0 -444
- build/lib/moat/kv/auth/_test.py +0 -166
- build/lib/moat/kv/auth/password.py +0 -234
- build/lib/moat/kv/auth/root.py +0 -58
- build/lib/moat/kv/backend/__init__.py +0 -67
- build/lib/moat/kv/backend/mqtt.py +0 -71
- build/lib/moat/kv/client.py +0 -1025
- build/lib/moat/kv/code.py +0 -236
- build/lib/moat/kv/codec.py +0 -11
- build/lib/moat/kv/command/__init__.py +0 -1
- build/lib/moat/kv/command/acl.py +0 -180
- build/lib/moat/kv/command/auth.py +0 -261
- build/lib/moat/kv/command/code.py +0 -293
- build/lib/moat/kv/command/codec.py +0 -186
- build/lib/moat/kv/command/data.py +0 -265
- build/lib/moat/kv/command/dump/__init__.py +0 -143
- build/lib/moat/kv/command/error.py +0 -149
- build/lib/moat/kv/command/internal.py +0 -248
- build/lib/moat/kv/command/job.py +0 -433
- build/lib/moat/kv/command/log.py +0 -53
- build/lib/moat/kv/command/server.py +0 -114
- build/lib/moat/kv/command/type.py +0 -201
- build/lib/moat/kv/config.py +0 -46
- build/lib/moat/kv/data.py +0 -216
- build/lib/moat/kv/errors.py +0 -561
- build/lib/moat/kv/exceptions.py +0 -126
- build/lib/moat/kv/mock/__init__.py +0 -101
- build/lib/moat/kv/mock/mqtt.py +0 -159
- build/lib/moat/kv/mock/tracer.py +0 -63
- build/lib/moat/kv/model.py +0 -1069
- build/lib/moat/kv/obj/__init__.py +0 -646
- build/lib/moat/kv/obj/command.py +0 -241
- build/lib/moat/kv/runner.py +0 -1347
- build/lib/moat/kv/server.py +0 -2809
- build/lib/moat/kv/types.py +0 -513
- ci/rtd-requirements.txt +0 -4
- ci/test-requirements.txt +0 -7
- ci/travis.sh +0 -96
- debian/.gitignore +0 -7
- debian/changelog +0 -1435
- debian/control +0 -43
- debian/moat-kv/usr/lib/python3/dist-packages/docs/source/conf.py +0 -201
- debian/moat-kv/usr/lib/python3/dist-packages/examples/pathify.py +0 -45
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/__init__.py +0 -19
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/_cfg.yaml +0 -93
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/_main.py +0 -91
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/actor/__init__.py +0 -98
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/actor/deletor.py +0 -139
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/__init__.py +0 -444
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/_test.py +0 -166
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/password.py +0 -234
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/root.py +0 -58
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/__init__.py +0 -67
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/mqtt.py +0 -71
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/client.py +0 -1025
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/code.py +0 -236
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/codec.py +0 -11
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/__init__.py +0 -1
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/acl.py +0 -180
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/auth.py +0 -261
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/code.py +0 -293
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/codec.py +0 -186
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/data.py +0 -265
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/dump/__init__.py +0 -143
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/error.py +0 -149
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/internal.py +0 -248
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/job.py +0 -433
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/log.py +0 -53
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/server.py +0 -114
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/type.py +0 -201
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/config.py +0 -46
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/data.py +0 -216
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/errors.py +0 -561
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/exceptions.py +0 -126
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/__init__.py +0 -101
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/mqtt.py +0 -159
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/tracer.py +0 -63
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/model.py +0 -1069
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/obj/__init__.py +0 -646
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/obj/command.py +0 -241
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/runner.py +0 -1347
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/server.py +0 -2809
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/types.py +0 -513
- debian/moat-kv.postinst +0 -3
- debian/rules +0 -20
- debian/source/format +0 -1
- debian/watch +0 -4
- docs/Makefile +0 -20
- docs/make.bat +0 -36
- docs/source/TODO.rst +0 -61
- docs/source/_static/.gitkeep +0 -0
- docs/source/acls.rst +0 -80
- docs/source/auth.rst +0 -84
- docs/source/client_protocol.rst +0 -456
- docs/source/code.rst +0 -341
- docs/source/command_line.rst +0 -1187
- docs/source/common_protocol.rst +0 -47
- docs/source/conf.py +0 -201
- docs/source/debugging.rst +0 -70
- docs/source/extend.rst +0 -37
- docs/source/history.rst +0 -36
- docs/source/index.rst +0 -75
- docs/source/model.rst +0 -54
- docs/source/overview.rst +0 -83
- docs/source/related.rst +0 -89
- docs/source/server_protocol.rst +0 -450
- docs/source/startup.rst +0 -31
- docs/source/translator.rst +0 -244
- docs/source/tutorial.rst +0 -711
- docs/source/v3.rst +0 -168
- examples/code/transform.scale.yml +0 -21
- examples/code/transform.switch.yml +0 -82
- examples/code/transform.timeslot.yml +0 -63
- examples/pathify.py +0 -45
- moat/kv/codec.py +0 -11
- moat_kv-0.71.0.dist-info/RECORD +0 -188
- moat_kv-0.71.0.dist-info/top_level.txt +0 -9
- scripts/current +0 -15
- scripts/env +0 -8
- scripts/init +0 -39
- scripts/recover +0 -17
- scripts/rotate +0 -33
- scripts/run +0 -29
- scripts/run-all +0 -10
- scripts/run-any +0 -10
- scripts/run-single +0 -15
- scripts/success +0 -4
- systemd/moat-kv-recover.service +0 -21
- systemd/moat-kv-rotate.service +0 -20
- systemd/moat-kv-rotate.timer +0 -10
- systemd/moat-kv-run-all.service +0 -26
- systemd/moat-kv-run-all@.service +0 -25
- systemd/moat-kv-run-any.service +0 -26
- systemd/moat-kv-run-any@.service +0 -25
- systemd/moat-kv-run-single.service +0 -26
- systemd/moat-kv-run-single@.service +0 -25
- systemd/moat-kv.service +0 -27
- systemd/postinst +0 -7
- systemd/sysusers +0 -3
- {moat_kv-0.71.0.dist-info → moat_kv-0.71.7.dist-info}/licenses/LICENSE.txt +0 -0
@@ -1,265 +0,0 @@
|
|
1
|
-
# command line interface
|
2
|
-
from __future__ import annotations
|
3
|
-
|
4
|
-
import datetime
|
5
|
-
import time
|
6
|
-
|
7
|
-
import asyncclick as click
|
8
|
-
from moat.util import MsgReader, NotGiven, P, PathLongener, attr_args, yprint
|
9
|
-
|
10
|
-
from moat.kv.client import StreamedRequest
|
11
|
-
from moat.kv.data import add_dates, data_get, node_attr
|
12
|
-
|
13
|
-
|
14
|
-
@click.group(short_help="Manage data.", invoke_without_command=True) # pylint: disable=undefined-variable
|
15
|
-
@click.argument("path", type=P, nargs=1)
|
16
|
-
@click.pass_context
|
17
|
-
async def cli(ctx, path):
|
18
|
-
"""
|
19
|
-
This subcommand accesses the actual user data stored in your MoaT-KV tree.
|
20
|
-
"""
|
21
|
-
if ctx.invoked_subcommand is None:
|
22
|
-
await data_get(ctx.obj, path, recursive=False)
|
23
|
-
else:
|
24
|
-
ctx.obj.path = path
|
25
|
-
|
26
|
-
|
27
|
-
@cli.command()
|
28
|
-
@click.option(
|
29
|
-
"-d",
|
30
|
-
"--as-dict",
|
31
|
-
default=None,
|
32
|
-
help="Structure as dictionary. The argument is the key to use "
|
33
|
-
"for values. Default: return as list",
|
34
|
-
)
|
35
|
-
@click.option(
|
36
|
-
"-m",
|
37
|
-
"--maxdepth",
|
38
|
-
type=int,
|
39
|
-
default=None,
|
40
|
-
help="Limit recursion depth. Default: whole tree",
|
41
|
-
)
|
42
|
-
@click.option(
|
43
|
-
"-M",
|
44
|
-
"--mindepth",
|
45
|
-
type=int,
|
46
|
-
default=None,
|
47
|
-
help="Starting depth. Default: whole tree",
|
48
|
-
)
|
49
|
-
@click.option("-r", "--recursive", is_flag=True, help="Read a complete subtree")
|
50
|
-
@click.option("-e", "--empty", is_flag=True, help="Include empty nodes")
|
51
|
-
@click.option("-R", "--raw", is_flag=True, help="Print string values without quotes etc.")
|
52
|
-
@click.option("-D", "--add-date", is_flag=True, help="Add *_date entries")
|
53
|
-
@click.pass_obj
|
54
|
-
async def get(obj, **k):
|
55
|
-
"""
|
56
|
-
Read a MoaT-KV value.
|
57
|
-
|
58
|
-
If you read a sub-tree recursively, be aware that the whole subtree
|
59
|
-
will be read before anything is printed. Use the "watch --state" subcommand
|
60
|
-
for incremental output.
|
61
|
-
"""
|
62
|
-
|
63
|
-
await data_get(obj, obj.path, **k)
|
64
|
-
|
65
|
-
|
66
|
-
@cli.command("list")
|
67
|
-
@click.option(
|
68
|
-
"-d",
|
69
|
-
"--as-dict",
|
70
|
-
default=None,
|
71
|
-
help="Structure as dictionary. The argument is the key to use "
|
72
|
-
"for values. Default: return as list",
|
73
|
-
)
|
74
|
-
@click.option(
|
75
|
-
"-m",
|
76
|
-
"--maxdepth",
|
77
|
-
type=int,
|
78
|
-
default=1,
|
79
|
-
help="Limit recursion depth. Default: 1 (single layer).",
|
80
|
-
)
|
81
|
-
@click.option(
|
82
|
-
"-M",
|
83
|
-
"--mindepth",
|
84
|
-
type=int,
|
85
|
-
default=1,
|
86
|
-
help="Starting depth. Default: 1 (single layer).",
|
87
|
-
)
|
88
|
-
@click.pass_obj
|
89
|
-
async def list_(obj, **k):
|
90
|
-
"""
|
91
|
-
List MoaT-KV values.
|
92
|
-
|
93
|
-
This is like "get" but with "--mindepth=1 --maxdepth=1 --recursive --empty"
|
94
|
-
|
95
|
-
If you read a sub-tree recursively, be aware that the whole subtree
|
96
|
-
will be read before anything is printed. Use the "watch --state" subcommand
|
97
|
-
for incremental output.
|
98
|
-
"""
|
99
|
-
|
100
|
-
k["recursive"] = True
|
101
|
-
k["raw"] = True
|
102
|
-
k["empty"] = True
|
103
|
-
await data_get(obj, obj.path, **k)
|
104
|
-
|
105
|
-
|
106
|
-
@cli.command("set", short_help="Add or update an entry")
|
107
|
-
@attr_args
|
108
|
-
@click.option("-l", "--last", nargs=2, help="Previous change entry (node serial)")
|
109
|
-
@click.option("-n", "--new", is_flag=True, help="This is a new entry.")
|
110
|
-
@click.pass_obj
|
111
|
-
async def set_(obj, last, new, **kw):
|
112
|
-
"""
|
113
|
-
Store a value at some MoaT-KV position.
|
114
|
-
|
115
|
-
If you update a value you can use "--last" to ensure that no other
|
116
|
-
change arrived between reading and writing the entry. (This is not
|
117
|
-
foolproof but reduces the window to a fraction of a second.)
|
118
|
-
|
119
|
-
When adding a new entry use "--new" to ensure that you don't
|
120
|
-
accidentally overwrite something.
|
121
|
-
|
122
|
-
MoaT-KV entries typically are mappings. Use a colon as the path if you
|
123
|
-
want to replace the top level.
|
124
|
-
"""
|
125
|
-
if new:
|
126
|
-
if last:
|
127
|
-
raise click.UsageError("'new' and 'last' are mutually exclusive")
|
128
|
-
kw["chain"] = None
|
129
|
-
else:
|
130
|
-
if last:
|
131
|
-
kw["chain"] = {"node": last[0], "tick": int(last[1])}
|
132
|
-
|
133
|
-
res = await node_attr(obj, obj.path, **kw)
|
134
|
-
|
135
|
-
if obj.meta:
|
136
|
-
yprint(res, stream=obj.stdout)
|
137
|
-
|
138
|
-
|
139
|
-
class nstr:
|
140
|
-
def __new__(cls, val):
|
141
|
-
if val is NotGiven:
|
142
|
-
return val
|
143
|
-
return str(val)
|
144
|
-
|
145
|
-
|
146
|
-
@cli.command(short_help="Delete an entry / subtree")
|
147
|
-
@click.option(
|
148
|
-
"-p",
|
149
|
-
"--prev",
|
150
|
-
type=nstr,
|
151
|
-
default=NotGiven,
|
152
|
-
help="Previous value. Deprecated; use 'last'",
|
153
|
-
)
|
154
|
-
@click.option("-l", "--last", nargs=2, help="Previous change entry (node serial)")
|
155
|
-
@click.option("-r", "--recursive", is_flag=True, help="Delete a complete subtree")
|
156
|
-
@click.option("--internal", is_flag=True, help="Affect the internal tree. DANGER.")
|
157
|
-
@click.option("-e", "--eval", "eval_", is_flag=True, help="The previous value shall be evaluated.")
|
158
|
-
@click.pass_obj
|
159
|
-
async def delete(obj, prev, last, recursive, eval_, internal):
|
160
|
-
"""
|
161
|
-
Delete an entry, or a whole subtree.
|
162
|
-
|
163
|
-
You really should use "--last" (preferred) or "--prev" (if you must) to
|
164
|
-
ensure that no other change arrived (but note that this doesn't work
|
165
|
-
when deleting a subtree).
|
166
|
-
|
167
|
-
Non-recursively deleting an entry with children works and does *not*
|
168
|
-
affect the child entries.
|
169
|
-
|
170
|
-
The root entry cannot be deleted.
|
171
|
-
"""
|
172
|
-
args = {}
|
173
|
-
if eval_ and prev is NotGiven:
|
174
|
-
raise click.UsageError("You need to add a value that can be evaluated")
|
175
|
-
if recursive:
|
176
|
-
if prev is not NotGiven or last:
|
177
|
-
raise click.UsageError("You can't use a prev value when deleting recursively.")
|
178
|
-
if internal:
|
179
|
-
raise click.UsageError("'internal' and 'recursive' are mutually exclusive")
|
180
|
-
else:
|
181
|
-
if prev is not NotGiven:
|
182
|
-
if eval_:
|
183
|
-
prev = eval(prev) # pylint: disable=eval-used
|
184
|
-
args["prev"] = prev
|
185
|
-
if last:
|
186
|
-
args["chain"] = {"node": last[0], "tick": int(last[1])}
|
187
|
-
|
188
|
-
res = await obj.client.delete(path=obj.path, nchain=obj.meta, recursive=recursive, **args)
|
189
|
-
if isinstance(res, StreamedRequest):
|
190
|
-
pl = PathLongener(obj.path)
|
191
|
-
async for r in res:
|
192
|
-
pl(r)
|
193
|
-
if obj.meta:
|
194
|
-
yprint(r, stream=obj.stdout)
|
195
|
-
else:
|
196
|
-
if obj.meta:
|
197
|
-
yprint(res, stream=obj.stdout)
|
198
|
-
|
199
|
-
|
200
|
-
@cli.command()
|
201
|
-
@click.option("-s", "--state", is_flag=True, help="Also get the current state.")
|
202
|
-
@click.option("-o", "--only", is_flag=True, help="Value only, nothing fancy.")
|
203
|
-
@click.option("-p", "--path-only", is_flag=True, help="Value only, nothing fancy.")
|
204
|
-
@click.option("-D", "--add-date", is_flag=True, help="Add *_date entries")
|
205
|
-
@click.option("-i", "--ignore", multiple=True, type=P, help="Skip this (sub)tree")
|
206
|
-
@click.pass_obj
|
207
|
-
async def monitor(obj, state, only, path_only, add_date, ignore):
|
208
|
-
"""Monitor a MoaT-KV subtree"""
|
209
|
-
|
210
|
-
flushing = not state
|
211
|
-
seen = False
|
212
|
-
|
213
|
-
async with obj.client.watch(
|
214
|
-
obj.path,
|
215
|
-
nchain=obj.meta,
|
216
|
-
fetch=state,
|
217
|
-
max_depth=0 if only else -1,
|
218
|
-
long_path=False,
|
219
|
-
) as res:
|
220
|
-
pl = PathLongener(() if path_only else obj.path)
|
221
|
-
async for r in res:
|
222
|
-
if add_date and "value" in r:
|
223
|
-
add_dates(r.value)
|
224
|
-
if any(p == r.path[: len(p)] for p in ignore):
|
225
|
-
continue
|
226
|
-
pl(r)
|
227
|
-
if r.get("state", "") == "uptodate":
|
228
|
-
if only and not seen:
|
229
|
-
# value doesn't exist
|
230
|
-
return
|
231
|
-
flushing = True
|
232
|
-
if only or path_only:
|
233
|
-
continue
|
234
|
-
else:
|
235
|
-
del r["seq"]
|
236
|
-
if only or path_only:
|
237
|
-
try:
|
238
|
-
if path_only:
|
239
|
-
print(f"{r.path} {r.value}", file=obj.stdout)
|
240
|
-
else:
|
241
|
-
print(r.value, file=obj.stdout)
|
242
|
-
continue
|
243
|
-
except AttributeError:
|
244
|
-
# value has been deleted
|
245
|
-
continue
|
246
|
-
if flushing:
|
247
|
-
r["time"] = time.time()
|
248
|
-
r["_time"] = datetime.datetime.now().isoformat(sep=" ", timespec="milliseconds")
|
249
|
-
yprint(r, stream=obj.stdout)
|
250
|
-
print("---", file=obj.stdout)
|
251
|
-
if flushing:
|
252
|
-
obj.stdout.flush()
|
253
|
-
seen = True
|
254
|
-
|
255
|
-
|
256
|
-
@cli.command()
|
257
|
-
@click.option("-i", "--infile", type=click.Path(), help="File to read (msgpack).")
|
258
|
-
@click.pass_obj
|
259
|
-
async def update(obj, infile):
|
260
|
-
"""Send a list of updates to a MoaT-KV subtree"""
|
261
|
-
async with MsgReader(path=infile) as reader:
|
262
|
-
async for msg in reader:
|
263
|
-
if not hasattr(msg, "path"):
|
264
|
-
continue
|
265
|
-
await obj.client.set(obj.path + msg.path, value=msg.value)
|
@@ -1,143 +0,0 @@
|
|
1
|
-
# command line interface
|
2
|
-
from __future__ import annotations
|
3
|
-
|
4
|
-
import datetime
|
5
|
-
import sys
|
6
|
-
from collections.abc import Mapping
|
7
|
-
|
8
|
-
import asyncclick as click
|
9
|
-
from moat.mqtt.codecs import MsgPackCodec
|
10
|
-
from moat.util import (
|
11
|
-
MsgReader,
|
12
|
-
MsgWriter,
|
13
|
-
P,
|
14
|
-
Path,
|
15
|
-
PathLongener,
|
16
|
-
load_subgroup,
|
17
|
-
yload,
|
18
|
-
yprint,
|
19
|
-
)
|
20
|
-
|
21
|
-
from moat.kv.codec import unpacker
|
22
|
-
|
23
|
-
|
24
|
-
@load_subgroup(short_help="Local data mangling", sub_pre="dump")
|
25
|
-
async def cli():
|
26
|
-
"""
|
27
|
-
Low-level tools that don't depend on a running MoaT-KV server.
|
28
|
-
"""
|
29
|
-
pass
|
30
|
-
|
31
|
-
|
32
|
-
@cli.command()
|
33
|
-
@click.argument("node", nargs=1)
|
34
|
-
@click.argument("file", type=click.Path(), nargs=1)
|
35
|
-
async def init(node, file):
|
36
|
-
"""Write an initial preload file.
|
37
|
-
|
38
|
-
Usage: moat kv dump init <node> <outfile>
|
39
|
-
|
40
|
-
Writes an initial MoaT-KV file that behaves as if it was generated by <node>.
|
41
|
-
|
42
|
-
Using this command, followed by "moat kv server -l <outfile> <node>", is
|
43
|
-
equivalent to running "moat kv server -i 'Initial data' <node>.
|
44
|
-
"""
|
45
|
-
async with MsgWriter(path=file) as f:
|
46
|
-
await f(
|
47
|
-
dict(
|
48
|
-
chain=dict(node=node, tick=1, prev=None),
|
49
|
-
depth=0,
|
50
|
-
path=[],
|
51
|
-
tock=1,
|
52
|
-
value="Initial data",
|
53
|
-
),
|
54
|
-
)
|
55
|
-
|
56
|
-
|
57
|
-
@cli.command("msg")
|
58
|
-
@click.argument("path", nargs=1)
|
59
|
-
@click.pass_obj
|
60
|
-
async def msg_(obj, path):
|
61
|
-
"""
|
62
|
-
Monitor the server-to-sever message stream.
|
63
|
-
|
64
|
-
Use ':' for the main server's "update" stream.
|
65
|
-
Use '+NAME' to monitor a different stream instead.
|
66
|
-
Use '+' to monitor all streams.
|
67
|
-
Otherwise use the given name as-is; Mosquitto wildcard rules apply.
|
68
|
-
|
69
|
-
\b
|
70
|
-
Common streams (prefix with '+'):
|
71
|
-
* ping all servers
|
72
|
-
* update data changes
|
73
|
-
* del nodes responsible for cleaning up deleted records
|
74
|
-
"""
|
75
|
-
from moat.kv.backend import get_backend
|
76
|
-
|
77
|
-
class _Unpack:
|
78
|
-
def __init__(self):
|
79
|
-
self._part_cache = dict()
|
80
|
-
|
81
|
-
import moat.kv.server
|
82
|
-
|
83
|
-
_Unpack._unpack_multiple = moat.kv.server.Server._unpack_multiple
|
84
|
-
_unpacker = _Unpack()._unpack_multiple
|
85
|
-
|
86
|
-
path = P(path)
|
87
|
-
if len(path) == 0:
|
88
|
-
path = P(obj.cfg.server["root"]) | "update"
|
89
|
-
elif len(path) == 1 and path[0].startswith("+"): # pylint: disable=no-member # owch
|
90
|
-
p = path[0][1:]
|
91
|
-
path = P(obj.cfg.server["root"])
|
92
|
-
path |= p or "#"
|
93
|
-
be = obj.cfg.server.backend
|
94
|
-
kw = obj.cfg.server[be]
|
95
|
-
|
96
|
-
async with get_backend(be)(**kw) as conn:
|
97
|
-
async with conn.monitor(*path) as stream:
|
98
|
-
async for msg in stream:
|
99
|
-
v = vars(msg)
|
100
|
-
if isinstance(v.get("payload"), (bytearray, bytes)):
|
101
|
-
t = msg.topic
|
102
|
-
v = unpacker(v["payload"])
|
103
|
-
v = _unpacker(v)
|
104
|
-
if v is None:
|
105
|
-
continue
|
106
|
-
if not isinstance(v, Mapping):
|
107
|
-
v = {"_data": v}
|
108
|
-
v["_topic"] = Path.build(t)
|
109
|
-
else:
|
110
|
-
v["_type"] = type(msg).__name__
|
111
|
-
|
112
|
-
v["_timestamp"] = datetime.datetime.now().isoformat(
|
113
|
-
sep=" ",
|
114
|
-
timespec="milliseconds",
|
115
|
-
)
|
116
|
-
|
117
|
-
yprint(v, stream=obj.stdout)
|
118
|
-
print("---", file=obj.stdout)
|
119
|
-
|
120
|
-
|
121
|
-
@cli.command("post")
|
122
|
-
@click.argument("path", nargs=1)
|
123
|
-
@click.pass_obj
|
124
|
-
async def post_(obj, path):
|
125
|
-
"""
|
126
|
-
Send a msgpack-encoded message (or several) to a specific MQTT topic.
|
127
|
-
|
128
|
-
Messages are read from YAML.
|
129
|
-
Common streams:
|
130
|
-
* ping: sync: all servers (default)
|
131
|
-
* update: data changes
|
132
|
-
* del: sync: nodes responsible for cleaning up deleted records
|
133
|
-
"""
|
134
|
-
from moat.kv.backend import get_backend
|
135
|
-
|
136
|
-
path = P(path)
|
137
|
-
be = obj.cfg.server.backend
|
138
|
-
kw = obj.cfg.server[be]
|
139
|
-
|
140
|
-
async with get_backend(be)(codec=MsgPackCodec, **kw) as conn:
|
141
|
-
for d in yload(sys.stdin, multi=True):
|
142
|
-
topic = d.pop("_topic", path)
|
143
|
-
await conn.send(*topic, payload=d)
|
@@ -1,149 +0,0 @@
|
|
1
|
-
# command line interface
|
2
|
-
from __future__ import annotations
|
3
|
-
|
4
|
-
import sys
|
5
|
-
|
6
|
-
import asyncclick as click
|
7
|
-
from moat.util import P, Path, yprint
|
8
|
-
|
9
|
-
from moat.kv.data import add_dates
|
10
|
-
from moat.kv.errors import ErrorRoot
|
11
|
-
|
12
|
-
|
13
|
-
@click.group() # pylint: disable=undefined-variable
|
14
|
-
@click.pass_obj
|
15
|
-
async def cli(obj):
|
16
|
-
"""Manage error records in MoaT-KV."""
|
17
|
-
obj.err = await ErrorRoot.as_handler(obj.client)
|
18
|
-
|
19
|
-
|
20
|
-
@cli.command()
|
21
|
-
@click.option("-s", "--subsys", help="Subsystem to access")
|
22
|
-
@click.argument("path", nargs=1)
|
23
|
-
@click.pass_obj
|
24
|
-
async def resolve(obj, path, subsys):
|
25
|
-
"""
|
26
|
-
Mark an error as resolved.
|
27
|
-
"""
|
28
|
-
path = P(path)
|
29
|
-
if subsys:
|
30
|
-
e = obj.err.get_error_record(subsys, path)
|
31
|
-
else:
|
32
|
-
e = obj.err.follow(path)
|
33
|
-
if e.resolved:
|
34
|
-
print("Already resolved.", file=sys.stderr)
|
35
|
-
return
|
36
|
-
await e.resolve()
|
37
|
-
|
38
|
-
|
39
|
-
@cli.command()
|
40
|
-
@click.option("-n", "--node", help="add details from this node")
|
41
|
-
@click.option("-s", "--subsystem", "subsys", help="only show errors from this subsystem")
|
42
|
-
@click.option("-r", "--resolved", is_flag=True, help="only resolved errors")
|
43
|
-
@click.option("-v", "--verbose", count=True, help="add per-node details (-vv for traces)")
|
44
|
-
@click.option("-a", "--all-errors", is_flag=True, help="add details from all nodes")
|
45
|
-
@click.option("-d", "--as-dict", default=None, help="Dump a list of all open (or resolved) error.")
|
46
|
-
@click.option("-p", "--path", default=":", help="only show errors below this subpath")
|
47
|
-
@click.pass_obj
|
48
|
-
async def dump(obj, as_dict, path, node, all_errors, verbose, resolved, subsys):
|
49
|
-
"""Dump error entries."""
|
50
|
-
path = P(path)
|
51
|
-
path_ = obj.cfg["errors"].prefix
|
52
|
-
d = 2
|
53
|
-
|
54
|
-
async def one(r):
|
55
|
-
nonlocal y
|
56
|
-
val = r.value
|
57
|
-
if subsys and val.get("subsystem", "") != subsys:
|
58
|
-
return
|
59
|
-
if path and val.get("path", ())[: len(path)] != path:
|
60
|
-
return
|
61
|
-
|
62
|
-
if not all_errors and bool(val.get("resolved", False)) != resolved:
|
63
|
-
return
|
64
|
-
|
65
|
-
add_dates(val)
|
66
|
-
try:
|
67
|
-
rp = val.path
|
68
|
-
if as_dict:
|
69
|
-
del val.path
|
70
|
-
elif not isinstance(rp, Path):
|
71
|
-
rp = Path.build(rp)
|
72
|
-
except AttributeError:
|
73
|
-
rp = Path("incomplete") + r.path
|
74
|
-
if not as_dict:
|
75
|
-
val.path = rp
|
76
|
-
if rp[: len(path)] != path:
|
77
|
-
return
|
78
|
-
rp = rp[len(path) :]
|
79
|
-
if node is None:
|
80
|
-
val.syspath = r.path
|
81
|
-
else:
|
82
|
-
val.syspath = Path(node) + r.path
|
83
|
-
|
84
|
-
rn = {}
|
85
|
-
|
86
|
-
def disp(rr):
|
87
|
-
if node and rr.path[-1] != node:
|
88
|
-
return
|
89
|
-
val = rr.value
|
90
|
-
add_dates(val)
|
91
|
-
if verbose < 2:
|
92
|
-
rr.value.pop("trace", None)
|
93
|
-
rn[rr.path[-1]] = rr if obj.meta else rr.value
|
94
|
-
|
95
|
-
if verbose:
|
96
|
-
rs = await obj.client._request(
|
97
|
-
action="get_tree",
|
98
|
-
min_depth=1,
|
99
|
-
max_depth=1,
|
100
|
-
path=path_ + r.path[-2:],
|
101
|
-
iter=True,
|
102
|
-
nchain=3 if obj.meta else 0,
|
103
|
-
)
|
104
|
-
async for rr in rs:
|
105
|
-
disp(rr)
|
106
|
-
|
107
|
-
elif node:
|
108
|
-
rs = await obj.client.get(path_ + r.path[-2:] | node)
|
109
|
-
if "value" in rs:
|
110
|
-
disp(rs)
|
111
|
-
|
112
|
-
if rn:
|
113
|
-
val["nodes"] = rn
|
114
|
-
|
115
|
-
if as_dict is not None:
|
116
|
-
yy = y
|
117
|
-
if subsys is None:
|
118
|
-
yy = yy.setdefault(val.get("subsystem", "unknown"), {})
|
119
|
-
for p in rp:
|
120
|
-
yy = yy.setdefault(p, {})
|
121
|
-
yy[as_dict] = r if obj.meta else val
|
122
|
-
else:
|
123
|
-
yy = r if obj.meta else r.value
|
124
|
-
|
125
|
-
yprint([yy], stream=obj.stdout)
|
126
|
-
print("---", file=obj.stdout)
|
127
|
-
|
128
|
-
y = {}
|
129
|
-
res = None
|
130
|
-
|
131
|
-
if node is None and len(path) == 2 and isinstance(path[-1], int): # single error?
|
132
|
-
r = await obj.client.get(path_ + path, nchain=3 if obj.meta else 0)
|
133
|
-
# Mangle a few variables so that the output is still OK
|
134
|
-
r.path = path
|
135
|
-
node = None
|
136
|
-
|
137
|
-
async def ait(r):
|
138
|
-
yield r
|
139
|
-
|
140
|
-
res = ait(r)
|
141
|
-
path = ()
|
142
|
-
|
143
|
-
if res is None:
|
144
|
-
res = obj.client.get_tree(path_, min_depth=d, max_depth=d, nchain=3 if obj.meta else 0)
|
145
|
-
async for r in res:
|
146
|
-
await one(r)
|
147
|
-
|
148
|
-
if as_dict is not None:
|
149
|
-
yprint(y, stream=obj.stdout)
|