omlish 0.0.0.dev74__py3-none-any.whl → 0.0.0.dev75__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.
omlish/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev74'
2
- __revision__ = '002d07c8f9579e6b60584c9b2c6c05cd6b7219af'
1
+ __version__ = '0.0.0.dev75'
2
+ __revision__ = 'dcdc82976a464689890fe99f9aaf5fc596ddd563'
3
3
 
4
4
 
5
5
  #
omlish/http/clients.py CHANGED
@@ -1,5 +1,6 @@
1
1
  """
2
2
  TODO:
3
+ - httpx catch
3
4
  - check=False
4
5
  - return non-200 HttpResponses
5
6
  - async
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
@@ -140,6 +140,7 @@ from .iterables import ( # noqa
140
140
  flatmap,
141
141
  flatten,
142
142
  ilen,
143
+ interleave,
143
144
  itergen,
144
145
  peek,
145
146
  prodrange,
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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: omlish
3
- Version: 0.0.0.dev74
3
+ Version: 0.0.0.dev75
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -1,5 +1,5 @@
1
1
  omlish/.manifests.json,sha256=TXvFdkAU0Zr2FKdo7fyvt9nr3UjCtrnAZ0diZXSAteE,1430
2
- omlish/__about__.py,sha256=6L5yBogPnXXOqNId_oVRZJ4kjVB68VskkdZvIJ7NlE0,3420
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=eOY4bmbGdXuOOabt9NLAcTO7G49u85-HoAFW28mCXS4,6004
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=-DRmyoSAwSWOh7nJh4UrpR-w_hGQfe-e06S9qLjHZF8,3636
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=_q6rHbdFfW3VBqez0IV3rUABoNxsA_oBv_sykm5zsbQ,2243
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.dev74.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
440
- omlish-0.0.0.dev74.dist-info/METADATA,sha256=fnVoKX28JDI8_XtMIkZGO22Po_cPDkZIi8nGwf6A_84,4167
441
- omlish-0.0.0.dev74.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
442
- omlish-0.0.0.dev74.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
443
- omlish-0.0.0.dev74.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
444
- omlish-0.0.0.dev74.dist-info/RECORD,,
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,,