aiomisc 17.5.17__py3-none-any.whl → 17.5.20__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.
- aiomisc/aggregate.py +10 -5
- aiomisc/backoff.py +12 -4
- aiomisc/process_pool.py +1 -3
- aiomisc/service/dns/__init__.py +14 -0
- aiomisc/service/dns/records.py +400 -0
- aiomisc/service/dns/service.py +131 -0
- aiomisc/service/dns/store.py +51 -0
- aiomisc/service/dns/tree.py +59 -0
- aiomisc/service/dns/zone.py +44 -0
- aiomisc/service/grpc_server.py +17 -1
- aiomisc/thread_pool.py +1 -3
- aiomisc/timeout.py +7 -4
- aiomisc/version.py +2 -2
- {aiomisc-17.5.17.dist-info → aiomisc-17.5.20.dist-info}/METADATA +3 -1
- {aiomisc-17.5.17.dist-info → aiomisc-17.5.20.dist-info}/RECORD +18 -12
- {aiomisc-17.5.17.dist-info → aiomisc-17.5.20.dist-info}/COPYING +0 -0
- {aiomisc-17.5.17.dist-info → aiomisc-17.5.20.dist-info}/WHEEL +0 -0
- {aiomisc-17.5.17.dist-info → aiomisc-17.5.20.dist-info}/entry_points.txt +0 -0
aiomisc/aggregate.py
CHANGED
@@ -7,8 +7,8 @@ from dataclasses import dataclass
|
|
7
7
|
from inspect import Parameter
|
8
8
|
from typing import (
|
9
9
|
Any,
|
10
|
-
Awaitable,
|
11
10
|
Callable,
|
11
|
+
Coroutine,
|
12
12
|
Generic,
|
13
13
|
Iterable,
|
14
14
|
List,
|
@@ -246,7 +246,7 @@ class Aggregator(AggregatorAsync[V, R], Generic[V, R]):
|
|
246
246
|
|
247
247
|
def aggregate(
|
248
248
|
leeway_ms: float, max_count: Optional[int] = None
|
249
|
-
) -> Callable[[AggregateFunc[V, R]], Callable[[V],
|
249
|
+
) -> Callable[[AggregateFunc[V, R]], Callable[[V], Coroutine[Any, Any, R]]]:
|
250
250
|
"""
|
251
251
|
Parametric decorator that aggregates multiple
|
252
252
|
(but no more than ``max_count`` defaulting to ``None``) single-argument
|
@@ -275,7 +275,9 @@ def aggregate(
|
|
275
275
|
|
276
276
|
:return:
|
277
277
|
"""
|
278
|
-
def decorator(
|
278
|
+
def decorator(
|
279
|
+
func: AggregateFunc[V, R]
|
280
|
+
) -> Callable[[V], Coroutine[Any, Any, R]]:
|
279
281
|
aggregator = Aggregator(
|
280
282
|
func, max_count=max_count, leeway_ms=leeway_ms,
|
281
283
|
)
|
@@ -285,7 +287,10 @@ def aggregate(
|
|
285
287
|
|
286
288
|
def aggregate_async(
|
287
289
|
leeway_ms: float, max_count: Optional[int] = None,
|
288
|
-
) -> Callable[
|
290
|
+
) -> Callable[
|
291
|
+
[AggregateAsyncFunc[V, R]],
|
292
|
+
Callable[[V], Coroutine[Any, Any, R]]
|
293
|
+
]:
|
289
294
|
"""
|
290
295
|
Same as ``aggregate``, but with ``func`` arguments of type ``Arg``
|
291
296
|
containing ``value`` and ``future`` attributes instead. In this setting
|
@@ -298,7 +303,7 @@ def aggregate_async(
|
|
298
303
|
"""
|
299
304
|
def decorator(
|
300
305
|
func: AggregateAsyncFunc[V, R]
|
301
|
-
) -> Callable[[V],
|
306
|
+
) -> Callable[[V], Coroutine[Any, Any, R]]:
|
302
307
|
aggregator = AggregatorAsync(
|
303
308
|
func, max_count=max_count, leeway_ms=leeway_ms,
|
304
309
|
)
|
aiomisc/backoff.py
CHANGED
@@ -2,7 +2,7 @@ import asyncio
|
|
2
2
|
import sys
|
3
3
|
from functools import wraps
|
4
4
|
from typing import (
|
5
|
-
Any,
|
5
|
+
Any, Callable, Coroutine, Optional, Tuple, Type, TypeVar, Union,
|
6
6
|
)
|
7
7
|
|
8
8
|
from .counters import Statistic
|
@@ -42,7 +42,10 @@ def asyncbackoff(
|
|
42
42
|
giveup: Optional[Callable[[Exception], bool]] = None,
|
43
43
|
statistic_name: Optional[str] = None,
|
44
44
|
statistic_class: Type[BackoffStatistic] = BackoffStatistic,
|
45
|
-
) -> Callable[
|
45
|
+
) -> Callable[
|
46
|
+
[Callable[P, Coroutine[Any, Any, T]]],
|
47
|
+
Callable[P, Coroutine[Any, Any, T]],
|
48
|
+
]:
|
46
49
|
"""
|
47
50
|
Patametric decorator that ensures that ``attempt_timeout`` and
|
48
51
|
``deadline`` time limits are met by decorated function.
|
@@ -85,7 +88,9 @@ def asyncbackoff(
|
|
85
88
|
exceptions = tuple(exceptions) or ()
|
86
89
|
exceptions += asyncio.TimeoutError,
|
87
90
|
|
88
|
-
def decorator(
|
91
|
+
def decorator(
|
92
|
+
func: Callable[P, Coroutine[Any, Any, T]]
|
93
|
+
) -> Callable[P, Coroutine[Any, Any, T]]:
|
89
94
|
if attempt_timeout is not None:
|
90
95
|
func = timeout(attempt_timeout)(func)
|
91
96
|
|
@@ -145,7 +150,10 @@ def asyncretry(
|
|
145
150
|
pause: Number = 0,
|
146
151
|
giveup: Optional[Callable[[Exception], bool]] = None,
|
147
152
|
statistic_name: Optional[str] = None,
|
148
|
-
) -> Callable[
|
153
|
+
) -> Callable[
|
154
|
+
[Callable[P, Coroutine[Any, Any, T]]],
|
155
|
+
Callable[P, Coroutine[Any, Any, T]],
|
156
|
+
]:
|
149
157
|
"""
|
150
158
|
Shortcut of ``asyncbackoff(None, None, 0, Exception)``.
|
151
159
|
|
aiomisc/process_pool.py
CHANGED
@@ -39,9 +39,7 @@ class ProcessPoolExecutor(ProcessPoolExecutorBase, EventLoopMixin):
|
|
39
39
|
self._statistic.sum_time += loop.time() - start_time
|
40
40
|
|
41
41
|
def submit(self, *args: Any, **kwargs: Any) -> Future:
|
42
|
-
"""
|
43
|
-
Submit blocking function to the pool
|
44
|
-
"""
|
42
|
+
"""Submit blocking function to the pool"""
|
45
43
|
loop = asyncio.get_running_loop()
|
46
44
|
start_time = loop.time()
|
47
45
|
future = super().submit(*args, **kwargs)
|
@@ -0,0 +1,14 @@
|
|
1
|
+
from . import records
|
2
|
+
from .service import DNSServer, TCPDNSServer, UDPDNSServer
|
3
|
+
from .store import DNSStore
|
4
|
+
from .zone import DNSZone
|
5
|
+
|
6
|
+
|
7
|
+
__all__ = (
|
8
|
+
"DNSServer",
|
9
|
+
"DNSStore",
|
10
|
+
"DNSZone",
|
11
|
+
"TCPDNSServer",
|
12
|
+
"UDPDNSServer",
|
13
|
+
"records",
|
14
|
+
)
|
@@ -0,0 +1,400 @@
|
|
1
|
+
import enum
|
2
|
+
from abc import ABC, abstractmethod
|
3
|
+
from dataclasses import dataclass, field
|
4
|
+
from typing import Any, Hashable, Iterable, List
|
5
|
+
|
6
|
+
import dnslib # type: ignore[import-untyped]
|
7
|
+
|
8
|
+
|
9
|
+
class RecordType(enum.IntEnum):
|
10
|
+
A = 1
|
11
|
+
A6 = 38
|
12
|
+
AAAA = 28
|
13
|
+
AFSDB = 18
|
14
|
+
ANY = 255
|
15
|
+
APL = 42
|
16
|
+
AXFR = 252
|
17
|
+
CAA = 257
|
18
|
+
CDNSKEY = 60
|
19
|
+
CDS = 59
|
20
|
+
CERT = 37
|
21
|
+
CNAME = 5
|
22
|
+
CSYNC = 62
|
23
|
+
DHCID = 49
|
24
|
+
DLV = 32769
|
25
|
+
DNAME = 39
|
26
|
+
DNSKEY = 48
|
27
|
+
DS = 43
|
28
|
+
EUI48 = 108
|
29
|
+
EUI64 = 109
|
30
|
+
HINFO = 13
|
31
|
+
HIP = 53
|
32
|
+
HIP_AC = 55
|
33
|
+
HTTPS = 65
|
34
|
+
IPSECKEY = 45
|
35
|
+
IXFR = 251
|
36
|
+
KEY = 25
|
37
|
+
KX = 36
|
38
|
+
LOC = 29
|
39
|
+
MX = 15
|
40
|
+
NAPTR = 35
|
41
|
+
NS = 2
|
42
|
+
NSEC = 47
|
43
|
+
NSEC3 = 50
|
44
|
+
NSEC3PARAM = 51
|
45
|
+
NULL = 10
|
46
|
+
OPENPGPKEY = 61
|
47
|
+
OPT = 41
|
48
|
+
PTR = 12
|
49
|
+
RP = 17
|
50
|
+
RRSIG = 46
|
51
|
+
SIG = 24
|
52
|
+
SOA = 6
|
53
|
+
SPF = 99
|
54
|
+
SRV = 33
|
55
|
+
SSHFP = 44
|
56
|
+
SVCB = 64
|
57
|
+
TA = 32768
|
58
|
+
TKEY = 249
|
59
|
+
TLSA = 52
|
60
|
+
TSIG = 250
|
61
|
+
TXT = 16
|
62
|
+
URI = 256
|
63
|
+
ZONEMD = 63
|
64
|
+
|
65
|
+
|
66
|
+
class DNSClass(enum.IntEnum):
|
67
|
+
IN = 1
|
68
|
+
CS = 2
|
69
|
+
CH = 3
|
70
|
+
Hesiod = 4
|
71
|
+
NONE = 254
|
72
|
+
ASTERISK = 255
|
73
|
+
|
74
|
+
|
75
|
+
class RD(dnslib.RD, Hashable, ABC):
|
76
|
+
def __hash__(self) -> int:
|
77
|
+
return hash(self.data)
|
78
|
+
|
79
|
+
@classmethod
|
80
|
+
@abstractmethod
|
81
|
+
def create(cls, *args: Any, **kwargs: Any) -> "DNSRecord":
|
82
|
+
raise NotImplementedError
|
83
|
+
|
84
|
+
|
85
|
+
@dataclass(frozen=True)
|
86
|
+
class DNSRecord:
|
87
|
+
name: str
|
88
|
+
type: RecordType
|
89
|
+
data: RD
|
90
|
+
cls: DNSClass = field(default=DNSClass.IN)
|
91
|
+
ttl: int = field(default=3600)
|
92
|
+
|
93
|
+
def rr(self, query_type: int) -> dnslib.RR:
|
94
|
+
return dnslib.RR(
|
95
|
+
rname=self.name,
|
96
|
+
rtype=query_type,
|
97
|
+
rclass=self.cls,
|
98
|
+
ttl=self.ttl,
|
99
|
+
rdata=self.data,
|
100
|
+
)
|
101
|
+
|
102
|
+
|
103
|
+
class A(dnslib.A, RD):
|
104
|
+
@classmethod
|
105
|
+
def create(cls, name: str, ip: str, ttl: int = 3600) -> DNSRecord:
|
106
|
+
return DNSRecord(
|
107
|
+
name=name,
|
108
|
+
type=RecordType.A,
|
109
|
+
data=cls(ip),
|
110
|
+
ttl=ttl,
|
111
|
+
)
|
112
|
+
|
113
|
+
|
114
|
+
class AAAA(dnslib.AAAA, RD):
|
115
|
+
@classmethod
|
116
|
+
def create(cls, name: str, ipv6: str, ttl: int = 3600) -> DNSRecord:
|
117
|
+
return DNSRecord(
|
118
|
+
name=name,
|
119
|
+
type=RecordType.AAAA,
|
120
|
+
data=cls(ipv6),
|
121
|
+
ttl=ttl,
|
122
|
+
)
|
123
|
+
|
124
|
+
|
125
|
+
class RDLabel(RD):
|
126
|
+
label: bytes
|
127
|
+
__type__: RecordType
|
128
|
+
|
129
|
+
@classmethod
|
130
|
+
def create(cls, name: str, label: str, ttl: int = 3600) -> DNSRecord:
|
131
|
+
return DNSRecord(
|
132
|
+
name=name,
|
133
|
+
type=cls.__type__,
|
134
|
+
data=cls(label),
|
135
|
+
ttl=ttl,
|
136
|
+
)
|
137
|
+
|
138
|
+
def __hash__(self) -> int:
|
139
|
+
return hash(self.label)
|
140
|
+
|
141
|
+
|
142
|
+
class CNAME(dnslib.CNAME, RDLabel):
|
143
|
+
__type__ = RecordType.CNAME
|
144
|
+
|
145
|
+
|
146
|
+
class NS(dnslib.NS, RDLabel):
|
147
|
+
__type__ = RecordType.NS
|
148
|
+
|
149
|
+
|
150
|
+
class PTR(dnslib.PTR, RDLabel):
|
151
|
+
__type__ = RecordType.PTR
|
152
|
+
|
153
|
+
|
154
|
+
class DNAME(dnslib.DNAME, RDLabel):
|
155
|
+
__type__ = RecordType.DNAME
|
156
|
+
|
157
|
+
|
158
|
+
class MX(dnslib.MX, RD):
|
159
|
+
@classmethod
|
160
|
+
def create(
|
161
|
+
cls, name: str, exchange: str, preference: int, ttl: int = 3600,
|
162
|
+
) -> DNSRecord:
|
163
|
+
return DNSRecord(
|
164
|
+
name=name,
|
165
|
+
type=RecordType.MX,
|
166
|
+
data=cls(exchange, preference),
|
167
|
+
ttl=ttl,
|
168
|
+
)
|
169
|
+
|
170
|
+
|
171
|
+
class TXT(dnslib.TXT, RD):
|
172
|
+
@classmethod
|
173
|
+
def create(cls, name: str, text: str, ttl: int = 3600) -> DNSRecord:
|
174
|
+
return DNSRecord(
|
175
|
+
name=name,
|
176
|
+
type=RecordType.TXT,
|
177
|
+
data=cls(text),
|
178
|
+
ttl=ttl,
|
179
|
+
)
|
180
|
+
|
181
|
+
|
182
|
+
class SOA(dnslib.SOA, RD):
|
183
|
+
@classmethod
|
184
|
+
def create(
|
185
|
+
cls, name: str, mname: str, rname: str, serial: int,
|
186
|
+
refresh: int, retry: int, expire: int, minimum: int,
|
187
|
+
ttl: int = 3600,
|
188
|
+
) -> DNSRecord:
|
189
|
+
return DNSRecord(
|
190
|
+
name=name,
|
191
|
+
type=RecordType.SOA,
|
192
|
+
data=cls(
|
193
|
+
mname, rname,
|
194
|
+
(serial, refresh, retry, expire, minimum),
|
195
|
+
),
|
196
|
+
ttl=ttl,
|
197
|
+
)
|
198
|
+
|
199
|
+
|
200
|
+
class SRV(dnslib.SRV, RD):
|
201
|
+
@classmethod
|
202
|
+
def create(
|
203
|
+
cls, name: str, priority: int, weight: int, port: int,
|
204
|
+
target: str, ttl: int = 3600,
|
205
|
+
) -> DNSRecord:
|
206
|
+
return DNSRecord(
|
207
|
+
name=name,
|
208
|
+
type=RecordType.SRV,
|
209
|
+
data=cls(priority, weight, port, target),
|
210
|
+
ttl=ttl,
|
211
|
+
)
|
212
|
+
|
213
|
+
|
214
|
+
class CAA(dnslib.CAA, RD):
|
215
|
+
@classmethod
|
216
|
+
def create(
|
217
|
+
cls, name: str, flags: int, tag: str, value: str,
|
218
|
+
ttl: int = 3600,
|
219
|
+
) -> DNSRecord:
|
220
|
+
return DNSRecord(
|
221
|
+
name=name,
|
222
|
+
type=RecordType.CAA,
|
223
|
+
data=cls(flags, tag, value),
|
224
|
+
ttl=ttl,
|
225
|
+
)
|
226
|
+
|
227
|
+
|
228
|
+
class NAPTR(dnslib.NAPTR, RD):
|
229
|
+
@classmethod
|
230
|
+
def create(
|
231
|
+
cls, name: str, order: int, preference: int, flags: str,
|
232
|
+
service: str, regexp: str, replacement: str, ttl: int = 3600,
|
233
|
+
) -> DNSRecord:
|
234
|
+
return DNSRecord(
|
235
|
+
name=name,
|
236
|
+
type=RecordType.NAPTR,
|
237
|
+
data=cls(order, preference, flags, service, regexp, replacement),
|
238
|
+
ttl=ttl,
|
239
|
+
)
|
240
|
+
|
241
|
+
|
242
|
+
class DS(dnslib.DS, RD):
|
243
|
+
@classmethod
|
244
|
+
def create(
|
245
|
+
cls, name: str, key_tag: int, algorithm: int, digest_type: int,
|
246
|
+
digest: str, ttl: int = 3600,
|
247
|
+
) -> DNSRecord:
|
248
|
+
return DNSRecord(
|
249
|
+
name=name,
|
250
|
+
type=RecordType.DS,
|
251
|
+
data=cls(key_tag, algorithm, digest_type, digest),
|
252
|
+
ttl=ttl,
|
253
|
+
)
|
254
|
+
|
255
|
+
|
256
|
+
class DNSKEY(dnslib.DNSKEY, RD):
|
257
|
+
@classmethod
|
258
|
+
def create(
|
259
|
+
cls, name: str, flags: int, protocol: int, algorithm: int,
|
260
|
+
key: str, ttl: int = 3600,
|
261
|
+
) -> DNSRecord:
|
262
|
+
return DNSRecord(
|
263
|
+
name=name,
|
264
|
+
type=RecordType.DNSKEY,
|
265
|
+
data=cls(flags, protocol, algorithm, key),
|
266
|
+
ttl=ttl,
|
267
|
+
)
|
268
|
+
|
269
|
+
|
270
|
+
class RRSIG(dnslib.RRSIG, RD):
|
271
|
+
@classmethod
|
272
|
+
def create(
|
273
|
+
cls, name: str, type_covered: int, algorithm: int, labels: int,
|
274
|
+
original_ttl: int, expiration: int, inception: int, key_tag: int,
|
275
|
+
signer: str, signature: str, ttl: int = 3600,
|
276
|
+
) -> DNSRecord:
|
277
|
+
return DNSRecord(
|
278
|
+
name=name,
|
279
|
+
type=RecordType.RRSIG,
|
280
|
+
data=cls(
|
281
|
+
type_covered, algorithm, labels, original_ttl, expiration,
|
282
|
+
inception, key_tag, signer, signature,
|
283
|
+
),
|
284
|
+
ttl=ttl,
|
285
|
+
)
|
286
|
+
|
287
|
+
|
288
|
+
class NSEC(dnslib.NSEC, RD):
|
289
|
+
@classmethod
|
290
|
+
def create(
|
291
|
+
cls, name: str, next_domain: str,
|
292
|
+
rrtypes: Iterable[int], ttl: int = 3600,
|
293
|
+
) -> DNSRecord:
|
294
|
+
return DNSRecord(
|
295
|
+
name=name,
|
296
|
+
type=RecordType.NSEC,
|
297
|
+
data=cls(next_domain, list(rrtypes)),
|
298
|
+
ttl=ttl,
|
299
|
+
)
|
300
|
+
|
301
|
+
|
302
|
+
class HTTPS(dnslib.HTTPS, RD):
|
303
|
+
@classmethod
|
304
|
+
def create(
|
305
|
+
cls, name: str, priority: int, target: str,
|
306
|
+
params: List[str], ttl: int = 3600,
|
307
|
+
) -> DNSRecord:
|
308
|
+
return DNSRecord(
|
309
|
+
name=name,
|
310
|
+
type=RecordType.HTTPS,
|
311
|
+
data=cls(priority, target, params),
|
312
|
+
ttl=ttl,
|
313
|
+
)
|
314
|
+
|
315
|
+
|
316
|
+
class LOC(dnslib.LOC, RD):
|
317
|
+
@classmethod
|
318
|
+
def create(
|
319
|
+
cls, name: str, latitude: float, longitude: float,
|
320
|
+
altitude: float, size: float, h_precision: float,
|
321
|
+
v_precision: float, ttl: int = 3600,
|
322
|
+
) -> DNSRecord:
|
323
|
+
return DNSRecord(
|
324
|
+
name=name,
|
325
|
+
type=RecordType.LOC,
|
326
|
+
data=cls(
|
327
|
+
latitude, longitude, altitude, size, h_precision, v_precision,
|
328
|
+
),
|
329
|
+
ttl=ttl,
|
330
|
+
)
|
331
|
+
|
332
|
+
|
333
|
+
class RP(dnslib.RP, RD):
|
334
|
+
@classmethod
|
335
|
+
def create(
|
336
|
+
cls, name: str, mbox: str, txt: str, ttl: int = 3600,
|
337
|
+
) -> DNSRecord:
|
338
|
+
return DNSRecord(
|
339
|
+
name=name,
|
340
|
+
type=RecordType.RP,
|
341
|
+
data=cls(mbox, txt),
|
342
|
+
ttl=ttl,
|
343
|
+
)
|
344
|
+
|
345
|
+
|
346
|
+
class TLSA(dnslib.TLSA, RD):
|
347
|
+
@classmethod
|
348
|
+
def create(
|
349
|
+
cls, name: str, usage: int, selector: int, mtype: int, cert: str,
|
350
|
+
ttl: int = 3600,
|
351
|
+
) -> DNSRecord:
|
352
|
+
return DNSRecord(
|
353
|
+
name=name,
|
354
|
+
type=RecordType.TLSA,
|
355
|
+
data=cls(usage, selector, mtype, cert),
|
356
|
+
ttl=ttl,
|
357
|
+
)
|
358
|
+
|
359
|
+
|
360
|
+
class SSHFP(dnslib.SSHFP, RD):
|
361
|
+
@classmethod
|
362
|
+
def create(
|
363
|
+
cls, name: str, algorithm: int, fptype: int,
|
364
|
+
fingerprint: str, ttl: int = 3600,
|
365
|
+
) -> DNSRecord:
|
366
|
+
return DNSRecord(
|
367
|
+
name=name,
|
368
|
+
type=RecordType.SSHFP,
|
369
|
+
data=cls(algorithm, fptype, fingerprint),
|
370
|
+
ttl=ttl,
|
371
|
+
)
|
372
|
+
|
373
|
+
|
374
|
+
__all__ = (
|
375
|
+
"AAAA",
|
376
|
+
"CAA",
|
377
|
+
"CNAME",
|
378
|
+
"DNAME",
|
379
|
+
"DNSKEY",
|
380
|
+
"DS",
|
381
|
+
"HTTPS",
|
382
|
+
"LOC",
|
383
|
+
"MX",
|
384
|
+
"NAPTR",
|
385
|
+
"NS",
|
386
|
+
"NSEC",
|
387
|
+
"PTR",
|
388
|
+
"RD",
|
389
|
+
"RP",
|
390
|
+
"RRSIG",
|
391
|
+
"SOA",
|
392
|
+
"SRV",
|
393
|
+
"SSHFP",
|
394
|
+
"TLSA",
|
395
|
+
"TXT",
|
396
|
+
"A",
|
397
|
+
"DNSRecord",
|
398
|
+
"DNSClass",
|
399
|
+
"RecordType",
|
400
|
+
)
|
@@ -0,0 +1,131 @@
|
|
1
|
+
import asyncio
|
2
|
+
import logging
|
3
|
+
from asyncio import StreamReader, StreamWriter
|
4
|
+
from struct import Struct
|
5
|
+
from typing import Optional, Tuple
|
6
|
+
|
7
|
+
import dnslib # type: ignore[import-untyped]
|
8
|
+
|
9
|
+
from aiomisc.service import TCPServer, UDPServer
|
10
|
+
|
11
|
+
from .records import RecordType
|
12
|
+
from .store import DNSStore
|
13
|
+
|
14
|
+
|
15
|
+
log = logging.getLogger(__name__)
|
16
|
+
|
17
|
+
|
18
|
+
class DNSServer:
|
19
|
+
store: DNSStore
|
20
|
+
|
21
|
+
__proto__: str
|
22
|
+
|
23
|
+
@staticmethod
|
24
|
+
def get_edns_max_size(record: dnslib.DNSRecord) -> Optional[int]:
|
25
|
+
for rr in record.ar:
|
26
|
+
if rr.rtype != dnslib.QTYPE.OPT:
|
27
|
+
continue
|
28
|
+
return rr.edns_len
|
29
|
+
return None
|
30
|
+
|
31
|
+
def handle_request(
|
32
|
+
self, addr: tuple, data: bytes, should_truncate: bool = False,
|
33
|
+
) -> Tuple[bytes, tuple]:
|
34
|
+
record = dnslib.DNSRecord.parse(data)
|
35
|
+
question: dnslib.DNSQuestion = record.get_q()
|
36
|
+
reply = record.reply()
|
37
|
+
query_name = str(question.get_qname())
|
38
|
+
query_type = question.qtype
|
39
|
+
|
40
|
+
try:
|
41
|
+
log.debug(
|
42
|
+
"Processing %s request from %r:\n%s\n",
|
43
|
+
self.__proto__, addr, record,
|
44
|
+
)
|
45
|
+
edns_max_size = self.get_edns_max_size(record)
|
46
|
+
if edns_max_size is not None:
|
47
|
+
reply.add_ar(dnslib.EDNS0(udp_len=edns_max_size))
|
48
|
+
|
49
|
+
records = self.store.query(query_name, RecordType(query_type))
|
50
|
+
for rec in records:
|
51
|
+
reply.add_answer(rec.rr(query_type))
|
52
|
+
|
53
|
+
if not records:
|
54
|
+
reply.header.rcode = dnslib.RCODE.NXDOMAIN
|
55
|
+
|
56
|
+
log.debug(
|
57
|
+
"Sending %s answer to %r:\n%s\n",
|
58
|
+
self.__proto__, addr, reply,
|
59
|
+
)
|
60
|
+
|
61
|
+
reply_body = reply.pack()
|
62
|
+
|
63
|
+
if len(reply_body) > 512:
|
64
|
+
reply.header.tc = 1
|
65
|
+
reply_body = reply.pack()
|
66
|
+
|
67
|
+
if should_truncate:
|
68
|
+
reply_body = reply_body[:edns_max_size]
|
69
|
+
except Exception as e:
|
70
|
+
log.exception(
|
71
|
+
"Failed to process %s request from %r: %s",
|
72
|
+
self.__proto__, addr, e,
|
73
|
+
)
|
74
|
+
reply = record.reply()
|
75
|
+
reply.header.set_rcode(dnslib.RCODE.SERVFAIL)
|
76
|
+
reply_body = reply.pack()
|
77
|
+
|
78
|
+
return reply_body, addr
|
79
|
+
|
80
|
+
|
81
|
+
class UDPDNSServer(DNSServer, UDPServer):
|
82
|
+
MAX_SIZE = 512
|
83
|
+
|
84
|
+
store: DNSStore
|
85
|
+
|
86
|
+
__required__ = ("store",)
|
87
|
+
__proto__ = "UDP"
|
88
|
+
|
89
|
+
async def handle_datagram(self, data: bytes, addr: tuple) -> None:
|
90
|
+
self.sendto(
|
91
|
+
*self.handle_request(
|
92
|
+
data=data, addr=addr, should_truncate=True,
|
93
|
+
),
|
94
|
+
)
|
95
|
+
|
96
|
+
|
97
|
+
TCP_HEADER_STRUCT: Struct = Struct("!H")
|
98
|
+
|
99
|
+
|
100
|
+
class TCPDNSServer(DNSServer, TCPServer):
|
101
|
+
store: DNSStore
|
102
|
+
|
103
|
+
__required__ = ("store",)
|
104
|
+
|
105
|
+
__proto__ = "TCP"
|
106
|
+
|
107
|
+
async def handle_client(
|
108
|
+
self, reader: StreamReader, writer: StreamWriter,
|
109
|
+
) -> None:
|
110
|
+
addr = writer.get_extra_info("peername")
|
111
|
+
try:
|
112
|
+
while True:
|
113
|
+
data = await reader.readexactly(TCP_HEADER_STRUCT.size)
|
114
|
+
if not data:
|
115
|
+
break
|
116
|
+
length = TCP_HEADER_STRUCT.unpack(data)[0]
|
117
|
+
packet = await reader.read(length)
|
118
|
+
|
119
|
+
if not data:
|
120
|
+
break
|
121
|
+
|
122
|
+
reply_body, _ = self.handle_request(data=packet, addr=addr)
|
123
|
+
|
124
|
+
writer.write(TCP_HEADER_STRUCT.pack(len(reply_body)))
|
125
|
+
writer.write(reply_body)
|
126
|
+
await writer.drain()
|
127
|
+
except asyncio.IncompleteReadError:
|
128
|
+
pass
|
129
|
+
finally:
|
130
|
+
writer.close()
|
131
|
+
await writer.wait_closed()
|
@@ -0,0 +1,51 @@
|
|
1
|
+
from typing import Optional, Sequence, Tuple
|
2
|
+
|
3
|
+
from .records import DNSRecord, RecordType
|
4
|
+
from .tree import RadixTree
|
5
|
+
from .zone import DNSZone
|
6
|
+
|
7
|
+
|
8
|
+
class DNSStore:
|
9
|
+
zones: RadixTree[DNSZone]
|
10
|
+
|
11
|
+
__slots__ = ("zones",)
|
12
|
+
|
13
|
+
def __init__(self) -> None:
|
14
|
+
self.zones = RadixTree()
|
15
|
+
|
16
|
+
def add_zone(self, zone: DNSZone) -> None:
|
17
|
+
zone_tuple = self.get_reverse_tuple(zone.name)
|
18
|
+
if self.zones.search(zone_tuple):
|
19
|
+
raise ValueError(f"Zone {zone.name} already exists.")
|
20
|
+
self.zones.insert(zone_tuple, zone)
|
21
|
+
|
22
|
+
def remove_zone(self, zone_name: str) -> None:
|
23
|
+
zone_tuple = self.get_reverse_tuple(zone_name)
|
24
|
+
if not self.zones.search(zone_tuple):
|
25
|
+
raise ValueError(
|
26
|
+
f"Zone {zone_name} does not exist.",
|
27
|
+
)
|
28
|
+
# Clear zone from RadixTree
|
29
|
+
self.zones.insert(zone_tuple, None)
|
30
|
+
|
31
|
+
def get_zone(self, zone_name: str) -> Optional[DNSZone]:
|
32
|
+
zone_tuple = self.get_reverse_tuple(zone_name)
|
33
|
+
return self.zones.search(zone_tuple)
|
34
|
+
|
35
|
+
def query(self, name: str, record_type: RecordType) -> Sequence[DNSRecord]:
|
36
|
+
if not name.endswith("."):
|
37
|
+
name += "."
|
38
|
+
zone_tuple = self.get_zone_for_name(name)
|
39
|
+
if not zone_tuple:
|
40
|
+
return ()
|
41
|
+
zone = self.zones.search(zone_tuple)
|
42
|
+
return zone.get_records(name, record_type) if zone is not None else ()
|
43
|
+
|
44
|
+
def get_zone_for_name(self, name: str) -> Optional[Tuple[str, ...]]:
|
45
|
+
labels = self.get_reverse_tuple(name)
|
46
|
+
result = self.zones.find_prefix(labels)
|
47
|
+
return result[0] if result else None
|
48
|
+
|
49
|
+
@staticmethod
|
50
|
+
def get_reverse_tuple(zone_name: str) -> Tuple[str, ...]:
|
51
|
+
return tuple(zone_name.strip(".").split("."))[::-1]
|
@@ -0,0 +1,59 @@
|
|
1
|
+
from typing import Any, Dict, Generic, Hashable, List, Optional, Tuple, TypeVar
|
2
|
+
|
3
|
+
|
4
|
+
K = Tuple[str, ...]
|
5
|
+
T = TypeVar("T", bound=Any)
|
6
|
+
|
7
|
+
|
8
|
+
class RadixNode(Generic[T]):
|
9
|
+
__slots__ = ("children", "value")
|
10
|
+
|
11
|
+
def __init__(self) -> None:
|
12
|
+
self.children: Dict[Hashable, RadixNode[T]] = {}
|
13
|
+
self.value: Optional[T] = None
|
14
|
+
|
15
|
+
|
16
|
+
class RadixTree(Generic[T]):
|
17
|
+
root: RadixNode[T]
|
18
|
+
|
19
|
+
__slots__ = ("root",)
|
20
|
+
|
21
|
+
def __init__(self) -> None:
|
22
|
+
self.root = RadixNode()
|
23
|
+
|
24
|
+
def insert(self, key: K, value: Optional[T]) -> None:
|
25
|
+
node = self.root
|
26
|
+
for part in key:
|
27
|
+
if part not in node.children:
|
28
|
+
node.children[part] = RadixNode()
|
29
|
+
node = node.children[part]
|
30
|
+
node.value = value
|
31
|
+
|
32
|
+
def search(self, key: K) -> Optional[T]:
|
33
|
+
node = self.root
|
34
|
+
for part in key:
|
35
|
+
if part not in node.children:
|
36
|
+
return None
|
37
|
+
node = node.children[part]
|
38
|
+
return node.value
|
39
|
+
|
40
|
+
def find_prefix(self, key: K) -> Optional[Tuple[K, T]]:
|
41
|
+
node = self.root
|
42
|
+
longest_prefix: List[str] = []
|
43
|
+
value = None
|
44
|
+
part: Hashable
|
45
|
+
if node.value is not None:
|
46
|
+
value = node.value
|
47
|
+
for part in key:
|
48
|
+
if part in node.children:
|
49
|
+
node = node.children[part]
|
50
|
+
longest_prefix.append(part)
|
51
|
+
if node.value is not None:
|
52
|
+
value = node.value
|
53
|
+
else:
|
54
|
+
break
|
55
|
+
|
56
|
+
if value is None:
|
57
|
+
return None
|
58
|
+
|
59
|
+
return tuple(longest_prefix), value
|
@@ -0,0 +1,44 @@
|
|
1
|
+
from collections import defaultdict
|
2
|
+
from typing import DefaultDict, Sequence, Set, Tuple
|
3
|
+
|
4
|
+
from .records import DNSRecord, RecordType
|
5
|
+
|
6
|
+
|
7
|
+
class DNSZone:
|
8
|
+
records: DefaultDict[Tuple[str, RecordType], Set[DNSRecord]]
|
9
|
+
name: str
|
10
|
+
|
11
|
+
__slots__ = ("name", "records")
|
12
|
+
|
13
|
+
def __init__(self, name: str):
|
14
|
+
if not name.endswith("."):
|
15
|
+
name += "."
|
16
|
+
self.name = name
|
17
|
+
self.records = defaultdict(set)
|
18
|
+
|
19
|
+
def add_record(self, record: DNSRecord) -> None:
|
20
|
+
if not self._is_valid_record(record):
|
21
|
+
raise ValueError(
|
22
|
+
f"Record {record.name} does not belong to zone {self.name}",
|
23
|
+
)
|
24
|
+
key = (record.name, record.type)
|
25
|
+
self.records[key].add(record)
|
26
|
+
|
27
|
+
def remove_record(self, record: DNSRecord) -> None:
|
28
|
+
key = (record.name, record.type)
|
29
|
+
if key in self.records:
|
30
|
+
self.records[key].discard(record)
|
31
|
+
if self.records[key]:
|
32
|
+
return
|
33
|
+
del self.records[key]
|
34
|
+
|
35
|
+
def get_records(
|
36
|
+
self, name: str, record_type: RecordType,
|
37
|
+
) -> Sequence[DNSRecord]:
|
38
|
+
if not name.endswith("."):
|
39
|
+
name += "."
|
40
|
+
key = (name, record_type)
|
41
|
+
return tuple(self.records.get(key, ()))
|
42
|
+
|
43
|
+
def _is_valid_record(self, record: DNSRecord) -> bool:
|
44
|
+
return record.name.endswith(self.name)
|
aiomisc/service/grpc_server.py
CHANGED
@@ -2,9 +2,12 @@ import asyncio
|
|
2
2
|
import logging
|
3
3
|
import re
|
4
4
|
import sys
|
5
|
+
from collections import defaultdict
|
5
6
|
from concurrent.futures import Executor
|
6
7
|
from types import MappingProxyType
|
7
|
-
from typing import
|
8
|
+
from typing import (
|
9
|
+
Any, DefaultDict, Dict, Mapping, Optional, Sequence, Set, Tuple,
|
10
|
+
)
|
8
11
|
|
9
12
|
from .base import Service
|
10
13
|
|
@@ -36,6 +39,7 @@ class GRPCService(Service):
|
|
36
39
|
_server_args: MappingProxyType
|
37
40
|
_insecure_ports: Set[Tuple[str, PortFuture]]
|
38
41
|
_secure_ports: Set[Tuple[str, grpc.ServerCredentials, PortFuture]]
|
42
|
+
_registered_services: DefaultDict[str, Dict[str, grpc.RpcMethodHandler]]
|
39
43
|
|
40
44
|
def __init__(
|
41
45
|
self, *,
|
@@ -60,6 +64,7 @@ class GRPCService(Service):
|
|
60
64
|
self._insecure_ports = set()
|
61
65
|
self._secure_ports = set()
|
62
66
|
self._reflection = reflection
|
67
|
+
self._registered_services = defaultdict(dict)
|
63
68
|
super().__init__(**kwds)
|
64
69
|
|
65
70
|
@classmethod
|
@@ -90,6 +95,12 @@ class GRPCService(Service):
|
|
90
95
|
service_names.append(reflection.SERVICE_NAME)
|
91
96
|
reflection.enable_server_reflection(service_names, self._server)
|
92
97
|
|
98
|
+
for name, handlers in self._registered_services.items():
|
99
|
+
# noinspection PyUnresolvedReferences
|
100
|
+
self._server.add_registered_method_handlers( # type: ignore
|
101
|
+
name, handlers,
|
102
|
+
)
|
103
|
+
|
93
104
|
self._server.add_generic_rpc_handlers(tuple(self._services))
|
94
105
|
await self._server.start()
|
95
106
|
|
@@ -102,6 +113,11 @@ class GRPCService(Service):
|
|
102
113
|
for service in generic_rpc_handlers:
|
103
114
|
self._services.add(service)
|
104
115
|
|
116
|
+
def add_registered_method_handlers(
|
117
|
+
self, name: str, handlers: Mapping[str, grpc.RpcMethodHandler],
|
118
|
+
) -> None:
|
119
|
+
self._registered_services[name].update(handlers)
|
120
|
+
|
105
121
|
def add_insecure_port(self, address: str) -> PortFuture:
|
106
122
|
future: PortFuture = asyncio.Future()
|
107
123
|
self._insecure_ports.add((address, future))
|
aiomisc/thread_pool.py
CHANGED
@@ -224,9 +224,7 @@ class ThreadPoolExecutor(ThreadPoolExecutorBase):
|
|
224
224
|
def submit( # type: ignore
|
225
225
|
self, fn: F, *args: Any, **kwargs: Any,
|
226
226
|
) -> asyncio.Future:
|
227
|
-
"""
|
228
|
-
Submit blocking function to the pool
|
229
|
-
"""
|
227
|
+
"""Submit blocking function to the pool"""
|
230
228
|
if fn is None or not callable(fn):
|
231
229
|
raise ValueError("First argument must be callable")
|
232
230
|
|
aiomisc/timeout.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
import asyncio
|
2
2
|
import sys
|
3
3
|
from functools import wraps
|
4
|
-
from typing import
|
4
|
+
from typing import Any, Callable, Coroutine, TypeVar, Union
|
5
5
|
|
6
6
|
|
7
7
|
if sys.version_info >= (3, 10):
|
@@ -17,10 +17,13 @@ Number = Union[int, float]
|
|
17
17
|
|
18
18
|
def timeout(
|
19
19
|
value: Number
|
20
|
-
) -> Callable[
|
20
|
+
) -> Callable[
|
21
|
+
[Callable[P, Coroutine[Any, Any, T]]],
|
22
|
+
Callable[P, Coroutine[Any, Any, T]],
|
23
|
+
]:
|
21
24
|
def decorator(
|
22
|
-
func: Callable[P,
|
23
|
-
) -> Callable[P,
|
25
|
+
func: Callable[P, Coroutine[Any, Any, T]],
|
26
|
+
) -> Callable[P, Coroutine[Any, Any, T]]:
|
24
27
|
if not asyncio.iscoroutinefunction(func):
|
25
28
|
raise TypeError("Function is not a coroutine function")
|
26
29
|
|
aiomisc/version.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: aiomisc
|
3
|
-
Version: 17.5.
|
3
|
+
Version: 17.5.20
|
4
4
|
Summary: aiomisc - miscellaneous utils for asyncio
|
5
5
|
Home-page: https://github.com/aiokitchen/aiomisc
|
6
6
|
License: MIT
|
@@ -44,6 +44,7 @@ Provides-Extra: aiohttp
|
|
44
44
|
Provides-Extra: asgi
|
45
45
|
Provides-Extra: carbon
|
46
46
|
Provides-Extra: cron
|
47
|
+
Provides-Extra: dns
|
47
48
|
Provides-Extra: grpc
|
48
49
|
Provides-Extra: raven
|
49
50
|
Provides-Extra: rich
|
@@ -55,6 +56,7 @@ Requires-Dist: aiohttp-asgi (>=0.5.2,<0.6.0) ; extra == "asgi"
|
|
55
56
|
Requires-Dist: asgiref (>=3.7,<4.0) ; extra == "uvicorn"
|
56
57
|
Requires-Dist: colorlog (>=6.0,<7.0)
|
57
58
|
Requires-Dist: croniter (==2.0) ; extra == "cron"
|
59
|
+
Requires-Dist: dnslib (>=0.9,<0.10) ; extra == "dns"
|
58
60
|
Requires-Dist: grpcio (>=1.56,<2.0) ; extra == "grpc"
|
59
61
|
Requires-Dist: grpcio-reflection (>=1.56,<2.0) ; extra == "grpc"
|
60
62
|
Requires-Dist: grpcio-tools (>=1.56,<2.0) ; extra == "grpc"
|
@@ -1,7 +1,7 @@
|
|
1
1
|
aiomisc/__init__.py,sha256=mgLaoGB3-WTAnrUfEN9nM_0Z_XU6xojkx1ISmW204UA,2253
|
2
2
|
aiomisc/_context_vars.py,sha256=28A7j_NABitKMtpxuFvxQ2wCr-Fq79dAC3YjqVLLeTQ,689
|
3
|
-
aiomisc/aggregate.py,sha256=
|
4
|
-
aiomisc/backoff.py,sha256=
|
3
|
+
aiomisc/aggregate.py,sha256=4yAsJhtRc-ikm9hXer05DgYhJFGf94IXeWJRTCSSDpw,8898
|
4
|
+
aiomisc/backoff.py,sha256=v8G4A-z9o6ou4oa-hSv3UtLPX2Km8PLsEGqHHAFtu24,5701
|
5
5
|
aiomisc/circuit_breaker.py,sha256=nZVLuGg4rKxn84CHaIvRfBYumgdwx2VZloF8OprQKuk,12581
|
6
6
|
aiomisc/compat.py,sha256=aYVe0J-2CvAzUHPAULNhrfMLFYNKN-z3Sg7nlBkzfxw,3291
|
7
7
|
aiomisc/context.py,sha256=j2YRNGDAJbrg94OkFyIMyYKHKzHRRL8tSAWma1yxNKE,1549
|
@@ -15,7 +15,7 @@ aiomisc/periodic.py,sha256=OFYZbPkcGgeMjBk8zwsLr2TqPRTS6MNewaL3q9qK5js,2252
|
|
15
15
|
aiomisc/plugins/__init__.py,sha256=eHKGec_217rBjXGf8u-Joyf7dzpO1O0QAuFan1IEyYE,1057
|
16
16
|
aiomisc/plugins/__main__.py,sha256=y3mykRQmimDRHb_PvY4n08vegjWTjd9CONFbCecAxxw,1365
|
17
17
|
aiomisc/pool.py,sha256=kmTEziX7U1kXxmNsTEain9Jkzn8ZbFubuCYWaozZoRk,5764
|
18
|
-
aiomisc/process_pool.py,sha256=
|
18
|
+
aiomisc/process_pool.py,sha256=EwzB_HqxoKX7h3A7DVeRy1AIg-rN0nSpCl9KfGW54Ss,1596
|
19
19
|
aiomisc/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
20
20
|
aiomisc/recurring.py,sha256=LJ13P_OhZM6mVOed8iUrS-M9PXY12516XBZcFtO7XtI,4923
|
21
21
|
aiomisc/service/__init__.py,sha256=_JSkI4Q78UljG4CvhK6He2UGLxJaQS6cVwZ9QT0cSWo,607
|
@@ -24,7 +24,13 @@ aiomisc/service/asgi.py,sha256=S6ejgg7GpHfUzuj46g8QuLXTHn0YWwqI_pAGbXfoNjE,1583
|
|
24
24
|
aiomisc/service/base.py,sha256=6xvD27Sh2jy-4JWer6n31FuSq798DZhLI730YYGQbYc,4322
|
25
25
|
aiomisc/service/carbon.py,sha256=o7x2LYUOfCcWYDAP9EFdCTtsduXhqkyMAuIqVio9YsM,1743
|
26
26
|
aiomisc/service/cron.py,sha256=VM-FjdAhy_zFkU-YTdpBSxF79upvrsCo-3kdI9cZTOo,1975
|
27
|
-
aiomisc/service/
|
27
|
+
aiomisc/service/dns/__init__.py,sha256=RT8_0H8LrQe8sz2xvA2BvLi6CiJVZeVFltNRAx9MN7o,254
|
28
|
+
aiomisc/service/dns/records.py,sha256=dN_yS3KKmRCF4fNk-fxRB2m-NCQaho42thG5OLFpyZU,8756
|
29
|
+
aiomisc/service/dns/service.py,sha256=F8KSmFry6WpA4tWNrKcXVhS6GWb_Cyl3uanGu4lbOPw,3616
|
30
|
+
aiomisc/service/dns/store.py,sha256=FchfLSoWAI2x4n5RyIcnx_i7RQjHmBbco4L4rSAanGU,1749
|
31
|
+
aiomisc/service/dns/tree.py,sha256=q9VRcVxggrClUHyGZZqqSByMaHdU4nYPC1NdT17cVWM,1575
|
32
|
+
aiomisc/service/dns/zone.py,sha256=Dc3bL7L3rz38J0_R790d_uJlisfJ78vwfdsxmtVfg_w,1343
|
33
|
+
aiomisc/service/grpc_server.py,sha256=G1iRGX3jAfv3fpzNnw_n6UuhORmIZK4cz6aAi7t7qcU,4553
|
28
34
|
aiomisc/service/periodic.py,sha256=BEKGWxWDOX11sfo7MFYp-hiB5iZ4-c2BRsjS56k5B-0,1241
|
29
35
|
aiomisc/service/process.py,sha256=mZf8muZJNWQo95pUmzchMk08MJMhLifFK8m4CnNX49k,4477
|
30
36
|
aiomisc/service/profiler.py,sha256=6KiJsU7tD5eO4YKZXYujV2E8P-G8GUqLAGIl0AuPNG4,1503
|
@@ -36,10 +42,10 @@ aiomisc/service/tracer.py,sha256=_dxk5y2JEteki9J1OXnOkI-EowD9vakSfsLaRDB4uMQ,282
|
|
36
42
|
aiomisc/service/udp.py,sha256=_uHzMAkpd7y-yt3tE9jN2llEETG7r47g2DF1SO7xyig,3616
|
37
43
|
aiomisc/service/uvicorn.py,sha256=dR-d5ge3KsCIEJfJ6SiFQ7rIyX8rKWNuGV2ZrONToj4,4323
|
38
44
|
aiomisc/signal.py,sha256=_iiC2jukXg7-LLirIl1YATlKIIsKLbmTNFr1Ezheu7g,1728
|
39
|
-
aiomisc/thread_pool.py,sha256=
|
40
|
-
aiomisc/timeout.py,sha256=
|
45
|
+
aiomisc/thread_pool.py,sha256=591u5HV1aBmBRcaVm4kAtMLPZOb6veqhT9wkts_knqE,14017
|
46
|
+
aiomisc/timeout.py,sha256=OhXCvQGbPZA9IQ7cRT7ZlZnsbmDgH5ioErQyjeqIEzY,919
|
41
47
|
aiomisc/utils.py,sha256=6yTfTpeRCVzfZp-MJCmB1oayOHUBVwQwzC3U5PBAjn8,11919
|
42
|
-
aiomisc/version.py,sha256=
|
48
|
+
aiomisc/version.py,sha256=gJ8ARCLt56OmyswE8_WbmF4gyB3IG7xwweORF-0BZu8,156
|
43
49
|
aiomisc/worker_pool.py,sha256=GA91KdOrBlqHthbVSTxu_d6BsBIbl-uKqW2NxqSafG0,11107
|
44
50
|
aiomisc_log/__init__.py,sha256=ZD-Q-YTWoZdxJpMqMNMIraA_gaN0QawgnVpXz6mxd2o,4855
|
45
51
|
aiomisc_log/enum.py,sha256=_zfCZPYCGyI9KL6TqHiYVlOfA5U5MCbsuCuDKxDHdxg,1549
|
@@ -57,8 +63,8 @@ aiomisc_worker/process_inner.py,sha256=8ZtjCSLrgySW57OIbuGrpEWxfysRLYKx1or1YaAqx
|
|
57
63
|
aiomisc_worker/protocol.py,sha256=1smmlBbdreSmnrxuhHaUMUC10FO9xMIEcedhweQJX_A,2705
|
58
64
|
aiomisc_worker/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
59
65
|
aiomisc_worker/worker.py,sha256=f8nCFhlKh84UUBUaEgCllwMRvVZiD8_UUXaeit6g3T8,3236
|
60
|
-
aiomisc-17.5.
|
61
|
-
aiomisc-17.5.
|
62
|
-
aiomisc-17.5.
|
63
|
-
aiomisc-17.5.
|
64
|
-
aiomisc-17.5.
|
66
|
+
aiomisc-17.5.20.dist-info/COPYING,sha256=Ky_8CQMaIixfyOreUBsl0hKN6A5fLnPF8KPQ9molMYA,1125
|
67
|
+
aiomisc-17.5.20.dist-info/METADATA,sha256=ytR2ply0GE1ehivdvQItBTdGr5PgoxeQkCmcDll4xfY,15737
|
68
|
+
aiomisc-17.5.20.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
69
|
+
aiomisc-17.5.20.dist-info/entry_points.txt,sha256=KRsSPCwKJyGTWrvzpwbS0yIDwzsgDA2X6f0CBWYmNao,55
|
70
|
+
aiomisc-17.5.20.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|