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