kisstdlib 0.0.12__tar.gz → 0.0.13__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.13}/PKG-INFO +4 -3
- kisstdlib-0.0.13/VERSION +1 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/kisstdlib/__init__.py +5 -4
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/kisstdlib/argparse_ext.py +4 -7
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/kisstdlib/base.py +94 -106
- kisstdlib-0.0.12/kisstdlib/sorted.py → kisstdlib-0.0.13/kisstdlib/collections_ext.py +340 -268
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/kisstdlib/failure.py +37 -14
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/kisstdlib/fs.py +192 -132
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/kisstdlib/io/base.py +20 -8
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/kisstdlib/io/stdio.py +11 -5
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/kisstdlib/itertools_ext.py +38 -44
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/kisstdlib/logging_ext.py +7 -3
- kisstdlib-0.0.13/kisstdlib/parsing.py +641 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/kisstdlib/pyrepr.py +1 -2
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/kisstdlib/sqlite3_ext.py +96 -16
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/kisstdlib/string_ext.py +58 -37
- kisstdlib-0.0.13/kisstdlib/time.py +1252 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/kisstdlib.egg-info/PKG-INFO +4 -3
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/kisstdlib.egg-info/SOURCES.txt +3 -2
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/pyproject.toml +5 -3
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/test/test_basics.py +21 -4
- kisstdlib-0.0.13/test/test_collections.py +200 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/test/test_fs.py +9 -9
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/test/test_parsing.py +78 -0
- kisstdlib-0.0.13/test/test_time.py +1000 -0
- kisstdlib-0.0.12/kisstdlib/parsing.py +0 -316
- kisstdlib-0.0.12/kisstdlib/time.py +0 -299
- 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.13}/LICENSE.txt +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/README.md +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/kisstdlib/compression.py +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/kisstdlib/getpass_ext.py +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/kisstdlib/io/__init__.py +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/kisstdlib/io/adapter.py +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/kisstdlib/io/encoder.py +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/kisstdlib/io/wrapper.py +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/kisstdlib/py.typed +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/kisstdlib/signal_ext.py +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/kisstdlib/tariter.py +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/kisstdlib.egg-info/dependency_links.txt +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/kisstdlib.egg-info/entry_points.txt +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/kisstdlib.egg-info/requires.txt +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/kisstdlib.egg-info/top_level.txt +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/kisstdlib_bin/describe_forest.py +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/setup.cfg +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/setup.py +0 -0
- {kisstdlib-0.0.12 → kisstdlib-0.0.13}/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.13
|
|
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.13/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.0.13
|
|
@@ -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,119 @@ 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
|
-
|
|
146
|
-
|
|
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: ...
|
|
147
144
|
|
|
148
145
|
|
|
149
|
-
|
|
150
|
-
|
|
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: ...
|
|
151
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: ...
|
|
152
153
|
|
|
153
|
-
def add_(x: Number, y: Number) -> Number:
|
|
154
|
-
"""`x + y` that always works."""
|
|
155
|
-
if isinstance(x, float) and isinstance(y, Decimal):
|
|
156
|
-
return Decimal(x) + y
|
|
157
|
-
if isinstance(x, Decimal) and isinstance(y, float):
|
|
158
|
-
return x + Decimal(y)
|
|
159
|
-
return x + y # type: ignore
|
|
160
154
|
|
|
155
|
+
def clamp[A: (int, float, Decimal)](minv: A, maxv: A, v: A) -> A:
|
|
156
|
+
return min(maxv, max(minv, v))
|
|
161
157
|
|
|
162
|
-
def sub_(x: Number, y: Number) -> Number:
|
|
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
|
|
169
158
|
|
|
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()
|
|
170
161
|
|
|
171
|
-
def mul_(x: Number, y: Number) -> Number:
|
|
172
|
-
"""`x * y` that always works."""
|
|
173
|
-
if isinstance(x, float) and isinstance(y, Decimal):
|
|
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
|
|
178
162
|
|
|
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.
|
|
179
166
|
|
|
180
|
-
|
|
181
|
-
"""
|
|
182
|
-
if
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
return
|
|
186
|
-
return
|
|
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)
|
|
187
174
|
|
|
188
175
|
|
|
189
176
|
### Simple functions
|
|
190
177
|
|
|
191
178
|
|
|
192
|
-
def
|
|
179
|
+
def noop() -> None:
|
|
180
|
+
"""A noop."""
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def noop_(*_args: _t.Any, **_kwargs: _t.Any) -> None:
|
|
184
|
+
"""A noop, with a bunch of ignored arguments."""
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def identity[T](x: T, /) -> T:
|
|
193
188
|
"""Identity function."""
|
|
194
189
|
return x
|
|
195
190
|
|
|
196
191
|
|
|
197
|
-
def identity_(x:
|
|
192
|
+
def identity_[T](x: T, /, *_args: _t.Any, **_kwargs: _t.Any) -> T:
|
|
198
193
|
"""Identity function, with a bunch of ignored arguments."""
|
|
199
194
|
return x
|
|
200
195
|
|
|
201
196
|
|
|
202
|
-
def const(x:
|
|
197
|
+
def const[A, B](x: A, /) -> _t.Callable[[B], A]:
|
|
203
198
|
"""Return a function that always returns `a`."""
|
|
204
199
|
return lambda y: x
|
|
205
200
|
|
|
206
201
|
|
|
207
|
-
def const_(x:
|
|
202
|
+
def const_[A, B, **PS](x: A, /) -> _t.Callable[_t.Concatenate[B, PS], A]:
|
|
208
203
|
"""Return a function that always returns `a`, and has a bunch of ignored arguments."""
|
|
209
204
|
|
|
210
|
-
def sub(_y:
|
|
205
|
+
def sub(_y: B, /, *_args: PS.args, **_kwargs: PS.kwargs) -> A:
|
|
211
206
|
return x
|
|
212
207
|
|
|
213
208
|
return sub
|
|
214
209
|
|
|
215
210
|
|
|
216
|
-
def fst(x: tuple[
|
|
211
|
+
def fst[A, B](x: tuple[A, B]) -> A:
|
|
217
212
|
"""Get the first element of a given 2-elemnt tuple."""
|
|
218
213
|
return x[0]
|
|
219
214
|
|
|
220
215
|
|
|
221
|
-
def snd(x: tuple[
|
|
216
|
+
def snd[A, B](x: tuple[A, B]) -> B:
|
|
222
217
|
"""Get the second element of a given 2-elemnt tuple."""
|
|
223
218
|
return x[1]
|
|
224
219
|
|
|
225
220
|
|
|
226
|
-
def first_def(*args:
|
|
221
|
+
def first_def[A](*args: A | None) -> A:
|
|
227
222
|
"""Return first argument that is not `None`."""
|
|
228
223
|
for e in args:
|
|
229
224
|
if e is not None:
|
|
@@ -231,7 +226,7 @@ def first_def(*args: AType | None) -> AType:
|
|
|
231
226
|
raise IndexError("no matching elements")
|
|
232
227
|
|
|
233
228
|
|
|
234
|
-
def first_ok(*args:
|
|
229
|
+
def first_ok[A](*args: A) -> A:
|
|
235
230
|
"""Return first `True`-like argument."""
|
|
236
231
|
for e in args:
|
|
237
232
|
if e:
|
|
@@ -239,19 +234,17 @@ def first_ok(*args: AType) -> AType:
|
|
|
239
234
|
raise IndexError("no matching elements")
|
|
240
235
|
|
|
241
236
|
|
|
242
|
-
def optional(b:
|
|
237
|
+
def optional[A, B](b: B, f: _t.Callable[[A], B], x: A | None) -> B:
|
|
243
238
|
"""`f(x) if x is not None else b`."""
|
|
244
239
|
return f(x) if x is not None else b
|
|
245
240
|
|
|
246
241
|
|
|
247
|
-
def maybe(b:
|
|
242
|
+
def maybe[A, B](b: B, f: _t.Callable[[A], B], x: Maybe[A]) -> B:
|
|
248
243
|
"""`f(x.just) if x is Just else b`."""
|
|
249
244
|
return f(x.just) if isinstance(x, Just) else b
|
|
250
245
|
|
|
251
246
|
|
|
252
|
-
def either(
|
|
253
|
-
fa: _t.Callable[[AType], CType], fb: _t.Callable[[BType], CType], x: Either[AType, BType]
|
|
254
|
-
) -> CType:
|
|
247
|
+
def either[A, B, C](fa: _t.Callable[[A], C], fb: _t.Callable[[B], C], x: Either[A, B]) -> C:
|
|
255
248
|
"""`fa(x.left) if x is Left`, `fb(x.right) if x is Right`, `AssertionError` otherwise."""
|
|
256
249
|
if isinstance(x, Left):
|
|
257
250
|
return fa(x.left)
|
|
@@ -263,22 +256,22 @@ def either(
|
|
|
263
256
|
### Injectors into `list`
|
|
264
257
|
|
|
265
258
|
|
|
266
|
-
def singleton(x:
|
|
259
|
+
def singleton[A](x: A) -> list[A]:
|
|
267
260
|
"""`[x]`."""
|
|
268
261
|
return [x]
|
|
269
262
|
|
|
270
263
|
|
|
271
|
-
def optlist(p: bool, x:
|
|
264
|
+
def optlist[A](p: bool, x: A) -> list[A]:
|
|
272
265
|
"""`[x] if p else []`."""
|
|
273
266
|
return [x] if p else []
|
|
274
267
|
|
|
275
268
|
|
|
276
|
-
def optslist(p: bool, xs: list[
|
|
269
|
+
def optslist[A](p: bool, xs: list[A]) -> list[A]:
|
|
277
270
|
"""`xs if p else []`."""
|
|
278
271
|
return xs if p else []
|
|
279
272
|
|
|
280
273
|
|
|
281
|
-
def optional2list(x:
|
|
274
|
+
def optional2list[A](x: A | None) -> list[A]:
|
|
282
275
|
"""`[x] if x is not None else []`."""
|
|
283
276
|
return [x] if x is not None else []
|
|
284
277
|
|
|
@@ -286,48 +279,46 @@ def optional2list(x: AType | None) -> list[AType]:
|
|
|
286
279
|
### Functors, i.e. applying functions to elements contained in structures
|
|
287
280
|
|
|
288
281
|
|
|
289
|
-
def map_optional(f: _t.Callable[[
|
|
282
|
+
def map_optional[A, B](f: _t.Callable[[A], B], x: A | None) -> B | None:
|
|
290
283
|
"""`f(x) if x is not None else x`."""
|
|
291
284
|
return f(x) if x is not None else x
|
|
292
285
|
|
|
293
286
|
|
|
294
|
-
def map_optional2list(f: _t.Callable[[
|
|
287
|
+
def map_optional2list[A, B](f: _t.Callable[[A], list[B]], x: A | None) -> list[B]:
|
|
295
288
|
"""`f(x) if x is not None else []`."""
|
|
296
289
|
return f(x) if x is not None else []
|
|
297
290
|
|
|
298
291
|
|
|
299
|
-
def map_maybe(f: _t.Callable[[
|
|
292
|
+
def map_maybe[A, B](f: _t.Callable[[A], B], x: Maybe[A]) -> Maybe[B]:
|
|
300
293
|
"""`Just(f(x.just)) if x is Just else x`."""
|
|
301
|
-
return Just(f(x.just)) if isinstance(x, Just) else _t.cast(Maybe[
|
|
294
|
+
return Just(f(x.just)) if isinstance(x, Just) else _t.cast(Maybe[B], x)
|
|
302
295
|
|
|
303
296
|
|
|
304
|
-
def map_maybe2list(f: _t.Callable[[
|
|
297
|
+
def map_maybe2list[A, B](f: _t.Callable[[A], list[B]], x: Maybe[A]) -> list[B]:
|
|
305
298
|
"""`f(x.just) if x is Just else []`."""
|
|
306
299
|
return f(x.just) if isinstance(x, Just) else []
|
|
307
300
|
|
|
308
301
|
|
|
309
|
-
def map_left(f: _t.Callable[[
|
|
302
|
+
def map_left[A, B, C](f: _t.Callable[[A], C], x: Either[A, B]) -> Either[C, B]:
|
|
310
303
|
"""`Right(f(x.left)) if x is Left else x`."""
|
|
311
|
-
return Left(f(x.left)) if isinstance(x, Left) else _t.cast(Either[
|
|
304
|
+
return Left(f(x.left)) if isinstance(x, Left) else _t.cast(Either[C, B], x)
|
|
312
305
|
|
|
313
306
|
|
|
314
|
-
def map_right(f: _t.Callable[[
|
|
307
|
+
def map_right[A, B, C](f: _t.Callable[[B], C], x: Either[A, B]) -> Either[A, C]:
|
|
315
308
|
"""`Right(f(x.right)) if x is Right else x`."""
|
|
316
|
-
return Right(f(x.right)) if isinstance(x, Right) else _t.cast(Either[
|
|
309
|
+
return Right(f(x.right)) if isinstance(x, Right) else _t.cast(Either[A, C], x)
|
|
317
310
|
|
|
318
311
|
|
|
319
312
|
### Function composition
|
|
320
313
|
|
|
321
314
|
|
|
322
|
-
def compose(
|
|
323
|
-
f: _t.Callable[[BType], CType], g: _t.Callable[[AType], BType]
|
|
324
|
-
) -> _t.Callable[[AType], CType]:
|
|
315
|
+
def compose[A, B, C](f: _t.Callable[[B], C], g: _t.Callable[[A], B]) -> _t.Callable[[A], C]:
|
|
325
316
|
return lambda x: f(g(x))
|
|
326
317
|
|
|
327
318
|
|
|
328
|
-
def compose_pipe(
|
|
329
|
-
pipe: list[_t.Callable[_t.Concatenate[
|
|
330
|
-
) -> _t.Callable[_t.Concatenate[
|
|
319
|
+
def compose_pipe[A, **PS](
|
|
320
|
+
pipe: list[_t.Callable[_t.Concatenate[A, PS], A]], /
|
|
321
|
+
) -> _t.Callable[_t.Concatenate[A, PS], A]:
|
|
331
322
|
"""Compose a list of functions, similarly to how a shell pipe ("|")
|
|
332
323
|
exression does it. I.e. compose them in reverse order.
|
|
333
324
|
|
|
@@ -342,7 +333,7 @@ def compose_pipe(
|
|
|
342
333
|
if len(pipe) == 1:
|
|
343
334
|
return pipe[0]
|
|
344
335
|
|
|
345
|
-
def sub(x:
|
|
336
|
+
def sub(x: A, /, *args: PS.args, **kwargs: PS.kwargs) -> A:
|
|
346
337
|
for func in pipe:
|
|
347
338
|
x = func(x, *args, **kwargs)
|
|
348
339
|
return x
|
|
@@ -374,17 +365,14 @@ def get_traceback(exc: BaseException, /, lines: int = 128) -> str:
|
|
|
374
365
|
return fobj.getvalue()
|
|
375
366
|
|
|
376
367
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
def flat_exceptions(
|
|
381
|
-
es: BaseExceptionGroup[_BaseExeceptionType], /
|
|
382
|
-
) -> _t.Iterator[_BaseExeceptionType]:
|
|
368
|
+
def flat_exceptions[ExceptionType: BaseException](
|
|
369
|
+
es: BaseExceptionGroup[ExceptionType], /
|
|
370
|
+
) -> _t.Iterator[ExceptionType]:
|
|
383
371
|
"""For simplifying some `try ... except*` expressions."""
|
|
384
372
|
|
|
385
373
|
def sub(
|
|
386
|
-
excs:
|
|
387
|
-
) -> _t.Iterator[
|
|
374
|
+
excs: ExceptionType | BaseExceptionGroup[ExceptionType],
|
|
375
|
+
) -> _t.Iterator[ExceptionType]:
|
|
388
376
|
if isinstance(excs, BaseExceptionGroup):
|
|
389
377
|
for e in excs.exceptions:
|
|
390
378
|
yield from sub(e)
|