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,234 +0,0 @@
1
- #
2
- """
3
- Password-based auth method.
4
-
5
- Does not limit anything, allows everything.
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- import nacl.secret
11
-
12
- from ..client import Client, NoData
13
- from ..exceptions import AuthFailedError
14
- from ..model import Entry
15
- from ..server import StreamCommand
16
- from . import (
17
- BaseClientAuth,
18
- BaseClientAuthMaker,
19
- BaseServerAuthMaker,
20
- RootServerUser,
21
- null_client_login,
22
- null_server_login,
23
- )
24
-
25
-
26
- def load(typ: str, *, make: bool = False, server: bool):
27
- if typ == "client":
28
- if server:
29
- return null_server_login
30
- else:
31
- return null_client_login
32
- if typ != "user":
33
- raise NotImplementedError("This module only handles users")
34
- if server:
35
- if make:
36
- return ServerUserMaker
37
- else:
38
- return ServerUser
39
- else:
40
- if make:
41
- return ClientUserMaker
42
- else:
43
- return ClientUser
44
-
45
-
46
- async def pack_pwd(client, password, length):
47
- """Client side: encrypt password"""
48
- secret = await client.dh_secret(length=length)
49
- from hashlib import sha256
50
-
51
- pwd = sha256(password).digest()
52
- box = nacl.secret.SecretBox(secret)
53
- pwd = box.encrypt(pwd)
54
- return pwd
55
-
56
-
57
- async def unpack_pwd(client, password):
58
- """Server side: extract password"""
59
- box = nacl.secret.SecretBox(client.dh_key)
60
- pwd = box.decrypt(password)
61
- return pwd
62
- # TODO check with Argon2
63
-
64
-
65
- class ServerUserMaker(BaseServerAuthMaker):
66
- _name = None
67
- _aux = None
68
- password: str = None
69
-
70
- @property
71
- def ident(self):
72
- return self._name
73
-
74
- @classmethod
75
- async def recv(cls, cmd, data):
76
- self = cls()
77
- self._name = data["ident"]
78
- self._aux = data.get("aux")
79
- pwd = data.get("password")
80
- pwd = await unpack_pwd(cmd.client, pwd)
81
-
82
- # TODO use Argon2 to re-hash this
83
- self.password = pwd
84
- return self
85
-
86
- async def send(self, cmd):
87
- return # nothing to do, we don't share the hash
88
-
89
- @classmethod
90
- def load(cls, data):
91
- self = super().load(data)
92
- self._name = data.path[-1]
93
- return self
94
-
95
- def save(self):
96
- res = super().save()
97
- res["password"] = self.password
98
- return res
99
-
100
-
101
- class ServerUser(RootServerUser):
102
- @classmethod
103
- def load(cls, data: Entry):
104
- """Create a ServerUser object from existing stored data"""
105
- self = super().load(data)
106
- self._name = data.name
107
- return self
108
-
109
- async def auth(self, cmd: StreamCommand, data):
110
- """Verify that @data authenticates this user."""
111
- await super().auth(cmd, data)
112
-
113
- pwd = await unpack_pwd(cmd.client, data.password)
114
- if pwd != self.password: # pylint: disable=no-member
115
- # pylint: disable=no-member
116
- raise AuthFailedError("Password hashes do not match", self._name)
117
-
118
-
119
- class ClientUserMaker(BaseClientAuthMaker):
120
- gen_schema = dict(
121
- type="object",
122
- additionalProperties=True,
123
- properties=dict(
124
- name=dict(type="string", minLength=1, pattern="^[a-zA-Z][a-zA-Z0-9_]*$"),
125
- password=dict(type="string", minLength=5),
126
- ),
127
- required=["name", "password"],
128
- )
129
- mod_schema = dict(
130
- type="object",
131
- additionalProperties=True,
132
- properties=dict(password=dict(type="string", minLength=5)),
133
- # required=[],
134
- )
135
- _name = None
136
- _pass = None
137
- _length = 1024
138
-
139
- @property
140
- def ident(self):
141
- return self._name
142
-
143
- # Overly-complicated methods of exchanging the user name
144
-
145
- @classmethod
146
- def build(cls, user, _initial=True):
147
- self = super().build(user, _initial=_initial)
148
- self._name = user["name"]
149
- if "password" in user:
150
- self._pass = user["password"].encode("utf-8")
151
- return self
152
-
153
- @classmethod
154
- async def recv(cls, client: Client, ident: str, _kind: str = "user", _initial=True):
155
- """Read a record representing a user from the server."""
156
- m = await client._request(
157
- action="auth_get",
158
- typ=cls._auth_method,
159
- kind=_kind,
160
- ident=ident,
161
- nchain=0 if _initial else 2,
162
- )
163
- # just to verify that the user exists
164
- # There's no reason to send the password hash back
165
- self = cls(_initial=_initial)
166
- self._name = m.name
167
- try:
168
- self._chain = m.chain
169
- except AttributeError:
170
- pass
171
- return self
172
-
173
- async def send(self, client: Client, _kind="user", **msg): # pylint: disable=unused-argument,arguments-differ
174
- """Send a record representing this user to the server."""
175
- if self._pass is not None:
176
- msg["password"] = await pack_pwd(client, self._pass, self._length)
177
-
178
- await client._request(
179
- action="auth_set",
180
- ident=self._name,
181
- typ=type(self)._auth_method,
182
- kind=_kind,
183
- chain=self._chain,
184
- **msg,
185
- )
186
-
187
- def export(self):
188
- """Return the data required to re-create the user via :meth:`build`."""
189
- res = super().export()
190
- res["name"] = self._name
191
- return res
192
-
193
-
194
- class ClientUser(BaseClientAuth):
195
- schema = dict(
196
- type="object",
197
- additionalProperties=True,
198
- properties=dict(
199
- name=dict(type="string", minLength=1, pattern="^[a-zA-Z][a-zA-Z0-9_]*$"),
200
- password=dict(type="string", minLength=5),
201
- ),
202
- required=["name", "password"],
203
- )
204
- _name = None
205
- _pass = None
206
- _length = 1024
207
-
208
- @property
209
- def ident(self):
210
- return self._name
211
-
212
- @classmethod
213
- def build(cls, user):
214
- self = super().build(user)
215
- self._name = user["name"]
216
- self._pass = user["password"].encode("utf-8")
217
- return self
218
-
219
- async def auth(self, client: Client, chroot=()):
220
- """
221
- Authorizes this user with the server.
222
- """
223
- try:
224
- pw = await pack_pwd(client, self._pass, self._length)
225
- await client._request(
226
- action="auth",
227
- typ=self._auth_method,
228
- iter=False,
229
- ident=self.ident,
230
- password=pw,
231
- **self.auth_data(),
232
- )
233
- except NoData:
234
- pass
@@ -1,58 +0,0 @@
1
- #
2
- """
3
- Null auth method.
4
-
5
- Does not limit anything, allows everything.
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- from . import (
11
- BaseClientAuth,
12
- BaseClientAuthMaker,
13
- BaseServerAuthMaker,
14
- RootServerUser,
15
- null_client_login,
16
- null_server_login,
17
- )
18
-
19
-
20
- def load(typ: str, *, make: bool = False, server: bool):
21
- if typ == "client":
22
- if server:
23
- return null_server_login
24
- else:
25
- return null_client_login
26
- if typ != "user":
27
- raise NotImplementedError("This module only handles users")
28
- if server:
29
- if make:
30
- return ServerUserMaker
31
- else:
32
- return ServerUser
33
- else:
34
- if make:
35
- return ClientUserMaker
36
- else:
37
- return ClientUser
38
-
39
-
40
- class ServerUserMaker(BaseServerAuthMaker):
41
- schema = {"type": "object", "additionalProperties": False}
42
-
43
-
44
- class ServerUser(RootServerUser):
45
- schema = {"type": "object", "additionalProperties": False}
46
-
47
-
48
- class ClientUserMaker(BaseClientAuthMaker):
49
- gen_schema = {"type": "object", "additionalProperties": False}
50
- mod_schema = {"type": "object", "additionalProperties": False}
51
-
52
- @property
53
- def ident(self):
54
- return "*"
55
-
56
-
57
- class ClientUser(BaseClientAuth):
58
- schema = {"type": "object", "additionalProperties": False}
@@ -1,67 +0,0 @@
1
- from __future__ import annotations
2
- from abc import ABCMeta, abstractmethod
3
- from contextlib import asynccontextmanager
4
-
5
- import anyio
6
-
7
- __all__ = ["get_backend", "Backend"]
8
-
9
-
10
- class Backend(metaclass=ABCMeta):
11
- def __init__(self, tg):
12
- self._tg = tg
13
- self._njobs = 0
14
- self._ended = None
15
-
16
- @abstractmethod
17
- @asynccontextmanager
18
- async def connect(self, *a, **k):
19
- """
20
- This async context manager returns a connection.
21
- """
22
-
23
- async def aclose(self):
24
- """
25
- Force-close the connection.
26
- """
27
- self._tg.cancel_scope.cancel()
28
- if self._njobs > 0:
29
- with anyio.move_on_after(2):
30
- await self._ended.wait()
31
-
32
- async def spawn(self, p, *a, **kw):
33
- async def _run(p, a, kw, *, task_status):
34
- if self._ended is None:
35
- self._ended = anyio.Event()
36
- self._njobs += 1
37
- task_status.started()
38
- try:
39
- return await p(*a, **kw)
40
- finally:
41
- self._njobs -= 1
42
- if not self._njobs:
43
- self._ended.set()
44
- self._ended = None
45
-
46
- return await self._tg.start(_run, p, a, kw)
47
-
48
- @abstractmethod
49
- @asynccontextmanager
50
- async def monitor(self, *topic):
51
- """
52
- Return an async iterator that listens to this topic.
53
- """
54
-
55
- @abstractmethod
56
- async def send(self, *topic, payload):
57
- """
58
- Send this payload to this topic.
59
- """
60
-
61
-
62
- def get_backend(name):
63
- from importlib import import_module
64
-
65
- if "." not in name:
66
- name = "moat.kv.backend." + name
67
- return import_module(name).connect
@@ -1,74 +0,0 @@
1
- from __future__ import annotations
2
- import logging
3
- from contextlib import asynccontextmanager
4
-
5
- import anyio
6
- from moat.mqtt.client import MQTTClient
7
- from moat.mqtt.codecs import NoopCodec
8
- from moat.util import NotGiven
9
-
10
- from . import Backend
11
-
12
- logger = logging.getLogger(__name__)
13
-
14
- # Simply setting connect=asyncserf.serf_client interferes with mocking
15
- # when testing.
16
-
17
-
18
- class MqttMessage:
19
- def __init__(self, topic, payload):
20
- self.topic = topic
21
- self.payload = payload
22
-
23
-
24
- class MqttBackend(Backend):
25
- client = None
26
-
27
- @asynccontextmanager
28
- async def connect(self, *a, **kw):
29
- codec = kw.pop("codec", NotGiven)
30
- C = MQTTClient(self._tg, codec=codec)
31
- try:
32
- await C.connect(*a, **kw)
33
- self.client = C
34
- yield self
35
- finally:
36
- self.client = None
37
- with anyio.CancelScope(shield=True):
38
- await self.aclose()
39
- await C.disconnect()
40
-
41
- @asynccontextmanager
42
- async def monitor(self, *topic):
43
- topic = "/".join(str(x) for x in topic)
44
- logger.info("Monitor %s start", topic)
45
- try:
46
- async with self.client.subscription(topic) as sub:
47
-
48
- async def sub_get(sub):
49
- async for msg in sub:
50
- yield MqttMessage(msg.topic.split("/"), msg.data)
51
-
52
- yield sub_get(sub)
53
- except anyio.get_cancelled_exc_class():
54
- raise
55
- except BaseException as exc:
56
- logger.exception("Monitor %s end: %r", topic, exc)
57
- raise
58
- else:
59
- logger.info("Monitor %s end", topic)
60
-
61
- def send(self, *topic, payload): # pylint: disable=invalid-overridden-method
62
- """
63
- Send this payload to this topic.
64
- """
65
- # client.publish is also async, pass-thru
66
- return self.client.publish("/".join(str(x) for x in topic), message=payload)
67
-
68
-
69
- @asynccontextmanager
70
- async def connect(**kw):
71
- async with anyio.create_task_group() as tg:
72
- c = MqttBackend(tg)
73
- async with c.connect(**kw):
74
- yield c
@@ -1,45 +0,0 @@
1
- from __future__ import annotations
2
- from contextlib import asynccontextmanager
3
-
4
- import anyio
5
- import asyncserf
6
-
7
- from . import Backend
8
-
9
- # Simply setting connect=asyncserf.serf_client interferes with mocking
10
- # when testing.
11
-
12
-
13
- class SerfBackend(Backend):
14
- client = None
15
-
16
- @asynccontextmanager
17
- async def connect(self, *a, **k):
18
- async with asyncserf.serf_client(*a, **k) as c:
19
- self.client = c
20
- try:
21
- yield self
22
- finally:
23
- with anyio.CancelScope(shield=True):
24
- await self.aclose()
25
- self.client = None
26
-
27
- def monitor(self, *topic): # pylint: disable=invalid-overridden-method
28
- topic = "user:" + ".".join(topic)
29
- # self.client.stream is also async, pass thru
30
- return self.client.stream(topic)
31
-
32
- def send(self, *topic, payload): # pylint: disable=invalid-overridden-method
33
- """
34
- Send this payload to this topic.
35
- """
36
- # self.client.event is also async, pass thru
37
- return self.client.event(".".join(topic), payload=payload, coalesce=False)
38
-
39
-
40
- @asynccontextmanager
41
- async def connect(*a, **kw):
42
- async with anyio.create_task_group() as tg:
43
- c = SerfBackend(tg)
44
- async with c.connect(*a, **kw):
45
- yield c