moat-kv 0.70.23__py3-none-any.whl → 0.70.24__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 (125) 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 +97 -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 +74 -0
  14. build/lib/moat/kv/backend/serf.py +45 -0
  15. build/lib/moat/kv/client.py +1025 -0
  16. build/lib/moat/kv/code.py +236 -0
  17. build/lib/moat/kv/codec.py +11 -0
  18. build/lib/moat/kv/command/__init__.py +1 -0
  19. build/lib/moat/kv/command/acl.py +180 -0
  20. build/lib/moat/kv/command/auth.py +261 -0
  21. build/lib/moat/kv/command/code.py +293 -0
  22. build/lib/moat/kv/command/codec.py +186 -0
  23. build/lib/moat/kv/command/data.py +265 -0
  24. build/lib/moat/kv/command/dump/__init__.py +143 -0
  25. build/lib/moat/kv/command/error.py +149 -0
  26. build/lib/moat/kv/command/internal.py +248 -0
  27. build/lib/moat/kv/command/job.py +433 -0
  28. build/lib/moat/kv/command/log.py +53 -0
  29. build/lib/moat/kv/command/server.py +114 -0
  30. build/lib/moat/kv/command/type.py +201 -0
  31. build/lib/moat/kv/config.py +46 -0
  32. build/lib/moat/kv/data.py +216 -0
  33. build/lib/moat/kv/errors.py +561 -0
  34. build/lib/moat/kv/exceptions.py +126 -0
  35. build/lib/moat/kv/mock/__init__.py +101 -0
  36. build/lib/moat/kv/mock/mqtt.py +159 -0
  37. build/lib/moat/kv/mock/serf.py +250 -0
  38. build/lib/moat/kv/mock/tracer.py +63 -0
  39. build/lib/moat/kv/model.py +1069 -0
  40. build/lib/moat/kv/obj/__init__.py +646 -0
  41. build/lib/moat/kv/obj/command.py +241 -0
  42. build/lib/moat/kv/runner.py +1347 -0
  43. build/lib/moat/kv/server.py +2809 -0
  44. build/lib/moat/kv/types.py +513 -0
  45. debian/moat-kv/usr/lib/python3/dist-packages/docs/source/conf.py +201 -0
  46. debian/moat-kv/usr/lib/python3/dist-packages/examples/pathify.py +45 -0
  47. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/__init__.py +19 -0
  48. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/_cfg.yaml +97 -0
  49. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/_main.py +91 -0
  50. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/actor/__init__.py +98 -0
  51. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/actor/deletor.py +139 -0
  52. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/__init__.py +444 -0
  53. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/_test.py +166 -0
  54. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/password.py +234 -0
  55. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/root.py +58 -0
  56. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/__init__.py +67 -0
  57. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/mqtt.py +74 -0
  58. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/serf.py +45 -0
  59. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/client.py +1025 -0
  60. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/code.py +236 -0
  61. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/codec.py +11 -0
  62. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/__init__.py +1 -0
  63. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/acl.py +180 -0
  64. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/auth.py +261 -0
  65. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/code.py +293 -0
  66. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/codec.py +186 -0
  67. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/data.py +265 -0
  68. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/dump/__init__.py +143 -0
  69. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/error.py +149 -0
  70. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/internal.py +248 -0
  71. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/job.py +433 -0
  72. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/log.py +53 -0
  73. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/server.py +114 -0
  74. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/type.py +201 -0
  75. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/config.py +46 -0
  76. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/data.py +216 -0
  77. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/errors.py +561 -0
  78. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/exceptions.py +126 -0
  79. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/__init__.py +101 -0
  80. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/mqtt.py +159 -0
  81. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/serf.py +250 -0
  82. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/tracer.py +63 -0
  83. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/model.py +1069 -0
  84. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/obj/__init__.py +646 -0
  85. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/obj/command.py +241 -0
  86. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/runner.py +1347 -0
  87. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/server.py +2809 -0
  88. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/types.py +513 -0
  89. docs/source/conf.py +201 -0
  90. examples/pathify.py +45 -0
  91. moat/kv/actor/__init__.py +98 -0
  92. moat/kv/actor/deletor.py +139 -0
  93. moat/kv/auth/__init__.py +444 -0
  94. moat/kv/auth/_test.py +166 -0
  95. moat/kv/auth/password.py +234 -0
  96. moat/kv/auth/root.py +58 -0
  97. moat/kv/backend/__init__.py +67 -0
  98. moat/kv/backend/mqtt.py +74 -0
  99. moat/kv/backend/serf.py +45 -0
  100. moat/kv/command/__init__.py +1 -0
  101. moat/kv/command/acl.py +180 -0
  102. moat/kv/command/auth.py +261 -0
  103. moat/kv/command/code.py +293 -0
  104. moat/kv/command/codec.py +186 -0
  105. moat/kv/command/data.py +265 -0
  106. moat/kv/command/dump/__init__.py +143 -0
  107. moat/kv/command/error.py +149 -0
  108. moat/kv/command/internal.py +248 -0
  109. moat/kv/command/job.py +433 -0
  110. moat/kv/command/log.py +53 -0
  111. moat/kv/command/server.py +114 -0
  112. moat/kv/command/type.py +201 -0
  113. moat/kv/mock/__init__.py +101 -0
  114. moat/kv/mock/mqtt.py +159 -0
  115. moat/kv/mock/serf.py +250 -0
  116. moat/kv/mock/tracer.py +63 -0
  117. moat/kv/obj/__init__.py +646 -0
  118. moat/kv/obj/command.py +241 -0
  119. {moat_kv-0.70.23.dist-info → moat_kv-0.70.24.dist-info}/METADATA +2 -2
  120. moat_kv-0.70.24.dist-info/RECORD +137 -0
  121. moat_kv-0.70.24.dist-info/top_level.txt +9 -0
  122. moat_kv-0.70.23.dist-info/RECORD +0 -19
  123. moat_kv-0.70.23.dist-info/top_level.txt +0 -1
  124. {moat_kv-0.70.23.dist-info → moat_kv-0.70.24.dist-info}/WHEEL +0 -0
  125. {moat_kv-0.70.23.dist-info → moat_kv-0.70.24.dist-info}/licenses/LICENSE.txt +0 -0
@@ -0,0 +1,261 @@
1
+ # command line interface
2
+ from __future__ import annotations
3
+
4
+ import asyncclick as click
5
+ from moat.util import NotGiven, Path, split_arg, yprint
6
+
7
+ from moat.kv.auth import gen_auth, loader
8
+
9
+
10
+ @click.group(short_help="Manage authorization") # pylint: disable=undefined-variable
11
+ @click.option("-m", "--method", default=None, help="Affect/use this auth method")
12
+ @click.pass_obj
13
+ async def cli(obj, method):
14
+ """Manage authorization. Usage: … auth METHOD command…. Use '.' for 'all methods'."""
15
+ a = await obj.client._request(action="auth_info")
16
+ a = a.get("value", None)
17
+ if a is not None:
18
+ a = a["current"]
19
+ obj.auth_current = a
20
+ if method is None:
21
+ obj.auth = a or (await one_auth(obj))
22
+ else:
23
+ if method == "-":
24
+ method = None
25
+ obj.auth = method
26
+
27
+
28
+ async def enum_auth(obj):
29
+ """List all configured auth types."""
30
+ if obj.get("auth", None) is not None:
31
+ yield obj.auth
32
+ return
33
+ # TODO create a method for this
34
+ res = await obj.client._request(
35
+ action="enum_internal",
36
+ path=Path("auth"),
37
+ iter=False,
38
+ with_data=False,
39
+ empty=True,
40
+ nchain=0,
41
+ )
42
+ for r in res.result:
43
+ print(r)
44
+ yield r
45
+
46
+
47
+ async def one_auth(obj):
48
+ """Return the current auth method (from the command line or as used by the server)."""
49
+ if obj.get("auth", None) is not None:
50
+ return obj.auth
51
+ auth = None
52
+ async for a in enum_auth(obj):
53
+ if auth is not None:
54
+ raise click.UsageError("You need to set the auth method")
55
+ auth = a
56
+ if auth is None:
57
+ raise click.UsageError("You need to set the auth method")
58
+ return auth
59
+
60
+
61
+ async def enum_typ(obj, kind="user", ident=None, nchain=0):
62
+ """List all known auth entries of a kind."""
63
+ async for auth in enum_auth(obj):
64
+ if ident is not None:
65
+ res = await obj.client._request(
66
+ action="auth_list",
67
+ typ=auth,
68
+ kind=kind,
69
+ ident=ident,
70
+ iter=False,
71
+ nchain=nchain,
72
+ )
73
+ yield res
74
+ else:
75
+ async with obj.client._stream(
76
+ action="auth_list",
77
+ typ=auth,
78
+ kind=kind,
79
+ nchain=nchain,
80
+ ) as res:
81
+ async for r in res:
82
+ yield r
83
+
84
+
85
+ @cli.command("list")
86
+ @click.pass_obj
87
+ async def list_(obj):
88
+ """List known auth methods"""
89
+ async for auth in enum_auth(obj):
90
+ print(auth, file=obj.stdout)
91
+
92
+
93
+ @cli.command()
94
+ @click.option("-s", "--switch", is_flag=True, help="Switch to a different auth method")
95
+ @click.pass_obj
96
+ async def init(obj, switch):
97
+ """Setup authorization"""
98
+ if obj.auth_current is not None and not switch:
99
+ raise click.UsageError("Authentication is already set up")
100
+
101
+ await obj.client._request(action="set_auth_typ", typ=obj.auth)
102
+ if obj.debug >= 0:
103
+ if obj.auth:
104
+ print("Authorization switched to", obj.auth, file=obj.stdout)
105
+ else:
106
+ print("Authorization turned off.", file=obj.stdout)
107
+
108
+
109
+ @cli.group()
110
+ async def user():
111
+ """Manage users."""
112
+ pass
113
+
114
+
115
+ @user.command("list")
116
+ @click.option(
117
+ "-v",
118
+ "--verbose",
119
+ is_flag=True,
120
+ help="Print complete results. Default: just the names",
121
+ )
122
+ @click.pass_obj # pylint: disable=function-redefined
123
+ async def list_user(obj, verbose):
124
+ """List all users (raw data)."""
125
+ async for r in enum_typ(obj, nchain=obj.meta):
126
+ if obj.meta or verbose:
127
+ if obj.debug < 2:
128
+ del r["seq"]
129
+ del r["tock"]
130
+ yprint(r, stream=obj.stdout)
131
+ else:
132
+ print(r.ident, file=obj.stdout)
133
+
134
+
135
+ @user.command()
136
+ @click.argument("ident", nargs=1)
137
+ @click.pass_obj
138
+ async def get(obj, ident):
139
+ """Retrieve a user (processed)."""
140
+ lv = loader(await one_auth(obj), "user", make=True, server=False)
141
+ if obj.DEBUG:
142
+ lv._length = 16
143
+
144
+ u = await lv.recv(obj.client, ident, _initial=False)
145
+ yprint(u.export(), stream=obj.stdout)
146
+
147
+
148
+ @user.command()
149
+ @click.argument("args", nargs=-1)
150
+ @click.pass_obj
151
+ async def add(obj, args):
152
+ """Add a user."""
153
+ await add_mod_user(obj, args, None)
154
+
155
+
156
+ @user.command()
157
+ @click.option("-n", "--new", is_flag=True, help="New: ignore previous content")
158
+ @click.argument("ident", nargs=1)
159
+ @click.argument("type", nargs=1)
160
+ @click.argument("key", nargs=1)
161
+ @click.argument("args", nargs=-1)
162
+ @click.pass_obj
163
+ async def param(obj, new, ident, type, key, args): # pylint: disable=redefined-builtin
164
+ """Set user parameters for auth, conversion, etc."""
165
+ auth = await one_auth(obj)
166
+ u = loader(auth, "user", make=True, server=False)
167
+ if obj.DEBUG:
168
+ u._length = 16
169
+ # ou = await u.recv(obj.client, ident, _initial=False) # unused
170
+ res = await obj.client._request(
171
+ action="get_internal",
172
+ path=("auth", auth, "user", ident, type),
173
+ iter=False,
174
+ nchain=3,
175
+ )
176
+
177
+ kw = res.get("value", NotGiven)
178
+ if new or kw is NotGiven:
179
+ kw = {}
180
+ res.chain = None
181
+ if key == "-":
182
+ if args:
183
+ raise click.UsageError("You can't set params when deleting")
184
+ res = await obj.client._request(
185
+ action="delete_internal",
186
+ path=("auth", auth, "user", ident, type),
187
+ iter=False,
188
+ chain=res.chain,
189
+ )
190
+
191
+ else:
192
+ kw["key"] = key
193
+
194
+ for a in args:
195
+ split_arg(a, kw)
196
+
197
+ res = await obj.client._request(
198
+ action="set_internal",
199
+ path=("auth", auth, "user", ident, type),
200
+ iter=False,
201
+ chain=res.chain,
202
+ value=kw,
203
+ )
204
+ if obj.meta:
205
+ # res.ident = ident
206
+ # res.type = type
207
+ yprint(res, stream=obj.stdout)
208
+ else:
209
+ print(ident, type, key, file=obj.stdout)
210
+
211
+
212
+ @user.command()
213
+ @click.argument("ident", nargs=1)
214
+ @click.argument("args", nargs=-1)
215
+ @click.pass_obj
216
+ async def mod(obj, ident, args):
217
+ """Change a user."""
218
+ await add_mod_user(obj, args, ident)
219
+
220
+
221
+ async def add_mod_user(obj, args, modify):
222
+ auth = await one_auth(obj)
223
+ u = loader(auth, "user", make=True, server=False)
224
+ if obj.DEBUG:
225
+ u._length = 16
226
+ if modify:
227
+ ou = await u.recv(obj.client, modify, _initial=False)
228
+ kw = ou.export()
229
+ else:
230
+ kw = {}
231
+ for a in args:
232
+ split_arg(a, kw)
233
+
234
+ u = u.build(kw, _initial=False)
235
+ if modify is None or u.ident != modify:
236
+ u._chain = None # new user
237
+ else:
238
+ u._chain = ou._chain
239
+ res = await u.send(obj.client)
240
+ if obj.meta:
241
+ res.ident = u.ident
242
+ yprint(res, stream=obj.stdout)
243
+ else:
244
+ print(u.ident, file=obj.stdout)
245
+
246
+
247
+ @user.command(name="auth")
248
+ @click.option(
249
+ "-a",
250
+ "--auth",
251
+ type=str,
252
+ default="root",
253
+ help="Auth params. =file or 'type param=value…' Default: root",
254
+ )
255
+ @click.pass_obj
256
+ async def auth_(obj, auth):
257
+ """Test user authorization."""
258
+ user = gen_auth(auth) # pylint: disable=redefined-outer-name
259
+ await user.auth(obj.client)
260
+ if obj.debug > 0:
261
+ print("OK.", file=obj.stdout)
@@ -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)