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.
Files changed (178) hide show
  1. moat/kv/__init__.py +6 -7
  2. moat/kv/_cfg.yaml +3 -2
  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 -5
  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 +36 -34
  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 +29 -33
  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 +10 -12
  25. {moat_kv-0.71.0.dist-info → moat_kv-0.71.7.dist-info}/METADATA +6 -2
  26. moat_kv-0.71.7.dist-info/RECORD +47 -0
  27. {moat_kv-0.71.0.dist-info → moat_kv-0.71.7.dist-info}/WHEEL +1 -1
  28. moat_kv-0.71.7.dist-info/licenses/LICENSE +3 -0
  29. moat_kv-0.71.7.dist-info/licenses/LICENSE.APACHE2 +202 -0
  30. moat_kv-0.71.7.dist-info/licenses/LICENSE.MIT +20 -0
  31. moat_kv-0.71.7.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 -93
  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 -71
  45. build/lib/moat/kv/client.py +0 -1025
  46. build/lib/moat/kv/code.py +0 -236
  47. build/lib/moat/kv/codec.py +0 -11
  48. build/lib/moat/kv/command/__init__.py +0 -1
  49. build/lib/moat/kv/command/acl.py +0 -180
  50. build/lib/moat/kv/command/auth.py +0 -261
  51. build/lib/moat/kv/command/code.py +0 -293
  52. build/lib/moat/kv/command/codec.py +0 -186
  53. build/lib/moat/kv/command/data.py +0 -265
  54. build/lib/moat/kv/command/dump/__init__.py +0 -143
  55. build/lib/moat/kv/command/error.py +0 -149
  56. build/lib/moat/kv/command/internal.py +0 -248
  57. build/lib/moat/kv/command/job.py +0 -433
  58. build/lib/moat/kv/command/log.py +0 -53
  59. build/lib/moat/kv/command/server.py +0 -114
  60. build/lib/moat/kv/command/type.py +0 -201
  61. build/lib/moat/kv/config.py +0 -46
  62. build/lib/moat/kv/data.py +0 -216
  63. build/lib/moat/kv/errors.py +0 -561
  64. build/lib/moat/kv/exceptions.py +0 -126
  65. build/lib/moat/kv/mock/__init__.py +0 -101
  66. build/lib/moat/kv/mock/mqtt.py +0 -159
  67. build/lib/moat/kv/mock/tracer.py +0 -63
  68. build/lib/moat/kv/model.py +0 -1069
  69. build/lib/moat/kv/obj/__init__.py +0 -646
  70. build/lib/moat/kv/obj/command.py +0 -241
  71. build/lib/moat/kv/runner.py +0 -1347
  72. build/lib/moat/kv/server.py +0 -2809
  73. build/lib/moat/kv/types.py +0 -513
  74. ci/rtd-requirements.txt +0 -4
  75. ci/test-requirements.txt +0 -7
  76. ci/travis.sh +0 -96
  77. debian/.gitignore +0 -7
  78. debian/changelog +0 -1435
  79. debian/control +0 -43
  80. debian/moat-kv/usr/lib/python3/dist-packages/docs/source/conf.py +0 -201
  81. debian/moat-kv/usr/lib/python3/dist-packages/examples/pathify.py +0 -45
  82. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/__init__.py +0 -19
  83. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/_cfg.yaml +0 -93
  84. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/_main.py +0 -91
  85. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/actor/__init__.py +0 -98
  86. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/actor/deletor.py +0 -139
  87. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/__init__.py +0 -444
  88. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/_test.py +0 -166
  89. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/password.py +0 -234
  90. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/root.py +0 -58
  91. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/__init__.py +0 -67
  92. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/mqtt.py +0 -71
  93. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/client.py +0 -1025
  94. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/code.py +0 -236
  95. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/codec.py +0 -11
  96. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/__init__.py +0 -1
  97. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/acl.py +0 -180
  98. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/auth.py +0 -261
  99. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/code.py +0 -293
  100. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/codec.py +0 -186
  101. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/data.py +0 -265
  102. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/dump/__init__.py +0 -143
  103. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/error.py +0 -149
  104. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/internal.py +0 -248
  105. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/job.py +0 -433
  106. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/log.py +0 -53
  107. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/server.py +0 -114
  108. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/type.py +0 -201
  109. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/config.py +0 -46
  110. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/data.py +0 -216
  111. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/errors.py +0 -561
  112. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/exceptions.py +0 -126
  113. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/__init__.py +0 -101
  114. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/mqtt.py +0 -159
  115. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/tracer.py +0 -63
  116. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/model.py +0 -1069
  117. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/obj/__init__.py +0 -646
  118. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/obj/command.py +0 -241
  119. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/runner.py +0 -1347
  120. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/server.py +0 -2809
  121. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/types.py +0 -513
  122. debian/moat-kv.postinst +0 -3
  123. debian/rules +0 -20
  124. debian/source/format +0 -1
  125. debian/watch +0 -4
  126. docs/Makefile +0 -20
  127. docs/make.bat +0 -36
  128. docs/source/TODO.rst +0 -61
  129. docs/source/_static/.gitkeep +0 -0
  130. docs/source/acls.rst +0 -80
  131. docs/source/auth.rst +0 -84
  132. docs/source/client_protocol.rst +0 -456
  133. docs/source/code.rst +0 -341
  134. docs/source/command_line.rst +0 -1187
  135. docs/source/common_protocol.rst +0 -47
  136. docs/source/conf.py +0 -201
  137. docs/source/debugging.rst +0 -70
  138. docs/source/extend.rst +0 -37
  139. docs/source/history.rst +0 -36
  140. docs/source/index.rst +0 -75
  141. docs/source/model.rst +0 -54
  142. docs/source/overview.rst +0 -83
  143. docs/source/related.rst +0 -89
  144. docs/source/server_protocol.rst +0 -450
  145. docs/source/startup.rst +0 -31
  146. docs/source/translator.rst +0 -244
  147. docs/source/tutorial.rst +0 -711
  148. docs/source/v3.rst +0 -168
  149. examples/code/transform.scale.yml +0 -21
  150. examples/code/transform.switch.yml +0 -82
  151. examples/code/transform.timeslot.yml +0 -63
  152. examples/pathify.py +0 -45
  153. moat/kv/codec.py +0 -11
  154. moat_kv-0.71.0.dist-info/RECORD +0 -188
  155. moat_kv-0.71.0.dist-info/top_level.txt +0 -9
  156. scripts/current +0 -15
  157. scripts/env +0 -8
  158. scripts/init +0 -39
  159. scripts/recover +0 -17
  160. scripts/rotate +0 -33
  161. scripts/run +0 -29
  162. scripts/run-all +0 -10
  163. scripts/run-any +0 -10
  164. scripts/run-single +0 -15
  165. scripts/success +0 -4
  166. systemd/moat-kv-recover.service +0 -21
  167. systemd/moat-kv-rotate.service +0 -20
  168. systemd/moat-kv-rotate.timer +0 -10
  169. systemd/moat-kv-run-all.service +0 -26
  170. systemd/moat-kv-run-all@.service +0 -25
  171. systemd/moat-kv-run-any.service +0 -26
  172. systemd/moat-kv-run-any@.service +0 -25
  173. systemd/moat-kv-run-single.service +0 -26
  174. systemd/moat-kv-run-single@.service +0 -25
  175. systemd/moat-kv.service +0 -27
  176. systemd/postinst +0 -7
  177. systemd/sysusers +0 -3
  178. {moat_kv-0.71.0.dist-info → moat_kv-0.71.7.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)