moat-kv 0.71.0__py3-none-any.whl → 0.71.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. moat/kv/__init__.py +6 -7
  2. moat/kv/_cfg.yaml +3 -2
  3. moat/kv/actor/__init__.py +2 -1
  4. moat/kv/actor/deletor.py +4 -1
  5. moat/kv/auth/__init__.py +12 -13
  6. moat/kv/auth/_test.py +4 -1
  7. moat/kv/auth/password.py +11 -7
  8. moat/kv/backend/mqtt.py +4 -5
  9. moat/kv/client.py +20 -39
  10. moat/kv/code.py +3 -3
  11. moat/kv/command/data.py +4 -3
  12. moat/kv/command/dump/__init__.py +36 -34
  13. moat/kv/command/internal.py +2 -3
  14. moat/kv/command/job.py +1 -2
  15. moat/kv/command/type.py +3 -6
  16. moat/kv/data.py +9 -8
  17. moat/kv/errors.py +16 -8
  18. moat/kv/mock/__init__.py +2 -12
  19. moat/kv/model.py +29 -33
  20. moat/kv/obj/__init__.py +3 -3
  21. moat/kv/obj/command.py +3 -3
  22. moat/kv/runner.py +4 -5
  23. moat/kv/server.py +106 -126
  24. moat/kv/types.py +10 -12
  25. {moat_kv-0.71.0.dist-info → moat_kv-0.71.7.dist-info}/METADATA +6 -2
  26. moat_kv-0.71.7.dist-info/RECORD +47 -0
  27. {moat_kv-0.71.0.dist-info → moat_kv-0.71.7.dist-info}/WHEEL +1 -1
  28. moat_kv-0.71.7.dist-info/licenses/LICENSE +3 -0
  29. moat_kv-0.71.7.dist-info/licenses/LICENSE.APACHE2 +202 -0
  30. moat_kv-0.71.7.dist-info/licenses/LICENSE.MIT +20 -0
  31. moat_kv-0.71.7.dist-info/top_level.txt +1 -0
  32. build/lib/docs/source/conf.py +0 -201
  33. build/lib/examples/pathify.py +0 -45
  34. build/lib/moat/kv/__init__.py +0 -19
  35. build/lib/moat/kv/_cfg.yaml +0 -93
  36. build/lib/moat/kv/_main.py +0 -91
  37. build/lib/moat/kv/actor/__init__.py +0 -98
  38. build/lib/moat/kv/actor/deletor.py +0 -139
  39. build/lib/moat/kv/auth/__init__.py +0 -444
  40. build/lib/moat/kv/auth/_test.py +0 -166
  41. build/lib/moat/kv/auth/password.py +0 -234
  42. build/lib/moat/kv/auth/root.py +0 -58
  43. build/lib/moat/kv/backend/__init__.py +0 -67
  44. build/lib/moat/kv/backend/mqtt.py +0 -71
  45. build/lib/moat/kv/client.py +0 -1025
  46. build/lib/moat/kv/code.py +0 -236
  47. build/lib/moat/kv/codec.py +0 -11
  48. build/lib/moat/kv/command/__init__.py +0 -1
  49. build/lib/moat/kv/command/acl.py +0 -180
  50. build/lib/moat/kv/command/auth.py +0 -261
  51. build/lib/moat/kv/command/code.py +0 -293
  52. build/lib/moat/kv/command/codec.py +0 -186
  53. build/lib/moat/kv/command/data.py +0 -265
  54. build/lib/moat/kv/command/dump/__init__.py +0 -143
  55. build/lib/moat/kv/command/error.py +0 -149
  56. build/lib/moat/kv/command/internal.py +0 -248
  57. build/lib/moat/kv/command/job.py +0 -433
  58. build/lib/moat/kv/command/log.py +0 -53
  59. build/lib/moat/kv/command/server.py +0 -114
  60. build/lib/moat/kv/command/type.py +0 -201
  61. build/lib/moat/kv/config.py +0 -46
  62. build/lib/moat/kv/data.py +0 -216
  63. build/lib/moat/kv/errors.py +0 -561
  64. build/lib/moat/kv/exceptions.py +0 -126
  65. build/lib/moat/kv/mock/__init__.py +0 -101
  66. build/lib/moat/kv/mock/mqtt.py +0 -159
  67. build/lib/moat/kv/mock/tracer.py +0 -63
  68. build/lib/moat/kv/model.py +0 -1069
  69. build/lib/moat/kv/obj/__init__.py +0 -646
  70. build/lib/moat/kv/obj/command.py +0 -241
  71. build/lib/moat/kv/runner.py +0 -1347
  72. build/lib/moat/kv/server.py +0 -2809
  73. build/lib/moat/kv/types.py +0 -513
  74. ci/rtd-requirements.txt +0 -4
  75. ci/test-requirements.txt +0 -7
  76. ci/travis.sh +0 -96
  77. debian/.gitignore +0 -7
  78. debian/changelog +0 -1435
  79. debian/control +0 -43
  80. debian/moat-kv/usr/lib/python3/dist-packages/docs/source/conf.py +0 -201
  81. debian/moat-kv/usr/lib/python3/dist-packages/examples/pathify.py +0 -45
  82. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/__init__.py +0 -19
  83. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/_cfg.yaml +0 -93
  84. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/_main.py +0 -91
  85. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/actor/__init__.py +0 -98
  86. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/actor/deletor.py +0 -139
  87. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/__init__.py +0 -444
  88. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/_test.py +0 -166
  89. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/password.py +0 -234
  90. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/root.py +0 -58
  91. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/__init__.py +0 -67
  92. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/mqtt.py +0 -71
  93. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/client.py +0 -1025
  94. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/code.py +0 -236
  95. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/codec.py +0 -11
  96. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/__init__.py +0 -1
  97. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/acl.py +0 -180
  98. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/auth.py +0 -261
  99. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/code.py +0 -293
  100. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/codec.py +0 -186
  101. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/data.py +0 -265
  102. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/dump/__init__.py +0 -143
  103. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/error.py +0 -149
  104. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/internal.py +0 -248
  105. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/job.py +0 -433
  106. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/log.py +0 -53
  107. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/server.py +0 -114
  108. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/type.py +0 -201
  109. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/config.py +0 -46
  110. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/data.py +0 -216
  111. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/errors.py +0 -561
  112. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/exceptions.py +0 -126
  113. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/__init__.py +0 -101
  114. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/mqtt.py +0 -159
  115. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/tracer.py +0 -63
  116. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/model.py +0 -1069
  117. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/obj/__init__.py +0 -646
  118. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/obj/command.py +0 -241
  119. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/runner.py +0 -1347
  120. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/server.py +0 -2809
  121. debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/types.py +0 -513
  122. debian/moat-kv.postinst +0 -3
  123. debian/rules +0 -20
  124. debian/source/format +0 -1
  125. debian/watch +0 -4
  126. docs/Makefile +0 -20
  127. docs/make.bat +0 -36
  128. docs/source/TODO.rst +0 -61
  129. docs/source/_static/.gitkeep +0 -0
  130. docs/source/acls.rst +0 -80
  131. docs/source/auth.rst +0 -84
  132. docs/source/client_protocol.rst +0 -456
  133. docs/source/code.rst +0 -341
  134. docs/source/command_line.rst +0 -1187
  135. docs/source/common_protocol.rst +0 -47
  136. docs/source/conf.py +0 -201
  137. docs/source/debugging.rst +0 -70
  138. docs/source/extend.rst +0 -37
  139. docs/source/history.rst +0 -36
  140. docs/source/index.rst +0 -75
  141. docs/source/model.rst +0 -54
  142. docs/source/overview.rst +0 -83
  143. docs/source/related.rst +0 -89
  144. docs/source/server_protocol.rst +0 -450
  145. docs/source/startup.rst +0 -31
  146. docs/source/translator.rst +0 -244
  147. docs/source/tutorial.rst +0 -711
  148. docs/source/v3.rst +0 -168
  149. examples/code/transform.scale.yml +0 -21
  150. examples/code/transform.switch.yml +0 -82
  151. examples/code/transform.timeslot.yml +0 -63
  152. examples/pathify.py +0 -45
  153. moat/kv/codec.py +0 -11
  154. moat_kv-0.71.0.dist-info/RECORD +0 -188
  155. moat_kv-0.71.0.dist-info/top_level.txt +0 -9
  156. scripts/current +0 -15
  157. scripts/env +0 -8
  158. scripts/init +0 -39
  159. scripts/recover +0 -17
  160. scripts/rotate +0 -33
  161. scripts/run +0 -29
  162. scripts/run-all +0 -10
  163. scripts/run-any +0 -10
  164. scripts/run-single +0 -15
  165. scripts/success +0 -4
  166. systemd/moat-kv-recover.service +0 -21
  167. systemd/moat-kv-rotate.service +0 -20
  168. systemd/moat-kv-rotate.timer +0 -10
  169. systemd/moat-kv-run-all.service +0 -26
  170. systemd/moat-kv-run-all@.service +0 -25
  171. systemd/moat-kv-run-any.service +0 -26
  172. systemd/moat-kv-run-any@.service +0 -25
  173. systemd/moat-kv-run-single.service +0 -26
  174. systemd/moat-kv-run-single@.service +0 -25
  175. systemd/moat-kv.service +0 -27
  176. systemd/postinst +0 -7
  177. systemd/sysusers +0 -3
  178. {moat_kv-0.71.0.dist-info → moat_kv-0.71.7.dist-info}/licenses/LICENSE.txt +0 -0
@@ -1,646 +0,0 @@
1
- """
2
- Object interface to moat.kv data
3
-
4
- """
5
- from __future__ import annotations
6
-
7
- import heapq
8
- import weakref
9
- from collections.abc import Mapping
10
- from functools import partial
11
-
12
- import anyio
13
- from asyncscope import scope
14
-
15
- try:
16
- from contextlib import asynccontextmanager
17
- except ImportError:
18
- from async_generator import asynccontextmanager
19
-
20
- from moat.util import (
21
- NoLock,
22
- NotGiven,
23
- Path,
24
- PathLongener,
25
- combine_dict,
26
- yload,
27
- ensure_cfg,
28
- CFG,
29
- )
30
-
31
- __all__ = ["ClientEntry", "AttrClientEntry", "ClientRoot"]
32
-
33
-
34
- class NamedRoot:
35
- """
36
- This is a mix-on class for the root of a subhierarchy that caches named
37
- sub-entries.
38
-
39
- Named children should call `_add_name` on this entry.
40
- """
41
-
42
- def __init__(self, *a, **k):
43
- self.__named = {}
44
- super().__init__(*a, **k)
45
-
46
- def by_name(self, name):
47
- if name is None:
48
- return None
49
- if not isinstance(name, str):
50
- raise ValueError("No string: " + repr(name))
51
- return self.__named.get(name)
52
-
53
- def _add_name(self, obj):
54
- n = obj.name
55
- if n is None:
56
- return
57
-
58
- self.__named[n] = obj
59
- obj.reg_del(self, "_del__name", obj, n)
60
-
61
- def _del__name(self, obj, n):
62
- old = self.__named.pop(n)
63
- if old is None or old is obj:
64
- return
65
- # Oops, that has been superseded. Put it back.
66
- self.__named[n] = old
67
-
68
-
69
- class ClientEntry:
70
- """A helper class that represents a node on the server, as returned by
71
- :meth:`Client.mirror`.
72
- """
73
-
74
- value = NotGiven
75
- chain = None
76
-
77
- def __init__(self, parent, name=None):
78
- self._init()
79
- self._path = parent._path + (name,)
80
- self._name = name
81
- self._parent = weakref.ref(parent)
82
- self._root = weakref.ref(parent.root)
83
- self.client = parent.client
84
-
85
- def _init(self):
86
- self._lock = anyio.Lock() # for saving etc.
87
- self.chain = None
88
- self.value = NotGiven
89
- self._children = dict()
90
-
91
- @classmethod
92
- def child_type(cls, name): # pylint: disable=unused-argument
93
- """Given a node, return the type which the child with that name should have.
94
- The default is "same as this class".
95
- """
96
- return cls
97
-
98
- def value_or(self, default, typ=None):
99
- """
100
- Shortcut to coerce the value to some type
101
- """
102
- val = self.value
103
- if val is NotGiven:
104
- return default
105
- if typ is not None and not isinstance(val, typ):
106
- return default
107
- return val
108
-
109
- def val(self, *attr):
110
- """
111
- Shortcut to get an attribute value
112
- """
113
- return self.val_d(NotGiven, *attr)
114
-
115
- def val_d(self, default, *attr):
116
- """
117
- Shortcut to get an attribute value, or a default
118
- """
119
- val = self.value
120
- if val is NotGiven:
121
- if default is NotGiven:
122
- raise ValueError("no value set")
123
- return default
124
- for a in attr:
125
- try:
126
- val = val[a]
127
- except KeyError:
128
- if default is NotGiven:
129
- raise
130
- return default
131
- return val
132
-
133
- def find_cfg(self, *k, default=NotGiven):
134
- """
135
- Convenience method to get a config value
136
-
137
- It is retrieved first from this node's value, then from the parent.
138
- """
139
- val = self.value_or({}, Mapping)
140
- try:
141
- for kk in k:
142
- try:
143
- val = val[kk]
144
- except TypeError:
145
- raise TypeError(self.value, k, val, kk)
146
- return val
147
- except KeyError:
148
- return self.parent.find_cfg(*k, default=default)
149
-
150
- @property
151
- def parent(self):
152
- return self._parent()
153
-
154
- @property
155
- def root(self):
156
- return self._root()
157
-
158
- @property
159
- def subpath(self):
160
- """Return the path to this entry, starting with its :class:`ClientRoot` base."""
161
- return self._path[len(self.root._path) :]
162
-
163
- @property
164
- def all_children(self):
165
- """Iterate all child nodes with data.
166
-
167
- You can send ``True`` to the iterator if you want to skip a subtree.
168
- """
169
- for k in self:
170
- if k.value is not NotGiven:
171
- res = yield k
172
- if res is True:
173
- continue
174
- yield from k.all_children
175
-
176
- def allocate(self, name: str, exists: bool = False):
177
- """
178
- Create the child named "name". It is created (locally) if it doesn't exist.
179
-
180
- Arguments:
181
- name: The child node's name.
182
- exists: return the existing value? otherwise error
183
-
184
- If this returns ``None``, the subtree shall not be tracked.
185
-
186
- """
187
- c = self._children.get(name, None)
188
- if c is not None:
189
- if exists:
190
- return c
191
- raise RuntimeError("Duplicate child", self, name, c)
192
- c = self.child_type(name)
193
- if c is None:
194
- raise KeyError(name)
195
- self._children[name] = c = c(self, name)
196
- return c
197
-
198
- def follow(self, path, *, create=None, empty_ok=False):
199
- """Look up a sub-entry.
200
-
201
- Arguments:
202
- path (Path): the path elements to follow.
203
- create (bool): Create the entries. Default ``False``.
204
- Otherwise return ``None`` if not found.
205
-
206
- The path may not be empty. It also must not be a string,
207
- because that indicates that you called ``.follow(*path)`` instead of
208
- ``.follow(path)``.
209
- """
210
- if isinstance(path, str):
211
- raise RuntimeError("You seem to have used '*path' instead of 'path'.")
212
- if not empty_ok and not len(path):
213
- raise RuntimeError("Empty path")
214
-
215
- node = self
216
- for n, elem in enumerate(path, start=1):
217
- next_node = node.get(elem)
218
- if next_node is None:
219
- if create is False:
220
- return None
221
- next_node = node.allocate(elem)
222
- elif create and n == len(path) and next_node.value is not NotGiven:
223
- raise RuntimeError("Duplicate child", self, path, n)
224
-
225
- node = next_node
226
-
227
- return node
228
-
229
- def __getitem__(self, name):
230
- return self._children[name]
231
-
232
- def by_name(self, name):
233
- """
234
- Lookup by a human-readable name?
235
- """
236
- return self._children[name]
237
-
238
- def __delitem__(self, name):
239
- del self._children[name]
240
-
241
- def get(self, name):
242
- return self._children.get(name, None)
243
-
244
- def __iter__(self):
245
- """Iterating an entry returns its children."""
246
- return iter(list(self._children.values()))
247
-
248
- def __bool__(self):
249
- return self.value is not NotGiven or bool(self._children)
250
-
251
- def __len__(self):
252
- return len(self._children)
253
-
254
- def __contains__(self, k):
255
- if isinstance(k, type(self)):
256
- k = k._name
257
- return k in self._children
258
-
259
- async def update(self, value, _locked=False, wait=False):
260
- """Update (or simply set) this node's value.
261
-
262
- This is a coroutine.
263
- """
264
- async with NoLock if _locked else self._lock:
265
- r = await self.root.client.set(
266
- self._path,
267
- chain=self.chain,
268
- value=value,
269
- nchain=3,
270
- idem=True,
271
- )
272
- if wait:
273
- await self.root.wait_chain(r.chain)
274
- self.value = value
275
- self.chain = r.chain
276
- return r
277
-
278
- async def delete(self, _locked=False, nchain=0, chain=True, wait=False, recursive=False):
279
- """Delete this node's value.
280
-
281
- This is a coroutine.
282
- """
283
- async with NoLock if _locked else self._lock:
284
- r = await self.root.client.delete(
285
- self._path,
286
- nchain=nchain,
287
- recursive=recursive,
288
- **({"chain": self.chain} if chain else {}),
289
- )
290
- if wait:
291
- await self.root.wait_chain(r.chain)
292
- self.chain = None
293
- return r
294
-
295
- async def set_value(self, value):
296
- """Callback to set the value when data has arrived.
297
-
298
- This method is strictly for overriding.
299
- Don't call me, I'll call you.
300
-
301
- This is a coroutine, for ease of integration.
302
- """
303
- self.value = value
304
-
305
- async def seen_value(self):
306
- """The current value was seen (again).
307
-
308
- Useful for syncing.
309
-
310
- The default action is to do nothing.
311
- """
312
- pass
313
-
314
- def mark_inconsistent(self, r):
315
- """There has been an inconsistent update.
316
-
317
- This call will immediately be followed by a call to
318
- :meth:`set_value`, thus it is not async.
319
-
320
- The default action is to do nothing.
321
- """
322
- pass
323
-
324
-
325
- def _node_gt(self, other):
326
- if other is None and self is not None:
327
- return True
328
- if self is None or self == other:
329
- return False
330
- while self["node"] != other["node"]:
331
- self = self["prev"]
332
- if self is None:
333
- return False
334
- return self["tick"] >= other["tick"]
335
-
336
-
337
- class AttrClientEntry(ClientEntry):
338
- """A ClientEntry which expects a dict as value and sets (some of) the clients'
339
- attributes appropriately.
340
-
341
- Set the classvar ``ATTRS`` to a list of the attrs you want saved. Note
342
- that these are not inherited: when you subclass, copy and extend the
343
- ``ATTRS`` of your superclass.
344
-
345
- If the entry is deleted (value set to ``None``, the attributes listed in
346
- ``ATTRS`` will be deleted too, or revert to the class values.
347
- """
348
-
349
- ATTRS = ()
350
-
351
- async def update(self, value, **kw): # pylint: disable=arguments-differ
352
- raise RuntimeError("Nope. Set attributes and call '.save()'.")
353
-
354
- async def set_value(self, value):
355
- """Callback to set the value when data has arrived.
356
-
357
- This method sets the actual attributes.
358
-
359
- This method is strictly for overriding.
360
- Don't call me, I'll call you.
361
- """
362
- await super().set_value(value)
363
- for k in self.ATTRS:
364
- if value is not NotGiven and k in value:
365
- setattr(self, k, value[k])
366
- else:
367
- try:
368
- delattr(self, k)
369
- except AttributeError:
370
- pass
371
-
372
- def get_value(self, skip_none=False, skip_empty=False):
373
- """
374
- Extract value from attrs
375
- """
376
- res = {}
377
- for attr in type(self).ATTRS:
378
- try:
379
- v = getattr(self, attr)
380
- except AttributeError:
381
- pass
382
- else:
383
- if v is NotGiven:
384
- continue
385
- if skip_none and v is None:
386
- continue
387
- if skip_empty and v == ():
388
- continue
389
- res[attr] = v
390
- return res
391
-
392
- async def save(self, wait=False):
393
- """
394
- Save myself to storage, by copying ATTRS to a new value.
395
- """
396
- async with self._lock:
397
- r = await super().update(value=self.get_value(), _locked=True)
398
- if wait:
399
- await self.root.wait_chain(r.chain)
400
- return r
401
-
402
-
403
- class MirrorRoot(ClientEntry):
404
- """
405
- This class represents the root of a subsystem's storage, used for
406
- object-agnostic data mirroring.
407
-
408
- Used internally.
409
- """
410
-
411
- _tg = None
412
-
413
- CFG = None # You need to override this with a dict(prefix=('where','ever'))
414
-
415
- def __init__(self, client, path, *, need_wait=False, cfg=None, require_client=True):
416
- # pylint: disable=super-init-not-called
417
- self._init()
418
- self.client = client
419
- self._path = path
420
- self._need_wait = need_wait
421
- self._loaded = anyio.Event()
422
- self._require_client = require_client
423
-
424
- if cfg is None:
425
- cfg = {}
426
- self._cfg = cfg
427
- self._name = self.client.name
428
-
429
- if need_wait:
430
- self._waiters = dict()
431
- self._seen = dict()
432
-
433
- @classmethod
434
- async def as_handler(cls, client, cfg=None, key="prefix", subpath=(), name=None, **kw):
435
- """Return an (or "the") instance of this class.
436
-
437
- The handler is created if it doesn't exist.
438
-
439
- Instances are distinguished by a key (from config), which
440
- must contain their path, and an optional subpath.
441
- """
442
- d = []
443
- if cfg is not None:
444
- d.append(cfg)
445
-
446
- defcfg = client._cfg.get(cls.CFG)
447
- if not defcfg:
448
- # seems we didn't load the class' default config yet.
449
- import inspect
450
- from pathlib import Path as _Path
451
-
452
- md = inspect.getmodule(cls)
453
- ensure_cfg("moat.kv")
454
- defcfg = CFG.kv.get(cls.CFG)
455
- try:
456
- f = (_Path(md.__file__).parent / "_nconfig.yaml").open("r")
457
- except OSError:
458
- pass
459
- else:
460
- with f:
461
- defcfg = yload(f, attr=True).get("kv", {}).get(cls.CFG)
462
- if cfg:
463
- if defcfg:
464
- cfg = combine_dict(cfg, defcfg)
465
- else:
466
- if not defcfg:
467
- raise RuntimeError("no config for " + repr(cls))
468
- cfg = defcfg
469
-
470
- if name is None:
471
- # if key != "prefix":
472
- # subpath = Path(key) + subpath
473
- name = str(Path("_moat.kv", client.name, cls.CFG, *subpath))
474
-
475
- def make():
476
- return client.mirror(cfg[key] + subpath, root_type=cls, need_wait=True, cfg=cfg, **kw)
477
-
478
- return await client.unique_helper(name, factory=make)
479
-
480
- @classmethod
481
- def child_type(cls, name):
482
- """Given a node, return the type which the child with that name should have.
483
- The default is :class:`ClientEntry`.
484
-
485
- This may return ``None``. In that case the subtree with this name
486
- shall not be tracked further.
487
- """
488
- return ClientEntry
489
-
490
- @property
491
- def root(self):
492
- """Returns this instance."""
493
- return self
494
-
495
- def find_cfg(self, *k, default=NotGiven):
496
- """
497
- Convenience method to get a config value.
498
-
499
- It is retrieved first from this node's value, then from the configuration read via CFG.
500
- """
501
- val = self.value_or({}, Mapping)
502
- try:
503
- for kk in k:
504
- val = val[kk]
505
- return val
506
- except KeyError:
507
- try:
508
- val = self._cfg
509
- for kk in k:
510
- val = val[kk]
511
- return val
512
- except KeyError:
513
- if default is NotGiven:
514
- raise
515
- return default
516
-
517
- async def run_starting(self):
518
- """Hook for 'about to start reading'"""
519
- pass
520
-
521
- async def running(self):
522
- """Hook for 'done reading current state'"""
523
- self._loaded.set()
524
-
525
- @asynccontextmanager
526
- async def run(self):
527
- """A coroutine that fetches, and continually updates, a subtree."""
528
- if self._require_client:
529
- scope.requires(self.client.scope)
530
-
531
- async with anyio.create_task_group() as tg:
532
- self._tg = tg
533
-
534
- async def monitor(*, task_status):
535
- pl = PathLongener(())
536
- await self.run_starting()
537
- async with self.client._stream(
538
- "watch",
539
- nchain=3,
540
- path=self._path,
541
- fetch=True,
542
- ) as w:
543
- async for r in w:
544
- if "path" not in r:
545
- if r.get("state", "") == "uptodate":
546
- await self.running()
547
- task_status.started()
548
- continue
549
- pl(r)
550
- val = r.get("value", NotGiven)
551
- entry = self.follow(r.path, create=None, empty_ok=True)
552
- if entry is not None:
553
- # Test for consistency
554
- try:
555
- if entry.chain == r.chain:
556
- # entry.update() has set this
557
- await entry.seen_value()
558
- elif _node_gt(entry.chain, r.chain):
559
- # stale data
560
- pass
561
- elif not _node_gt(r.chain, entry.chain):
562
- entry.mark_inconsistent(r)
563
- except AttributeError:
564
- pass
565
-
566
- # update entry
567
- entry.chain = None if val is NotGiven else r.get("chain", None)
568
- await entry.set_value(val)
569
-
570
- if val is NotGiven and not entry:
571
- # the entry has no value and no children,
572
- # so we delete it (and possibly its
573
- # parents) from our tree.
574
- n = list(entry.subpath)
575
- while n:
576
- # no-op except for class-specific side effects
577
- # like setting an event
578
- await entry.set_value(NotGiven)
579
-
580
- entry = entry.parent
581
- del entry[n.pop()]
582
- if entry:
583
- break
584
-
585
- if not self._need_wait or "chain" not in r:
586
- continue
587
- c = r.chain
588
- while c is not None:
589
- if self._seen.get(c.node, 0) < c.tick:
590
- self._seen[c.node] = c.tick
591
- try:
592
- w = self._waiters[c.node]
593
- except KeyError:
594
- pass
595
- else:
596
- while w and w[0][0] <= c.tick:
597
- heapq.heappop(w)[1].set()
598
- c = c.get("prev", None)
599
-
600
- await tg.start(monitor)
601
- try:
602
- yield self
603
- finally:
604
- with anyio.fail_after(2, shield=True):
605
- tg.cancel_scope.cancel()
606
- pass # end of 'run', closing taskgroup
607
-
608
- async def cancel(self):
609
- """Stop the monitor"""
610
- await self._tg.cancel_scope.cancel()
611
-
612
- async def wait_loaded(self):
613
- """Wait for the tree to be loaded completely."""
614
- await self._loaded.wait()
615
-
616
- async def wait_chain(self, chain):
617
- """Wait for a tree update containing this tick."""
618
- try:
619
- if chain.tick <= self._seen[chain.node]:
620
- return
621
- except KeyError:
622
- pass
623
- w = self._waiters.setdefault(chain.node, [])
624
- e = anyio.Event()
625
- heapq.heappush(w, (chain.tick, e))
626
- await e.wait()
627
-
628
- async def spawn(self, p, *a, **kw):
629
- p = partial(p, *a, **kw)
630
- self._tg.start_soon(p)
631
-
632
-
633
- class ClientRoot(MirrorRoot):
634
- """
635
- This class represents the root of a subsystem's storage.
636
-
637
- To use this class, create a subclass that, at minimum, overrides
638
- ``CFG`` and ``child_type``. ``CFG`` must be a dict with at least a
639
- ``prefix`` tuple. You instantiate the entry using :meth:`as_handler`.
640
-
641
- """
642
-
643
- def __init__(self, *a, **kw):
644
- if self.CFG is None:
645
- raise TypeError(f"You need to override .CFG in {type(self).__name__}")
646
- super().__init__(*a, **kw)