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,293 @@
1
+ # command line interface
2
+ from __future__ import annotations
3
+
4
+ import sys
5
+
6
+ import asyncclick as click
7
+ from moat.util import (
8
+ NotGiven,
9
+ P,
10
+ Path,
11
+ PathLongener,
12
+ attr_args,
13
+ process_args,
14
+ yload,
15
+ yprint,
16
+ )
17
+
18
+
19
+ @click.group(invoke_without_command=True) # pylint: disable=undefined-variable
20
+ @click.argument("path", nargs=1, type=P)
21
+ @click.pass_context
22
+ async def cli(ctx, path):
23
+ """Manage code stored in MoaT-KV."""
24
+ obj = ctx.obj
25
+ obj.path = obj.cfg["kv"]["codes"]["prefix"] + path
26
+ obj.codepath = path
27
+
28
+ if ctx.invoked_subcommand is None:
29
+ pl = PathLongener(path)
30
+ async for res in obj.client.get_tree(obj.path, long_path=False):
31
+ pl(res)
32
+ print(Path(*res.path), res.value.info, file=obj.stdout)
33
+
34
+
35
+ @cli.command()
36
+ @click.option("-s", "--script", type=click.File(mode="w", lazy=True), help="Save the code here")
37
+ @click.pass_obj
38
+ async def get(obj, script):
39
+ """Read a code entry"""
40
+ if not len(obj.codepath):
41
+ raise click.UsageError("You need a non-empty path.")
42
+
43
+ res = await obj.client._request(action="get_value", path=obj.path, iter=False, nchain=obj.meta)
44
+ if "value" not in res:
45
+ if obj.debug:
46
+ print("No entry here.", file=sys.stderr)
47
+ sys.exit(1)
48
+ if not obj.meta:
49
+ res = res.value
50
+ if script:
51
+ code = res.pop("code", None)
52
+ if code is not None:
53
+ print(code, file=script)
54
+ yprint(res, stream=obj.stdout)
55
+
56
+
57
+ @cli.command("set")
58
+ @click.option(
59
+ "-a/-A",
60
+ "--async/--sync",
61
+ "async_",
62
+ is_flag=True,
63
+ help="The code is async / sync (default: async)",
64
+ default=True,
65
+ )
66
+ @click.option("-t", "--thread", is_flag=True, help="The code should run in a worker thread")
67
+ @click.option("-s", "--script", type=click.File(mode="r"), help="File with the code")
68
+ @click.option("-i", "--info", type=str, help="one-liner info about the code")
69
+ @click.option("-d", "--data", type=click.File(mode="r"), help="load the metadata (YAML)")
70
+ @attr_args
71
+ @click.pass_obj
72
+ async def set_(obj, thread, script, data, async_, info, **kw):
73
+ """Save Python code.
74
+
75
+ The code may have inputs. You specify the inputs and their default
76
+ values with '-v VAR VALUE' (string), '-p VAR PATH' (MoaT-KV path), or
77
+ '-e VAR EXPR' (simple Python expression). Use '-e VAR -' to state that
78
+ VAR shall not have a default value, and '-e VAR /' to delete VAR from
79
+ the list of inputs entirely.
80
+ """
81
+ if thread:
82
+ async_ = False
83
+ elif not async_:
84
+ async_ = None
85
+
86
+ if not len(obj.codepath):
87
+ raise click.UsageError("You need a non-empty path.")
88
+ path = obj.path
89
+
90
+ if data:
91
+ msg = yload(data)
92
+ else:
93
+ msg = await obj.client.get(path, nchain=3)
94
+ chain = NotGiven
95
+ if "value" in msg:
96
+ chain = msg.get("chain", NotGiven)
97
+ msg = msg["value"]
98
+ if async_ is not None or "is_async" not in msg:
99
+ msg["is_async"] = async_
100
+
101
+ if info is not None:
102
+ msg["info"] = info
103
+
104
+ if script:
105
+ msg["code"] = script.read()
106
+ elif "code" not in msg:
107
+ raise click.UsageError("Missing script")
108
+
109
+ if "vars" in msg:
110
+ vs = set(msg["vars"])
111
+ else:
112
+ vs = set()
113
+ vd = msg.setdefault("default", {})
114
+
115
+ vd = process_args(vd, vs=vs, **kw)
116
+ msg["vars"] = list(vs)
117
+ msg["default"] = vd
118
+
119
+ kv = {}
120
+ if chain is not NotGiven:
121
+ kv["chain"] = chain
122
+
123
+ res = await obj.client.set(obj.path, value=msg, nchain=obj.meta, **kv)
124
+ if obj.meta:
125
+ yprint(res, stream=obj.stdout)
126
+
127
+
128
+ # disabled for now
129
+ @cli.group("module", hidden=True)
130
+ async def mod():
131
+ """
132
+ Change the code of a module stored in MoaT-KV
133
+ """
134
+
135
+
136
+ @mod.command("get")
137
+ @click.option("-s", "--script", type=click.File(mode="w", lazy=True), help="Save the code here")
138
+ @click.argument("path", nargs=1)
139
+ @click.pass_obj # pylint: disable=function-redefined
140
+ async def get_mod(obj, path, script):
141
+ """Read a module entry"""
142
+ path = P(path)
143
+ if not len(path):
144
+ raise click.UsageError("You need a non-empty path.")
145
+ res = await obj.client._request(
146
+ action="get_value",
147
+ path=obj.cfg["kv"]["modules"]["prefix"] + path,
148
+ iter=False,
149
+ nchain=obj.meta,
150
+ )
151
+ if not obj.meta:
152
+ res = res.value
153
+
154
+ code = res.pop("code", None)
155
+ if code is not None:
156
+ code = code.rstrip("\n \t") + "\n"
157
+ if script:
158
+ print(code, file=script)
159
+ else:
160
+ res["code"] = code
161
+
162
+ yprint(res, stream=obj.stdout)
163
+
164
+
165
+ @mod.command("set")
166
+ @click.option("-s", "--script", type=click.File(mode="r"), help="File with the module's code")
167
+ @click.option("-d", "--data", type=click.File(mode="r"), help="load the metadata (YAML)")
168
+ @click.argument("path", nargs=1) # pylint: disable=function-redefined
169
+ @click.pass_obj
170
+ async def set_mod(obj, path, script, data):
171
+ """Save a Python module to MoaT-KV."""
172
+ path = P(path)
173
+ if not len(path):
174
+ raise click.UsageError("You need a non-empty path.")
175
+
176
+ if data:
177
+ msg = yload(data)
178
+ else:
179
+ msg = {}
180
+ chain = None
181
+ if "value" in msg:
182
+ chain = msg.get("chain", None)
183
+ msg = msg["value"]
184
+
185
+ if "code" not in msg:
186
+ if script:
187
+ raise click.UsageError("Duplicate script")
188
+ else:
189
+ if not script:
190
+ raise click.UsageError("Missing script")
191
+ msg["code"] = script.read()
192
+
193
+ res = await obj.client.set(
194
+ *obj.cfg["kv"]["modules"]["prefix"],
195
+ *path,
196
+ value=msg,
197
+ iter=False,
198
+ nchain=obj.meta,
199
+ chain=chain,
200
+ )
201
+ if obj.meta:
202
+ yprint(res, stream=obj.stdout)
203
+
204
+
205
+ @cli.command("list")
206
+ @click.option(
207
+ "-d",
208
+ "--as-dict",
209
+ default=None,
210
+ help="Structure as dictionary. The argument is the key to use "
211
+ "for values. Default: return as list",
212
+ )
213
+ @click.option(
214
+ "-m",
215
+ "--maxdepth",
216
+ type=int,
217
+ default=None,
218
+ help="Limit recursion depth. Default: whole tree",
219
+ )
220
+ @click.option(
221
+ "-M",
222
+ "--mindepth",
223
+ type=int,
224
+ default=None,
225
+ help="Starting depth. Default: whole tree",
226
+ )
227
+ @click.option("-f", "--full", is_flag=True, help="print complete entries.")
228
+ @click.option("-s", "--short", is_flag=True, help="print shortened entries.")
229
+ @click.pass_obj
230
+ async def list_(obj, as_dict, maxdepth, mindepth, full, short):
231
+ """
232
+ List code entries.
233
+
234
+ Be aware that the whole subtree will be read before anything is
235
+ printed if you use the `--as-dict` option.
236
+ """
237
+
238
+ if (full or as_dict) and short:
239
+ raise click.UsageError("'-f'/'-d' and '-s' are incompatible.")
240
+ kw = {}
241
+ if maxdepth is not None:
242
+ kw["max_depth"] = maxdepth
243
+ if mindepth is not None:
244
+ kw["min_depth"] = mindepth
245
+ y = {}
246
+ async for r in obj.client.get_tree(obj.path, nchain=obj.meta, **kw):
247
+ r.pop("seq", None)
248
+ path = r.pop("path")
249
+ if not full:
250
+ if "info" not in r.value:
251
+ r.value.info = f"<{len(r.value.code.splitlines())} lines>"
252
+ del r.value["code"]
253
+
254
+ if short:
255
+ print(path, "::", r.value.info)
256
+ continue
257
+
258
+ if as_dict is not None:
259
+ yy = y
260
+ for p in path:
261
+ yy = yy.setdefault(p, {})
262
+ try:
263
+ yy[as_dict] = r if obj.meta else r.value
264
+ except AttributeError:
265
+ continue
266
+ else:
267
+ y = {}
268
+ try:
269
+ y[path] = r if obj.meta else r.value
270
+ except AttributeError:
271
+ continue
272
+ yprint([y], stream=obj.stdout)
273
+
274
+ if as_dict is not None:
275
+ yprint(y, stream=obj.stdout)
276
+ return
277
+
278
+
279
+ @cli.command()
280
+ @click.pass_obj
281
+ async def delete(obj):
282
+ """Remove a code entry"""
283
+ res = await obj.client.get(obj.path, nchain=3)
284
+ if "value" not in res:
285
+ res.info = "Does not exist."
286
+ else:
287
+ res = await obj.client.delete(obj.path, chain=res.chain)
288
+ res.info = "Deleted."
289
+
290
+ if obj.meta:
291
+ yprint(res, stream=obj.stdout)
292
+ elif obj.debug:
293
+ print(res.info, file=obj.stdout)
@@ -0,0 +1,186 @@
1
+ # command line interface
2
+ from __future__ import annotations
3
+
4
+ import asyncclick as click
5
+ from moat.util import NotGiven, P, Path, PathLongener, yload, yprint
6
+
7
+
8
+ @click.group() # pylint: disable=undefined-variable
9
+ async def cli():
10
+ """Manage codecs and converters. Usage: … codec …"""
11
+ pass
12
+
13
+
14
+ @cli.command()
15
+ @click.option("-e", "--encode", type=click.File(mode="w", lazy=True), help="Save the encoder here")
16
+ @click.option("-d", "--decode", type=click.File(mode="w", lazy=True), help="Save the decoder here")
17
+ @click.option("-s", "--script", type=click.File(mode="w", lazy=True), help="Save the data here")
18
+ @click.argument("path", nargs=1)
19
+ @click.pass_obj
20
+ async def get(obj, path, script, encode, decode):
21
+ """Read type information"""
22
+ path = P(path)
23
+ if not len(path):
24
+ raise click.UsageError("You need a non-empty path.")
25
+ res = await obj.client._request(
26
+ action="get_internal",
27
+ path=Path("codec") + path,
28
+ iter=False,
29
+ nchain=obj.meta,
30
+ )
31
+ if encode and res.get("encode", None) is not None:
32
+ encode.write(res.pop("encode"))
33
+ if decode and res.get("decode", None) is not None:
34
+ decode.write(res.pop("decode"))
35
+
36
+ if not obj.meta:
37
+ res = res.value
38
+ yprint(res, stream=script or obj.stdout)
39
+
40
+
41
+ @cli.command(name="list")
42
+ @click.pass_obj
43
+ @click.argument("path", nargs=1)
44
+ async def list_(obj, path):
45
+ """List type information entries"""
46
+ res = await obj.client._request(
47
+ action="get_tree_internal",
48
+ path=Path("codec") + P(path),
49
+ iter=True,
50
+ nchain=obj.meta,
51
+ )
52
+ pl = PathLongener(())
53
+ async for r in res:
54
+ pl(r)
55
+ print(" ".join(str(x) for x in r.path), file=obj.stdout)
56
+
57
+
58
+ @cli.command("set")
59
+ @click.option("-e", "--encode", type=click.File(mode="r"), help="File with the encoder")
60
+ @click.option("-d", "--decode", type=click.File(mode="r"), help="File with the decoder")
61
+ @click.option("-D", "--data", type=click.File(mode="r"), help="File with the rest")
62
+ @click.option("-i", "--in", "in_", nargs=2, multiple=True, help="Decoding sample")
63
+ @click.option("-o", "--out", nargs=2, multiple=True, help="Encoding sample")
64
+ @click.argument("path", nargs=1)
65
+ @click.pass_obj
66
+ async def set_(obj, path, encode, decode, data, in_, out):
67
+ """Save codec information"""
68
+ path = P(path)
69
+ if not len(path):
70
+ raise click.UsageError("You need a non-empty path.")
71
+
72
+ if data:
73
+ msg = yload(data)
74
+ else:
75
+ msg = {}
76
+ chain = NotGiven
77
+ if "value" in msg:
78
+ chain = msg.get("chain", NotGiven)
79
+ msg = msg["value"]
80
+
81
+ if "encode" in msg:
82
+ if encode:
83
+ raise click.UsageError("Duplicate encode script")
84
+ else:
85
+ if not encode:
86
+ raise click.UsageError("Missing encode script")
87
+ msg["encode"] = encode.read()
88
+ if "decode" in msg:
89
+ if decode:
90
+ raise click.UsageError("Duplicate decode script")
91
+ else:
92
+ if not decode:
93
+ raise click.UsageError("Missing decode script")
94
+ msg["decode"] = decode.read()
95
+ if in_:
96
+ msg["in"] = [(eval(a), eval(b)) for a, b in in_] # pylint: disable=eval-used
97
+ if out:
98
+ msg["out"] = [(eval(a), eval(b)) for a, b in out] # pylint: disable=eval-used
99
+
100
+ if not msg["in"]:
101
+ raise click.UsageError("Missing decode tests")
102
+ if not msg["out"]:
103
+ raise click.UsageError("Missing encode tests")
104
+
105
+ res = await obj.client._request(
106
+ action="set_internal",
107
+ value=msg,
108
+ path=Path("codec") + path,
109
+ iter=False,
110
+ nchain=obj.meta,
111
+ **({} if chain is NotGiven else {"chain": chain}),
112
+ )
113
+ if obj.meta:
114
+ yprint(res, stream=obj.stdout)
115
+
116
+
117
+ @cli.command()
118
+ @click.option("-c", "--codec", type=P, help="Codec to link to.")
119
+ @click.option("-d", "--delete", is_flag=True, help="Use to delete this converter.")
120
+ @click.option(
121
+ "-l",
122
+ "--list",
123
+ "list_",
124
+ is_flag=True,
125
+ help="Use to list this converter; '-' to list all.",
126
+ )
127
+ @click.argument("name", nargs=1)
128
+ @click.argument("path", type=P, nargs=1)
129
+ @click.pass_obj
130
+ async def convert(obj, path, codec, name, delete, list_):
131
+ """Match a codec to a path (read, if no codec given)"""
132
+ path = P(path)
133
+ if delete and list_:
134
+ raise click.UsageError("You can't both list and delete a path.")
135
+ if not len(path) and not list_:
136
+ raise click.UsageError("You need a non-empty path.")
137
+ if codec and delete:
138
+ raise click.UsageError("You can't both set and delete a path.")
139
+
140
+ if list_:
141
+ if name == "-":
142
+ if len(path):
143
+ raise click.UsageError("You can't use a path here.")
144
+ res = await obj.client._request(
145
+ action="enum_internal",
146
+ path=Path("conv"),
147
+ iter=False,
148
+ nchain=0,
149
+ empty=True,
150
+ )
151
+ for r in res.result:
152
+ print(r, file=obj.stdout)
153
+
154
+ else:
155
+ res = await obj.client._request(
156
+ action="get_tree_internal",
157
+ path=Path("conv", name) + path,
158
+ iter=True,
159
+ nchain=obj.meta,
160
+ )
161
+ pl = PathLongener(())
162
+ async for r in res:
163
+ pl(r)
164
+ try:
165
+ print(f"{r.path} : {Path.build(r.value['codec'])}", file=obj.stdout)
166
+ except Exception as e:
167
+ print(f"{Path(r.path)} {e!r}", file=obj.stdout)
168
+
169
+ return
170
+ if delete:
171
+ res = await obj.client._request(action="delete_internal", path=Path("conv", name) + path)
172
+ else:
173
+ msg = {"codec": codec}
174
+ res = await obj.client._request(
175
+ action="set_internal",
176
+ value=msg,
177
+ path=Path("conv", name) + path,
178
+ iter=False,
179
+ nchain=obj.meta,
180
+ )
181
+ if obj.meta:
182
+ yprint(res, stream=obj.stdout)
183
+ elif type or delete:
184
+ print(res.tock, file=obj.stdout)
185
+ else:
186
+ print(" ".join(res.type), file=obj.stdout)