pythonwrench 0.6.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pythonwrench/__init__.py +490 -0
- pythonwrench/__main__.py +7 -0
- pythonwrench/_core.py +192 -0
- pythonwrench/abc.py +30 -0
- pythonwrench/argparse/__init__.py +81 -0
- pythonwrench/argparse/dataclass_.py +284 -0
- pythonwrench/argparse/parsers.py +619 -0
- pythonwrench/cast.py +247 -0
- pythonwrench/checksum.py +427 -0
- pythonwrench/collections/__init__.py +104 -0
- pythonwrench/collections/collections.py +900 -0
- pythonwrench/collections/prop.py +104 -0
- pythonwrench/collections/reducers.py +330 -0
- pythonwrench/concurrent.py +73 -0
- pythonwrench/csv.py +12 -0
- pythonwrench/dataclasses.py +117 -0
- pythonwrench/datetime.py +17 -0
- pythonwrench/difflib.py +39 -0
- pythonwrench/disk_cache.py +615 -0
- pythonwrench/entrypoints/info.py +44 -0
- pythonwrench/entrypoints/safe_rmdir.py +98 -0
- pythonwrench/entrypoints/tree.py +113 -0
- pythonwrench/enum.py +55 -0
- pythonwrench/functools.py +234 -0
- pythonwrench/hashlib.py +95 -0
- pythonwrench/importlib.py +243 -0
- pythonwrench/inspect.py +69 -0
- pythonwrench/json.py +12 -0
- pythonwrench/jsonl.py +12 -0
- pythonwrench/logging.py +252 -0
- pythonwrench/math.py +107 -0
- pythonwrench/os.py +226 -0
- pythonwrench/pickle.py +12 -0
- pythonwrench/random.py +60 -0
- pythonwrench/re.py +139 -0
- pythonwrench/semver.py +406 -0
- pythonwrench/serialization/__init__.py +70 -0
- pythonwrench/serialization/_core.py +70 -0
- pythonwrench/serialization/csv.py +493 -0
- pythonwrench/serialization/json.py +178 -0
- pythonwrench/serialization/jsonl.py +215 -0
- pythonwrench/serialization/pickle.py +186 -0
- pythonwrench/time.py +34 -0
- pythonwrench/typing/__init__.py +125 -0
- pythonwrench/typing/checks.py +551 -0
- pythonwrench/typing/classes.py +251 -0
- pythonwrench/warnings.py +118 -0
- pythonwrench-0.6.4.dist-info/METADATA +242 -0
- pythonwrench-0.6.4.dist-info/RECORD +52 -0
- pythonwrench-0.6.4.dist-info/WHEEL +4 -0
- pythonwrench-0.6.4.dist-info/entry_points.txt +10 -0
- pythonwrench-0.6.4.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
from typing import (
|
|
5
|
+
Any,
|
|
6
|
+
Callable,
|
|
7
|
+
Iterable,
|
|
8
|
+
Literal,
|
|
9
|
+
Optional,
|
|
10
|
+
TypeVar,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from pythonwrench.functools import function_alias
|
|
14
|
+
|
|
15
|
+
K = TypeVar("K", covariant=True)
|
|
16
|
+
T = TypeVar("T", covariant=True)
|
|
17
|
+
U = TypeVar("U", covariant=True)
|
|
18
|
+
V = TypeVar("V", covariant=True)
|
|
19
|
+
W = TypeVar("W", covariant=True)
|
|
20
|
+
X = TypeVar("X", covariant=True)
|
|
21
|
+
Y = TypeVar("Y", covariant=True)
|
|
22
|
+
|
|
23
|
+
KeyMode = Literal["intersect", "same", "union"]
|
|
24
|
+
Order = Literal["left", "right"]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def all_eq(it: Iterable[T], eq_fn: Optional[Callable[[T, T], bool]] = None) -> bool:
|
|
28
|
+
"""Returns true if all elements in iterable are equal.
|
|
29
|
+
|
|
30
|
+
Note: This function returns True for iterable that contains 0 or 1 element.
|
|
31
|
+
"""
|
|
32
|
+
it = list(it)
|
|
33
|
+
try:
|
|
34
|
+
first = next(iter(it))
|
|
35
|
+
except StopIteration:
|
|
36
|
+
return True
|
|
37
|
+
|
|
38
|
+
if eq_fn is None:
|
|
39
|
+
return all(first == elt for elt in it)
|
|
40
|
+
else:
|
|
41
|
+
return all(eq_fn(first, elt) for elt in it)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def all_ne(
|
|
45
|
+
it: Iterable[T],
|
|
46
|
+
ne_fn: Optional[Callable[[T, T], bool]] = None,
|
|
47
|
+
use_set: bool = False,
|
|
48
|
+
) -> bool:
|
|
49
|
+
"""Returns true if all elements in iterable are differents.
|
|
50
|
+
|
|
51
|
+
Note: This function returns True for iterable that contains 0 or 1 element.
|
|
52
|
+
"""
|
|
53
|
+
if isinstance(it, (set, frozenset, dict)):
|
|
54
|
+
return True
|
|
55
|
+
if use_set and ne_fn is not None:
|
|
56
|
+
raise ValueError(f"Cannot use arguments {use_set=} with {ne_fn=}.")
|
|
57
|
+
|
|
58
|
+
it = list(it)
|
|
59
|
+
if use_set:
|
|
60
|
+
return len(it) == len(set(it))
|
|
61
|
+
elif ne_fn is None:
|
|
62
|
+
return all(
|
|
63
|
+
it[i] != it[j] for i in range(len(it)) for j in range(i + 1, len(it))
|
|
64
|
+
)
|
|
65
|
+
else:
|
|
66
|
+
return all(
|
|
67
|
+
ne_fn(it[i], it[j]) for i in range(len(it)) for j in range(i + 1, len(it))
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@function_alias(all_eq)
|
|
72
|
+
def is_full(*args, **kwargs):
|
|
73
|
+
"""Return whether full."""
|
|
74
|
+
...
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def is_sorted(
|
|
78
|
+
x: Iterable[Any],
|
|
79
|
+
*,
|
|
80
|
+
reverse: bool = False,
|
|
81
|
+
strict: bool = False,
|
|
82
|
+
) -> bool:
|
|
83
|
+
"""Return whether sorted."""
|
|
84
|
+
it = iter(x)
|
|
85
|
+
try:
|
|
86
|
+
prev = next(it)
|
|
87
|
+
except StopIteration:
|
|
88
|
+
return True
|
|
89
|
+
|
|
90
|
+
for xi in it:
|
|
91
|
+
if not reverse and prev > xi:
|
|
92
|
+
return False
|
|
93
|
+
if reverse and prev < xi:
|
|
94
|
+
return False
|
|
95
|
+
if strict and prev == xi:
|
|
96
|
+
return False
|
|
97
|
+
prev = xi
|
|
98
|
+
return True
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@function_alias(all_ne)
|
|
102
|
+
def is_unique(*args, **kwargs):
|
|
103
|
+
"""Return whether unique."""
|
|
104
|
+
...
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
import operator
|
|
5
|
+
from typing import (
|
|
6
|
+
Any,
|
|
7
|
+
Callable,
|
|
8
|
+
Iterable,
|
|
9
|
+
Iterator,
|
|
10
|
+
Optional,
|
|
11
|
+
Tuple,
|
|
12
|
+
Type,
|
|
13
|
+
TypeVar,
|
|
14
|
+
overload,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
from pythonwrench.functools import function_alias
|
|
18
|
+
from pythonwrench.typing.checks import isinstance_generic
|
|
19
|
+
from pythonwrench.typing.classes import (
|
|
20
|
+
SupportsAdd,
|
|
21
|
+
SupportsAnd,
|
|
22
|
+
SupportsMatmul,
|
|
23
|
+
SupportsMul,
|
|
24
|
+
SupportsOr,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
T = TypeVar("T")
|
|
28
|
+
T_SupportsAdd = TypeVar("T_SupportsAdd", bound=SupportsAdd)
|
|
29
|
+
T_SupportsAnd = TypeVar("T_SupportsAnd", bound=SupportsAnd)
|
|
30
|
+
T_SupportsMul = TypeVar("T_SupportsMul", bound=SupportsMul)
|
|
31
|
+
T_SupportsOr = TypeVar("T_SupportsOr", bound=SupportsOr)
|
|
32
|
+
T_SupportsMatmul = TypeVar("T_SupportsMatmul", bound=SupportsMatmul)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@overload
|
|
36
|
+
def reduce_add(
|
|
37
|
+
args: Iterable[T_SupportsAdd],
|
|
38
|
+
/,
|
|
39
|
+
*,
|
|
40
|
+
start: T_SupportsAdd,
|
|
41
|
+
) -> T_SupportsAdd:
|
|
42
|
+
"""Perform the reduce add operation."""
|
|
43
|
+
...
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@overload
|
|
47
|
+
def reduce_add(
|
|
48
|
+
*args: T_SupportsAdd,
|
|
49
|
+
start: T_SupportsAdd,
|
|
50
|
+
) -> T_SupportsAdd:
|
|
51
|
+
"""Perform the reduce add operation."""
|
|
52
|
+
...
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@overload
|
|
56
|
+
def reduce_add(
|
|
57
|
+
arg0: T_SupportsAdd,
|
|
58
|
+
/,
|
|
59
|
+
*args: T_SupportsAdd,
|
|
60
|
+
start: Optional[T_SupportsAdd] = None,
|
|
61
|
+
) -> T_SupportsAdd:
|
|
62
|
+
"""Perform the reduce add operation."""
|
|
63
|
+
...
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def reduce_add(*args, start=None):
|
|
67
|
+
"""Reduce elements using "add" operator (+)."""
|
|
68
|
+
return _reduce(*args, start=start, op_fn=operator.add, type_=SupportsAdd)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@overload
|
|
72
|
+
def reduce_and(
|
|
73
|
+
args: Iterable[T_SupportsAnd],
|
|
74
|
+
/,
|
|
75
|
+
*,
|
|
76
|
+
start: T_SupportsAnd,
|
|
77
|
+
) -> T_SupportsAnd:
|
|
78
|
+
"""Perform the reduce and operation."""
|
|
79
|
+
...
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@overload
|
|
83
|
+
def reduce_and(
|
|
84
|
+
*args: T_SupportsAnd,
|
|
85
|
+
start: T_SupportsAnd,
|
|
86
|
+
) -> T_SupportsAnd:
|
|
87
|
+
"""Perform the reduce and operation."""
|
|
88
|
+
...
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@overload
|
|
92
|
+
def reduce_and(
|
|
93
|
+
arg0: T_SupportsAnd,
|
|
94
|
+
/,
|
|
95
|
+
*args: T_SupportsAnd,
|
|
96
|
+
start: Optional[T_SupportsAnd] = None,
|
|
97
|
+
) -> T_SupportsAnd:
|
|
98
|
+
"""Perform the reduce and operation."""
|
|
99
|
+
...
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def reduce_and(*args, start=None):
|
|
103
|
+
"""Reduce elements using "and" operator (&)."""
|
|
104
|
+
return _reduce(*args, start=start, op_fn=operator.and_, type_=SupportsAnd)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@overload
|
|
108
|
+
def reduce_matmul(
|
|
109
|
+
args: Iterable[T_SupportsMatmul],
|
|
110
|
+
/,
|
|
111
|
+
*,
|
|
112
|
+
start: T_SupportsMatmul,
|
|
113
|
+
) -> T_SupportsMatmul:
|
|
114
|
+
"""Perform the reduce matmul operation."""
|
|
115
|
+
...
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@overload
|
|
119
|
+
def reduce_matmul(
|
|
120
|
+
*args: T_SupportsMatmul,
|
|
121
|
+
start: T_SupportsMatmul,
|
|
122
|
+
) -> T_SupportsMatmul:
|
|
123
|
+
"""Perform the reduce matmul operation."""
|
|
124
|
+
...
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@overload
|
|
128
|
+
def reduce_matmul(
|
|
129
|
+
arg0: T_SupportsMatmul,
|
|
130
|
+
/,
|
|
131
|
+
*args: T_SupportsMatmul,
|
|
132
|
+
start: Optional[T_SupportsMatmul] = None,
|
|
133
|
+
) -> T_SupportsMatmul:
|
|
134
|
+
"""Perform the reduce matmul operation."""
|
|
135
|
+
...
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def reduce_matmul(*args, start=None):
|
|
139
|
+
"""Reduce elements using "mul" operator (*)."""
|
|
140
|
+
return _reduce(*args, start=start, op_fn=operator.matmul, type_=SupportsMatmul)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@overload
|
|
144
|
+
def reduce_mul(
|
|
145
|
+
args: Iterable[T_SupportsMul],
|
|
146
|
+
/,
|
|
147
|
+
*,
|
|
148
|
+
start: T_SupportsMul,
|
|
149
|
+
) -> T_SupportsMul:
|
|
150
|
+
"""Perform the reduce mul operation."""
|
|
151
|
+
...
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@overload
|
|
155
|
+
def reduce_mul(
|
|
156
|
+
*args: T_SupportsMul,
|
|
157
|
+
start: T_SupportsMul,
|
|
158
|
+
) -> T_SupportsMul:
|
|
159
|
+
"""Perform the reduce mul operation."""
|
|
160
|
+
...
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@overload
|
|
164
|
+
def reduce_mul(
|
|
165
|
+
arg0: T_SupportsMul,
|
|
166
|
+
/,
|
|
167
|
+
*args: T_SupportsMul,
|
|
168
|
+
start: Optional[T_SupportsMul] = None,
|
|
169
|
+
) -> T_SupportsMul:
|
|
170
|
+
"""Perform the reduce mul operation."""
|
|
171
|
+
...
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def reduce_mul(*args, start=None):
|
|
175
|
+
"""Reduce elements using "mul" operator (*)."""
|
|
176
|
+
return _reduce(*args, start=start, op_fn=operator.mul, type_=SupportsMul)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@overload
|
|
180
|
+
def reduce_or(
|
|
181
|
+
args: Iterable[T_SupportsOr],
|
|
182
|
+
/,
|
|
183
|
+
*,
|
|
184
|
+
start: T_SupportsOr,
|
|
185
|
+
) -> T_SupportsOr:
|
|
186
|
+
"""Perform the reduce or operation."""
|
|
187
|
+
...
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@overload
|
|
191
|
+
def reduce_or(
|
|
192
|
+
*args: T_SupportsOr,
|
|
193
|
+
start: T_SupportsOr,
|
|
194
|
+
) -> T_SupportsOr:
|
|
195
|
+
"""Perform the reduce or operation."""
|
|
196
|
+
...
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@overload
|
|
200
|
+
def reduce_or(
|
|
201
|
+
arg0: T_SupportsOr,
|
|
202
|
+
/,
|
|
203
|
+
*args: T_SupportsOr,
|
|
204
|
+
start: Optional[T_SupportsOr] = None,
|
|
205
|
+
) -> T_SupportsOr:
|
|
206
|
+
"""Perform the reduce or operation."""
|
|
207
|
+
...
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def reduce_or(*args, start=None):
|
|
211
|
+
"""Reduce elements using "or" operator (|)."""
|
|
212
|
+
return _reduce(*args, start=start, op_fn=operator.or_, type_=SupportsOr)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _reduce(
|
|
216
|
+
*args,
|
|
217
|
+
start: Optional[T] = None,
|
|
218
|
+
op_fn: Callable[[T, T], T],
|
|
219
|
+
type_: Type[T],
|
|
220
|
+
) -> T:
|
|
221
|
+
"""Perform the reduce operation."""
|
|
222
|
+
if isinstance_generic(args, Tuple[Iterable[type_]]):
|
|
223
|
+
it_or_args = args[0]
|
|
224
|
+
elif isinstance_generic(args, Tuple[type_, ...]):
|
|
225
|
+
it_or_args = args
|
|
226
|
+
else:
|
|
227
|
+
msg = f"Invalid positional arguments {args}. (expected {Tuple[type_, ...]} or {Tuple[Iterable[type_]]})"
|
|
228
|
+
raise TypeError(msg)
|
|
229
|
+
|
|
230
|
+
it: Iterator[T] = iter(it_or_args)
|
|
231
|
+
|
|
232
|
+
if isinstance(start, type_):
|
|
233
|
+
accumulator = start
|
|
234
|
+
elif start is None or start is ...:
|
|
235
|
+
try:
|
|
236
|
+
accumulator = next(it)
|
|
237
|
+
except StopIteration:
|
|
238
|
+
msg = f"Invalid combinaison of arguments {args=} and {start=}. (expected at least 1 non-empty argument or start object that supports operator.)"
|
|
239
|
+
raise ValueError(msg)
|
|
240
|
+
else:
|
|
241
|
+
msg = f"Invalid argument type {type(start)}."
|
|
242
|
+
raise TypeError(msg)
|
|
243
|
+
|
|
244
|
+
for arg in it:
|
|
245
|
+
accumulator = op_fn(accumulator, arg)
|
|
246
|
+
return accumulator
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
@overload
|
|
250
|
+
def sum(
|
|
251
|
+
args: Iterable[T_SupportsAdd],
|
|
252
|
+
/,
|
|
253
|
+
*,
|
|
254
|
+
start: T_SupportsAdd = 0,
|
|
255
|
+
) -> T_SupportsAdd:
|
|
256
|
+
"""Perform the sum operation."""
|
|
257
|
+
...
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
@overload
|
|
261
|
+
def sum(
|
|
262
|
+
*args: T_SupportsAdd,
|
|
263
|
+
start: T_SupportsAdd = 0,
|
|
264
|
+
) -> T_SupportsAdd:
|
|
265
|
+
"""Perform the sum operation."""
|
|
266
|
+
...
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
@overload
|
|
270
|
+
def sum(
|
|
271
|
+
arg0: T_SupportsAdd,
|
|
272
|
+
/,
|
|
273
|
+
*args: T_SupportsAdd,
|
|
274
|
+
start: Optional[T_SupportsAdd] = 0,
|
|
275
|
+
) -> T_SupportsAdd:
|
|
276
|
+
"""Perform the sum operation."""
|
|
277
|
+
...
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def sum(*args, start: Any = 0):
|
|
281
|
+
"""Compute sum of elements."""
|
|
282
|
+
return reduce_add(*args, start=start)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
@overload
|
|
286
|
+
def prod(
|
|
287
|
+
args: Iterable[T_SupportsMul],
|
|
288
|
+
/,
|
|
289
|
+
*,
|
|
290
|
+
start: T_SupportsMul = 1,
|
|
291
|
+
) -> T_SupportsMul:
|
|
292
|
+
"""Perform the prod operation."""
|
|
293
|
+
...
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
@overload
|
|
297
|
+
def prod(
|
|
298
|
+
*args: T_SupportsMul,
|
|
299
|
+
start: T_SupportsMul = 1,
|
|
300
|
+
) -> T_SupportsMul:
|
|
301
|
+
"""Perform the prod operation."""
|
|
302
|
+
...
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
@overload
|
|
306
|
+
def prod(
|
|
307
|
+
arg0: T_SupportsMul,
|
|
308
|
+
/,
|
|
309
|
+
*args: T_SupportsMul,
|
|
310
|
+
start: Optional[T_SupportsMul] = 1,
|
|
311
|
+
) -> T_SupportsMul:
|
|
312
|
+
"""Perform the prod operation."""
|
|
313
|
+
...
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def prod(*args, start: Any = 1):
|
|
317
|
+
"""Compute product of elements."""
|
|
318
|
+
return reduce_mul(*args, start=start)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
@function_alias(reduce_and)
|
|
322
|
+
def intersect(*args, **kwargs):
|
|
323
|
+
"""Perform the intersect operation."""
|
|
324
|
+
...
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
@function_alias(reduce_or)
|
|
328
|
+
def union(*args, **kwargs):
|
|
329
|
+
"""Perform the union operation."""
|
|
330
|
+
...
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
import copy
|
|
5
|
+
import logging
|
|
6
|
+
from concurrent.futures import Future, ThreadPoolExecutor
|
|
7
|
+
from typing import Any, Callable, Dict, Generic, Iterable, List, Optional, TypeVar
|
|
8
|
+
|
|
9
|
+
from typing_extensions import ParamSpec
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
P = ParamSpec("P")
|
|
15
|
+
T = TypeVar("T")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ThreadPoolExecutorHelper(Generic[P, T]):
|
|
19
|
+
# Note: use commas for typing because Future is not generic in older python versions
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
fn: Callable[P, T],
|
|
24
|
+
*,
|
|
25
|
+
executor_kwds: Optional[Dict[str, Any]] = None,
|
|
26
|
+
executor: Optional[ThreadPoolExecutor] = None,
|
|
27
|
+
futures: "Iterable[Future[T]]" = (),
|
|
28
|
+
**default_fn_kwds,
|
|
29
|
+
) -> None:
|
|
30
|
+
"""Initialize the instance."""
|
|
31
|
+
futures = list(futures)
|
|
32
|
+
|
|
33
|
+
super().__init__()
|
|
34
|
+
self.fn = fn
|
|
35
|
+
self.executor_kwds = executor_kwds
|
|
36
|
+
self.executor = executor
|
|
37
|
+
self.futures = futures
|
|
38
|
+
self.default_kwargs = default_fn_kwds
|
|
39
|
+
|
|
40
|
+
def submit(self, *args: P.args, **kwargs: P.kwargs) -> "Future[T]":
|
|
41
|
+
"""Perform the submit operation."""
|
|
42
|
+
if self.executor is None:
|
|
43
|
+
executor_kwds = self.executor_kwds
|
|
44
|
+
if executor_kwds is None:
|
|
45
|
+
executor_kwds = {}
|
|
46
|
+
self.executor = ThreadPoolExecutor(**executor_kwds)
|
|
47
|
+
|
|
48
|
+
default_kwargs = copy.copy(self.default_kwargs)
|
|
49
|
+
default_kwargs.update(kwargs)
|
|
50
|
+
del kwargs
|
|
51
|
+
|
|
52
|
+
future = self.executor.submit(self.fn, *args, **default_kwargs)
|
|
53
|
+
self.futures.append(future)
|
|
54
|
+
return future
|
|
55
|
+
|
|
56
|
+
def wait_all(self, shutdown: bool = True, verbose: bool = True) -> List[T]:
|
|
57
|
+
"""Perform the wait all operation."""
|
|
58
|
+
futures = self.futures
|
|
59
|
+
if verbose:
|
|
60
|
+
try:
|
|
61
|
+
import tqdm # type: ignore
|
|
62
|
+
|
|
63
|
+
futures = tqdm.tqdm(futures, disable=not verbose)
|
|
64
|
+
except ImportError:
|
|
65
|
+
msg = "Cannot display verbose bar because tqdm is not installed."
|
|
66
|
+
logger.warning(msg)
|
|
67
|
+
|
|
68
|
+
results = [future.result() for future in futures]
|
|
69
|
+
self.futures.clear()
|
|
70
|
+
if shutdown and self.executor is not None:
|
|
71
|
+
self.executor.shutdown()
|
|
72
|
+
self.executor = None
|
|
73
|
+
return results
|
pythonwrench/csv.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
from dataclasses import MISSING, dataclass, is_dataclass # noqa: F401
|
|
5
|
+
from typing import Any, Dict, Type, TypeVar, cast
|
|
6
|
+
|
|
7
|
+
from typing_extensions import dataclass_transform
|
|
8
|
+
|
|
9
|
+
from pythonwrench.typing.checks import ( # noqa: F401
|
|
10
|
+
is_dataclass_instance,
|
|
11
|
+
is_dataclass_type,
|
|
12
|
+
)
|
|
13
|
+
from pythonwrench.typing.classes import Dataclass, DataclassInstance # noqa: F401
|
|
14
|
+
|
|
15
|
+
T = TypeVar("T")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass_transform()
|
|
19
|
+
def dataclassdict(cls: Type[T]) -> Type[T]:
|
|
20
|
+
"""Decorate a class so it becomes both a dataclass and a dictionary."""
|
|
21
|
+
return add_dict_methods(dataclass(cls))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def add_dict_methods(cls: Type[T]) -> Type[T]:
|
|
25
|
+
"""Return a dictionary subclass of an already-defined dataclass.
|
|
26
|
+
|
|
27
|
+
Field values are kept synchronized between attribute and mapping access.
|
|
28
|
+
"""
|
|
29
|
+
if not is_dataclass_type(cls):
|
|
30
|
+
raise TypeError("add_dict_methods expects a dataclass type.")
|
|
31
|
+
|
|
32
|
+
dataclass_cls = cls
|
|
33
|
+
conflicting_fields = sorted(
|
|
34
|
+
set(dataclass_cls.__dataclass_fields__).intersection(dir(dict)) # type: ignore
|
|
35
|
+
)
|
|
36
|
+
if conflicting_fields:
|
|
37
|
+
names = ", ".join(repr(name) for name in conflicting_fields)
|
|
38
|
+
msg = f"Dataclass fields conflict with dict attributes: {names}."
|
|
39
|
+
raise RuntimeError(msg)
|
|
40
|
+
|
|
41
|
+
def setattr_(self: Any, name: str, value: Any) -> None:
|
|
42
|
+
"""Perform the setattr operation."""
|
|
43
|
+
object.__setattr__(self, name, value)
|
|
44
|
+
if name in self.__dataclass_fields__:
|
|
45
|
+
dict.__setitem__(self, name, value)
|
|
46
|
+
|
|
47
|
+
def setitem(self: Any, key: Any, value: Any) -> None:
|
|
48
|
+
"""Perform the setitem operation."""
|
|
49
|
+
dict.__setitem__(self, key, value)
|
|
50
|
+
if key in self.__dataclass_fields__:
|
|
51
|
+
object.__setattr__(self, key, value)
|
|
52
|
+
|
|
53
|
+
def delitem(self: Any, key: Any) -> None:
|
|
54
|
+
"""Perform the delitem operation."""
|
|
55
|
+
dict.__delitem__(self, key)
|
|
56
|
+
if key in self.__dataclass_fields__ and hasattr(self, key):
|
|
57
|
+
object.__delattr__(self, key)
|
|
58
|
+
|
|
59
|
+
def update(self: Any, *args: Any, **kwargs: Any) -> None:
|
|
60
|
+
"""Perform the update operation."""
|
|
61
|
+
for key, value in dict(*args, **kwargs).items():
|
|
62
|
+
setitem(self, key, value)
|
|
63
|
+
|
|
64
|
+
def setdefault(self: Any, key: Any, default: Any = None) -> Any:
|
|
65
|
+
"""Perform the setdefault operation."""
|
|
66
|
+
if key not in self:
|
|
67
|
+
setitem(self, key, default)
|
|
68
|
+
return self[key]
|
|
69
|
+
|
|
70
|
+
def pop(self: Any, key: Any, *default: Any) -> Any:
|
|
71
|
+
"""Perform the pop operation."""
|
|
72
|
+
if len(default) > 1:
|
|
73
|
+
raise TypeError("pop expected at most 2 arguments")
|
|
74
|
+
if key not in self:
|
|
75
|
+
if default:
|
|
76
|
+
return default[0]
|
|
77
|
+
raise KeyError(key)
|
|
78
|
+
value = self[key]
|
|
79
|
+
delitem(self, key)
|
|
80
|
+
return value
|
|
81
|
+
|
|
82
|
+
def clear(self: Any) -> None:
|
|
83
|
+
"""Perform the clear operation."""
|
|
84
|
+
for key in list(self):
|
|
85
|
+
delitem(self, key)
|
|
86
|
+
|
|
87
|
+
namespace = {
|
|
88
|
+
"__module__": cls.__module__,
|
|
89
|
+
"__doc__": cls.__doc__,
|
|
90
|
+
"__setattr__": setattr_,
|
|
91
|
+
"__setitem__": setitem,
|
|
92
|
+
"__delitem__": delitem,
|
|
93
|
+
"update": update,
|
|
94
|
+
"setdefault": setdefault,
|
|
95
|
+
"pop": pop,
|
|
96
|
+
"clear": clear,
|
|
97
|
+
}
|
|
98
|
+
result = type(cls.__name__, (dataclass_cls, dict), namespace) # type: ignore
|
|
99
|
+
result.__qualname__ = cls.__qualname__
|
|
100
|
+
result = dataclass(result)
|
|
101
|
+
return cast(Type[T], result)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def get_defaults_values(obj: DataclassInstance) -> Dict[str, Any]:
|
|
105
|
+
"""Return defaults values."""
|
|
106
|
+
defaults = {}
|
|
107
|
+
|
|
108
|
+
for field in obj.__dataclass_fields__.values():
|
|
109
|
+
if callable(field.default_factory):
|
|
110
|
+
default = field.default_factory()
|
|
111
|
+
else:
|
|
112
|
+
default = field.default
|
|
113
|
+
|
|
114
|
+
if default != MISSING:
|
|
115
|
+
defaults[field.name] = default
|
|
116
|
+
|
|
117
|
+
return defaults
|
pythonwrench/datetime.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
import datetime
|
|
5
|
+
|
|
6
|
+
ISO8601_DAY_FORMAT = r"%Y-%m-%d"
|
|
7
|
+
ISO8601_HOUR_FORMAT_DOUBLE_DOT = r"%Y-%m-%dT%H:%M:%S"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_now_iso8601() -> str:
|
|
11
|
+
"""Returns current datetime as string with the ISO8601 format."""
|
|
12
|
+
return get_now(ISO8601_HOUR_FORMAT_DOUBLE_DOT)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_now(fmt: str = ISO8601_HOUR_FORMAT_DOUBLE_DOT) -> str:
|
|
16
|
+
"""Returns current datetime as string with the specified format."""
|
|
17
|
+
return datetime.datetime.now().strftime(fmt)
|
pythonwrench/difflib.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
import math
|
|
5
|
+
from difflib import SequenceMatcher
|
|
6
|
+
from typing import Callable, Iterable, Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def find_closest_in_list(
|
|
10
|
+
x: str,
|
|
11
|
+
lst: Iterable[str],
|
|
12
|
+
sim_fn: Optional[Callable[[str, str], float]] = None,
|
|
13
|
+
higher_is_closer: bool = True,
|
|
14
|
+
) -> Optional[str]:
|
|
15
|
+
"""Find closest element in a list based on matches ratio."""
|
|
16
|
+
if sim_fn is None:
|
|
17
|
+
sim_fn = sequence_matcher_ratio
|
|
18
|
+
|
|
19
|
+
best_sim = -int(higher_is_closer) * math.inf
|
|
20
|
+
closest = None
|
|
21
|
+
|
|
22
|
+
for elt in lst:
|
|
23
|
+
sim = sim_fn(x, elt)
|
|
24
|
+
if (higher_is_closer and best_sim < sim) or (
|
|
25
|
+
not higher_is_closer and best_sim > sim
|
|
26
|
+
):
|
|
27
|
+
best_sim = sim
|
|
28
|
+
closest = elt
|
|
29
|
+
|
|
30
|
+
if math.isinf(best_sim):
|
|
31
|
+
msg = f"Invalid argument {lst=}. (expected non-empty iterable of strings)"
|
|
32
|
+
raise ValueError(msg)
|
|
33
|
+
|
|
34
|
+
return closest
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def sequence_matcher_ratio(a: str, b: str) -> float:
|
|
38
|
+
"""Compute distance ratio of two strings."""
|
|
39
|
+
return SequenceMatcher(None, a, b).ratio()
|