moat-kv 0.70.24__py3-none-any.whl → 0.71.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. moat/kv/__init__.py +6 -7
  2. moat/kv/_cfg.yaml +5 -8
  3. moat/kv/actor/__init__.py +2 -1
  4. moat/kv/actor/deletor.py +4 -1
  5. moat/kv/auth/__init__.py +12 -13
  6. moat/kv/auth/_test.py +4 -1
  7. moat/kv/auth/password.py +11 -7
  8. moat/kv/backend/mqtt.py +4 -8
  9. moat/kv/client.py +20 -39
  10. moat/kv/code.py +3 -3
  11. moat/kv/command/data.py +4 -3
  12. moat/kv/command/dump/__init__.py +29 -29
  13. moat/kv/command/internal.py +2 -3
  14. moat/kv/command/job.py +1 -2
  15. moat/kv/command/type.py +3 -6
  16. moat/kv/data.py +9 -8
  17. moat/kv/errors.py +16 -8
  18. moat/kv/mock/__init__.py +2 -12
  19. moat/kv/model.py +28 -32
  20. moat/kv/obj/__init__.py +3 -3
  21. moat/kv/obj/command.py +3 -3
  22. moat/kv/runner.py +4 -5
  23. moat/kv/server.py +106 -126
  24. moat/kv/types.py +8 -6
  25. {moat_kv-0.70.24.dist-info → moat_kv-0.71.6.dist-info}/METADATA +7 -6
  26. moat_kv-0.71.6.dist-info/RECORD +47 -0
  27. {moat_kv-0.70.24.dist-info → moat_kv-0.71.6.dist-info}/WHEEL +1 -1
  28. moat_kv-0.71.6.dist-info/licenses/LICENSE +3 -0
  29. moat_kv-0.71.6.dist-info/licenses/LICENSE.APACHE2 +202 -0
  30. moat_kv-0.71.6.dist-info/licenses/LICENSE.MIT +20 -0
  31. moat_kv-0.71.6.dist-info/top_level.txt +1 -0
  32. build/lib/docs/source/conf.py +0 -201
  33. build/lib/examples/pathify.py +0 -45
  34. build/lib/moat/kv/__init__.py +0 -19
  35. build/lib/moat/kv/_cfg.yaml +0 -97
  36. build/lib/moat/kv/_main.py +0 -91
  37. build/lib/moat/kv/actor/__init__.py +0 -98
  38. build/lib/moat/kv/actor/deletor.py +0 -139
  39. build/lib/moat/kv/auth/__init__.py +0 -444
  40. build/lib/moat/kv/auth/_test.py +0 -166
  41. build/lib/moat/kv/auth/password.py +0 -234
  42. build/lib/moat/kv/auth/root.py +0 -58
  43. build/lib/moat/kv/backend/__init__.py +0 -67
  44. build/lib/moat/kv/backend/mqtt.py +0 -74
  45. build/lib/moat/kv/backend/serf.py +0 -45
  46. build/lib/moat/kv/client.py +0 -1025
  47. build/lib/moat/kv/code.py +0 -236
  48. build/lib/moat/kv/codec.py +0 -11
  49. build/lib/moat/kv/command/__init__.py +0 -1
  50. build/lib/moat/kv/command/acl.py +0 -180
  51. build/lib/moat/kv/command/auth.py +0 -261
  52. build/lib/moat/kv/command/code.py +0 -293
  53. build/lib/moat/kv/command/codec.py +0 -186
  54. build/lib/moat/kv/command/data.py +0 -265
  55. build/lib/moat/kv/command/dump/__init__.py +0 -143
  56. build/lib/moat/kv/command/error.py +0 -149
  57. build/lib/moat/kv/command/internal.py +0 -248
  58. build/lib/moat/kv/command/job.py +0 -433
  59. build/lib/moat/kv/command/log.py +0 -53
  60. build/lib/moat/kv/command/server.py +0 -114
  61. build/lib/moat/kv/command/type.py +0 -201
  62. build/lib/moat/kv/config.py +0 -46
  63. build/lib/moat/kv/data.py +0 -216
  64. build/lib/moat/kv/errors.py +0 -561
  65. build/lib/moat/kv/exceptions.py +0 -126
  66. build/lib/moat/kv/mock/__init__.py +0 -101
  67. build/lib/moat/kv/mock/mqtt.py +0 -159
  68. build/lib/moat/kv/mock/serf.py +0 -250
  69. build/lib/moat/kv/mock/tracer.py +0 -63
  70. build/lib/moat/kv/model.py +0 -1069
  71. build/lib/moat/kv/obj/__init__.py +0 -646
  72. build/lib/moat/kv/obj/command.py +0 -241
  73. build/lib/moat/kv/runner.py +0 -1347
  74. build/lib/moat/kv/server.py +0 -2809
  75. build/lib/moat/kv/types.py +0 -513
  76. debian/moat-kv/usr/lib/python3/dist-packages/docs/source/conf.py +0 -201
  77. debian/moat-kv/usr/lib/python3/dist-packages/examples/pathify.py +0 -45
  78. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/__init__.py +0 -19
  79. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/_cfg.yaml +0 -97
  80. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/_main.py +0 -91
  81. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/actor/__init__.py +0 -98
  82. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/actor/deletor.py +0 -139
  83. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/__init__.py +0 -444
  84. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/_test.py +0 -166
  85. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/password.py +0 -234
  86. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/root.py +0 -58
  87. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/__init__.py +0 -67
  88. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/mqtt.py +0 -74
  89. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/serf.py +0 -45
  90. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/client.py +0 -1025
  91. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/code.py +0 -236
  92. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/codec.py +0 -11
  93. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/__init__.py +0 -1
  94. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/acl.py +0 -180
  95. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/auth.py +0 -261
  96. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/code.py +0 -293
  97. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/codec.py +0 -186
  98. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/data.py +0 -265
  99. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/dump/__init__.py +0 -143
  100. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/error.py +0 -149
  101. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/internal.py +0 -248
  102. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/job.py +0 -433
  103. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/log.py +0 -53
  104. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/server.py +0 -114
  105. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/type.py +0 -201
  106. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/config.py +0 -46
  107. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/data.py +0 -216
  108. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/errors.py +0 -561
  109. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/exceptions.py +0 -126
  110. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/__init__.py +0 -101
  111. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/mqtt.py +0 -159
  112. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/serf.py +0 -250
  113. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/tracer.py +0 -63
  114. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/model.py +0 -1069
  115. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/obj/__init__.py +0 -646
  116. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/obj/command.py +0 -241
  117. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/runner.py +0 -1347
  118. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/server.py +0 -2809
  119. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/types.py +0 -513
  120. docs/source/conf.py +0 -201
  121. examples/pathify.py +0 -45
  122. moat/kv/backend/serf.py +0 -45
  123. moat/kv/codec.py +0 -11
  124. moat/kv/mock/serf.py +0 -250
  125. moat_kv-0.70.24.dist-info/RECORD +0 -137
  126. moat_kv-0.70.24.dist-info/top_level.txt +0 -9
  127. {moat_kv-0.70.24.dist-info → moat_kv-0.71.6.dist-info}/licenses/LICENSE.txt +0 -0
@@ -1,53 +0,0 @@
1
- # command line interface
2
- from __future__ import annotations
3
-
4
- import asyncclick as click
5
- from moat.util import yprint
6
-
7
-
8
- @click.group(short_help="Manage logging.") # pylint: disable=undefined-variable
9
- async def cli():
10
- """
11
- This subcommand controls a server's logging.
12
- """
13
- pass
14
-
15
-
16
- @cli.command()
17
- @click.option("-i", "--incremental", is_flag=True, help="Don't write the initial state")
18
- @click.argument("path", nargs=1)
19
- @click.pass_obj
20
- async def dest(obj, path, incremental):
21
- """
22
- Log changes to a file.
23
-
24
- Any previously open log (on the server you talk to) is closed as soon
25
- as the new one is opened and ready.
26
- """
27
- res = await obj.client._request("log", path=path, fetch=not incremental)
28
- if obj.meta:
29
- yprint(res, stream=obj.stdout)
30
-
31
-
32
- @cli.command()
33
- @click.option("-f", "--full", is_flag=1, help="Also dump internal state")
34
- @click.argument("path", nargs=1)
35
- @click.pass_obj
36
- async def save(obj, path, full):
37
- """
38
- Write the server's current state to a file.
39
- """
40
- res = await obj.client._request("save", path=path, full=full)
41
- if obj.meta:
42
- yprint(res, stream=obj.stdout)
43
-
44
-
45
- @cli.command()
46
- @click.pass_obj
47
- async def stop(obj):
48
- """
49
- Stop logging changes.
50
- """
51
- res = await obj.client._request("log") # no path == stop
52
- if obj.meta:
53
- yprint(res, stream=obj.stdout)
@@ -1,114 +0,0 @@
1
- # command line interface
2
- from __future__ import annotations
3
-
4
- import asyncclick as click
5
-
6
- from moat.kv.server import Server
7
-
8
-
9
- @click.command(short_help="Run the MoaT-KV server.") # pylint: disable=undefined-variable
10
- @click.option(
11
- "-l",
12
- "--load",
13
- type=click.Path(readable=True, exists=True, allow_dash=False),
14
- default=None,
15
- help="Event log to preload.",
16
- )
17
- @click.option(
18
- "-s",
19
- "--save",
20
- type=click.Path(writable=True, allow_dash=False),
21
- default=None,
22
- help="Event log to write to.",
23
- hidden=True,
24
- )
25
- @click.option(
26
- "-i",
27
- "--incremental",
28
- default=None,
29
- help="Save incremental changes, not the complete state",
30
- hidden=True,
31
- )
32
- @click.option(
33
- "-I",
34
- "--init",
35
- default=None,
36
- help="Initial value to set the root to. Use only when setting up "
37
- "a cluster for the first time!",
38
- hidden=True,
39
- )
40
- @click.option(
41
- "-e",
42
- "--eval",
43
- "eval_",
44
- is_flag=True,
45
- help="The 'init' value shall be evaluated.",
46
- hidden=True,
47
- )
48
- @click.option(
49
- "-a",
50
- "--auth",
51
- "--authoritative",
52
- is_flag=True,
53
- help="Data in this file is complete: mark anything missing as known even if not.",
54
- )
55
- @click.option(
56
- "-f",
57
- "--force",
58
- is_flag=True,
59
- help="Force 'successful' startup even if data are missing.",
60
- )
61
- @click.argument("name", nargs=1)
62
- @click.argument("nodes", nargs=-1)
63
- @click.pass_obj
64
- async def cli(obj, name, load, save, init, incremental, eval_, auth, force, nodes):
65
- """
66
- This command starts a MoaT-KV server. It defaults to connecting to the local Serf
67
- agent.
68
-
69
- All MoaT-KV servers must have a unique name. Its uniqueness cannot be
70
- verified reliably.
71
-
72
- One server in your network needs either an initial datum, or a copy of
73
- a previously-saved MoaT-KV state. Otherwise, no client connections will
74
- be accepted until synchronization with the other servers in your MoaT-KV
75
- network is complete.
76
-
77
- This command requires a unique NAME argument. The name identifies this
78
- server on the network. Never start two servers with the same name!
79
-
80
- You can force the server to fetch its data from a specific node, in
81
- case some data are corrupted. (This should never be necessary.)
82
-
83
- A server will refuse to start up as long as it knows about missing
84
- entries. Use the 'force' flag to disable that. You should disable
85
- any clients which use this server until the situation is resolved!
86
-
87
- An auhthoritative server doesn't have missing data in its storage by
88
- definition. This flag is used in the 'run' script when loading from a
89
- file.
90
- """
91
-
92
- kw = {}
93
- if eval_:
94
- kw["init"] = eval(init) # pylint: disable=eval-used
95
- elif init == "-":
96
- kw["init"] = None
97
- elif init is not None:
98
- kw["init"] = init
99
-
100
- from moat.util import as_service
101
-
102
- if load and nodes:
103
- raise click.UsageError("Either read from a file or fetch from a node. Not both.")
104
- if auth and force:
105
- raise click.UsageError("Using both '-a' and '-f' is redundant. Choose one.")
106
-
107
- async with as_service(obj) as evt:
108
- s = Server(name, cfg=obj.cfg["kv"], **kw)
109
- if load is not None:
110
- await s.load(path=load, local=True, authoritative=auth)
111
- if nodes:
112
- await s.fetch_data(nodes, authoritative=auth)
113
-
114
- await s.serve(log_path=save, log_inc=incremental, force=force, ready_evt=evt)
@@ -1,201 +0,0 @@
1
- # command line interface
2
- from __future__ import annotations
3
-
4
- import json
5
-
6
- import asyncclick as click
7
- from moat.util import NotGiven, P, Path, PathLongener, yload, yprint
8
-
9
-
10
- @click.group() # pylint: disable=undefined-variable
11
- async def cli():
12
- """Manage types and type matches. Usage: … type …"""
13
- pass
14
-
15
-
16
- @cli.command()
17
- @click.option("-s", "--script", type=click.File(mode="w", lazy=True), help="Save the script here")
18
- @click.option("-S", "--schema", type=click.File(mode="w", lazy=True), help="Save the schema here")
19
- @click.option("-y", "--yaml", "yaml_", is_flag=True, help="Write schema as YAML. Default: JSON.")
20
- @click.argument("path", type=P, nargs=1)
21
- @click.pass_obj
22
- async def get(obj, path, script, schema, yaml_):
23
- """Read type checker information"""
24
- if not len(path):
25
- raise click.UsageError("You need a non-empty path.")
26
- res = await obj.client._request(
27
- action="get_internal",
28
- path=Path("type") + path,
29
- iter=False,
30
- nchain=obj.meta,
31
- )
32
- try:
33
- r = res.value
34
- except AttributeError:
35
- raise click.UsageError(f"No data at {Path('type') + path}") from None
36
-
37
- if not obj.meta:
38
- res = res.value
39
- if script:
40
- script.write(r.pop("code"))
41
- if schema:
42
- if yaml_:
43
- yprint(r.pop("schema"), stream=schema)
44
- else:
45
- json.dump(r.pop("schema"), schema)
46
- yprint(res, stream=obj.stdout)
47
-
48
-
49
- @cli.command("set")
50
- @click.option("-g", "--good", multiple=True, help="Example for passing values")
51
- @click.option("-b", "--bad", multiple=True, help="Example for failing values")
52
- @click.option("-d", "--data", type=click.File(mode="r"), help="Load metadata from this YAML file.")
53
- @click.option("-s", "--script", type=click.File(mode="r"), help="File with the checking script")
54
- @click.option("-S", "--schema", type=click.File(mode="r"), help="File with the JSON schema")
55
- @click.option("-y", "--yaml", "yaml_", is_flag=True, help="load the schema as YAML. Default: JSON")
56
- @click.argument("path", type=P, nargs=1)
57
- @click.pass_obj
58
- async def set_(obj, path, good, bad, script, schema, yaml_, data):
59
- """Write type checker information."""
60
- if not len(path):
61
- raise click.UsageError("You need a non-empty path.")
62
-
63
- if data:
64
- msg = yload(data)
65
- else:
66
- msg = {}
67
- chain = NotGiven
68
- if "value" in msg:
69
- chain = msg.get("chain", NotGiven)
70
- msg = msg["value"]
71
-
72
- msg.setdefault("good", [])
73
- msg.setdefault("bad", [])
74
- for x in good:
75
- msg["good"].append(eval(x)) # pylint: disable=eval-used
76
- for x in bad:
77
- msg["bad"].append(eval(x)) # pylint: disable=eval-used
78
-
79
- if "code" in msg:
80
- if script:
81
- raise click.UsageError("Duplicate script")
82
- elif script:
83
- msg["code"] = script.read()
84
-
85
- if "schema" in msg:
86
- raise click.UsageError("Missing schema")
87
- elif schema:
88
- if yaml_:
89
- msg["schema"] = yload(schema)
90
- else:
91
- msg["schema"] = json.load(schema)
92
-
93
- if "schema" not in msg and "code" not in msg:
94
- raise click.UsageError("I need a schema, Python code, or both.")
95
-
96
- if len(msg["good"]) < 2:
97
- raise click.UsageError("Missing known-good test values (at least two)")
98
- if not msg["bad"]:
99
- raise click.UsageError("Missing known-bad test values")
100
-
101
- res = await obj.client._request(
102
- action="set_internal",
103
- value=msg,
104
- path=Path("type") + path,
105
- iter=False,
106
- nchain=obj.meta,
107
- **({} if chain is NotGiven else {"chain": chain}),
108
- )
109
- if obj.meta:
110
- yprint(res, stream=obj.stdout)
111
-
112
-
113
- @cli.command()
114
- @click.option("-R", "--raw", is_flag=True, help="Print just the path.")
115
- @click.option("-t", "--type", "type_", help="Type path to link to.")
116
- @click.option("-d", "--delete", help="Use to delete this mapping.")
117
- @click.argument("path", type=P, nargs=1)
118
- @click.pass_obj
119
- async def match(obj, path, type_, delete, raw): # pylint: disable=redefined-builtin
120
- """Match a type to a path (read, if no type given; list if empty path)"""
121
- if not len(path):
122
- if raw or type_ or delete:
123
- raise click.UsageError("No options allowed when dumping the match tree..")
124
- y = {}
125
- pl = PathLongener()
126
- async for r in await obj.client._request(
127
- "get_tree_internal",
128
- path=Path("match") + path,
129
- iter=True,
130
- nchain=0,
131
- ):
132
- pl(r)
133
- path = r["path"]
134
- yy = y
135
- for p in path:
136
- yy = yy.setdefault(p, {})
137
- try:
138
- yy["_"] = r["value"]
139
- except KeyError:
140
- pass
141
- yprint(y, stream=obj.stdout)
142
- return
143
-
144
- if type_ and delete:
145
- raise click.UsageError("You can't both set and delete a path.")
146
- if raw and (type_ or delete):
147
- raise click.UsageError("You can only print the raw path when reading a match.")
148
-
149
- if delete:
150
- res = await obj.client._request(action="delete_internal", path=Path("type") + path)
151
- if obj.meta:
152
- yprint(res, stream=obj.stdout)
153
- return
154
-
155
- msg = {}
156
- if type_:
157
- msg["type"] = P(type_)
158
- act = "set_internal"
159
- elif delete:
160
- act = "delete_internal"
161
- else:
162
- act = "get_internal"
163
- res = await obj.client._request(
164
- action=act,
165
- value=msg,
166
- path=Path("match") + path,
167
- iter=False,
168
- nchain=obj.meta,
169
- )
170
- if obj.meta:
171
- yprint(res, stream=obj.stdout)
172
- elif type_ or delete:
173
- pass
174
- else:
175
- print(" ".join(str(x) for x in res.type), file=obj.stdout)
176
-
177
-
178
- @cli.command()
179
- @click.argument("path", type=P, nargs=1)
180
- @click.pass_obj
181
- async def list(obj, path): # pylint: disable=redefined-builtin
182
- """Dump type data"""
183
-
184
- y = {}
185
- pl = PathLongener()
186
- async for r in await obj.client._request(
187
- "get_tree_internal",
188
- path=Path("type") + path,
189
- iter=True,
190
- nchain=0,
191
- ):
192
- pl(r)
193
- path = r["path"]
194
- yy = y
195
- for p in path:
196
- yy = yy.setdefault(p, {})
197
- try:
198
- yy["_"] = r["value"]
199
- except KeyError:
200
- pass
201
- yprint(y, stream=obj.stdout)
@@ -1,46 +0,0 @@
1
- """
2
- An online-updated config store
3
-
4
- """
5
-
6
- from __future__ import annotations
7
-
8
- try:
9
- from contextlib import asynccontextmanager
10
- except ImportError: # pragma: no cover
11
- from async_generator import asynccontextmanager
12
-
13
- import logging
14
-
15
- from .exceptions import ServerClosedError, ServerError
16
- from .obj import ClientEntry, ClientRoot
17
-
18
- logger = logging.getLogger(__name__)
19
-
20
-
21
- class ConfigEntry(ClientEntry):
22
- @classmethod
23
- def child_type(cls, name): # pragma: no cover
24
- logger.warning("Online config sub-entries are ignored")
25
- return ClientEntry
26
-
27
- async def set_value(self, value):
28
- await self.root.client.config._update(self._name, value)
29
-
30
-
31
- class ConfigRoot(ClientRoot):
32
- CFG = "config"
33
-
34
- @classmethod
35
- def child_type(cls, name):
36
- return ConfigEntry
37
-
38
- @asynccontextmanager
39
- async def run(self):
40
- try:
41
- async with super().run() as x:
42
- yield x
43
- except ServerClosedError: # pragma: no cover
44
- pass
45
- except ServerError: # pragma: no cover
46
- logger.exception("No config data")
build/lib/moat/kv/data.py DELETED
@@ -1,216 +0,0 @@
1
- """
2
- Data access
3
- """
4
- from __future__ import annotations
5
-
6
- import datetime
7
- import os
8
- import sys
9
- import time
10
- from collections.abc import Mapping
11
-
12
- from moat.util import NotGiven, Path, attrdict, process_args, yprint
13
-
14
-
15
- def add_dates(d):
16
- """
17
- Given a dict with int/float entries that might conceivably be dates,
18
- add ``_*`` with a textual representation.
19
- """
20
-
21
- t = time.time()
22
- start = t - 366 * 24 * 3600
23
- stop = t + 366 * 24 * 3600
24
-
25
- def _add(d):
26
- if isinstance(d, (list, tuple)):
27
- for dd in d:
28
- _add(dd)
29
- return
30
- if not isinstance(d, Mapping):
31
- return
32
- for k, v in list(d.items()):
33
- if isinstance(k, str) and k.startswith("_"):
34
- continue
35
- if not isinstance(v, (int, float)):
36
- _add(v)
37
- continue
38
- if start <= v <= stop:
39
- d[f"_{k}"] = datetime.datetime.fromtimestamp(v).isoformat(
40
- sep=" ",
41
- timespec="milliseconds",
42
- )
43
-
44
- _add(d)
45
-
46
-
47
- async def data_get(
48
- obj,
49
- path,
50
- *,
51
- recursive=True,
52
- as_dict="_",
53
- maxdepth=-1,
54
- mindepth=0,
55
- empty=False,
56
- raw=False,
57
- internal=False,
58
- path_mangle=None,
59
- item_mangle=None,
60
- add_date=False,
61
- ):
62
- """Generic code to dump a subtree.
63
-
64
- `path_mangle` accepts a path and the as_dict parameter. It should
65
- return the new path. This is used for e.g. prefixing the path with a
66
- device name. Returning ``None`` causes the entry to be skipped.
67
- """
68
- if path_mangle is None:
69
- path_mangle = lambda x: x
70
- if item_mangle is None:
71
-
72
- async def item_mangle(x): # pylint: disable=function-redefined
73
- return x
74
-
75
- if recursive:
76
- kw = {}
77
- if maxdepth is not None and maxdepth >= 0:
78
- kw["max_depth"] = maxdepth
79
- if mindepth:
80
- kw["min_depth"] = mindepth
81
- if empty:
82
- kw["empty"] = True
83
- if obj.meta:
84
- kw.setdefault("nchain", obj.meta)
85
- y = {}
86
- if internal:
87
- res = await obj.client._request(action="get_tree_internal", path=path, iter=True, **kw)
88
- else:
89
- res = obj.client.get_tree(path, **kw)
90
- async for r in res:
91
- r = await item_mangle(r)
92
- if r is None:
93
- continue
94
- r.pop("seq", None)
95
- path = r.pop("path")
96
- path = path_mangle(path)
97
- if path is None:
98
- continue
99
- if add_date and "value" in r:
100
- add_dates(r.value)
101
-
102
- if as_dict is not None:
103
- yy = y
104
- for p in path:
105
- yy = yy.setdefault(p, {})
106
- try:
107
- yy[as_dict] = r if obj.meta else r.value
108
- except AttributeError:
109
- if empty:
110
- yy[as_dict] = None
111
- else:
112
- if raw:
113
- y = path
114
- else:
115
- y = {}
116
- try:
117
- y[path] = r if obj.meta else r.value
118
- except AttributeError:
119
- if empty:
120
- y[path] = None
121
- else:
122
- continue
123
- yprint([y], stream=obj.stdout)
124
-
125
- if as_dict is not None:
126
- if maxdepth:
127
-
128
- def simplex(d):
129
- for k, v in d.items():
130
- if isinstance(v, dict):
131
- d[k] = simplex(d[k])
132
- if as_dict in d and d[as_dict] is None:
133
- if len(d) == 1:
134
- return None
135
- else:
136
- del d[as_dict]
137
- return d
138
-
139
- y = simplex(y)
140
- yprint(y, stream=obj.stdout)
141
- return # end "if recursive"
142
-
143
- res = await obj.client.get(path, nchain=obj.meta)
144
- if not obj.meta:
145
- try:
146
- res = res.value
147
- except AttributeError:
148
- if obj.debug:
149
- print("No data at", path, file=sys.stderr)
150
- return
151
-
152
- if not raw:
153
- yprint(res, stream=obj.stdout)
154
- elif isinstance(res, bytes):
155
- os.write(obj.stdout.fileno(), res)
156
- else:
157
- obj.stdout.write(str(res))
158
- pass # end get
159
-
160
-
161
- def res_get(res, attr: Path, **kw): # pylint: disable=redefined-outer-name
162
- """
163
- Get a node's value and access the dict items beneath it.
164
-
165
- The node value must be an attrdict.
166
- """
167
- val = res.get("value", None)
168
- if val is None:
169
- return None
170
- return val._get(attr, **kw)
171
-
172
-
173
- def res_update(res, attr: Path, value=None, **kw): # pylint: disable=redefined-outer-name
174
- """
175
- Set a node's sub-item's value, possibly merging dicts.
176
- Entries set to 'NotGiven' are deleted.
177
-
178
- The node value must be an attrdict.
179
-
180
- Returns the new value.
181
- """
182
- val = res.get("value", attrdict())
183
- return val._update(attr, value=value, **kw)
184
-
185
-
186
- async def node_attr(obj, path, res=None, chain=None, **kw):
187
- """
188
- Sub-attr setter.
189
-
190
- Args:
191
- obj: command object
192
- path: address of the node to change
193
- res: old node, if it has been read already
194
- chain: change chain of node, copied from res if clear
195
- **kw: the results of `attr_args`
196
-
197
- Returns the result of setting the attribute.
198
- """
199
- if res is None:
200
- res = await obj.client.get(path, nchain=obj.meta or 2)
201
- if chain is None:
202
- try:
203
- chain = res.chain
204
- except AttributeError:
205
- pass
206
- try:
207
- val = res.value
208
- except AttributeError:
209
- chain = None
210
- val = NotGiven
211
- val = process_args(val, **kw)
212
- if val is NotGiven:
213
- res = await obj.client.delete(path, nchain=obj.meta, chain=chain)
214
- else:
215
- res = await obj.client.set(path, value=val, nchain=obj.meta, chain=chain)
216
- return res