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.
- moat/kv/__init__.py +6 -7
- moat/kv/_cfg.yaml +5 -8
- moat/kv/actor/__init__.py +2 -1
- moat/kv/actor/deletor.py +4 -1
- moat/kv/auth/__init__.py +12 -13
- moat/kv/auth/_test.py +4 -1
- moat/kv/auth/password.py +11 -7
- moat/kv/backend/mqtt.py +4 -8
- moat/kv/client.py +20 -39
- moat/kv/code.py +3 -3
- moat/kv/command/data.py +4 -3
- moat/kv/command/dump/__init__.py +29 -29
- moat/kv/command/internal.py +2 -3
- moat/kv/command/job.py +1 -2
- moat/kv/command/type.py +3 -6
- moat/kv/data.py +9 -8
- moat/kv/errors.py +16 -8
- moat/kv/mock/__init__.py +2 -12
- moat/kv/model.py +28 -32
- moat/kv/obj/__init__.py +3 -3
- moat/kv/obj/command.py +3 -3
- moat/kv/runner.py +4 -5
- moat/kv/server.py +106 -126
- moat/kv/types.py +8 -6
- {moat_kv-0.70.24.dist-info → moat_kv-0.71.6.dist-info}/METADATA +7 -6
- moat_kv-0.71.6.dist-info/RECORD +47 -0
- {moat_kv-0.70.24.dist-info → moat_kv-0.71.6.dist-info}/WHEEL +1 -1
- moat_kv-0.71.6.dist-info/licenses/LICENSE +3 -0
- moat_kv-0.71.6.dist-info/licenses/LICENSE.APACHE2 +202 -0
- moat_kv-0.71.6.dist-info/licenses/LICENSE.MIT +20 -0
- moat_kv-0.71.6.dist-info/top_level.txt +1 -0
- build/lib/docs/source/conf.py +0 -201
- build/lib/examples/pathify.py +0 -45
- build/lib/moat/kv/__init__.py +0 -19
- build/lib/moat/kv/_cfg.yaml +0 -97
- build/lib/moat/kv/_main.py +0 -91
- build/lib/moat/kv/actor/__init__.py +0 -98
- build/lib/moat/kv/actor/deletor.py +0 -139
- build/lib/moat/kv/auth/__init__.py +0 -444
- build/lib/moat/kv/auth/_test.py +0 -166
- build/lib/moat/kv/auth/password.py +0 -234
- build/lib/moat/kv/auth/root.py +0 -58
- build/lib/moat/kv/backend/__init__.py +0 -67
- build/lib/moat/kv/backend/mqtt.py +0 -74
- build/lib/moat/kv/backend/serf.py +0 -45
- build/lib/moat/kv/client.py +0 -1025
- build/lib/moat/kv/code.py +0 -236
- build/lib/moat/kv/codec.py +0 -11
- build/lib/moat/kv/command/__init__.py +0 -1
- build/lib/moat/kv/command/acl.py +0 -180
- build/lib/moat/kv/command/auth.py +0 -261
- build/lib/moat/kv/command/code.py +0 -293
- build/lib/moat/kv/command/codec.py +0 -186
- build/lib/moat/kv/command/data.py +0 -265
- build/lib/moat/kv/command/dump/__init__.py +0 -143
- build/lib/moat/kv/command/error.py +0 -149
- build/lib/moat/kv/command/internal.py +0 -248
- build/lib/moat/kv/command/job.py +0 -433
- build/lib/moat/kv/command/log.py +0 -53
- build/lib/moat/kv/command/server.py +0 -114
- build/lib/moat/kv/command/type.py +0 -201
- build/lib/moat/kv/config.py +0 -46
- build/lib/moat/kv/data.py +0 -216
- build/lib/moat/kv/errors.py +0 -561
- build/lib/moat/kv/exceptions.py +0 -126
- build/lib/moat/kv/mock/__init__.py +0 -101
- build/lib/moat/kv/mock/mqtt.py +0 -159
- build/lib/moat/kv/mock/serf.py +0 -250
- build/lib/moat/kv/mock/tracer.py +0 -63
- build/lib/moat/kv/model.py +0 -1069
- build/lib/moat/kv/obj/__init__.py +0 -646
- build/lib/moat/kv/obj/command.py +0 -241
- build/lib/moat/kv/runner.py +0 -1347
- build/lib/moat/kv/server.py +0 -2809
- build/lib/moat/kv/types.py +0 -513
- debian/moat-kv/usr/lib/python3/dist-packages/docs/source/conf.py +0 -201
- debian/moat-kv/usr/lib/python3/dist-packages/examples/pathify.py +0 -45
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/__init__.py +0 -19
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/_cfg.yaml +0 -97
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/_main.py +0 -91
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/actor/__init__.py +0 -98
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/actor/deletor.py +0 -139
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/__init__.py +0 -444
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/_test.py +0 -166
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/password.py +0 -234
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/root.py +0 -58
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/__init__.py +0 -67
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/mqtt.py +0 -74
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/serf.py +0 -45
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/client.py +0 -1025
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/code.py +0 -236
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/codec.py +0 -11
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/__init__.py +0 -1
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/acl.py +0 -180
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/auth.py +0 -261
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/code.py +0 -293
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/codec.py +0 -186
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/data.py +0 -265
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/dump/__init__.py +0 -143
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/error.py +0 -149
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/internal.py +0 -248
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/job.py +0 -433
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/log.py +0 -53
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/server.py +0 -114
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/type.py +0 -201
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/config.py +0 -46
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/data.py +0 -216
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/errors.py +0 -561
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/exceptions.py +0 -126
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/__init__.py +0 -101
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/mqtt.py +0 -159
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/serf.py +0 -250
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/tracer.py +0 -63
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/model.py +0 -1069
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/obj/__init__.py +0 -646
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/obj/command.py +0 -241
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/runner.py +0 -1347
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/server.py +0 -2809
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/types.py +0 -513
- docs/source/conf.py +0 -201
- examples/pathify.py +0 -45
- moat/kv/backend/serf.py +0 -45
- moat/kv/codec.py +0 -11
- moat/kv/mock/serf.py +0 -250
- moat_kv-0.70.24.dist-info/RECORD +0 -137
- moat_kv-0.70.24.dist-info/top_level.txt +0 -9
- {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
|