omlish 0.0.0.dev74__py3-none-any.whl → 0.0.0.dev75__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- omlish/__about__.py +2 -2
- omlish/http/clients.py +1 -0
- omlish/http/sse.py +96 -0
- omlish/io.py +142 -0
- omlish/lang/__init__.py +1 -0
- omlish/lang/iterables.py +7 -0
- {omlish-0.0.0.dev74.dist-info → omlish-0.0.0.dev75.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev74.dist-info → omlish-0.0.0.dev75.dist-info}/RECORD +12 -10
- {omlish-0.0.0.dev74.dist-info → omlish-0.0.0.dev75.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev74.dist-info → omlish-0.0.0.dev75.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev74.dist-info → omlish-0.0.0.dev75.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev74.dist-info → omlish-0.0.0.dev75.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
omlish/http/clients.py
CHANGED
omlish/http/sse.py
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
"""
|
2
|
+
TODO:
|
3
|
+
- end-of-line = ( cr lf / cr / lf )
|
4
|
+
"""
|
5
|
+
import string
|
6
|
+
import typing as ta
|
7
|
+
|
8
|
+
from .. import dataclasses as dc
|
9
|
+
from .. import lang
|
10
|
+
|
11
|
+
|
12
|
+
class SseDecoderOutput(lang.Abstract):
|
13
|
+
pass
|
14
|
+
|
15
|
+
|
16
|
+
@dc.dataclass(frozen=True)
|
17
|
+
class SseComment(SseDecoderOutput, lang.Final):
|
18
|
+
data: bytes
|
19
|
+
|
20
|
+
|
21
|
+
SseEventId: ta.TypeAlias = bytes
|
22
|
+
|
23
|
+
|
24
|
+
@dc.dataclass(frozen=True)
|
25
|
+
class SseEvent(SseDecoderOutput, lang.Final):
|
26
|
+
type: bytes
|
27
|
+
data: bytes
|
28
|
+
last_id: SseEventId = dc.xfield(b'', repr_fn=dc.truthy_repr)
|
29
|
+
|
30
|
+
|
31
|
+
_DIGIT_BYTES = string.digits.encode('ascii')
|
32
|
+
|
33
|
+
|
34
|
+
class SseDecoder:
|
35
|
+
"""https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation"""
|
36
|
+
|
37
|
+
def __init__(self) -> None:
|
38
|
+
super().__init__()
|
39
|
+
|
40
|
+
self._reset()
|
41
|
+
self._last_event_id = b''
|
42
|
+
self._reconnection_time: int | None = None
|
43
|
+
|
44
|
+
_event_type: bytes
|
45
|
+
_data: list[bytes]
|
46
|
+
|
47
|
+
def _reset(self) -> None:
|
48
|
+
self._event_type = b'message'
|
49
|
+
self._data = []
|
50
|
+
|
51
|
+
def _process_field(self, name: bytes, value: bytes) -> None:
|
52
|
+
if name == b'event':
|
53
|
+
self._event_type = value
|
54
|
+
|
55
|
+
elif name == b'data':
|
56
|
+
self._data.append(value)
|
57
|
+
|
58
|
+
elif name == b'id':
|
59
|
+
if 0 not in value:
|
60
|
+
self._last_event_id = value
|
61
|
+
|
62
|
+
elif name == b'retry':
|
63
|
+
if all(c in _DIGIT_BYTES for c in value):
|
64
|
+
self._reconnection_time = int(value)
|
65
|
+
|
66
|
+
def _dispatch_event(self) -> SseEvent:
|
67
|
+
data = b''.join(lang.interleave(self._data, b'\n'))
|
68
|
+
|
69
|
+
e = SseEvent(
|
70
|
+
type=self._event_type,
|
71
|
+
data=data,
|
72
|
+
last_id=self._last_event_id,
|
73
|
+
)
|
74
|
+
|
75
|
+
self._reset()
|
76
|
+
|
77
|
+
return e
|
78
|
+
|
79
|
+
def process_line(self, line: bytes) -> ta.Iterable[SseDecoderOutput]:
|
80
|
+
if b'\r' in line or b'\n' in line:
|
81
|
+
raise ValueError(line)
|
82
|
+
|
83
|
+
if not line:
|
84
|
+
yield self._dispatch_event()
|
85
|
+
|
86
|
+
elif line[0] == b':'[0]:
|
87
|
+
yield SseComment(line)
|
88
|
+
|
89
|
+
elif (c := line.find(b':')) >= 0:
|
90
|
+
d = c + 1
|
91
|
+
if len(line) > d and line[d] == b' '[0]:
|
92
|
+
d += 1
|
93
|
+
self._process_field(line[:c], line[d:])
|
94
|
+
|
95
|
+
else:
|
96
|
+
self._process_field(line, b'')
|
omlish/io.py
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
import dataclasses as dc
|
2
|
+
import io
|
3
|
+
import typing as ta
|
4
|
+
|
5
|
+
from . import check
|
6
|
+
|
7
|
+
|
8
|
+
@dc.dataclass(frozen=True)
|
9
|
+
class DelimitingBufferError(Exception):
|
10
|
+
buffer: 'DelimitingBuffer'
|
11
|
+
|
12
|
+
|
13
|
+
class ClosedDelimitingBufferError(DelimitingBufferError):
|
14
|
+
pass
|
15
|
+
|
16
|
+
|
17
|
+
class FullDelimitingBufferError(DelimitingBufferError):
|
18
|
+
pass
|
19
|
+
|
20
|
+
|
21
|
+
class IncompleteDelimitingBufferError(DelimitingBufferError):
|
22
|
+
pass
|
23
|
+
|
24
|
+
|
25
|
+
class DelimitingBuffer:
|
26
|
+
"""
|
27
|
+
https://github.com/python-trio/trio/issues/796 :|
|
28
|
+
"""
|
29
|
+
|
30
|
+
DEFAULT_DELIMITERS: bytes = b'\n'
|
31
|
+
|
32
|
+
def __init__(
|
33
|
+
self,
|
34
|
+
delimiters: ta.Iterable[int] = DEFAULT_DELIMITERS,
|
35
|
+
*,
|
36
|
+
keep_ends: bool = False,
|
37
|
+
max_size: int | None = None,
|
38
|
+
on_full: ta.Literal['raise', 'yield'] = 'raise',
|
39
|
+
on_incomplete: ta.Literal['raise', 'yield'] = 'yield',
|
40
|
+
) -> None:
|
41
|
+
super().__init__()
|
42
|
+
|
43
|
+
self._delimiters = frozenset(check.isinstance(d, int) for d in delimiters)
|
44
|
+
self._keep_ends = keep_ends
|
45
|
+
self._max_size = max_size
|
46
|
+
self._on_full = on_full
|
47
|
+
self._on_incomplete = on_incomplete
|
48
|
+
|
49
|
+
self._buf: io.BytesIO | None = io.BytesIO()
|
50
|
+
|
51
|
+
@property
|
52
|
+
def is_closed(self) -> bool:
|
53
|
+
return self._buf is None
|
54
|
+
|
55
|
+
def tell(self) -> int:
|
56
|
+
if (buf := self._buf) is None:
|
57
|
+
raise ClosedDelimitingBufferError(self)
|
58
|
+
return buf.tell()
|
59
|
+
|
60
|
+
def peek(self) -> bytes:
|
61
|
+
if (buf := self._buf) is None:
|
62
|
+
raise ClosedDelimitingBufferError(self)
|
63
|
+
return buf.getvalue()
|
64
|
+
|
65
|
+
def _find_delim(self, data: bytes | bytearray, i: int) -> int | None:
|
66
|
+
r = None # type: int | None
|
67
|
+
for d in self._delimiters:
|
68
|
+
if (p := data.find(d, i)) >= 0:
|
69
|
+
if r is None or p < r:
|
70
|
+
r = p
|
71
|
+
return r
|
72
|
+
|
73
|
+
def _append_and_reset(self, chunk: bytes) -> bytes:
|
74
|
+
buf = check.not_none(self._buf)
|
75
|
+
if not buf.tell():
|
76
|
+
return chunk
|
77
|
+
|
78
|
+
buf.write(chunk)
|
79
|
+
ret = buf.getvalue()
|
80
|
+
buf.seek(0)
|
81
|
+
return ret
|
82
|
+
|
83
|
+
def feed(self, data: bytes | bytearray) -> ta.Generator[bytes, None, None]:
|
84
|
+
if (buf := self._buf) is None:
|
85
|
+
raise ClosedDelimitingBufferError(self)
|
86
|
+
|
87
|
+
if not data:
|
88
|
+
self._buf = None
|
89
|
+
|
90
|
+
if buf.tell():
|
91
|
+
if self._on_incomplete == 'raise':
|
92
|
+
raise IncompleteDelimitingBufferError(self)
|
93
|
+
|
94
|
+
elif self._on_incomplete == 'yield':
|
95
|
+
yield buf.getvalue()
|
96
|
+
|
97
|
+
else:
|
98
|
+
raise ValueError(f'Unknown on_incomplete value: {self._on_incomplete!r}')
|
99
|
+
|
100
|
+
return
|
101
|
+
|
102
|
+
l = len(data)
|
103
|
+
i = 0
|
104
|
+
while i < l:
|
105
|
+
if (p := self._find_delim(data, i)) is None:
|
106
|
+
break
|
107
|
+
|
108
|
+
n = p + 1
|
109
|
+
if self._keep_ends:
|
110
|
+
p = n
|
111
|
+
|
112
|
+
yield self._append_and_reset(data[i:p])
|
113
|
+
|
114
|
+
i = n
|
115
|
+
|
116
|
+
if i >= l:
|
117
|
+
return
|
118
|
+
|
119
|
+
if self._max_size is None:
|
120
|
+
buf.write(data[i:])
|
121
|
+
return
|
122
|
+
|
123
|
+
while i < l:
|
124
|
+
remaining_data_len = l - i
|
125
|
+
remaining_buf_capacity = self._max_size - buf.tell()
|
126
|
+
|
127
|
+
if remaining_data_len < remaining_buf_capacity:
|
128
|
+
buf.write(data[i:])
|
129
|
+
return
|
130
|
+
|
131
|
+
if self._on_full == 'raise':
|
132
|
+
raise FullDelimitingBufferError(self)
|
133
|
+
|
134
|
+
elif self._on_full == 'yield':
|
135
|
+
p = i + remaining_buf_capacity
|
136
|
+
|
137
|
+
yield self._append_and_reset(data[i:p])
|
138
|
+
|
139
|
+
i = p
|
140
|
+
|
141
|
+
else:
|
142
|
+
raise ValueError(f'Unknown on_full value: {self._on_full!r}')
|
omlish/lang/__init__.py
CHANGED
omlish/lang/iterables.py
CHANGED
@@ -37,6 +37,13 @@ def peek(vs: ta.Iterable[T]) -> tuple[T, ta.Iterator[T]]:
|
|
37
37
|
return v, itertools.chain(iter((v,)), it)
|
38
38
|
|
39
39
|
|
40
|
+
def interleave(vs: ta.Iterable[T], d: T) -> ta.Iterable[T]:
|
41
|
+
for i, v in enumerate(vs):
|
42
|
+
if i:
|
43
|
+
yield d
|
44
|
+
yield v
|
45
|
+
|
46
|
+
|
40
47
|
Rangeable: ta.TypeAlias = ta.Union[ # noqa
|
41
48
|
int,
|
42
49
|
tuple[int],
|
@@ -1,5 +1,5 @@
|
|
1
1
|
omlish/.manifests.json,sha256=TXvFdkAU0Zr2FKdo7fyvt9nr3UjCtrnAZ0diZXSAteE,1430
|
2
|
-
omlish/__about__.py,sha256=
|
2
|
+
omlish/__about__.py,sha256=KmKwb6iUcg1fxhudcMoQVNM13U_ufTfnRbe5K2n-Za0,3420
|
3
3
|
omlish/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
4
|
omlish/argparse.py,sha256=Dc73G8lyoQBLvXhMYUbzQUh4SJu_OTvKUXjSUxq_ang,7499
|
5
5
|
omlish/c3.py,sha256=4vogWgwPb8TbNS2KkZxpoWbwjj7MuHG2lQG-hdtkvjI,8062
|
@@ -11,6 +11,7 @@ omlish/dynamic.py,sha256=35C_cCX_Vq2HrHzGk5T-zbrMvmUdiIiwDzDNixczoDo,6541
|
|
11
11
|
omlish/fnpairs.py,sha256=Sl8CMFNyDS-1JYAjSWqnT5FmUm9Lj6o7FxSRo7g4jww,10875
|
12
12
|
omlish/fnpipes.py,sha256=AJkgz9nvRRm7oqw7ZgYyz21klu276LWi54oYCLg-vOg,2196
|
13
13
|
omlish/genmachine.py,sha256=LCMiqvK32dAWtrlB6lKw9tXdQFiXC8rRdk4TMQYIroU,1603
|
14
|
+
omlish/io.py,sha256=4_0naIRniQ_xGhCW44xkk3sKqgddCbtIjbs72SGqK9g,3679
|
14
15
|
omlish/iterators.py,sha256=GGLC7RIT86uXMjhIIIqnff_Iu5SI_b9rXYywYGFyzmo,7292
|
15
16
|
omlish/libc.py,sha256=8r7Ejyhttk9ruCfBkxNTrlzir5WPbDE2vmY7VPlceMA,15362
|
16
17
|
omlish/matchfns.py,sha256=I1IlQGfEyk_AcFSy6ulVS3utC-uwyZM2YfUXYHc9Bw0,6152
|
@@ -202,7 +203,7 @@ omlish/graphs/dot/rendering.py,sha256=2UgXvMRN4Z9cfIqLlC7Iu_8bWbwUDEL4opHHkFfSqT
|
|
202
203
|
omlish/graphs/dot/utils.py,sha256=_FMwn77WfiiAfLsRTOKWm4IYbNv5kQN22YJ5psw6CWg,801
|
203
204
|
omlish/http/__init__.py,sha256=-ENDALr8ehHvivRD6cxIbEC94t0RHhrakf6CQRDTc8o,625
|
204
205
|
omlish/http/asgi.py,sha256=wXhBZ21bEl32Kv9yBrRwUR_7pHEgVtHP8ZZwbasQ6-4,3307
|
205
|
-
omlish/http/clients.py,sha256=
|
206
|
+
omlish/http/clients.py,sha256=phwWe4a6aVU2_E-Z_zSzrmAb5iX9zoCDIMa2l6Trzck,6019
|
206
207
|
omlish/http/collections.py,sha256=s8w5s4Gewgxxhe2Ai0R45PgJYYifrLgTbU3VXVflHj4,260
|
207
208
|
omlish/http/consts.py,sha256=FTolezLknKU6WJjk_x2T3a5LEMlnZSqv7gzTq55lxcU,2147
|
208
209
|
omlish/http/cookies.py,sha256=uuOYlHR6e2SC3GM41V0aozK10nef9tYg83Scqpn5-HM,6351
|
@@ -211,6 +212,7 @@ omlish/http/encodings.py,sha256=w2WoKajpaZnQH8j-IBvk5ZFL2O2pAU_iBvZnkocaTlw,164
|
|
211
212
|
omlish/http/headers.py,sha256=S66wiXezBHybrnjAM15E9x4GJvPRvFQHeKaXdJ799fw,5028
|
212
213
|
omlish/http/json.py,sha256=9XwAsl4966Mxrv-1ytyCqhcE6lbBJw-0_tFZzGszgHE,7440
|
213
214
|
omlish/http/sessions.py,sha256=VZ_WS5uiQG5y7i3u8oKuQMqf8dPKUOjFm_qk_0OvI8c,4793
|
215
|
+
omlish/http/sse.py,sha256=T2_EXTcDfEhCF4E9B68YtEYLFb803MPnh8eCNjdPlRo,2223
|
214
216
|
omlish/http/wsgi.py,sha256=czZsVUX-l2YTlMrUjKN49wRoP4rVpS0qpeBn4O5BoMY,948
|
215
217
|
omlish/inject/__init__.py,sha256=JQ7x8l9MjU-kJ5ap7cPVq7SY7zbbCIrjyJAF0UeE5-s,1886
|
216
218
|
omlish/inject/binder.py,sha256=H8AQ4ecmBOtDL8fMgrU1yUJl1gBADLNcdysRbvO8Wso,4167
|
@@ -242,7 +244,7 @@ omlish/inject/impl/privates.py,sha256=alpCYyk5VJ9lJknbRH2nLVNFYVvFhkj-VC1Vco3zCF
|
|
242
244
|
omlish/inject/impl/providers.py,sha256=QnwhsujJFIHC0JTgd2Wlo1kP53i3CWTrj1nKU2DNxwg,2375
|
243
245
|
omlish/inject/impl/proxy.py,sha256=1ko0VaKqzu9UG8bIldp9xtUrAVUOFTKWKTjOCqIGr4s,1636
|
244
246
|
omlish/inject/impl/scopes.py,sha256=ASfULXgP_ETlsAqFJfrZmyEaZt64Zr8tNn5ScA-EoXk,5900
|
245
|
-
omlish/lang/__init__.py,sha256
|
247
|
+
omlish/lang/__init__.py,sha256=p63qmjZwg2CjLhMaZncnZKsuLZ6B_QVfpPGZKVH8FTw,3652
|
246
248
|
omlish/lang/cached.py,sha256=92TvRZQ6sWlm7dNn4hgl7aWKbX0J1XUEo3DRjBpgVQk,7834
|
247
249
|
omlish/lang/clsdct.py,sha256=AjtIWLlx2E6D5rC97zQ3Lwq2SOMkbg08pdO_AxpzEHI,1744
|
248
250
|
omlish/lang/cmp.py,sha256=5vbzWWbqdzDmNKAGL19z6ZfUKe5Ci49e-Oegf9f4BsE,1346
|
@@ -252,7 +254,7 @@ omlish/lang/descriptors.py,sha256=RRBbkMgTzg82fFFE4D0muqobpM-ZZaOta6yB1lpX3s8,66
|
|
252
254
|
omlish/lang/exceptions.py,sha256=qJBo3NU1mOWWm-NhQUHCY5feYXR3arZVyEHinLsmRH4,47
|
253
255
|
omlish/lang/functions.py,sha256=kkPfcdocg-OmyN7skIqrFxNvqAv89Zc_kXKYAN8vw8g,3895
|
254
256
|
omlish/lang/imports.py,sha256=Oy7iInOTqgZv6nyRbnvGrPv4cKKIAzPbhfDXCajDUcc,6626
|
255
|
-
omlish/lang/iterables.py,sha256=
|
257
|
+
omlish/lang/iterables.py,sha256=xRwktm6i2RHSb_ELfAXdjITIfE69qDyMEzgeZqvQXiU,2386
|
256
258
|
omlish/lang/maybes.py,sha256=NYHZDjqDtwPMheDrj2VtUVujxRPf8Qpgk4ZlZCTvBZc,3492
|
257
259
|
omlish/lang/objects.py,sha256=t7Pvj9ILoxfdAMy5HC7bb9LfUokW5WfpLaoH2YYyTjQ,4460
|
258
260
|
omlish/lang/resolving.py,sha256=OuN2mDTPNyBUbcrswtvFKtj4xgH4H4WglgqSKv3MTy0,1606
|
@@ -436,9 +438,9 @@ omlish/text/delimit.py,sha256=ubPXcXQmtbOVrUsNh5gH1mDq5H-n1y2R4cPL5_DQf68,4928
|
|
436
438
|
omlish/text/glyphsplit.py,sha256=Ug-dPRO7x-OrNNr8g1y6DotSZ2KH0S-VcOmUobwa4B0,3296
|
437
439
|
omlish/text/indent.py,sha256=6Jj6TFY9unaPa4xPzrnZemJ-fHsV53IamP93XGjSUHs,1274
|
438
440
|
omlish/text/parts.py,sha256=7vPF1aTZdvLVYJ4EwBZVzRSy8XB3YqPd7JwEnNGGAOo,6495
|
439
|
-
omlish-0.0.0.
|
440
|
-
omlish-0.0.0.
|
441
|
-
omlish-0.0.0.
|
442
|
-
omlish-0.0.0.
|
443
|
-
omlish-0.0.0.
|
444
|
-
omlish-0.0.0.
|
441
|
+
omlish-0.0.0.dev75.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
|
442
|
+
omlish-0.0.0.dev75.dist-info/METADATA,sha256=GuYbZyayGe8TDZ3NyirRfVwE06aEFatiflv_jOd9zQI,4167
|
443
|
+
omlish-0.0.0.dev75.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
|
444
|
+
omlish-0.0.0.dev75.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
|
445
|
+
omlish-0.0.0.dev75.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
|
446
|
+
omlish-0.0.0.dev75.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|