moat-kv 0.70.24__py3-none-any.whl → 0.71.6__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 (127) hide show
  1. moat/kv/__init__.py +6 -7
  2. moat/kv/_cfg.yaml +5 -8
  3. moat/kv/actor/__init__.py +2 -1
  4. moat/kv/actor/deletor.py +4 -1
  5. moat/kv/auth/__init__.py +12 -13
  6. moat/kv/auth/_test.py +4 -1
  7. moat/kv/auth/password.py +11 -7
  8. moat/kv/backend/mqtt.py +4 -8
  9. moat/kv/client.py +20 -39
  10. moat/kv/code.py +3 -3
  11. moat/kv/command/data.py +4 -3
  12. moat/kv/command/dump/__init__.py +29 -29
  13. moat/kv/command/internal.py +2 -3
  14. moat/kv/command/job.py +1 -2
  15. moat/kv/command/type.py +3 -6
  16. moat/kv/data.py +9 -8
  17. moat/kv/errors.py +16 -8
  18. moat/kv/mock/__init__.py +2 -12
  19. moat/kv/model.py +28 -32
  20. moat/kv/obj/__init__.py +3 -3
  21. moat/kv/obj/command.py +3 -3
  22. moat/kv/runner.py +4 -5
  23. moat/kv/server.py +106 -126
  24. moat/kv/types.py +8 -6
  25. {moat_kv-0.70.24.dist-info → moat_kv-0.71.6.dist-info}/METADATA +7 -6
  26. moat_kv-0.71.6.dist-info/RECORD +47 -0
  27. {moat_kv-0.70.24.dist-info → moat_kv-0.71.6.dist-info}/WHEEL +1 -1
  28. moat_kv-0.71.6.dist-info/licenses/LICENSE +3 -0
  29. moat_kv-0.71.6.dist-info/licenses/LICENSE.APACHE2 +202 -0
  30. moat_kv-0.71.6.dist-info/licenses/LICENSE.MIT +20 -0
  31. moat_kv-0.71.6.dist-info/top_level.txt +1 -0
  32. build/lib/docs/source/conf.py +0 -201
  33. build/lib/examples/pathify.py +0 -45
  34. build/lib/moat/kv/__init__.py +0 -19
  35. build/lib/moat/kv/_cfg.yaml +0 -97
  36. build/lib/moat/kv/_main.py +0 -91
  37. build/lib/moat/kv/actor/__init__.py +0 -98
  38. build/lib/moat/kv/actor/deletor.py +0 -139
  39. build/lib/moat/kv/auth/__init__.py +0 -444
  40. build/lib/moat/kv/auth/_test.py +0 -166
  41. build/lib/moat/kv/auth/password.py +0 -234
  42. build/lib/moat/kv/auth/root.py +0 -58
  43. build/lib/moat/kv/backend/__init__.py +0 -67
  44. build/lib/moat/kv/backend/mqtt.py +0 -74
  45. build/lib/moat/kv/backend/serf.py +0 -45
  46. build/lib/moat/kv/client.py +0 -1025
  47. build/lib/moat/kv/code.py +0 -236
  48. build/lib/moat/kv/codec.py +0 -11
  49. build/lib/moat/kv/command/__init__.py +0 -1
  50. build/lib/moat/kv/command/acl.py +0 -180
  51. build/lib/moat/kv/command/auth.py +0 -261
  52. build/lib/moat/kv/command/code.py +0 -293
  53. build/lib/moat/kv/command/codec.py +0 -186
  54. build/lib/moat/kv/command/data.py +0 -265
  55. build/lib/moat/kv/command/dump/__init__.py +0 -143
  56. build/lib/moat/kv/command/error.py +0 -149
  57. build/lib/moat/kv/command/internal.py +0 -248
  58. build/lib/moat/kv/command/job.py +0 -433
  59. build/lib/moat/kv/command/log.py +0 -53
  60. build/lib/moat/kv/command/server.py +0 -114
  61. build/lib/moat/kv/command/type.py +0 -201
  62. build/lib/moat/kv/config.py +0 -46
  63. build/lib/moat/kv/data.py +0 -216
  64. build/lib/moat/kv/errors.py +0 -561
  65. build/lib/moat/kv/exceptions.py +0 -126
  66. build/lib/moat/kv/mock/__init__.py +0 -101
  67. build/lib/moat/kv/mock/mqtt.py +0 -159
  68. build/lib/moat/kv/mock/serf.py +0 -250
  69. build/lib/moat/kv/mock/tracer.py +0 -63
  70. build/lib/moat/kv/model.py +0 -1069
  71. build/lib/moat/kv/obj/__init__.py +0 -646
  72. build/lib/moat/kv/obj/command.py +0 -241
  73. build/lib/moat/kv/runner.py +0 -1347
  74. build/lib/moat/kv/server.py +0 -2809
  75. build/lib/moat/kv/types.py +0 -513
  76. debian/moat-kv/usr/lib/python3/dist-packages/docs/source/conf.py +0 -201
  77. debian/moat-kv/usr/lib/python3/dist-packages/examples/pathify.py +0 -45
  78. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/__init__.py +0 -19
  79. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/_cfg.yaml +0 -97
  80. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/_main.py +0 -91
  81. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/actor/__init__.py +0 -98
  82. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/actor/deletor.py +0 -139
  83. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/__init__.py +0 -444
  84. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/_test.py +0 -166
  85. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/password.py +0 -234
  86. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/root.py +0 -58
  87. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/__init__.py +0 -67
  88. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/mqtt.py +0 -74
  89. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/serf.py +0 -45
  90. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/client.py +0 -1025
  91. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/code.py +0 -236
  92. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/codec.py +0 -11
  93. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/__init__.py +0 -1
  94. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/acl.py +0 -180
  95. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/auth.py +0 -261
  96. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/code.py +0 -293
  97. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/codec.py +0 -186
  98. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/data.py +0 -265
  99. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/dump/__init__.py +0 -143
  100. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/error.py +0 -149
  101. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/internal.py +0 -248
  102. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/job.py +0 -433
  103. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/log.py +0 -53
  104. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/server.py +0 -114
  105. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/type.py +0 -201
  106. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/config.py +0 -46
  107. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/data.py +0 -216
  108. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/errors.py +0 -561
  109. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/exceptions.py +0 -126
  110. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/__init__.py +0 -101
  111. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/mqtt.py +0 -159
  112. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/serf.py +0 -250
  113. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/tracer.py +0 -63
  114. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/model.py +0 -1069
  115. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/obj/__init__.py +0 -646
  116. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/obj/command.py +0 -241
  117. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/runner.py +0 -1347
  118. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/server.py +0 -2809
  119. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/types.py +0 -513
  120. docs/source/conf.py +0 -201
  121. examples/pathify.py +0 -45
  122. moat/kv/backend/serf.py +0 -45
  123. moat/kv/codec.py +0 -11
  124. moat/kv/mock/serf.py +0 -250
  125. moat_kv-0.70.24.dist-info/RECORD +0 -137
  126. moat_kv-0.70.24.dist-info/top_level.txt +0 -9
  127. {moat_kv-0.70.24.dist-info → moat_kv-0.71.6.dist-info}/licenses/LICENSE.txt +0 -0
@@ -1,248 +0,0 @@
1
- # command line interface
2
- from __future__ import annotations
3
-
4
- from collections.abc import Mapping
5
-
6
- import asyncclick as click
7
- from moat.util import P, PathLongener, yprint
8
- from range_set import RangeSet
9
-
10
-
11
- @click.group(short_help="Control internal state.") # pylint: disable=undefined-variable
12
- async def cli():
13
- """
14
- This subcommand queries and controls the server's internal state.
15
- """
16
- pass
17
-
18
-
19
- @cli.command()
20
- @click.option("-n", "--nodes", is_flag=True, help="Get node status.")
21
- @click.option("-d", "--deleted", is_flag=True, help="Get deletion status.")
22
- @click.option("-m", "--missing", is_flag=True, help="Get missing-node status.")
23
- @click.option(
24
- "-r",
25
- "--remote-missing",
26
- "remote_missing",
27
- is_flag=True,
28
- help="Get remote-missing-node status.",
29
- )
30
- @click.option("-p", "--present", is_flag=True, help="Get known-data status.")
31
- @click.option("-s", "--superseded", is_flag=True, help="Get superseded-data status.")
32
- @click.option("-D", "--debug", is_flag=True, help="Get internal verbosity.")
33
- @click.option("--debugger", is_flag=True, help="Start a remote debugger. DO NOT USE.")
34
- @click.option("-k", "--known", hidden=True, is_flag=True, help="Get superseded-data status.")
35
- @click.option("-a", "--all", is_flag=True, help="All available data.")
36
- @click.pass_obj
37
- async def state(obj, **flags):
38
- """
39
- Dump the server's state.
40
- """
41
- if flags.pop("known", None):
42
- flags["superseded"] = True
43
- if flags.pop("all", None):
44
- flags["superseded"] = True
45
- flags["present"] = True
46
- flags["nodes"] = True
47
- flags["deleted"] = True
48
- flags["missing"] = True
49
- flags["remote_missing"] = True
50
- res = await obj.client._request("get_state", iter=False, **flags)
51
- k = res.pop("known", None)
52
- if k is not None:
53
- res["superseded"] = k
54
- yprint(res, stream=obj.stdout)
55
-
56
-
57
- @cli.command()
58
- @click.option("-d", "--deleted", is_flag=True, help="Mark as deleted. Default: superseded")
59
- @click.option(
60
- "-n",
61
- "--node",
62
- "source",
63
- default="?",
64
- help="The node this message is faked as being from.",
65
- )
66
- @click.option("-b", "--broadcast", is_flag=True, help="Send to all servers")
67
- @click.argument("node", nargs=1)
68
- @click.argument("items", type=int, nargs=-1)
69
- @click.pass_obj
70
- async def mark(obj, deleted, source, node, items, broadcast):
71
- """
72
- Fix internal state. Use no items to fetch the current list from the
73
- server's ``missing`` state. Use an empty node name to add the whole
74
- list, not just a single node's.
75
-
76
- This is a dangerous command.
77
- """
78
-
79
- k = "deleted" if deleted else "superseded"
80
- if not items:
81
- r = await obj.client._request("get_state", iter=False, missing=True)
82
- r = r["missing"]
83
- if node != "":
84
- r = {node: r[node]}
85
- elif node == "":
86
- raise click.UsageError("You can't do that with an empty node")
87
- else:
88
- r = RangeSet()
89
- for i in items:
90
- r.add(i)
91
- r = {node: r.__getstate__()}
92
-
93
- msg = {k: r, "node": source}
94
-
95
- await obj.client._request("fake_info", iter=False, **msg)
96
- if broadcast:
97
- await obj.client._request("fake_info_send", iter=False, **msg)
98
-
99
- res = await obj.client._request("get_state", iter=False, **{k: True})
100
- yprint(res, stream=obj.stdout)
101
-
102
-
103
- @cli.command(short_help="Manage the Deleter list")
104
- @click.option("-d", "--delete", is_flag=True, help="Remove these nodes")
105
- @click.argument("nodes", nargs=-1)
106
- @click.pass_obj
107
- async def deleter(obj, delete, nodes):
108
- """
109
- Manage the Deleter list
110
-
111
- This is the set of nodes that must be online for removal of deleted
112
- entries from MoaT-KV's data.
113
-
114
- There should be one such node in every possible network partition.
115
- Also, all nodes with permanent storage should be on the list.
116
-
117
- Usage:
118
- - … deleter -- list state
119
- - … deleter NODE… -- add this node
120
- - … deleter -d NODE… -- remove this node
121
- """
122
-
123
- res = await obj.client._request(
124
- action="get_internal",
125
- path=("actor", "del"),
126
- iter=False,
127
- nchain=3 if delete or nodes else 2,
128
- )
129
- val = res.get("value", {})
130
- if isinstance(val, Mapping):
131
- val = val.get("nodes", [])
132
- # else: compatibility, TODO remove
133
- val = set(val)
134
- if delete:
135
- if nodes:
136
- val -= set(nodes)
137
- else:
138
- val = set()
139
- elif nodes:
140
- val |= set(nodes)
141
- else:
142
- yprint(res, stream=obj.stdout)
143
- return
144
-
145
- val = {"nodes": list(val)}
146
- res = await obj.client._request(
147
- action="set_internal",
148
- path=("actor", "del"),
149
- iter=False,
150
- chain=res.chain,
151
- value=val,
152
- )
153
- res.value = val
154
- yprint(res, stream=obj.stdout)
155
-
156
-
157
- @cli.command()
158
- @click.argument("path", nargs=1)
159
- @click.pass_obj
160
- async def dump(obj, path):
161
- """
162
- Dump internal state.
163
-
164
- This displays MoaT-KV's internal state.
165
- """
166
-
167
- path = P(path)
168
- y = {}
169
- pl = PathLongener()
170
- async for r in await obj.client._request("get_tree_internal", path=path, iter=True, nchain=0):
171
- pl(r)
172
- path = r["path"]
173
- yy = y
174
- for p in path:
175
- yy = yy.setdefault(p, {})
176
- try:
177
- yy["_"] = r["value"]
178
- except KeyError:
179
- pass
180
- yprint(y, stream=obj.stdout)
181
-
182
-
183
- @cli.command()
184
- @click.argument("node", nargs=1)
185
- @click.argument("tick", type=int, nargs=1)
186
- @click.pass_obj
187
- async def get(obj, node, tick):
188
- """
189
- Fetch data by node+tick.
190
-
191
- This looks up internal data.
192
- """
193
-
194
- res = await obj.client._request("get_value", node=node, tick=tick, nchain=99)
195
- if not obj.meta:
196
- res = res.value
197
- yprint(res, stream=obj.stdout)
198
-
199
-
200
- @cli.command()
201
- @click.option("-n", "--num", type=int, help="Return at most this many IDs")
202
- @click.option("-c", "--current", is_flag=True, help="Return only IDs with current data")
203
- @click.option("-C", "--copy", is_flag=True, help="Create an no-op change")
204
- @click.argument("node", nargs=1)
205
- @click.pass_obj
206
- async def enum(obj, node, num, current, copy):
207
- """
208
- List IDs of live data by a specific node.
209
-
210
- Can be used to determine whether a node still has live data,
211
- otherwise it can be deleted.
212
-
213
- If '--current' is set, only the IDs of the entries that have last been
214
- updated by that node are shown. '--copy' rewrites these entries.
215
-
216
- Increase verbosity to also show the oject paths.
217
- """
218
-
219
- res = await obj.client._request("enum_node", node=node, max=num, current=current)
220
- if obj.meta and not copy and obj.debug <= 1:
221
- yprint(res, stream=obj.stdout)
222
- else:
223
- for k in res.result:
224
- if copy or obj.debug > 1:
225
- res = await obj.client._request("get_value", node=node, tick=k, nchain=3)
226
- if obj.debug > 1:
227
- print(k, res.path)
228
- else:
229
- print(k)
230
- if copy and res.chain.node == node:
231
- res = await obj.client.set(res.path, value=res.value, chain=res.chain)
232
- else:
233
- print(k)
234
-
235
-
236
- @cli.command()
237
- @click.argument("node", nargs=1)
238
- @click.pass_obj
239
- async def kill(obj, node):
240
- """
241
- Remove a node from the node list.
242
-
243
- This command only works if this node does not have any current data in
244
- the system.
245
- """
246
- res = await obj.client._request("kill_node", node=node)
247
- if obj.meta:
248
- yprint(res, stream=obj.stdout)
@@ -1,433 +0,0 @@
1
- # command line interface
2
- from __future__ import annotations
3
-
4
- import sys
5
- import time
6
- from functools import partial
7
-
8
- import anyio
9
- import asyncclick as click
10
- from moat.util import P, Path, attr_args, attrdict, process_args, yprint
11
-
12
- from moat.kv.code import CodeRoot
13
- from moat.kv.data import add_dates, data_get
14
- from moat.kv.runner import AllRunnerRoot, AnyRunnerRoot, SingleRunnerRoot
15
-
16
-
17
- @click.group() # pylint: disable=undefined-variable
18
- @click.option("-n", "--node", help="node to run this code on. Empty: any one node, '-': all nodes")
19
- @click.option("-g", "--group", help="group to run this code on. Empty: default")
20
- @click.pass_context
21
- async def cli(ctx, node, group):
22
- """Run code stored in MoaT-KV.
23
-
24
- \b
25
- The option '-n' is somewhat special:
26
- -n - Jobs for all hosts
27
- -n XXX Jobs for host XXX
28
- (no -n) Jobs for any host
29
-
30
- The default group is 'default'.
31
- """
32
- obj = ctx.obj
33
- if group is None:
34
- group = "default"
35
- if group == "-":
36
- if node is not None:
37
- raise click.UsageError("'-g -' doesn't make sense with '-n'")
38
- if ctx.invoked_subcommand != "info":
39
- raise click.UsageError("'-g -' only makes sense with the 'info' command")
40
- obj.runner_root = SingleRunnerRoot
41
- subpath = (None,)
42
- elif not node:
43
- obj.runner_root = AnyRunnerRoot
44
- subpath = (group,)
45
- elif node == "-":
46
- obj.runner_root = AllRunnerRoot
47
- subpath = (group,)
48
- else:
49
- obj.runner_root = SingleRunnerRoot
50
- subpath = (node, group)
51
-
52
- cfg = obj.cfg["kv"]["runner"]
53
- obj.subpath = Path(cfg["sub"][obj.runner_root.SUB]) + subpath
54
- obj.path = cfg["prefix"] + obj.subpath
55
- obj.statepath = cfg["state"] + obj.subpath
56
-
57
-
58
- @cli.group("at", short_help="path of the job to operate on", invoke_without_command=True)
59
- @click.argument("path", nargs=1, type=P)
60
- @click.pass_context
61
- async def at_cli(ctx, path):
62
- """
63
- Add, list, modify, delete jobs at/under this path.
64
- """
65
- obj = ctx.obj
66
- obj.jobpath = path
67
-
68
- if ctx.invoked_subcommand is None:
69
- res = await obj.client.get(obj.path + path, nchain=obj.meta)
70
- yprint(
71
- res if obj.meta else res.value if "value" in res else None,
72
- stream=obj.stdout,
73
- )
74
-
75
-
76
- @cli.command("info")
77
- @click.pass_obj
78
- async def info_(obj):
79
- """
80
- List available groups for the node in question.
81
-
82
- \b
83
- Options (between 'job' and 'info')
84
- (none) list groups with jobs for any host
85
- -n - list groups with jobs for all hosts
86
- -g - list hosts that have specific jobs
87
- -n XXX list groups with jobs for a specific host
88
- """
89
- path = obj.path[:-1]
90
- async for r in obj.client.get_tree(path=path, min_depth=1, max_depth=1, empty=True):
91
- print(r.path[-1], file=obj.stdout)
92
-
93
-
94
- @at_cli.command("--help", hidden=True)
95
- @click.pass_context
96
- def help_(ctx): # pylint:disable=unused-variable # oh boy
97
- print(at_cli.get_help(ctx))
98
-
99
-
100
- @at_cli.command("path")
101
- @click.pass_obj
102
- async def path__(obj):
103
- """
104
- Emit the full path leading to the specified runner object.
105
-
106
- Useful for copying or for state monitoring.
107
-
108
- NEVER directly write to the state object. It's controlled by the
109
- runner. You'll confuse it if you do that.
110
-
111
- Updating the control object will cancel any running code.
112
- """
113
- path = obj.jobpath
114
- res = dict(command=obj.path + path, state=obj.statepath + path)
115
- yprint(res, stream=obj.stdout)
116
-
117
-
118
- @cli.command("run")
119
- @click.option(
120
- "-n",
121
- "--nodes",
122
- type=int,
123
- default=0,
124
- help="Size of the group (not for single-node runners)",
125
- )
126
- @click.pass_obj
127
- async def run(obj, nodes):
128
- """
129
- Run code that needs to run.
130
-
131
- This does not return.
132
- """
133
- from moat.util import as_service
134
-
135
- if obj.subpath[-1] == "-":
136
- raise click.UsageError("Group '-' can only be used for listing.")
137
- if nodes and obj.runner_root is SingleRunnerRoot:
138
- raise click.UsageError("A single-site runner doesn't have a size.")
139
-
140
- async with as_service(obj) as evt:
141
- c = obj.client
142
- cr = await CodeRoot.as_handler(c)
143
- await obj.runner_root.as_handler(
144
- c,
145
- subpath=obj.subpath,
146
- code=cr,
147
- **({"nodes": nodes} if nodes else {}),
148
- )
149
- evt.set()
150
- await anyio.sleep_forever()
151
-
152
-
153
- async def _state_fix(obj, state, state_only, path, r):
154
- try:
155
- val = r.value
156
- except AttributeError:
157
- return
158
- if state:
159
- rs = await obj.client._request(
160
- action="get_value",
161
- path=state + r.path,
162
- iter=False,
163
- nchain=obj.meta,
164
- )
165
- if state_only:
166
- r.value = rs
167
- else:
168
- if obj.meta:
169
- val["state"] = rs
170
- elif "value" in rs:
171
- val["state"] = rs.value
172
- if "value" in rs:
173
- add_dates(rs.value)
174
- if not state_only:
175
- if path:
176
- r.path = path + r.path
177
- add_dates(val)
178
-
179
- return r
180
-
181
-
182
- @at_cli.command("list")
183
- @click.option("-s", "--state", is_flag=True, help="Add state data")
184
- @click.option("-S", "--state-only", is_flag=True, help="Only output state data")
185
- @click.option("-t", "--table", is_flag=True, help="one-line output")
186
- @click.option(
187
- "-d",
188
- "--as-dict",
189
- default=None,
190
- help="Structure as dictionary. The argument is the key to use "
191
- "for values. Default: return as list",
192
- )
193
- @click.pass_obj
194
- async def list_(obj, state, state_only, table, as_dict):
195
- """List run entries."""
196
- if table and state:
197
- raise click.UsageError("'--table' and '--state' are mutually exclusive")
198
-
199
- path = obj.jobpath
200
-
201
- if state or state_only or table:
202
- state = obj.statepath + path
203
-
204
- if table:
205
- from moat.kv.errors import ErrorRoot
206
-
207
- err = await ErrorRoot.as_handler(obj.client)
208
-
209
- async for r in obj.client.get_tree(obj.path + path):
210
- p = path + r.path
211
- s = await obj.client.get(state + r.path)
212
- if "value" not in s:
213
- st = "-never-"
214
- elif s.value.started > s.value.stopped:
215
- st = s.value.node
216
- else:
217
- try:
218
- e = await err.get_error_record("run", obj.path + p, create=False)
219
- except KeyError:
220
- st = "-stopped-"
221
- else:
222
- if e is None or e.resolved:
223
- st = "-stopped-"
224
- else:
225
- st = " | ".join(
226
- "%s %s"
227
- % (
228
- Path.build(e.subpath)
229
- if e._path[-2] == ee._path[-1]
230
- else Path.build(ee.subpath),
231
- getattr(ee, "message", None)
232
- or getattr(ee, "comment", None)
233
- or "-",
234
- )
235
- for ee in e
236
- )
237
- print(p, r.value.code, st, file=obj.stdout)
238
-
239
- else:
240
- await data_get(
241
- obj,
242
- obj.path + path,
243
- as_dict=as_dict,
244
- item_mangle=partial(_state_fix, obj, state, state_only, None if as_dict else path),
245
- )
246
-
247
-
248
- @at_cli.command("state")
249
- @click.option("-r", "--result", is_flag=True, help="Just print the actual result.")
250
- @click.pass_obj
251
- async def state_(obj, result):
252
- """Get the status of a runner entry."""
253
- path = obj.jobpath
254
-
255
- if obj.subpath[-1] == "-":
256
- raise click.UsageError("Group '-' can only be used for listing.")
257
- if result and obj.meta:
258
- raise click.UsageError("You can't use '-v' and '-r' at the same time.")
259
- if not len(path):
260
- raise click.UsageError("You need a non-empty path.")
261
- path = obj.statepath + obj.jobpath
262
-
263
- res = await obj.client.get(path, nchain=obj.meta)
264
- if "value" not in res:
265
- if obj.debug:
266
- print("Not found (yet?)", file=sys.stderr)
267
- sys.exit(1)
268
-
269
- add_dates(res.value)
270
- if not obj.meta:
271
- res = res.value
272
- yprint(res, stream=obj.stdout)
273
-
274
-
275
- @at_cli.command()
276
- @click.option("-s", "--state", is_flag=True, help="Add state data")
277
- @click.pass_obj
278
- async def get(obj, state):
279
- """Read a runner entry"""
280
- path = obj.jobpath
281
- if obj.subpath[-1] == "-":
282
- raise click.UsageError("Group '-' can only be used for listing.")
283
- if not path:
284
- raise click.UsageError("You need a non-empty path.")
285
-
286
- res = await obj.client._request(
287
- action="get_value",
288
- path=obj.path + path,
289
- iter=False,
290
- nchain=obj.meta,
291
- )
292
- if "value" not in res:
293
- print("Not found.", file=sys.stderr)
294
- return
295
- res.path = path
296
- if state:
297
- state = obj.statepath
298
- await _state_fix(obj, state, False, path, res)
299
- if not obj.meta:
300
- res = res.value
301
-
302
- yprint(res, stream=obj.stdout)
303
-
304
-
305
- @at_cli.command()
306
- @click.option("-f", "--force", is_flag=True, help="Force deletion even if messy")
307
- @click.pass_obj
308
- async def delete(obj, force):
309
- """Remove a runner entry"""
310
- path = obj.jobpath
311
-
312
- if obj.subpath[-1] == "-":
313
- raise click.UsageError("Group '-' can only be used for listing.")
314
- if not path:
315
- raise click.UsageError("You need a non-empty path.")
316
-
317
- res = await obj.client.get(obj.path + path, nchain=3)
318
- if "value" not in res:
319
- res.info = "Does not exist."
320
- else:
321
- val = res.value
322
- if "target" not in val:
323
- val.target = None
324
- if val.target is not None:
325
- val.target = None
326
- res = await obj.client.set(obj.path + path, value=val, nchain=3, chain=res.chain)
327
- if not force:
328
- res.info = "'target' was set: cleared but not deleted."
329
- if force or val.target is None:
330
- sres = await obj.client.get(obj.statepath + path, nchain=3)
331
- if not force and "value" in sres and sres.value.stopped < sres.value.started:
332
- res.info = "Still running, not deleted."
333
- else:
334
- sres = await obj.client.delete(obj.statepath + path, chain=sres.chain)
335
- res = await obj.client.delete(obj.path + path, chain=res.chain)
336
- if "value" in res and res.value.stopped < res.value.started:
337
- res.info = "Deleted (unclean!)."
338
- else:
339
- res.info = "Deleted."
340
-
341
- if obj.meta:
342
- yprint(res, stream=obj.stdout)
343
- elif obj.debug:
344
- print(res.info)
345
-
346
-
347
- @at_cli.command("set")
348
- @click.option("-c", "--code", help="Path to the code that should run.")
349
- @click.option("-C", "--copy", help="Use this entry as a template.")
350
- @click.option("-t", "--time", "tm", help="time the code should next run at. '-':not")
351
- @click.option("-r", "--repeat", type=int, help="Seconds the code should re-run after")
352
- @click.option("-k", "--ok", type=float, help="Code is OK if it ran this many seconds")
353
- @click.option("-b", "--backoff", type=float, help="Back-off factor. Default: 1.4")
354
- @click.option("-d", "--delay", type=int, help="Seconds the code should retry after (w/ backoff)")
355
- @click.option("-i", "--info", help="Short human-readable information")
356
- @attr_args
357
- @click.pass_obj
358
- async def set_(obj, code, tm, info, ok, repeat, delay, backoff, copy, **kw):
359
- """Add or modify a runner.
360
-
361
- Code typically requires some input parameters.
362
-
363
- You should use '-v NAME VALUE' for string values, '-p NAME VALUE' for
364
- paths, and '-e NAME VALUE' for other data. '-e NAME -' deletes an item.
365
- """
366
- path = obj.jobpath
367
-
368
- if obj.subpath[-1] == "-":
369
- raise click.UsageError("Group '-' can only be used for listing.")
370
-
371
- if code is not None:
372
- code = P(code)
373
- if copy:
374
- copy = P(copy)
375
- path = obj.path + P(path)
376
-
377
- res = await obj.client._request(action="get_value", path=copy or path, iter=False, nchain=3)
378
- if "value" not in res:
379
- if copy:
380
- raise click.UsageError("--copy: use the complete path to an existing entry")
381
- elif code is None:
382
- raise click.UsageError("New entry, need code")
383
- res = {}
384
- chain = None
385
- else:
386
- chain = None if copy else res["chain"]
387
- res = res["value"]
388
- if copy and "code" not in res:
389
- raise click.UsageError("'--copy' needs a runner entry")
390
-
391
- vl = attrdict(**res.setdefault("data", {}))
392
- vl = process_args(vl, **kw)
393
- res["data"] = vl
394
-
395
- if code is not None:
396
- res["code"] = code
397
- if ok is not None:
398
- res["ok_after"] = ok
399
- if info is not None:
400
- res["info"] = info
401
- if backoff is not None:
402
- res["backoff"] = backoff
403
- if delay is not None:
404
- res["delay"] = delay
405
- if repeat is not None:
406
- res["repeat"] = repeat
407
- if tm is not None:
408
- if tm == "-":
409
- res["target"] = None
410
- else:
411
- res["target"] = time.time() + float(tm)
412
-
413
- res = await obj.client.set(path, value=res, nchain=3, chain=chain)
414
- if obj.meta:
415
- yprint(res, stream=obj.stdout)
416
-
417
-
418
- @cli.command(short_help="Show runners' keepalive messages")
419
- @click.pass_obj
420
- async def monitor(obj):
421
- """
422
- Runners periodically send a keepalive message. Show them.
423
- """
424
-
425
- # TODO this does not watch changes in MoaT-KV.
426
- # It also should watch individual jobs' state changes.
427
- if obj.subpath[-1] == "-":
428
- raise click.UsageError("Group '-' can only be used for listing.")
429
-
430
- async with obj.client.msg_monitor("run") as cl:
431
- async for msg in cl:
432
- yprint(msg, stream=obj.stdout)
433
- print("---", file=obj.stdout)