omlish 0.0.0.dev136__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/__about__.py +2 -2
- omlish/cached.py +2 -2
- omlish/collections/mappings.py +1 -1
- omlish/diag/_pycharm/runhack.py +3 -0
- omlish/funcs/genmachine.py +4 -2
- 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-0.0.0.dev136.dist-info → omlish-0.0.0.dev137.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev136.dist-info → omlish-0.0.0.dev137.dist-info}/RECORD +27 -18
- /omlish/collections/{_abc.py → abc.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.dev136.dist-info → omlish-0.0.0.dev137.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev136.dist-info → omlish-0.0.0.dev137.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev136.dist-info → omlish-0.0.0.dev137.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev136.dist-info → omlish-0.0.0.dev137.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
omlish/cached.py
CHANGED
@@ -10,9 +10,9 @@ builtins and thus not distinguish it from a normal property.
|
|
10
10
|
|
11
11
|
"""
|
12
12
|
from .lang.cached import _CachedProperty # noqa
|
13
|
-
from .lang.cached import cached_function
|
13
|
+
from .lang.cached import cached_function as _cached_function
|
14
14
|
|
15
|
-
function =
|
15
|
+
function = _cached_function
|
16
16
|
|
17
17
|
property = property # noqa
|
18
18
|
|
omlish/collections/mappings.py
CHANGED
@@ -86,7 +86,7 @@ class DynamicTypeMap(ta.Generic[V]):
|
|
86
86
|
self._items = list(items)
|
87
87
|
self._weak = bool(weak)
|
88
88
|
|
89
|
-
self._cache: ta.MutableMapping[type, ta.Any] = weakref.WeakKeyDictionary()
|
89
|
+
self._cache: ta.MutableMapping[type, ta.Any] = weakref.WeakKeyDictionary() if weak else {}
|
90
90
|
|
91
91
|
@property
|
92
92
|
def items(self) -> ta.Sequence[V]:
|
omlish/diag/_pycharm/runhack.py
CHANGED
@@ -3,7 +3,10 @@
|
|
3
3
|
.venv/bin/python $(curl -LsSf https://raw.githubusercontent.com/wrmsr/omlish/master/omlish/diag/_pycharm/runhack.py -o $(mktemp) && echo "$_") install
|
4
4
|
|
5
5
|
==
|
6
|
+
TODO:
|
7
|
+
- check for existing files - can't run regular dep entrypoints now
|
6
8
|
|
9
|
+
==
|
7
10
|
See:
|
8
11
|
- https://github.com/JetBrains/intellij-community/blob/6400f70dde6f743e39a257a5a78cc51b644c835e/python/helpers/pycharm/_jb_pytest_runner.py
|
9
12
|
- https://github.com/JetBrains/intellij-community/blob/5a4e584aa59767f2e7cf4bd377adfaaf7503984b/python/helpers/pycharm/_jb_runner_tools.py
|
omlish/funcs/genmachine.py
CHANGED
@@ -12,7 +12,7 @@ import typing as ta
|
|
12
12
|
I = ta.TypeVar('I')
|
13
13
|
O = ta.TypeVar('O')
|
14
14
|
|
15
|
-
# MachineGen: ta.TypeAlias = ta.Generator[ta.Iterable[O] | None, I, ta.Optional[MachineGen[I, O]]]
|
15
|
+
# MachineGen: ta.TypeAlias = ta.Generator[ta.Iterable[O] | None, I | None, ta.Optional[MachineGen[I, O]]]
|
16
16
|
MachineGen: ta.TypeAlias = ta.Generator[ta.Any, ta.Any, ta.Any]
|
17
17
|
|
18
18
|
|
@@ -93,8 +93,10 @@ class GenMachine(ta.Generic[I, O]):
|
|
93
93
|
if self._gen is None:
|
94
94
|
raise GenMachine.ClosedError
|
95
95
|
|
96
|
+
gi: I | None = i
|
96
97
|
try:
|
97
|
-
while (o := self._gen.send(
|
98
|
+
while (o := self._gen.send(gi)) is not None:
|
99
|
+
gi = None
|
98
100
|
yield from o
|
99
101
|
|
100
102
|
except StopIteration as s:
|
File without changes
|
@@ -0,0 +1,104 @@
|
|
1
|
+
"""
|
2
|
+
https://docs.python.org/3/library/bz2.html#bz2.BZ2Compressor
|
3
|
+
https://docs.python.org/3/library/zlib.html#zlib.decompressobj
|
4
|
+
https://docs.python.org/3/library/lzma.html#lzma.LZMADecompressor
|
5
|
+
"""
|
6
|
+
import abc
|
7
|
+
|
8
|
+
|
9
|
+
##
|
10
|
+
|
11
|
+
|
12
|
+
class Compressor(abc.ABC):
|
13
|
+
@abc.abstractmethod
|
14
|
+
def compress(self, data: bytes) -> bytes:
|
15
|
+
"""
|
16
|
+
Provide data to the compressor object. Returns a chunk of compressed data if possible, or an empty byte string
|
17
|
+
otherwise.
|
18
|
+
|
19
|
+
When you have finished providing data to the compressor, call the flush() method to finish the compression
|
20
|
+
process.
|
21
|
+
"""
|
22
|
+
|
23
|
+
raise NotImplementedError
|
24
|
+
|
25
|
+
@abc.abstractmethod
|
26
|
+
def flush(self) -> bytes:
|
27
|
+
"""
|
28
|
+
Finish the compression process. Returns the compressed data left in internal buffers.
|
29
|
+
|
30
|
+
The compressor object may not be used after this method has been called.
|
31
|
+
"""
|
32
|
+
|
33
|
+
raise NotImplementedError
|
34
|
+
|
35
|
+
|
36
|
+
##
|
37
|
+
|
38
|
+
|
39
|
+
class Decompressor(abc.ABC):
|
40
|
+
@property
|
41
|
+
@abc.abstractmethod
|
42
|
+
def unused_data(self) -> bytes:
|
43
|
+
"""
|
44
|
+
Data found after the end of the compressed stream.
|
45
|
+
|
46
|
+
If this attribute is accessed before the end of the stream has been reached, its value will be b''.
|
47
|
+
"""
|
48
|
+
|
49
|
+
raise NotImplementedError
|
50
|
+
|
51
|
+
@property
|
52
|
+
@abc.abstractmethod
|
53
|
+
def eof(self) -> bool:
|
54
|
+
"""True if the end-of-stream marker has been reached."""
|
55
|
+
|
56
|
+
raise NotImplementedError
|
57
|
+
|
58
|
+
@abc.abstractmethod
|
59
|
+
def decompress(self, data: bytes, *max_length: int) -> bytes:
|
60
|
+
"""
|
61
|
+
Decompress data, returning a bytes object containing the uncompressed data corresponding to at least part of the
|
62
|
+
data in string. This data should be concatenated to the output produced by any preceding calls to the
|
63
|
+
decompress() method. Some of the input data may be preserved in internal buffers for later processing.
|
64
|
+
|
65
|
+
If the optional parameter max_length is non-zero then the return value will be no longer than max_length.
|
66
|
+
"""
|
67
|
+
|
68
|
+
raise NotImplementedError
|
69
|
+
|
70
|
+
|
71
|
+
class NeedsInputDecompressor(Decompressor):
|
72
|
+
"""
|
73
|
+
Used by:
|
74
|
+
- bz2.BZ2Decompressor
|
75
|
+
- lzma.LZMADecompressor
|
76
|
+
"""
|
77
|
+
|
78
|
+
@property
|
79
|
+
@abc.abstractmethod
|
80
|
+
def needs_input(self) -> bool:
|
81
|
+
"""
|
82
|
+
False if the decompress() method can provide more decompressed data before requiring new uncompressed input.
|
83
|
+
"""
|
84
|
+
|
85
|
+
raise NotImplementedError
|
86
|
+
|
87
|
+
|
88
|
+
class UnconsumedTailDecompressor(Decompressor):
|
89
|
+
"""
|
90
|
+
Used by:
|
91
|
+
- zlib.decompressobj
|
92
|
+
"""
|
93
|
+
|
94
|
+
@property
|
95
|
+
@abc.abstractmethod
|
96
|
+
def unconsumed_tail(self) -> bytes:
|
97
|
+
"""
|
98
|
+
A bytes object that contains any data that was not consumed by the last decompress() call because it exceeded
|
99
|
+
the limit for the uncompressed data buffer. This data has not yet been seen by the zlib machinery, so you must
|
100
|
+
feed it (possibly with further data concatenated to it) back to a subsequent decompress() method call in order
|
101
|
+
to get correct output.
|
102
|
+
"""
|
103
|
+
|
104
|
+
raise NotImplementedError
|
@@ -0,0 +1,147 @@
|
|
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
|
+
# ~> https://github.com/python/cpython/blob/f19c50a4817ffebb26132182ed8cec6a72342cc0/Lib/_compression.py
|
37
|
+
import typing as ta
|
38
|
+
|
39
|
+
from ... import check
|
40
|
+
from .abc import Compressor
|
41
|
+
from .abc import NeedsInputDecompressor
|
42
|
+
from .abc import UnconsumedTailDecompressor
|
43
|
+
from .types import IncrementalCompressor
|
44
|
+
from .types import IncrementalDecompressor
|
45
|
+
|
46
|
+
|
47
|
+
##
|
48
|
+
|
49
|
+
|
50
|
+
class CompressorIncrementalAdapter:
|
51
|
+
def __init__(
|
52
|
+
self,
|
53
|
+
factory: ta.Callable[..., Compressor],
|
54
|
+
) -> None:
|
55
|
+
super().__init__()
|
56
|
+
|
57
|
+
self._factory = factory
|
58
|
+
|
59
|
+
def __call__(self) -> IncrementalCompressor:
|
60
|
+
compressor = self._factory()
|
61
|
+
|
62
|
+
while True:
|
63
|
+
data = check.isinstance((yield None), bytes)
|
64
|
+
if not data:
|
65
|
+
break
|
66
|
+
|
67
|
+
compressed = compressor.compress(data)
|
68
|
+
if compressed:
|
69
|
+
check.none((yield compressed))
|
70
|
+
|
71
|
+
if (fl := compressor.flush()):
|
72
|
+
check.none((yield fl))
|
73
|
+
|
74
|
+
check.none((yield b''))
|
75
|
+
|
76
|
+
|
77
|
+
##
|
78
|
+
|
79
|
+
|
80
|
+
class DecompressorIncrementalAdapter:
|
81
|
+
def __init__(
|
82
|
+
self,
|
83
|
+
factory: ta.Callable[..., NeedsInputDecompressor | UnconsumedTailDecompressor],
|
84
|
+
*,
|
85
|
+
trailing_error: type[BaseException] | tuple[type[BaseException], ...] = (),
|
86
|
+
) -> None:
|
87
|
+
super().__init__()
|
88
|
+
|
89
|
+
self._factory = factory
|
90
|
+
self._trailing_error = trailing_error
|
91
|
+
|
92
|
+
def __call__(self) -> IncrementalDecompressor:
|
93
|
+
pos = 0
|
94
|
+
|
95
|
+
data = None # Default if EOF is encountered
|
96
|
+
|
97
|
+
decompressor = self._factory()
|
98
|
+
|
99
|
+
while True:
|
100
|
+
# Depending on the input data, our call to the decompressor may not return any data. In this case, try again
|
101
|
+
# after reading another block.
|
102
|
+
while True:
|
103
|
+
if decompressor.eof:
|
104
|
+
rawblock = decompressor.unused_data
|
105
|
+
if not rawblock:
|
106
|
+
rawblock = check.isinstance((yield None), bytes)
|
107
|
+
if not rawblock:
|
108
|
+
break
|
109
|
+
|
110
|
+
# Continue to next stream.
|
111
|
+
decompressor = self._factory()
|
112
|
+
|
113
|
+
try:
|
114
|
+
data = decompressor.decompress(rawblock)
|
115
|
+
except self._trailing_error:
|
116
|
+
# Trailing data isn't a valid compressed stream; ignore it.
|
117
|
+
break
|
118
|
+
|
119
|
+
else:
|
120
|
+
if hasattr(decompressor, 'needs_input'):
|
121
|
+
if decompressor.needs_input:
|
122
|
+
rawblock = check.isinstance((yield None), bytes)
|
123
|
+
if not rawblock:
|
124
|
+
raise EOFError('Compressed file ended before the end-of-stream marker was reached')
|
125
|
+
else:
|
126
|
+
rawblock = b''
|
127
|
+
|
128
|
+
elif hasattr(decompressor, 'unconsumed_tail'):
|
129
|
+
if not (rawblock := decompressor.unconsumed_tail):
|
130
|
+
rawblock = check.isinstance((yield None), bytes)
|
131
|
+
if not rawblock:
|
132
|
+
raise EOFError('Compressed file ended before the end-of-stream marker was reached')
|
133
|
+
|
134
|
+
else:
|
135
|
+
raise TypeError(decompressor)
|
136
|
+
|
137
|
+
data = decompressor.decompress(rawblock)
|
138
|
+
|
139
|
+
if data:
|
140
|
+
break
|
141
|
+
|
142
|
+
if not data:
|
143
|
+
check.none((yield b''))
|
144
|
+
return
|
145
|
+
|
146
|
+
pos += len(data)
|
147
|
+
check.none((yield data))
|
@@ -0,0 +1,41 @@
|
|
1
|
+
import functools
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from ... import lang
|
5
|
+
from .adapters import CompressorIncrementalAdapter
|
6
|
+
from .adapters import DecompressorIncrementalAdapter
|
7
|
+
from .types import IncrementalCompressor
|
8
|
+
from .types import IncrementalDecompressor
|
9
|
+
|
10
|
+
|
11
|
+
if ta.TYPE_CHECKING:
|
12
|
+
import bz2
|
13
|
+
else:
|
14
|
+
bz2 = lang.proxy_import('bz2')
|
15
|
+
|
16
|
+
|
17
|
+
class IncrementalBz2Compressor:
|
18
|
+
def __init__(
|
19
|
+
self,
|
20
|
+
*,
|
21
|
+
compresslevel: int = 9,
|
22
|
+
) -> None:
|
23
|
+
super().__init__()
|
24
|
+
|
25
|
+
self._compresslevel = compresslevel
|
26
|
+
|
27
|
+
def __call__(self) -> IncrementalCompressor:
|
28
|
+
return CompressorIncrementalAdapter(
|
29
|
+
functools.partial(
|
30
|
+
bz2.BZ2Compressor, # type: ignore
|
31
|
+
self._compresslevel,
|
32
|
+
),
|
33
|
+
)()
|
34
|
+
|
35
|
+
|
36
|
+
class IncrementalBz2Decompressor:
|
37
|
+
def __call__(self) -> IncrementalDecompressor:
|
38
|
+
return DecompressorIncrementalAdapter(
|
39
|
+
bz2.BZ2Decompressor, # type: ignore
|
40
|
+
trailing_error=OSError,
|
41
|
+
)()
|
@@ -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
|
omlish/lang/iterables.py
CHANGED
@@ -4,7 +4,6 @@ import typing as ta
|
|
4
4
|
|
5
5
|
|
6
6
|
T = ta.TypeVar('T')
|
7
|
-
S = ta.TypeVar('S')
|
8
7
|
R = ta.TypeVar('R')
|
9
8
|
|
10
9
|
|
@@ -15,6 +14,9 @@ BUILTIN_SCALAR_ITERABLE_TYPES: tuple[type, ...] = (
|
|
15
14
|
)
|
16
15
|
|
17
16
|
|
17
|
+
##
|
18
|
+
|
19
|
+
|
18
20
|
def ilen(it: ta.Iterable) -> int:
|
19
21
|
c = 0
|
20
22
|
for _ in it:
|
@@ -44,6 +46,31 @@ def interleave(vs: ta.Iterable[T], d: T) -> ta.Iterable[T]:
|
|
44
46
|
yield v
|
45
47
|
|
46
48
|
|
49
|
+
@dc.dataclass(frozen=True)
|
50
|
+
class IterGen(ta.Generic[T]):
|
51
|
+
fn: ta.Callable[[], ta.Iterable[T]]
|
52
|
+
|
53
|
+
def __iter__(self):
|
54
|
+
return iter(self.fn())
|
55
|
+
|
56
|
+
|
57
|
+
itergen = IterGen
|
58
|
+
|
59
|
+
|
60
|
+
def renumerate(it: ta.Iterable[T]) -> ta.Iterable[tuple[T, int]]:
|
61
|
+
return ((e, i) for i, e in enumerate(it))
|
62
|
+
|
63
|
+
|
64
|
+
flatten = itertools.chain.from_iterable
|
65
|
+
|
66
|
+
|
67
|
+
def flatmap(fn: ta.Callable[[T], ta.Iterable[R]], it: ta.Iterable[T]) -> ta.Iterable[R]:
|
68
|
+
return flatten(map(fn, it))
|
69
|
+
|
70
|
+
|
71
|
+
##
|
72
|
+
|
73
|
+
|
47
74
|
Rangeable: ta.TypeAlias = ta.Union[ # noqa
|
48
75
|
int,
|
49
76
|
tuple[int],
|
@@ -68,53 +95,3 @@ def prodrange(*dims: Rangeable) -> ta.Iterable[ta.Sequence[int]]:
|
|
68
95
|
if not dims:
|
69
96
|
return []
|
70
97
|
return itertools.product(*map(asrange, dims))
|
71
|
-
|
72
|
-
|
73
|
-
@dc.dataclass(frozen=True)
|
74
|
-
class itergen(ta.Generic[T]): # noqa
|
75
|
-
fn: ta.Callable[[], ta.Iterable[T]]
|
76
|
-
|
77
|
-
def __iter__(self):
|
78
|
-
return iter(self.fn())
|
79
|
-
|
80
|
-
|
81
|
-
def renumerate(it: ta.Iterable[T]) -> ta.Iterable[tuple[T, int]]:
|
82
|
-
return ((e, i) for i, e in enumerate(it))
|
83
|
-
|
84
|
-
|
85
|
-
flatten = itertools.chain.from_iterable
|
86
|
-
|
87
|
-
|
88
|
-
def flatmap(fn: ta.Callable[[T], ta.Iterable[R]], it: ta.Iterable[T]) -> ta.Iterable[R]:
|
89
|
-
return flatten(map(fn, it))
|
90
|
-
|
91
|
-
|
92
|
-
class Generator(ta.Generator[T, S, R]):
|
93
|
-
def __init__(self, gen: ta.Generator[T, S, R]) -> None:
|
94
|
-
super().__init__()
|
95
|
-
self.gen = gen
|
96
|
-
|
97
|
-
value: R
|
98
|
-
|
99
|
-
def __iter__(self):
|
100
|
-
return self
|
101
|
-
|
102
|
-
def __next__(self):
|
103
|
-
return self.send(None)
|
104
|
-
|
105
|
-
def send(self, v):
|
106
|
-
try:
|
107
|
-
return self.gen.send(v)
|
108
|
-
except StopIteration as e:
|
109
|
-
self.value = e.value
|
110
|
-
raise
|
111
|
-
|
112
|
-
def throw(self, *args):
|
113
|
-
try:
|
114
|
-
return self.gen.throw(*args)
|
115
|
-
except StopIteration as e:
|
116
|
-
self.value = e.value
|
117
|
-
raise
|
118
|
-
|
119
|
-
def close(self):
|
120
|
-
self.gen.close()
|
omlish/lang/maybes.py
CHANGED
@@ -50,11 +50,11 @@ class Maybe(abc.ABC, ta.Generic[T]):
|
|
50
50
|
raise NotImplementedError
|
51
51
|
|
52
52
|
@abc.abstractmethod
|
53
|
-
def or_else(self, other: T) -> T:
|
53
|
+
def or_else(self, other: T | U) -> T | U:
|
54
54
|
raise NotImplementedError
|
55
55
|
|
56
56
|
@abc.abstractmethod
|
57
|
-
def or_else_get(self, supplier: ta.Callable[[],
|
57
|
+
def or_else_get(self, supplier: ta.Callable[[], U]) -> T | U:
|
58
58
|
raise NotImplementedError
|
59
59
|
|
60
60
|
@abc.abstractmethod
|
@@ -109,10 +109,10 @@ class _Maybe(Maybe[T], tuple):
|
|
109
109
|
return value
|
110
110
|
return _empty # noqa
|
111
111
|
|
112
|
-
def or_else(self, other: T) -> T:
|
112
|
+
def or_else(self, other: T | U) -> T | U:
|
113
113
|
return self.must() if self else other
|
114
114
|
|
115
|
-
def or_else_get(self, supplier: ta.Callable[[], T]) -> T:
|
115
|
+
def or_else_get(self, supplier: ta.Callable[[], T | U]) -> T | U:
|
116
116
|
return self.must() if self else supplier()
|
117
117
|
|
118
118
|
def or_else_raise(self, exception_supplier: ta.Callable[[], Exception]) -> T:
|
@@ -1,9 +1,9 @@
|
|
1
1
|
omlish/.manifests.json,sha256=RX24SRc6DCEg77PUVnaXOKCWa5TF_c9RQJdGIf7gl9c,1135
|
2
|
-
omlish/__about__.py,sha256=
|
2
|
+
omlish/__about__.py,sha256=keq7273S1vADjELx7wkIYWHvdZy7TM3pyrMqOHXwNYg,3379
|
3
3
|
omlish/__init__.py,sha256=SsyiITTuK0v74XpKV8dqNaCmjOlan1JZKrHQv5rWKPA,253
|
4
4
|
omlish/argparse.py,sha256=cqKGAqcxuxv_s62z0gq29L9KAvg_3-_rFvXKjVpRJjo,8126
|
5
5
|
omlish/c3.py,sha256=ubu7lHwss5V4UznbejAI0qXhXahrU01MysuHOZI9C4U,8116
|
6
|
-
omlish/cached.py,sha256=
|
6
|
+
omlish/cached.py,sha256=UI-XTFBwA6YXWJJJeBn-WkwBkfzDjLBBaZf4nIJA9y0,510
|
7
7
|
omlish/check.py,sha256=Li5xmecEyWKMzlwWyd6xDHpq3F4lE6IFOPBWdylCnpU,10540
|
8
8
|
omlish/datetimes.py,sha256=HajeM1kBvwlTa-uR1TTZHmZ3zTPnnUr1uGGQhiO1XQ0,2152
|
9
9
|
omlish/defs.py,sha256=T3bq_7h_tO3nDB5RAFBn7DkdeQgqheXzkFColbOHZko,4890
|
@@ -92,14 +92,14 @@ omlish/bootstrap/main.py,sha256=yZhOHDDlj4xB5a89dRdT8z58FsqqnpoBg1-tvY2CJe4,5903
|
|
92
92
|
omlish/bootstrap/marshal.py,sha256=ZxdAeMNd2qXRZ1HUK89HmEhz8tqlS9OduW34QBscKw0,516
|
93
93
|
omlish/bootstrap/sys.py,sha256=iLHUNIuIPv-k-Mc6aHj5sSET78olCVt7t0HquFDO4iQ,8762
|
94
94
|
omlish/collections/__init__.py,sha256=zeUvcAz073ekko37QKya6sElTMfKTuF1bKrdbMtaRpI,2142
|
95
|
-
omlish/collections/
|
95
|
+
omlish/collections/abc.py,sha256=sP7BpTVhx6s6C59mTFeosBi4rHOWC6tbFBYbxdZmvh0,2365
|
96
96
|
omlish/collections/coerce.py,sha256=o11AMrUiyoadd8WkdqeKPIpXf2xd0LyylzNCyJivCLU,7036
|
97
97
|
omlish/collections/exceptions.py,sha256=shcS-NCnEUudF8qC_SmO2TQyjivKlS4TDjaz_faqQ0c,44
|
98
98
|
omlish/collections/frozen.py,sha256=DGxemj_pVID85tSBm-Wns_x4ov0wOEIT6X5bVgJtmkA,4152
|
99
99
|
omlish/collections/hasheq.py,sha256=XcOCE6f2lXizDCOXxSX6vJv-rLcpDo2OWCYIKGSWuic,3697
|
100
100
|
omlish/collections/identity.py,sha256=jhEpC8tnfh3Sg-MJff1Fp9eMydt150wits_UeVdctUk,2723
|
101
101
|
omlish/collections/indexed.py,sha256=tFQsIWH4k9QqsF5VB7DsIVNsRThiQNx-ooRF36X_PnU,2203
|
102
|
-
omlish/collections/mappings.py,sha256=
|
102
|
+
omlish/collections/mappings.py,sha256=laXV4WU1VZPGwaJQsJCQsmL3BVeUfELljTZ6a5sfg0s,3206
|
103
103
|
omlish/collections/ordered.py,sha256=RzEC3fHvrDeJQSWThVDNYQKke263Vje1II5YwtDwT1Q,2335
|
104
104
|
omlish/collections/persistent.py,sha256=KG471s0bhhReQrjlmX0xaN9HeAIcrtT264ddZCxsExo,875
|
105
105
|
omlish/collections/skiplist.py,sha256=xjuKZtSScp1VnOi9lpf7I090vGp1DnjA5ELjFhMeGps,5987
|
@@ -156,7 +156,7 @@ omlish/diag/pycharm.py,sha256=7-r_F-whXt8v-0dehxAX-MeMFPM3iZXX9IfeL0GfUtk,4643
|
|
156
156
|
omlish/diag/pydevd.py,sha256=UN55ZjkWLCVyHxE2CNRRYamuvSKfzWsn0D5oczRTXO4,7536
|
157
157
|
omlish/diag/threads.py,sha256=1-x02VCDZ407gfbtXm1pWK-ubqhqfePm9PMqkHCVoqk,3642
|
158
158
|
omlish/diag/_pycharm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
159
|
-
omlish/diag/_pycharm/runhack.py,sha256=
|
159
|
+
omlish/diag/_pycharm/runhack.py,sha256=JFz4GVN4AXndJo38iwnK5X_vH2MS6wyO8YmVZss5YRE,35157
|
160
160
|
omlish/diag/replserver/__init__.py,sha256=uLo6V2aQ29v9z3IMELlPDSlG3_2iOT4-_X8VniF-EgE,235
|
161
161
|
omlish/diag/replserver/__main__.py,sha256=LmU41lQ58bm1h4Mx7S8zhE_uEBSC6kPcp9mn5JRpulA,32
|
162
162
|
omlish/diag/replserver/console.py,sha256=XzBDVhYlr8FY6ym4OwoaIHuFOHnGK3dTYlMDIOMUUlA,7410
|
@@ -198,7 +198,7 @@ omlish/formats/json/stream/lex.py,sha256=_JYBFnAyHsw_3hu8I0rvZqSSkRCU1BvQzgO81Kf
|
|
198
198
|
omlish/formats/json/stream/parse.py,sha256=s21PgiuNTcqc_i9QS1ggmEp8Qwp_hOqtosr5d0zpg_o,5204
|
199
199
|
omlish/formats/json/stream/render.py,sha256=NtmDsN92xZi5dkgSSuMeMXMAiJblmjz1arB4Ft7vBhc,3715
|
200
200
|
omlish/funcs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
201
|
-
omlish/funcs/genmachine.py,sha256=
|
201
|
+
omlish/funcs/genmachine.py,sha256=XEHy8SFgHCDYSAqlRm-7wlYQX2h6UWehR2_uMw9EOXU,2509
|
202
202
|
omlish/funcs/match.py,sha256=gMLZn1enNiFvQaWrQubY300M1BrmdKWzeePihBS7Ywc,6153
|
203
203
|
omlish/funcs/pairs.py,sha256=OzAwnALkRJXVpD47UvBZHKzQfHtFNry_EgjTcC7vgLU,10606
|
204
204
|
omlish/funcs/pipes.py,sha256=E7Sz8Aj8ke_vCs5AMNwg1I36kRdHVGTnzxVQaDyn43U,2490
|
@@ -256,10 +256,18 @@ omlish/inject/impl/providers.py,sha256=QnwhsujJFIHC0JTgd2Wlo1kP53i3CWTrj1nKU2DNx
|
|
256
256
|
omlish/inject/impl/proxy.py,sha256=1ko0VaKqzu9UG8bIldp9xtUrAVUOFTKWKTjOCqIGr4s,1636
|
257
257
|
omlish/inject/impl/scopes.py,sha256=hKnzNieB-fJSFEXDP_QG1mCfIKoVFIfFlf9LiIt5tk4,5920
|
258
258
|
omlish/io/__init__.py,sha256=aaIEsXTSfytW-oEkUWczdUJ_ifFY7ihIpyidIbfjkwY,56
|
259
|
-
omlish/io/
|
259
|
+
omlish/io/abc.py,sha256=Cxs8KB1B_69rxpUYxI-MTsilAmNooJJn3w07DKqYKkE,1255
|
260
|
+
omlish/io/generators.py,sha256=ZlAp_t0ZD_aKztlio1i_hezmpIFFjaiXtrnY6-2QsPs,1123
|
260
261
|
omlish/io/pyio.py,sha256=YB3g6yg64MzcFwbzKBo4adnbsbZ3FZMlOZfjNtWmYoc,95316
|
261
262
|
omlish/io/trampoline.py,sha256=oUKTQg1F5xQS1431Kt7MbK-NZpX509ubcXU-s86xJr8,7171
|
262
|
-
omlish/
|
263
|
+
omlish/io/compress/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
264
|
+
omlish/io/compress/abc.py,sha256=R9ebpSjJK4VAimV3OevPJB-jSDTGB_xi2FKNZKbTdYE,3054
|
265
|
+
omlish/io/compress/adapters.py,sha256=wS7cA_quham3C23j3_H6sf2EQ4gI0vURTQdPhapiiFE,6088
|
266
|
+
omlish/io/compress/bz2.py,sha256=XxpAKdQ5pdWfa23a0F6ZspU_GxHhyJVVPjv2SCyw4hM,1006
|
267
|
+
omlish/io/compress/gzip.py,sha256=TUjbE5cjiHXd2ZMsRgXBlSW3mCF7xMt_NvIRTYddBCQ,11054
|
268
|
+
omlish/io/compress/lzma.py,sha256=rM6FXWeD6s-K-Sfxn04AaFILS-v4rliWKlPnAl_RrJw,803
|
269
|
+
omlish/io/compress/types.py,sha256=IuCyxFX8v12fGqCq2ofCCRM5ZM-4zngHeeBW_PWqYbM,557
|
270
|
+
omlish/lang/__init__.py,sha256=U6-WtzQL48e9wqWHmxuA1X2PoGlx6Di2HSAi13gTwjw,3866
|
263
271
|
omlish/lang/cached.py,sha256=92TvRZQ6sWlm7dNn4hgl7aWKbX0J1XUEo3DRjBpgVQk,7834
|
264
272
|
omlish/lang/clsdct.py,sha256=AjtIWLlx2E6D5rC97zQ3Lwq2SOMkbg08pdO_AxpzEHI,1744
|
265
273
|
omlish/lang/cmp.py,sha256=5vbzWWbqdzDmNKAGL19z6ZfUKe5Ci49e-Oegf9f4BsE,1346
|
@@ -268,9 +276,10 @@ omlish/lang/datetimes.py,sha256=ehI_DhQRM-bDxAavnp470XcekbbXc4Gdw9y1KpHDJT0,223
|
|
268
276
|
omlish/lang/descriptors.py,sha256=RRBbkMgTzg82fFFE4D0muqobpM-ZZaOta6yB1lpX3s8,6617
|
269
277
|
omlish/lang/exceptions.py,sha256=qJBo3NU1mOWWm-NhQUHCY5feYXR3arZVyEHinLsmRH4,47
|
270
278
|
omlish/lang/functions.py,sha256=kkPfcdocg-OmyN7skIqrFxNvqAv89Zc_kXKYAN8vw8g,3895
|
279
|
+
omlish/lang/generators.py,sha256=AShh0x-9Z9qolAYEOZJgYJcxQuyA3HKq0c9tLwNcFs4,3766
|
271
280
|
omlish/lang/imports.py,sha256=TXLbj2F53LsmozlM05bQhvow9kEgWJOi9qYKsnm2D18,9258
|
272
|
-
omlish/lang/iterables.py,sha256=
|
273
|
-
omlish/lang/maybes.py,sha256=
|
281
|
+
omlish/lang/iterables.py,sha256=1bc-Vn-b34T6Gy3li2tMNYpUvuwCC7fjg7dpjXkTfWY,1746
|
282
|
+
omlish/lang/maybes.py,sha256=1RN7chX_x2XvgUwryZRz0W7hAX-be3eEFcFub5vvf6M,3417
|
274
283
|
omlish/lang/objects.py,sha256=LOC3JvX1g5hPxJ7Sv2TK9kNkAo9c8J-Jw2NmClR_rkA,4576
|
275
284
|
omlish/lang/resolving.py,sha256=OuN2mDTPNyBUbcrswtvFKtj4xgH4H4WglgqSKv3MTy0,1606
|
276
285
|
omlish/lang/resources.py,sha256=yywDWhh0tsgw24l7mHYv49ll4oZS8Kc8MSCa8f4UbbI,2280
|
@@ -324,7 +333,7 @@ omlish/lite/http/handlers.py,sha256=Yu0P3nqz-frklwCM2PbiWvoJNE-NqeTFLBvpNpqcdtA,
|
|
324
333
|
omlish/lite/http/parsing.py,sha256=jLdbBTQQhKU701j3_Ixl77nQE3rZld2qbJNAFhuW_cc,13977
|
325
334
|
omlish/lite/http/versions.py,sha256=M6WhZeeyun-3jL_NCViNONOfLCiApuFOfe5XNJwzSvw,394
|
326
335
|
omlish/logs/__init__.py,sha256=FbOyAW-lGH8gyBlSVArwljdYAU6RnwZLI5LwAfuNnrk,438
|
327
|
-
omlish/logs/
|
336
|
+
omlish/logs/abc.py,sha256=rWySJcr1vatu-AR1EYtODRhi-TjFaixqUzXeWg1c0GA,8006
|
328
337
|
omlish/logs/configs.py,sha256=EE0jlNaXJbGnM7V-y4xS5VwyTBSTzFzc0BYaVjg0JmA,1283
|
329
338
|
omlish/logs/formatters.py,sha256=q79nMnR2mRIStPyGrydQHpYTXgC5HHptt8lH3W2Wwbs,671
|
330
339
|
omlish/logs/handlers.py,sha256=UpzUf3kWBBzWOnrtljoZsLjISw3Ix-ePz3Nsmp6lRgE,255
|
@@ -419,7 +428,7 @@ omlish/specs/openapi/__init__.py,sha256=zilQhafjvteRDF_TUIRgF293dBC6g-TJChmUb6T9
|
|
419
428
|
omlish/specs/openapi/marshal.py,sha256=Z-E2Knm04C81N8AA8cibCVSl2ImhSpHZVc7yAhmPx88,2135
|
420
429
|
omlish/specs/openapi/openapi.py,sha256=y4h04jeB7ORJSVrcy7apaBdpwLjIyscv1Ub5SderH2c,12682
|
421
430
|
omlish/sql/__init__.py,sha256=TpZLsEJKJzvJ0eMzuV8hwOJJbkxBCV1RZPUMLAVB6io,173
|
422
|
-
omlish/sql/
|
431
|
+
omlish/sql/abc.py,sha256=kiOitW_ZhTXrJanJ582wD7o9K69v6HXqDPkxuHEAxrc,1606
|
423
432
|
omlish/sql/dbapi.py,sha256=5ghJH-HexsmDlYdWlhf00nCGQX2IC98_gxIxMkucOas,3195
|
424
433
|
omlish/sql/dbs.py,sha256=lpdFmm2vTwLoBiVYGj9yPsVcTEYYNCxlYZZpjfChzkY,1870
|
425
434
|
omlish/sql/params.py,sha256=Z4VPet6GhNqD1T_MXSWSHkdy3cpUEhST-OplC4B_fYI,4433
|
@@ -480,9 +489,9 @@ omlish/text/glyphsplit.py,sha256=Ug-dPRO7x-OrNNr8g1y6DotSZ2KH0S-VcOmUobwa4B0,329
|
|
480
489
|
omlish/text/indent.py,sha256=6Jj6TFY9unaPa4xPzrnZemJ-fHsV53IamP93XGjSUHs,1274
|
481
490
|
omlish/text/parts.py,sha256=7vPF1aTZdvLVYJ4EwBZVzRSy8XB3YqPd7JwEnNGGAOo,6495
|
482
491
|
omlish/text/random.py,sha256=jNWpqiaKjKyTdMXC-pWAsSC10AAP-cmRRPVhm59ZWLk,194
|
483
|
-
omlish-0.0.0.
|
484
|
-
omlish-0.0.0.
|
485
|
-
omlish-0.0.0.
|
486
|
-
omlish-0.0.0.
|
487
|
-
omlish-0.0.0.
|
488
|
-
omlish-0.0.0.
|
492
|
+
omlish-0.0.0.dev137.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
|
493
|
+
omlish-0.0.0.dev137.dist-info/METADATA,sha256=iU_JPwXqYl0APiFu32-g6dTXny6kbXZWslNaWevwHEs,4173
|
494
|
+
omlish-0.0.0.dev137.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
495
|
+
omlish-0.0.0.dev137.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
|
496
|
+
omlish-0.0.0.dev137.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
|
497
|
+
omlish-0.0.0.dev137.dist-info/RECORD,,
|
File without changes
|
/omlish/io/{_abc.py → abc.py}
RENAMED
File without changes
|
/omlish/logs/{_abc.py → abc.py}
RENAMED
File without changes
|
/omlish/sql/{_abc.py → abc.py}
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|