kisstdlib 0.0.12__tar.gz → 0.0.14__tar.gz
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.
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/PKG-INFO +4 -3
- kisstdlib-0.0.14/VERSION +1 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/kisstdlib/__init__.py +5 -4
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/kisstdlib/argparse_ext.py +4 -7
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/kisstdlib/base.py +126 -119
- kisstdlib-0.0.12/kisstdlib/sorted.py → kisstdlib-0.0.14/kisstdlib/collections_ext.py +340 -268
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/kisstdlib/failure.py +37 -14
- kisstdlib-0.0.14/kisstdlib/fs.py +1486 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/kisstdlib/io/base.py +20 -8
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/kisstdlib/io/stdio.py +11 -5
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/kisstdlib/itertools_ext.py +38 -44
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/kisstdlib/logging_ext.py +25 -4
- kisstdlib-0.0.14/kisstdlib/parsing.py +641 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/kisstdlib/pyrepr.py +1 -2
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/kisstdlib/sqlite3_ext.py +96 -16
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/kisstdlib/string_ext.py +58 -37
- kisstdlib-0.0.14/kisstdlib/time.py +1268 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/kisstdlib.egg-info/PKG-INFO +4 -3
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/kisstdlib.egg-info/SOURCES.txt +3 -2
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/pyproject.toml +5 -3
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/test/test_basics.py +21 -4
- kisstdlib-0.0.14/test/test_collections.py +200 -0
- kisstdlib-0.0.14/test/test_fs.py +772 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/test/test_parsing.py +78 -0
- kisstdlib-0.0.14/test/test_time.py +1079 -0
- kisstdlib-0.0.12/kisstdlib/fs.py +0 -1115
- kisstdlib-0.0.12/kisstdlib/parsing.py +0 -316
- kisstdlib-0.0.12/kisstdlib/time.py +0 -299
- kisstdlib-0.0.12/test/test_fs.py +0 -343
- kisstdlib-0.0.12/test/test_sorted.py +0 -153
- kisstdlib-0.0.12/test/test_time.py +0 -154
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/LICENSE.txt +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/README.md +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/kisstdlib/compression.py +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/kisstdlib/getpass_ext.py +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/kisstdlib/io/__init__.py +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/kisstdlib/io/adapter.py +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/kisstdlib/io/encoder.py +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/kisstdlib/io/wrapper.py +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/kisstdlib/py.typed +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/kisstdlib/signal_ext.py +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/kisstdlib/tariter.py +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/kisstdlib.egg-info/dependency_links.txt +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/kisstdlib.egg-info/entry_points.txt +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/kisstdlib.egg-info/requires.txt +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/kisstdlib.egg-info/top_level.txt +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/kisstdlib_bin/describe_forest.py +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/setup.cfg +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/setup.py +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.14}/test/test_io.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: kisstdlib
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.14
|
|
4
4
|
Summary: System programming with conceptually and algebraically simple primitives.
|
|
5
5
|
Author-email: Jan Malakhovski <oxij@oxij.org>
|
|
6
6
|
Project-URL: Homepage, https://oxij.org/software/kisstdlib/
|
|
@@ -12,10 +12,11 @@ Classifier: Programming Language :: Python :: 3
|
|
|
12
12
|
Classifier: License :: OSI Approved :: MIT License
|
|
13
13
|
Classifier: License :: OSI Approved :: Python Software Foundation License
|
|
14
14
|
Classifier: License :: OSI Approved :: BSD License
|
|
15
|
-
Requires-Python: >=3.
|
|
15
|
+
Requires-Python: >=3.13
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
License-File: LICENSE.txt
|
|
18
18
|
Requires-Dist: sortedcontainers==2.4.*
|
|
19
|
+
Dynamic: license-file
|
|
19
20
|
|
|
20
21
|
# Table of Contents
|
|
21
22
|
<details><summary>(Click me to see it.)</summary>
|
kisstdlib-0.0.14/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.0.14
|
|
@@ -32,6 +32,7 @@ from .base import *
|
|
|
32
32
|
from .failure import *
|
|
33
33
|
from .string_ext import *
|
|
34
34
|
from .itertools_ext import *
|
|
35
|
+
from .collections_ext import *
|
|
35
36
|
from .io.stdio import *
|
|
36
37
|
from .logging_ext import *
|
|
37
38
|
from .signal_ext import *
|
|
@@ -63,12 +64,12 @@ def setup_kisstdlib(
|
|
|
63
64
|
return res
|
|
64
65
|
|
|
65
66
|
|
|
66
|
-
def run_kisstdlib_main(
|
|
67
|
+
def run_kisstdlib_main[**PS](
|
|
67
68
|
setup_logging_result: tuple[LogCounter, ANSILogHandler],
|
|
68
|
-
func: _t.Callable[
|
|
69
|
+
func: _t.Callable[PS, None],
|
|
69
70
|
/,
|
|
70
|
-
*args:
|
|
71
|
-
**kwargs:
|
|
71
|
+
*args: PS.args,
|
|
72
|
+
**kwargs: PS.kwargs,
|
|
72
73
|
) -> None:
|
|
73
74
|
"""Run given function with given arguments under `with no_signals()` block while
|
|
74
75
|
properly handling all exceptions in `ExceptionGroup`s,
|
|
@@ -343,15 +343,12 @@ class BetterArgumentParser(ArgumentParser):
|
|
|
343
343
|
return res
|
|
344
344
|
|
|
345
345
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
def make_argparser_and_run(
|
|
346
|
+
def make_argparser_and_run[**PS](
|
|
350
347
|
make_argparser: _t.Callable[[bool], BetterArgumentParser],
|
|
351
|
-
func: _t.Callable[_t.Concatenate[Namespace,
|
|
348
|
+
func: _t.Callable[_t.Concatenate[Namespace, PS], None],
|
|
352
349
|
/,
|
|
353
|
-
*args:
|
|
354
|
-
**kwargs:
|
|
350
|
+
*args: PS.args,
|
|
351
|
+
**kwargs: PS.kwargs,
|
|
355
352
|
) -> None:
|
|
356
353
|
"""Given `make_argparser` function that produces "real" and "fake"
|
|
357
354
|
`BetterArgumentParser`s, run `parse_args` with on real one, but then, if
|
|
@@ -35,23 +35,9 @@ from functools import partial, partialmethod # pylint: disable=unused-import
|
|
|
35
35
|
|
|
36
36
|
### Primitive types
|
|
37
37
|
|
|
38
|
-
# Placeholders
|
|
39
|
-
AType = _t.TypeVar("AType")
|
|
40
|
-
BType = _t.TypeVar("BType")
|
|
41
|
-
CType = _t.TypeVar("CType")
|
|
42
|
-
DType = _t.TypeVar("DType")
|
|
43
|
-
EType = _t.TypeVar("EType")
|
|
44
|
-
AParamSpec = _t.ParamSpec("AParamSpec")
|
|
45
|
-
BParamSpec = _t.ParamSpec("BParamSpec")
|
|
46
|
-
|
|
47
38
|
# Bytes-like object
|
|
48
39
|
BytesLike = bytes | bytearray | memoryview
|
|
49
40
|
|
|
50
|
-
# Numbers
|
|
51
|
-
Number = Decimal | float | int
|
|
52
|
-
AnyNumber = _t.TypeVar("AnyNumber", bound=Number)
|
|
53
|
-
|
|
54
|
-
|
|
55
41
|
### Common constants
|
|
56
42
|
|
|
57
43
|
# OS detection
|
|
@@ -60,16 +46,23 @@ WINDOWS = _os.name == "nt"
|
|
|
60
46
|
|
|
61
47
|
# Time lengths, in seconds
|
|
62
48
|
MINUTE = 60
|
|
63
|
-
HOUR =
|
|
64
|
-
DAY =
|
|
65
|
-
|
|
66
|
-
|
|
49
|
+
HOUR = 3600
|
|
50
|
+
DAY = 86400
|
|
51
|
+
WEEK = 604800
|
|
52
|
+
|
|
53
|
+
# Common metric prefixes
|
|
54
|
+
MILLI = Decimal((0, (1,), -3))
|
|
55
|
+
MICRO = Decimal((0, (1,), -6))
|
|
56
|
+
NANO = Decimal((0, (1,), -9))
|
|
67
57
|
|
|
68
58
|
# Common byte sizes
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
59
|
+
KB = 1000
|
|
60
|
+
MB = 1000000
|
|
61
|
+
GB = 1000000000
|
|
72
62
|
|
|
63
|
+
KiB = 1024
|
|
64
|
+
MiB = 1048576
|
|
65
|
+
GiB = 1073741824
|
|
73
66
|
|
|
74
67
|
### Placeholder values
|
|
75
68
|
|
|
@@ -104,8 +97,8 @@ MISSING = Missing()
|
|
|
104
97
|
### Structured unions
|
|
105
98
|
|
|
106
99
|
|
|
107
|
-
class Maybe
|
|
108
|
-
"""Similar to `Optional[
|
|
100
|
+
class Maybe[T]:
|
|
101
|
+
"""Similar to `Optional[T]`, i.e. `T | None`, but allows
|
|
109
102
|
arbitrary nesting.
|
|
110
103
|
|
|
111
104
|
E.g. `Maybe[Maybe[int]]` does not get flattened into `Maybe[int]`.
|
|
@@ -113,117 +106,118 @@ class Maybe(_t.Generic[AType]):
|
|
|
113
106
|
|
|
114
107
|
|
|
115
108
|
@_dc.dataclass(order=True)
|
|
116
|
-
class Nothing(Maybe[
|
|
109
|
+
class Nothing[T](Maybe[T]):
|
|
117
110
|
pass
|
|
118
111
|
|
|
119
112
|
|
|
120
113
|
@_dc.dataclass(order=True, slots=True)
|
|
121
|
-
class Just(Maybe[
|
|
122
|
-
just:
|
|
114
|
+
class Just[T](Maybe[T]):
|
|
115
|
+
just: T
|
|
123
116
|
|
|
124
117
|
|
|
125
|
-
class Either
|
|
126
|
-
"""Either a value of `
|
|
118
|
+
class Either[A, B]():
|
|
119
|
+
"""Either a value of type `A` or a value of type `B`, without flattening.
|
|
127
120
|
|
|
128
121
|
E.g., `Either[int, int]` is not just `int`.
|
|
129
122
|
"""
|
|
130
123
|
|
|
131
124
|
|
|
132
125
|
@_dc.dataclass(order=True, slots=True)
|
|
133
|
-
class Left
|
|
134
|
-
left:
|
|
126
|
+
class Left[A, B](Either[A, B]):
|
|
127
|
+
left: A
|
|
135
128
|
|
|
136
129
|
|
|
137
130
|
@_dc.dataclass(order=True, slots=True)
|
|
138
|
-
class Right
|
|
139
|
-
right:
|
|
131
|
+
class Right[A, B](Either[A, B]):
|
|
132
|
+
right: B
|
|
140
133
|
|
|
141
134
|
|
|
142
135
|
### Operations on Numbers
|
|
143
136
|
|
|
144
137
|
|
|
145
|
-
|
|
138
|
+
class SupportsCmp(_t.Protocol):
|
|
139
|
+
# `__eq__` and `__ne__` belong to `object` and are always available
|
|
140
|
+
def __lt__(self, x: int | _t.Self) -> bool: ...
|
|
141
|
+
def __le__(self, x: int | _t.Self) -> bool: ...
|
|
142
|
+
def __gt__(self, x: int | _t.Self) -> bool: ...
|
|
143
|
+
def __ge__(self, x: int | _t.Self) -> bool: ...
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class SupportsCounting(SupportsCmp, _t.Protocol):
|
|
147
|
+
def __add__(self, x: int | _t.Self) -> _t.Self: ...
|
|
148
|
+
def __sub__(self, x: int | _t.Self) -> _t.Self: ...
|
|
149
|
+
|
|
150
|
+
# because this can be implemented using the above
|
|
151
|
+
# note that from the result, only `SupportsCmp` is required
|
|
152
|
+
def __abs__(self) -> SupportsCmp: ...
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def clamp[A: (int, float, Decimal)](minv: A, maxv: A, v: A) -> A:
|
|
146
156
|
return min(maxv, max(minv, v))
|
|
147
157
|
|
|
148
158
|
|
|
149
|
-
def is_infinite(x:
|
|
150
|
-
return
|
|
159
|
+
def is_infinite(x: _t.Any) -> bool:
|
|
160
|
+
return isinstance(x, float) and _math.isinf(x) or hasattr(x, "is_infinite") and x.is_infinite()
|
|
151
161
|
|
|
152
162
|
|
|
153
|
-
def
|
|
154
|
-
"""`
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
163
|
+
def is_closer[A: SupportsCounting](ideal: A, a: A, b: A) -> bool:
|
|
164
|
+
"""Check whether `a` is closer to `ideal` than `b` is. That is, essentially, `abs(ideal - a) <
|
|
165
|
+
abs(ideal - b)`, but with infinite `ideal` cases handled properly.
|
|
166
|
+
|
|
167
|
+
Note that the above implies that this function always returns `False` when `a == b`.
|
|
168
|
+
"""
|
|
169
|
+
if is_infinite(ideal):
|
|
170
|
+
if ideal < 0:
|
|
171
|
+
return a < b
|
|
172
|
+
return b < a
|
|
173
|
+
return abs(ideal - a) < abs(ideal - b)
|
|
160
174
|
|
|
161
175
|
|
|
162
|
-
|
|
163
|
-
"""`x - y` that always works."""
|
|
164
|
-
if isinstance(x, float) and isinstance(y, Decimal):
|
|
165
|
-
return Decimal(x) - y
|
|
166
|
-
if isinstance(x, Decimal) and isinstance(y, float):
|
|
167
|
-
return x - Decimal(y)
|
|
168
|
-
return x - y # type: ignore
|
|
176
|
+
### Exception handling hooks
|
|
169
177
|
|
|
170
178
|
|
|
171
|
-
def
|
|
172
|
-
"""
|
|
173
|
-
|
|
174
|
-
return Decimal(x) * y
|
|
175
|
-
if isinstance(x, Decimal) and isinstance(y, float):
|
|
176
|
-
return x * Decimal(y)
|
|
177
|
-
return x * y # type: ignore
|
|
179
|
+
def raise_one(exc: Exception, /, *_args: _t.Any, **_kwargs: _t.Any) -> _t.NoReturn:
|
|
180
|
+
"""Raise the given `Exception`."""
|
|
181
|
+
raise exc
|
|
178
182
|
|
|
179
183
|
|
|
180
|
-
def
|
|
181
|
-
"""
|
|
182
|
-
|
|
183
|
-
return Decimal(x) / y
|
|
184
|
-
if isinstance(x, Decimal) and isinstance(y, float):
|
|
185
|
-
return x / Decimal(y)
|
|
186
|
-
return x / y # type: ignore
|
|
184
|
+
def raise_all(excs: list[Exception], /, *_args: _t.Any, **_kwargs: _t.Any) -> _t.NoReturn:
|
|
185
|
+
"""Raise give exceptions as a trivial `ExceptionGroup`."""
|
|
186
|
+
raise ExceptionGroup("multiple failures", excs)
|
|
187
187
|
|
|
188
188
|
|
|
189
189
|
### Simple functions
|
|
190
190
|
|
|
191
191
|
|
|
192
|
-
def
|
|
193
|
-
"""
|
|
194
|
-
return x
|
|
192
|
+
def noop(*_args: _t.Any, **_kwargs: _t.Any) -> None:
|
|
193
|
+
"""A noop, with a bunch of ignored arguments."""
|
|
195
194
|
|
|
196
195
|
|
|
197
|
-
def
|
|
196
|
+
def identity[T](x: T, /, *_args: _t.Any, **_kwargs: _t.Any) -> T:
|
|
198
197
|
"""Identity function, with a bunch of ignored arguments."""
|
|
199
198
|
return x
|
|
200
199
|
|
|
201
200
|
|
|
202
|
-
def const(x:
|
|
203
|
-
"""Return a function that always returns `a
|
|
204
|
-
return lambda y: x
|
|
201
|
+
def const[A, B, **PS](x: A, /) -> _t.Callable[_t.Concatenate[B, PS], A]:
|
|
202
|
+
"""Return a function that always returns `a`, with a bunch of ignored arguments."""
|
|
205
203
|
|
|
206
|
-
|
|
207
|
-
def const_(x: AType, /) -> _t.Callable[_t.Concatenate[BType, BParamSpec], AType]:
|
|
208
|
-
"""Return a function that always returns `a`, and has a bunch of ignored arguments."""
|
|
209
|
-
|
|
210
|
-
def sub(_y: BType, /, *_args: BParamSpec.args, **_kwargs: BParamSpec.kwargs) -> AType:
|
|
204
|
+
def sub(_y: B, /, *_args: PS.args, **_kwargs: PS.kwargs) -> A:
|
|
211
205
|
return x
|
|
212
206
|
|
|
213
207
|
return sub
|
|
214
208
|
|
|
215
209
|
|
|
216
|
-
def fst(x: tuple[
|
|
210
|
+
def fst[A, B](x: tuple[A, B]) -> A:
|
|
217
211
|
"""Get the first element of a given 2-elemnt tuple."""
|
|
218
212
|
return x[0]
|
|
219
213
|
|
|
220
214
|
|
|
221
|
-
def snd(x: tuple[
|
|
215
|
+
def snd[A, B](x: tuple[A, B]) -> B:
|
|
222
216
|
"""Get the second element of a given 2-elemnt tuple."""
|
|
223
217
|
return x[1]
|
|
224
218
|
|
|
225
219
|
|
|
226
|
-
def first_def(*args:
|
|
220
|
+
def first_def[A](*args: A | None) -> A:
|
|
227
221
|
"""Return first argument that is not `None`."""
|
|
228
222
|
for e in args:
|
|
229
223
|
if e is not None:
|
|
@@ -231,7 +225,7 @@ def first_def(*args: AType | None) -> AType:
|
|
|
231
225
|
raise IndexError("no matching elements")
|
|
232
226
|
|
|
233
227
|
|
|
234
|
-
def first_ok(*args:
|
|
228
|
+
def first_ok[A](*args: A) -> A:
|
|
235
229
|
"""Return first `True`-like argument."""
|
|
236
230
|
for e in args:
|
|
237
231
|
if e:
|
|
@@ -239,19 +233,17 @@ def first_ok(*args: AType) -> AType:
|
|
|
239
233
|
raise IndexError("no matching elements")
|
|
240
234
|
|
|
241
235
|
|
|
242
|
-
def optional(b:
|
|
236
|
+
def optional[A, B](b: B, f: _t.Callable[[A], B], x: A | None) -> B:
|
|
243
237
|
"""`f(x) if x is not None else b`."""
|
|
244
238
|
return f(x) if x is not None else b
|
|
245
239
|
|
|
246
240
|
|
|
247
|
-
def maybe(b:
|
|
241
|
+
def maybe[A, B](b: B, f: _t.Callable[[A], B], x: Maybe[A]) -> B:
|
|
248
242
|
"""`f(x.just) if x is Just else b`."""
|
|
249
243
|
return f(x.just) if isinstance(x, Just) else b
|
|
250
244
|
|
|
251
245
|
|
|
252
|
-
def either(
|
|
253
|
-
fa: _t.Callable[[AType], CType], fb: _t.Callable[[BType], CType], x: Either[AType, BType]
|
|
254
|
-
) -> CType:
|
|
246
|
+
def either[A, B, C](fa: _t.Callable[[A], C], fb: _t.Callable[[B], C], x: Either[A, B]) -> C:
|
|
255
247
|
"""`fa(x.left) if x is Left`, `fb(x.right) if x is Right`, `AssertionError` otherwise."""
|
|
256
248
|
if isinstance(x, Left):
|
|
257
249
|
return fa(x.left)
|
|
@@ -263,22 +255,22 @@ def either(
|
|
|
263
255
|
### Injectors into `list`
|
|
264
256
|
|
|
265
257
|
|
|
266
|
-
def singleton(x:
|
|
258
|
+
def singleton[A](x: A) -> list[A]:
|
|
267
259
|
"""`[x]`."""
|
|
268
260
|
return [x]
|
|
269
261
|
|
|
270
262
|
|
|
271
|
-
def optlist(p: bool, x:
|
|
263
|
+
def optlist[A](p: bool, x: A) -> list[A]:
|
|
272
264
|
"""`[x] if p else []`."""
|
|
273
265
|
return [x] if p else []
|
|
274
266
|
|
|
275
267
|
|
|
276
|
-
def optslist(p: bool, xs: list[
|
|
268
|
+
def optslist[A](p: bool, xs: list[A]) -> list[A]:
|
|
277
269
|
"""`xs if p else []`."""
|
|
278
270
|
return xs if p else []
|
|
279
271
|
|
|
280
272
|
|
|
281
|
-
def optional2list(x:
|
|
273
|
+
def optional2list[A](x: A | None) -> list[A]:
|
|
282
274
|
"""`[x] if x is not None else []`."""
|
|
283
275
|
return [x] if x is not None else []
|
|
284
276
|
|
|
@@ -286,63 +278,81 @@ def optional2list(x: AType | None) -> list[AType]:
|
|
|
286
278
|
### Functors, i.e. applying functions to elements contained in structures
|
|
287
279
|
|
|
288
280
|
|
|
289
|
-
def map_optional(f: _t.Callable[[
|
|
281
|
+
def map_optional[A, B](f: _t.Callable[[A], B], x: A | None) -> B | None:
|
|
290
282
|
"""`f(x) if x is not None else x`."""
|
|
291
283
|
return f(x) if x is not None else x
|
|
292
284
|
|
|
293
285
|
|
|
294
|
-
def map_optional2list(f: _t.Callable[[
|
|
286
|
+
def map_optional2list[A, B](f: _t.Callable[[A], list[B]], x: A | None) -> list[B]:
|
|
295
287
|
"""`f(x) if x is not None else []`."""
|
|
296
288
|
return f(x) if x is not None else []
|
|
297
289
|
|
|
298
290
|
|
|
299
|
-
def map_maybe(f: _t.Callable[[
|
|
291
|
+
def map_maybe[A, B](f: _t.Callable[[A], B], x: Maybe[A]) -> Maybe[B]:
|
|
300
292
|
"""`Just(f(x.just)) if x is Just else x`."""
|
|
301
|
-
return Just(f(x.just)) if isinstance(x, Just) else _t.cast(Maybe[
|
|
293
|
+
return Just(f(x.just)) if isinstance(x, Just) else _t.cast(Maybe[B], x)
|
|
302
294
|
|
|
303
295
|
|
|
304
|
-
def map_maybe2list(f: _t.Callable[[
|
|
296
|
+
def map_maybe2list[A, B](f: _t.Callable[[A], list[B]], x: Maybe[A]) -> list[B]:
|
|
305
297
|
"""`f(x.just) if x is Just else []`."""
|
|
306
298
|
return f(x.just) if isinstance(x, Just) else []
|
|
307
299
|
|
|
308
300
|
|
|
309
|
-
def map_left(f: _t.Callable[[
|
|
301
|
+
def map_left[A, B, C](f: _t.Callable[[A], C], x: Either[A, B]) -> Either[C, B]:
|
|
310
302
|
"""`Right(f(x.left)) if x is Left else x`."""
|
|
311
|
-
return Left(f(x.left)) if isinstance(x, Left) else _t.cast(Either[
|
|
303
|
+
return Left(f(x.left)) if isinstance(x, Left) else _t.cast(Either[C, B], x)
|
|
312
304
|
|
|
313
305
|
|
|
314
|
-
def map_right(f: _t.Callable[[
|
|
306
|
+
def map_right[A, B, C](f: _t.Callable[[B], C], x: Either[A, B]) -> Either[A, C]:
|
|
315
307
|
"""`Right(f(x.right)) if x is Right else x`."""
|
|
316
|
-
return Right(f(x.right)) if isinstance(x, Right) else _t.cast(Either[
|
|
308
|
+
return Right(f(x.right)) if isinstance(x, Right) else _t.cast(Either[A, C], x)
|
|
317
309
|
|
|
318
310
|
|
|
319
311
|
### Function composition
|
|
320
312
|
|
|
321
313
|
|
|
322
|
-
def compose(
|
|
323
|
-
f: _t.Callable[[
|
|
324
|
-
) -> _t.Callable[[
|
|
325
|
-
|
|
314
|
+
def compose[A, B, C, **PS](
|
|
315
|
+
f: _t.Callable[[B], C], g: _t.Callable[_t.Concatenate[A, PS], B]
|
|
316
|
+
) -> _t.Callable[_t.Concatenate[A, PS], C]:
|
|
317
|
+
"""Compose two functions `f` and `g` while passing all the additional `PS` arguments to `g`."""
|
|
326
318
|
|
|
319
|
+
def sub(x: A, /, *args: PS.args, **kwargs: PS.kwargs) -> C:
|
|
320
|
+
return f(g(x, *args, **kwargs))
|
|
327
321
|
|
|
328
|
-
|
|
329
|
-
pipe: list[_t.Callable[_t.Concatenate[AType, AParamSpec], AType]], /
|
|
330
|
-
) -> _t.Callable[_t.Concatenate[AType, AParamSpec], AType]:
|
|
331
|
-
"""Compose a list of functions, similarly to how a shell pipe ("|")
|
|
332
|
-
exression does it. I.e. compose them in reverse order.
|
|
322
|
+
return sub
|
|
333
323
|
|
|
334
|
-
The mutating value gets passed as the first argument, all other
|
|
335
|
-
arguments get passed unchanged.
|
|
336
324
|
|
|
337
|
-
|
|
325
|
+
def compose_wide[A, B, C, **PS](
|
|
326
|
+
f: _t.Callable[_t.Concatenate[B, PS], C], g: _t.Callable[_t.Concatenate[A, PS], B]
|
|
327
|
+
) -> _t.Callable[_t.Concatenate[A, PS], C]:
|
|
328
|
+
"""Compose two functions `f` and `g` while passing all the additional `PS` arguments to both `f`
|
|
329
|
+
and `g`.
|
|
330
|
+
"""
|
|
331
|
+
|
|
332
|
+
def sub(x: A, /, *args: PS.args, **kwargs: PS.kwargs) -> C:
|
|
333
|
+
return f(g(x, *args, **kwargs), *args, **kwargs)
|
|
334
|
+
|
|
335
|
+
return sub
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def compose_pipe[A, **PS](
|
|
339
|
+
pipe: list[_t.Callable[_t.Concatenate[A, PS], A]], /
|
|
340
|
+
) -> _t.Callable[_t.Concatenate[A, PS], A]:
|
|
341
|
+
"""Compose a list of functions, similarly to how a shell pipe ("|") exression does it. I.e.
|
|
342
|
+
compose them in reverse order.
|
|
343
|
+
|
|
344
|
+
The mutating value gets passed as the first argument, all other arguments get passed unchanged
|
|
345
|
+
like with `compose_wide`.
|
|
346
|
+
|
|
347
|
+
`compose_pipe([])` is `identity`.
|
|
338
348
|
`compose_pipe([x])` is `x`.
|
|
339
349
|
"""
|
|
340
350
|
if len(pipe) == 0:
|
|
341
|
-
return
|
|
351
|
+
return identity
|
|
342
352
|
if len(pipe) == 1:
|
|
343
353
|
return pipe[0]
|
|
344
354
|
|
|
345
|
-
def sub(x:
|
|
355
|
+
def sub(x: A, /, *args: PS.args, **kwargs: PS.kwargs) -> A:
|
|
346
356
|
for func in pipe:
|
|
347
357
|
x = func(x, *args, **kwargs)
|
|
348
358
|
return x
|
|
@@ -374,17 +384,14 @@ def get_traceback(exc: BaseException, /, lines: int = 128) -> str:
|
|
|
374
384
|
return fobj.getvalue()
|
|
375
385
|
|
|
376
386
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
def flat_exceptions(
|
|
381
|
-
es: BaseExceptionGroup[_BaseExeceptionType], /
|
|
382
|
-
) -> _t.Iterator[_BaseExeceptionType]:
|
|
387
|
+
def flat_exceptions[ExceptionType: BaseException](
|
|
388
|
+
es: BaseExceptionGroup[ExceptionType], /
|
|
389
|
+
) -> _t.Iterator[ExceptionType]:
|
|
383
390
|
"""For simplifying some `try ... except*` expressions."""
|
|
384
391
|
|
|
385
392
|
def sub(
|
|
386
|
-
excs:
|
|
387
|
-
) -> _t.Iterator[
|
|
393
|
+
excs: ExceptionType | BaseExceptionGroup[ExceptionType],
|
|
394
|
+
) -> _t.Iterator[ExceptionType]:
|
|
388
395
|
if isinstance(excs, BaseExceptionGroup):
|
|
389
396
|
for e in excs.exceptions:
|
|
390
397
|
yield from sub(e)
|