omlish 0.0.0.dev134__py3-none-any.whl → 0.0.0.dev137__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- omlish/.manifests.json +0 -12
- omlish/__about__.py +2 -2
- omlish/cached.py +2 -2
- omlish/collections/mappings.py +1 -1
- omlish/diag/_pycharm/runhack.py +3 -0
- omlish/formats/json/stream/lex.py +1 -1
- omlish/formats/json/stream/parse.py +1 -1
- omlish/{genmachine.py → funcs/genmachine.py} +4 -2
- omlish/{matchfns.py → funcs/match.py} +1 -1
- omlish/{fnpairs.py → funcs/pairs.py} +3 -3
- omlish/http/sessions.py +1 -1
- omlish/io/compress/__init__.py +0 -0
- omlish/io/compress/abc.py +104 -0
- omlish/io/compress/adapters.py +147 -0
- omlish/io/compress/bz2.py +41 -0
- omlish/io/compress/gzip.py +301 -0
- omlish/io/compress/lzma.py +31 -0
- omlish/io/compress/types.py +29 -0
- omlish/io/generators.py +50 -0
- omlish/lang/__init__.py +8 -1
- omlish/lang/generators.py +182 -0
- omlish/lang/iterables.py +28 -51
- omlish/lang/maybes.py +4 -4
- omlish/lite/fdio/corohttp.py +5 -1
- omlish/lite/marshal.py +9 -6
- omlish/marshal/base.py +1 -1
- omlish/marshal/factories.py +1 -1
- omlish/marshal/forbidden.py +1 -1
- omlish/marshal/iterables.py +1 -1
- omlish/marshal/mappings.py +1 -1
- omlish/marshal/maybes.py +1 -1
- omlish/marshal/standard.py +1 -1
- omlish/marshal/unions.py +1 -1
- omlish/secrets/pwhash.py +1 -1
- omlish/secrets/subprocesses.py +3 -1
- omlish/specs/jsonrpc/marshal.py +1 -1
- omlish/specs/openapi/marshal.py +1 -1
- {omlish-0.0.0.dev134.dist-info → omlish-0.0.0.dev137.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev134.dist-info → omlish-0.0.0.dev137.dist-info}/RECORD +49 -47
- omlish/formats/json/cli/__main__.py +0 -11
- omlish/formats/json/cli/cli.py +0 -298
- omlish/formats/json/cli/formats.py +0 -71
- omlish/formats/json/cli/io.py +0 -74
- omlish/formats/json/cli/parsing.py +0 -82
- omlish/formats/json/cli/processing.py +0 -48
- omlish/formats/json/cli/rendering.py +0 -92
- /omlish/collections/{_abc.py → abc.py} +0 -0
- /omlish/{formats/json/cli → funcs}/__init__.py +0 -0
- /omlish/{fnpipes.py → funcs/pipes.py} +0 -0
- /omlish/io/{_abc.py → abc.py} +0 -0
- /omlish/logs/{_abc.py → abc.py} +0 -0
- /omlish/sql/{_abc.py → abc.py} +0 -0
- {omlish-0.0.0.dev134.dist-info → omlish-0.0.0.dev137.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev134.dist-info → omlish-0.0.0.dev137.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev134.dist-info → omlish-0.0.0.dev137.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev134.dist-info → omlish-0.0.0.dev137.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,301 @@
|
|
1
|
+
# PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
|
2
|
+
# --------------------------------------------
|
3
|
+
#
|
4
|
+
# 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and the Individual or Organization
|
5
|
+
# ("Licensee") accessing and otherwise using this software ("Python") in source or binary form and its associated
|
6
|
+
# documentation.
|
7
|
+
#
|
8
|
+
# 2. Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive,
|
9
|
+
# royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative
|
10
|
+
# works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License
|
11
|
+
# Agreement and PSF's notice of copyright, i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
|
12
|
+
# 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 Python Software Foundation; All Rights Reserved" are retained in Python
|
13
|
+
# alone or in any derivative version prepared by Licensee.
|
14
|
+
#
|
15
|
+
# 3. In the event Licensee prepares a derivative work that is based on or incorporates Python or any part thereof, and
|
16
|
+
# wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in
|
17
|
+
# any such work a brief summary of the changes made to Python.
|
18
|
+
#
|
19
|
+
# 4. PSF is making Python available to Licensee on an "AS IS" basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES,
|
20
|
+
# EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY
|
21
|
+
# OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT INFRINGE ANY THIRD PARTY
|
22
|
+
# RIGHTS.
|
23
|
+
#
|
24
|
+
# 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL
|
25
|
+
# DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE THEREOF, EVEN IF
|
26
|
+
# ADVISED OF THE POSSIBILITY THEREOF.
|
27
|
+
#
|
28
|
+
# 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions.
|
29
|
+
#
|
30
|
+
# 7. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint
|
31
|
+
# venture between PSF and Licensee. This License Agreement does not grant permission to use PSF trademarks or trade
|
32
|
+
# name in a trademark sense to endorse or promote products or services of Licensee, or any third party.
|
33
|
+
#
|
34
|
+
# 8. By copying, installing or otherwise using Python, Licensee agrees to be bound by the terms and conditions of this
|
35
|
+
# License Agreement.
|
36
|
+
import functools
|
37
|
+
import os.path
|
38
|
+
import struct
|
39
|
+
import time
|
40
|
+
import typing as ta
|
41
|
+
|
42
|
+
from ... import cached
|
43
|
+
from ... import check
|
44
|
+
from ... import lang
|
45
|
+
from ..generators import PrependableBytesGeneratorReader
|
46
|
+
from .types import IncrementalCompressor
|
47
|
+
from .types import IncrementalDecompressor
|
48
|
+
|
49
|
+
|
50
|
+
if ta.TYPE_CHECKING:
|
51
|
+
import gzip
|
52
|
+
import zlib
|
53
|
+
else:
|
54
|
+
gzip = lang.proxy_import('gzip')
|
55
|
+
zlib = lang.proxy_import('zlib')
|
56
|
+
|
57
|
+
|
58
|
+
##
|
59
|
+
|
60
|
+
|
61
|
+
COMPRESS_LEVEL_FAST = 1
|
62
|
+
COMPRESS_LEVEL_TRADEOFF = 6
|
63
|
+
COMPRESS_LEVEL_BEST = 9
|
64
|
+
|
65
|
+
|
66
|
+
@cached.function
|
67
|
+
def _zero_crc() -> int:
|
68
|
+
return zlib.crc32(b'')
|
69
|
+
|
70
|
+
|
71
|
+
##
|
72
|
+
|
73
|
+
|
74
|
+
class IncrementalGzipCompressor:
|
75
|
+
def __init__(
|
76
|
+
self,
|
77
|
+
*,
|
78
|
+
compresslevel: int = COMPRESS_LEVEL_BEST,
|
79
|
+
name: str | bytes | None = None,
|
80
|
+
mtime: float | None = None,
|
81
|
+
) -> None:
|
82
|
+
super().__init__()
|
83
|
+
|
84
|
+
self._name = name or ''
|
85
|
+
self._compresslevel = compresslevel
|
86
|
+
self._mtime = mtime
|
87
|
+
|
88
|
+
def _write_gzip_header(self) -> ta.Generator[bytes, None, None]:
|
89
|
+
check.none((yield b'\037\213')) # magic header
|
90
|
+
check.none((yield b'\010')) # compression method
|
91
|
+
|
92
|
+
try:
|
93
|
+
# RFC 1952 requires the FNAME field to be Latin-1. Do not include filenames that cannot be represented that
|
94
|
+
# way.
|
95
|
+
fname = os.path.basename(self._name)
|
96
|
+
if not isinstance(fname, bytes):
|
97
|
+
fname = fname.encode('latin-1')
|
98
|
+
if fname.endswith(b'.gz'):
|
99
|
+
fname = fname[:-3]
|
100
|
+
except UnicodeEncodeError:
|
101
|
+
fname = b''
|
102
|
+
|
103
|
+
flags = 0
|
104
|
+
if fname:
|
105
|
+
flags = gzip.FNAME
|
106
|
+
check.none((yield chr(flags).encode('latin-1')))
|
107
|
+
|
108
|
+
mtime = self._mtime
|
109
|
+
if mtime is None:
|
110
|
+
mtime = time.time()
|
111
|
+
check.none((yield struct.pack('<L', int(mtime))))
|
112
|
+
|
113
|
+
if self._compresslevel == COMPRESS_LEVEL_BEST:
|
114
|
+
xfl = b'\002'
|
115
|
+
elif self._compresslevel == COMPRESS_LEVEL_FAST:
|
116
|
+
xfl = b'\004'
|
117
|
+
else:
|
118
|
+
xfl = b'\000'
|
119
|
+
check.none((yield xfl))
|
120
|
+
|
121
|
+
check.none((yield b'\377'))
|
122
|
+
|
123
|
+
if fname:
|
124
|
+
check.none((yield fname + b'\000'))
|
125
|
+
|
126
|
+
def __call__(self) -> IncrementalCompressor:
|
127
|
+
crc = _zero_crc()
|
128
|
+
size = 0
|
129
|
+
offset = 0 # Current file offset for seek(), tell(), etc
|
130
|
+
|
131
|
+
compress = zlib.compressobj(
|
132
|
+
self._compresslevel,
|
133
|
+
zlib.DEFLATED,
|
134
|
+
-zlib.MAX_WBITS,
|
135
|
+
zlib.DEF_MEM_LEVEL,
|
136
|
+
0,
|
137
|
+
)
|
138
|
+
|
139
|
+
yield from self._write_gzip_header()
|
140
|
+
|
141
|
+
while True:
|
142
|
+
data: ta.Any = check.isinstance((yield None), bytes)
|
143
|
+
if not data:
|
144
|
+
break
|
145
|
+
|
146
|
+
# Called by our self._buffer underlying BufferedWriterDelegate.
|
147
|
+
if isinstance(data, (bytes, bytearray)):
|
148
|
+
length = len(data)
|
149
|
+
else:
|
150
|
+
# accept any data that supports the buffer protocol
|
151
|
+
data = memoryview(data)
|
152
|
+
length = data.nbytes
|
153
|
+
|
154
|
+
if length > 0:
|
155
|
+
if (fl := compress.compress(data)):
|
156
|
+
check.none((yield fl))
|
157
|
+
size += length
|
158
|
+
crc = zlib.crc32(data, crc)
|
159
|
+
offset += length
|
160
|
+
|
161
|
+
if (fl := compress.flush()):
|
162
|
+
check.none((yield fl))
|
163
|
+
|
164
|
+
yield struct.pack('<L', crc)
|
165
|
+
# size may exceed 2 GiB, or even 4 GiB
|
166
|
+
yield struct.pack('<L', size & 0xffffffff)
|
167
|
+
|
168
|
+
yield b''
|
169
|
+
|
170
|
+
|
171
|
+
##
|
172
|
+
|
173
|
+
|
174
|
+
class IncrementalGzipDecompressor:
|
175
|
+
def __init__(self) -> None:
|
176
|
+
super().__init__()
|
177
|
+
|
178
|
+
self._factory = functools.partial(
|
179
|
+
zlib.decompressobj,
|
180
|
+
wbits=-zlib.MAX_WBITS,
|
181
|
+
)
|
182
|
+
|
183
|
+
def _read_gzip_header(
|
184
|
+
self,
|
185
|
+
rdr: PrependableBytesGeneratorReader,
|
186
|
+
) -> ta.Generator[int | None, bytes, int | None]:
|
187
|
+
magic = yield from rdr.read(2)
|
188
|
+
if magic == b'':
|
189
|
+
return None
|
190
|
+
|
191
|
+
if magic != b'\037\213':
|
192
|
+
raise gzip.BadGzipFile(f'Not a gzipped file ({magic!r})')
|
193
|
+
|
194
|
+
buf = yield from rdr.read(8)
|
195
|
+
method, flag, last_mtime = struct.unpack('<BBIxx', buf)
|
196
|
+
if method != 8:
|
197
|
+
raise gzip.BadGzipFile('Unknown compression method')
|
198
|
+
|
199
|
+
if flag & gzip.FEXTRA:
|
200
|
+
# Read & discard the extra field, if present
|
201
|
+
buf = yield from rdr.read(2)
|
202
|
+
extra_len, = struct.unpack('<H', buf)
|
203
|
+
if extra_len:
|
204
|
+
yield from rdr.read(extra_len)
|
205
|
+
|
206
|
+
if flag & gzip.FNAME:
|
207
|
+
# Read and discard a null-terminated string containing the filename
|
208
|
+
while True:
|
209
|
+
s = yield from rdr.read(1)
|
210
|
+
if not s or s == b'\000':
|
211
|
+
break
|
212
|
+
|
213
|
+
if flag & gzip.FCOMMENT:
|
214
|
+
# Read and discard a null-terminated string containing a comment
|
215
|
+
while True:
|
216
|
+
s = yield from rdr.read(1)
|
217
|
+
if not s or s == b'\000':
|
218
|
+
break
|
219
|
+
|
220
|
+
if flag & gzip.FHCRC:
|
221
|
+
yield from rdr.read(2) # Read & discard the 16-bit header CRC
|
222
|
+
|
223
|
+
return last_mtime
|
224
|
+
|
225
|
+
def _read_eof(
|
226
|
+
self,
|
227
|
+
rdr: PrependableBytesGeneratorReader,
|
228
|
+
crc: int,
|
229
|
+
stream_size: int,
|
230
|
+
) -> ta.Generator[int | None, bytes, None]:
|
231
|
+
# We've read to the end of the file.
|
232
|
+
# We check that the computed CRC and size of the uncompressed data matches the stored values. Note that the size
|
233
|
+
# stored is the true file size mod 2**32.
|
234
|
+
buf = yield from rdr.read(8)
|
235
|
+
crc32, isize = struct.unpack('<II', buf)
|
236
|
+
if crc32 != crc:
|
237
|
+
raise gzip.BadGzipFile(f'CRC check failed {hex(crc32)} != {hex(crc)}')
|
238
|
+
elif isize != (stream_size & 0xffffffff):
|
239
|
+
raise gzip.BadGzipFile('Incorrect length of data produced')
|
240
|
+
|
241
|
+
# Gzip files can be padded with zeroes and still have archives. Consume all zero bytes and set the file position
|
242
|
+
# to the first non-zero byte. See http://www.gzip.org/#faq8
|
243
|
+
c = b'\0'
|
244
|
+
while c == b'\0':
|
245
|
+
c = yield from rdr.read(1)
|
246
|
+
if c:
|
247
|
+
rdr.prepend(c)
|
248
|
+
|
249
|
+
def __call__(self) -> IncrementalDecompressor:
|
250
|
+
rdr = PrependableBytesGeneratorReader()
|
251
|
+
|
252
|
+
pos = 0 # Current offset in decompressed stream
|
253
|
+
|
254
|
+
crc = _zero_crc()
|
255
|
+
stream_size = 0 # Decompressed size of unconcatenated stream
|
256
|
+
new_member = True
|
257
|
+
|
258
|
+
decompressor = self._factory()
|
259
|
+
|
260
|
+
while True:
|
261
|
+
# For certain input data, a single call to decompress() may not return any data. In this case, retry until
|
262
|
+
# we get some data or reach EOF.
|
263
|
+
while True:
|
264
|
+
if decompressor.eof:
|
265
|
+
# Ending case: we've come to the end of a member in the file, so finish up this member, and read a
|
266
|
+
# new gzip header. Check the CRC and file size, and set the flag so we read a new member
|
267
|
+
yield from self._read_eof(rdr, crc, stream_size)
|
268
|
+
new_member = True
|
269
|
+
decompressor = self._factory()
|
270
|
+
|
271
|
+
if new_member:
|
272
|
+
# If the _new_member flag is set, we have to jump to the next member, if there is one.
|
273
|
+
crc = _zero_crc()
|
274
|
+
stream_size = 0 # Decompressed size of unconcatenated stream
|
275
|
+
last_mtime = yield from self._read_gzip_header(rdr)
|
276
|
+
if not last_mtime:
|
277
|
+
check.none((yield b''))
|
278
|
+
return
|
279
|
+
new_member = False
|
280
|
+
|
281
|
+
# Read a chunk of data from the file
|
282
|
+
if not decompressor.unconsumed_tail:
|
283
|
+
buf = yield from rdr.read(None)
|
284
|
+
uncompress = decompressor.decompress(buf)
|
285
|
+
else:
|
286
|
+
uncompress = decompressor.decompress(b'')
|
287
|
+
|
288
|
+
if decompressor.unused_data != b'':
|
289
|
+
# Prepend the already read bytes to the fileobj so they can be seen by _read_eof() and
|
290
|
+
# _read_gzip_header()
|
291
|
+
rdr.prepend(decompressor.unused_data)
|
292
|
+
|
293
|
+
if uncompress != b'':
|
294
|
+
break
|
295
|
+
if buf == b'': # noqa
|
296
|
+
raise EOFError('Compressed file ended before the end-of-stream marker was reached')
|
297
|
+
|
298
|
+
crc = zlib.crc32(uncompress, crc)
|
299
|
+
stream_size += len(uncompress)
|
300
|
+
pos += len(uncompress)
|
301
|
+
check.none((yield uncompress))
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import typing as ta
|
2
|
+
|
3
|
+
from ... import lang
|
4
|
+
from .adapters import CompressorIncrementalAdapter
|
5
|
+
from .adapters import DecompressorIncrementalAdapter
|
6
|
+
from .types import IncrementalCompressor
|
7
|
+
from .types import IncrementalDecompressor
|
8
|
+
|
9
|
+
|
10
|
+
if ta.TYPE_CHECKING:
|
11
|
+
import lzma
|
12
|
+
else:
|
13
|
+
lzma = lang.proxy_import('lzma')
|
14
|
+
|
15
|
+
|
16
|
+
class IncrementalLzmaCompressor:
|
17
|
+
def __init__(self) -> None:
|
18
|
+
super().__init__()
|
19
|
+
|
20
|
+
def __call__(self) -> IncrementalCompressor:
|
21
|
+
return CompressorIncrementalAdapter(
|
22
|
+
lzma.LZMACompressor, # type: ignore
|
23
|
+
)()
|
24
|
+
|
25
|
+
|
26
|
+
class IncrementalLzmaDecompressor:
|
27
|
+
def __call__(self) -> IncrementalDecompressor:
|
28
|
+
return DecompressorIncrementalAdapter(
|
29
|
+
lzma.LZMADecompressor, # type: ignore
|
30
|
+
trailing_error=lzma.LZMAError,
|
31
|
+
)()
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# ruff: noqa: UP007
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
|
5
|
+
IncrementalCompressor: ta.TypeAlias = ta.Generator[
|
6
|
+
ta.Union[
|
7
|
+
bytes, # Compressed output
|
8
|
+
None, # Need input
|
9
|
+
],
|
10
|
+
ta.Union[
|
11
|
+
bytes, # Input bytes
|
12
|
+
None, # Need output
|
13
|
+
],
|
14
|
+
None,
|
15
|
+
]
|
16
|
+
|
17
|
+
|
18
|
+
IncrementalDecompressor: ta.TypeAlias = ta.Generator[
|
19
|
+
ta.Union[
|
20
|
+
bytes, # Uncompressed output
|
21
|
+
int, # Need exactly n bytes
|
22
|
+
None, # Need any amount of bytes
|
23
|
+
],
|
24
|
+
ta.Union[
|
25
|
+
bytes, # Input bytes
|
26
|
+
None, # Need output
|
27
|
+
],
|
28
|
+
None,
|
29
|
+
]
|
omlish/io/generators.py
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
"""
|
2
|
+
TODO:
|
3
|
+
- BufferedBytesGeneratorReader
|
4
|
+
"""
|
5
|
+
import typing as ta
|
6
|
+
|
7
|
+
from .. import check
|
8
|
+
|
9
|
+
|
10
|
+
class PrependableBytesGeneratorReader:
|
11
|
+
def __init__(self) -> None:
|
12
|
+
super().__init__()
|
13
|
+
|
14
|
+
self._p: list[bytes] = []
|
15
|
+
|
16
|
+
def read(self, sz: int | None) -> ta.Generator[int | None, bytes, bytes]:
|
17
|
+
if not self._p:
|
18
|
+
d = check.isinstance((yield sz), bytes)
|
19
|
+
return d
|
20
|
+
|
21
|
+
if sz is None:
|
22
|
+
return self._p.pop(0)
|
23
|
+
|
24
|
+
l: list[bytes] = []
|
25
|
+
r = sz
|
26
|
+
while r > 0 and self._p:
|
27
|
+
c = self._p[0]
|
28
|
+
|
29
|
+
if len(c) > r:
|
30
|
+
l.append(c[:r])
|
31
|
+
self._p[0] = c[r:]
|
32
|
+
return b''.join(l)
|
33
|
+
|
34
|
+
l.append(c)
|
35
|
+
r -= len(c)
|
36
|
+
self._p.pop(0)
|
37
|
+
|
38
|
+
if r:
|
39
|
+
c = check.isinstance((yield r), bytes)
|
40
|
+
if not c:
|
41
|
+
return b''
|
42
|
+
if len(c) != r:
|
43
|
+
raise EOFError(f'Reader got {len(c)} bytes, expected {r}')
|
44
|
+
l.append(c)
|
45
|
+
|
46
|
+
return b''.join(l)
|
47
|
+
|
48
|
+
def prepend(self, d: bytes) -> None:
|
49
|
+
if d:
|
50
|
+
self._p.append(d)
|
omlish/lang/__init__.py
CHANGED
@@ -120,6 +120,14 @@ from .functions import ( # noqa
|
|
120
120
|
void,
|
121
121
|
)
|
122
122
|
|
123
|
+
from .generators import ( # noqa
|
124
|
+
CoroutineGenerator,
|
125
|
+
Generator,
|
126
|
+
GeneratorLike,
|
127
|
+
corogen,
|
128
|
+
nextgen,
|
129
|
+
)
|
130
|
+
|
123
131
|
from .imports import ( # noqa
|
124
132
|
can_import,
|
125
133
|
import_all,
|
@@ -136,7 +144,6 @@ from .imports import ( # noqa
|
|
136
144
|
|
137
145
|
from .iterables import ( # noqa
|
138
146
|
BUILTIN_SCALAR_ITERABLE_TYPES,
|
139
|
-
Generator,
|
140
147
|
asrange,
|
141
148
|
exhaust,
|
142
149
|
flatmap,
|
@@ -0,0 +1,182 @@
|
|
1
|
+
import abc
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from .maybes import Maybe
|
5
|
+
|
6
|
+
|
7
|
+
T = ta.TypeVar('T')
|
8
|
+
I = ta.TypeVar('I')
|
9
|
+
O = ta.TypeVar('O')
|
10
|
+
R = ta.TypeVar('R')
|
11
|
+
I_contra = ta.TypeVar('I_contra', contravariant=True)
|
12
|
+
O_co = ta.TypeVar('O_co', covariant=True)
|
13
|
+
R_co = ta.TypeVar('R_co', covariant=True)
|
14
|
+
|
15
|
+
|
16
|
+
##
|
17
|
+
|
18
|
+
|
19
|
+
def nextgen(g: T) -> T:
|
20
|
+
next(g) # type: ignore
|
21
|
+
return g
|
22
|
+
|
23
|
+
|
24
|
+
##
|
25
|
+
|
26
|
+
|
27
|
+
@ta.runtime_checkable
|
28
|
+
class GeneratorLike(ta.Protocol[O_co, I_contra, R_co]):
|
29
|
+
def send(self, i: I_contra) -> O_co: # Raises[StopIteration[R_co]]
|
30
|
+
...
|
31
|
+
|
32
|
+
def close(self) -> None:
|
33
|
+
...
|
34
|
+
|
35
|
+
|
36
|
+
class GeneratorLike_(abc.ABC, ta.Generic[O, I, R]): # noqa
|
37
|
+
@abc.abstractmethod
|
38
|
+
def send(self, i: I) -> O: # Raises[StopIteration[R]]
|
39
|
+
raise NotImplementedError
|
40
|
+
|
41
|
+
def close(self) -> None:
|
42
|
+
pass
|
43
|
+
|
44
|
+
|
45
|
+
@ta.overload
|
46
|
+
def adapt_generator_like(gl: GeneratorLike_[O, I, R]) -> ta.Generator[O, I, R]:
|
47
|
+
...
|
48
|
+
|
49
|
+
|
50
|
+
@ta.overload
|
51
|
+
def adapt_generator_like(gl: GeneratorLike[O, I, R]) -> ta.Generator[O, I, R]:
|
52
|
+
...
|
53
|
+
|
54
|
+
|
55
|
+
def adapt_generator_like(gl):
|
56
|
+
try:
|
57
|
+
i = yield
|
58
|
+
while True:
|
59
|
+
i = yield gl.send(i)
|
60
|
+
except StopIteration as e:
|
61
|
+
return e.value
|
62
|
+
finally:
|
63
|
+
gl.close()
|
64
|
+
|
65
|
+
|
66
|
+
##
|
67
|
+
|
68
|
+
|
69
|
+
class Generator(ta.Generator[O, I, R]):
|
70
|
+
def __init__(self, g: ta.Generator[O, I, R]) -> None:
|
71
|
+
super().__init__()
|
72
|
+
self._g = g
|
73
|
+
|
74
|
+
@property
|
75
|
+
def g(self) -> ta.Generator[O, I, R]:
|
76
|
+
return self._g
|
77
|
+
|
78
|
+
value: R
|
79
|
+
|
80
|
+
def __iter__(self):
|
81
|
+
return self
|
82
|
+
|
83
|
+
def __next__(self):
|
84
|
+
try:
|
85
|
+
return next(self._g)
|
86
|
+
except StopIteration as e:
|
87
|
+
self.value = e.value
|
88
|
+
raise
|
89
|
+
|
90
|
+
def send(self, v):
|
91
|
+
try:
|
92
|
+
return self._g.send(v)
|
93
|
+
except StopIteration as e:
|
94
|
+
self.value = e.value
|
95
|
+
raise
|
96
|
+
|
97
|
+
def throw(self, *args):
|
98
|
+
try:
|
99
|
+
return self._g.throw(*args)
|
100
|
+
except StopIteration as e:
|
101
|
+
self.value = e.value
|
102
|
+
raise
|
103
|
+
|
104
|
+
def close(self):
|
105
|
+
self._g.close()
|
106
|
+
|
107
|
+
|
108
|
+
##
|
109
|
+
|
110
|
+
|
111
|
+
class CoroutineGenerator(ta.Generic[O, I, R]):
|
112
|
+
def __init__(self, g: ta.Generator[O, I, R]) -> None:
|
113
|
+
super().__init__()
|
114
|
+
self._g = g
|
115
|
+
|
116
|
+
@property
|
117
|
+
def g(self) -> ta.Generator[O, I, R]:
|
118
|
+
return self._g
|
119
|
+
|
120
|
+
#
|
121
|
+
|
122
|
+
def close(self) -> None:
|
123
|
+
self._g.close()
|
124
|
+
|
125
|
+
def __enter__(self) -> ta.Self:
|
126
|
+
return self
|
127
|
+
|
128
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
129
|
+
self._g.close()
|
130
|
+
|
131
|
+
#
|
132
|
+
|
133
|
+
class Output(ta.NamedTuple, ta.Generic[T]):
|
134
|
+
v: T
|
135
|
+
|
136
|
+
@property
|
137
|
+
def is_return(self) -> bool:
|
138
|
+
raise NotImplementedError
|
139
|
+
|
140
|
+
class Yield(Output[T]):
|
141
|
+
@property
|
142
|
+
def is_return(self) -> bool:
|
143
|
+
return False
|
144
|
+
|
145
|
+
class Return(Output[T]):
|
146
|
+
@property
|
147
|
+
def is_return(self) -> bool:
|
148
|
+
return True
|
149
|
+
|
150
|
+
class Nothing:
|
151
|
+
def __new__(cls):
|
152
|
+
raise TypeError
|
153
|
+
|
154
|
+
#
|
155
|
+
|
156
|
+
def send(self, /, v: I | type[Nothing] = Nothing) -> Yield[O] | Return[R]:
|
157
|
+
try:
|
158
|
+
if v is self.Nothing:
|
159
|
+
o = next(self._g)
|
160
|
+
else:
|
161
|
+
o = self._g.send(v) # type: ignore[arg-type]
|
162
|
+
except StopIteration as e:
|
163
|
+
return self.Return(e.value)
|
164
|
+
else:
|
165
|
+
return self.Yield(o)
|
166
|
+
|
167
|
+
def send_opt(self, v: I | None) -> Yield[O] | Return[R]:
|
168
|
+
return self.send(v if v is not None else self.Nothing)
|
169
|
+
|
170
|
+
def send_maybe(self, v: Maybe[I]) -> Yield[O] | Return[R]:
|
171
|
+
return self.send(v.or_else(self.Nothing))
|
172
|
+
|
173
|
+
def throw(self, v: BaseException) -> Yield[O] | Return[R]:
|
174
|
+
try:
|
175
|
+
o = self._g.throw(v)
|
176
|
+
except StopIteration as e:
|
177
|
+
return self.Return(e.value)
|
178
|
+
else:
|
179
|
+
return self.Yield(o)
|
180
|
+
|
181
|
+
|
182
|
+
corogen = CoroutineGenerator
|