vt-commons 0.0.1__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.
- vt/utils/commons/commons/__init__.py +6 -0
- vt/utils/commons/commons/collections/__init__.py +9 -0
- vt/utils/commons/commons/collections/utils.py +110 -0
- vt/utils/commons/commons/core_py/__init__.py +42 -0
- vt/utils/commons/commons/core_py/base.py +47 -0
- vt/utils/commons/commons/core_py/utils.py +827 -0
- vt/utils/commons/commons/op/__init__.py +11 -0
- vt/utils/commons/commons/op/base.py +121 -0
- vt/utils/commons/commons/os/__init__.py +18 -0
- vt/utils/commons/commons/os/_base_utils.py +17 -0
- vt/utils/commons/commons/os/linux.py +29 -0
- vt/utils/commons/commons/os/mac.py +29 -0
- vt/utils/commons/commons/os/posix.py +30 -0
- vt/utils/commons/commons/os/windows.py +29 -0
- vt/utils/commons/commons/py.typed +0 -0
- vt/utils/commons/commons/state/__init__.py +13 -0
- vt/utils/commons/commons/state/done.py +163 -0
- vt_commons-0.0.1.dist-info/METADATA +112 -0
- vt_commons-0.0.1.dist-info/RECORD +22 -0
- vt_commons-0.0.1.dist-info/WHEEL +5 -0
- vt_commons-0.0.1.dist-info/licenses/LICENSE +201 -0
- vt_commons-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,827 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
# coding=utf-8
|
3
|
+
|
4
|
+
"""
|
5
|
+
Reusable utilities related to core python.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from collections.abc import Callable, Sequence
|
9
|
+
from typing import Any, cast, TypeGuard, overload, Literal
|
10
|
+
|
11
|
+
from vt.utils.commons.commons.core_py.base import MISSING, Missing, UNSET, Unset
|
12
|
+
|
13
|
+
|
14
|
+
def is_missing[T](obj: T) -> TypeGuard[Missing]:
|
15
|
+
"""
|
16
|
+
Determine whether an ``obj`` is ``MISSING``, i.e. not supplied by the caller.
|
17
|
+
|
18
|
+
Examples:
|
19
|
+
|
20
|
+
* ``obj`` is ``MISSING``, i.e. not supplied by the caller:
|
21
|
+
|
22
|
+
>>> obj_to_test = MISSING
|
23
|
+
>>> is_missing(obj_to_test)
|
24
|
+
True
|
25
|
+
|
26
|
+
* ``obj`` is supplied but ``None``, i.e. it is supplied by the caller and hence, not missing:
|
27
|
+
|
28
|
+
>>> is_missing(None)
|
29
|
+
False
|
30
|
+
|
31
|
+
* ``obj`` is truthy primitive and supplied but non ``None``, i.e. it is supplied by the caller and
|
32
|
+
hence, not missing:
|
33
|
+
|
34
|
+
>>> is_missing(2) or is_missing('a') or is_missing(2.5) or is_missing(True) or is_missing(1+0j) or is_missing(b'y')
|
35
|
+
False
|
36
|
+
|
37
|
+
* ``obj`` is falsy primitive and supplied but non ``None``, i.e. it is supplied by the caller and
|
38
|
+
hence, not missing:
|
39
|
+
|
40
|
+
>>> is_missing(0) or is_missing('') or is_missing(0.0) or is_missing(False) or is_missing(0j) or is_missing(b'')
|
41
|
+
False
|
42
|
+
|
43
|
+
* ``obj`` is truthy non-primitive and supplied but non ``None``, i.e. it is supplied by the caller and
|
44
|
+
hence, not missing:
|
45
|
+
|
46
|
+
>>> is_missing([1, 2, 3]) or is_missing({1: 'a', 2: 'b'}) or is_missing({2.5, 2.0})
|
47
|
+
False
|
48
|
+
|
49
|
+
* ``obj`` is falsy non-primitive and supplied but non ``None``, i.e. it is supplied by the caller and
|
50
|
+
hence, not missing:
|
51
|
+
|
52
|
+
>>> is_missing([]) or is_missing({}) or is_missing(set())
|
53
|
+
False
|
54
|
+
|
55
|
+
:param obj: object to be tested whether it was supplied by caller or not.
|
56
|
+
:return: ``True`` if the ``obj`` is missing and not supplied by caller, ``False`` otherwise.
|
57
|
+
"""
|
58
|
+
return obj is MISSING
|
59
|
+
|
60
|
+
|
61
|
+
def is_unset[T](obj: T) -> TypeGuard[Unset]:
|
62
|
+
"""
|
63
|
+
Determine whether an ``obj`` is ``UNSET``, i.e. deliberately unset an already set value by the caller.
|
64
|
+
|
65
|
+
Examples:
|
66
|
+
|
67
|
+
* ``obj`` is ``UNSET``, i.e. deliberately unset by the caller:
|
68
|
+
|
69
|
+
>>> obj_to_test = UNSET
|
70
|
+
>>> is_unset(obj_to_test)
|
71
|
+
True
|
72
|
+
|
73
|
+
* ``obj`` is supplied but ``None``, i.e. it is supplied by the caller and hence, not unset:
|
74
|
+
|
75
|
+
>>> is_unset(None)
|
76
|
+
False
|
77
|
+
|
78
|
+
* ``obj`` is truthy primitive and supplied but non ``None``, i.e. it is supplied by the caller and
|
79
|
+
hence, not unset:
|
80
|
+
|
81
|
+
>>> is_unset(2) or is_unset('a') or is_unset(2.5) or is_unset(True) or is_unset(1+0j) or is_unset(b'y')
|
82
|
+
False
|
83
|
+
|
84
|
+
* ``obj`` is falsy primitive and supplied but non ``None``, i.e. it is supplied by the caller and
|
85
|
+
hence, not unset:
|
86
|
+
|
87
|
+
>>> is_unset(0) or is_unset('') or is_unset(0.0) or is_unset(False) or is_unset(0j) or is_unset(b'')
|
88
|
+
False
|
89
|
+
|
90
|
+
* ``obj`` is truthy non-primitive and supplied but non ``None``, i.e. it is supplied by the caller and
|
91
|
+
hence, not unset:
|
92
|
+
|
93
|
+
>>> is_unset([1, 2, 3]) or is_unset({1: 'a', 2: 'b'}) or is_unset({2.5, 2.0})
|
94
|
+
False
|
95
|
+
|
96
|
+
* ``obj`` is falsy non-primitive and supplied but non ``None``, i.e. it is supplied by the caller and
|
97
|
+
hence, not unset:
|
98
|
+
|
99
|
+
>>> is_unset([]) or is_unset({}) or is_unset(set())
|
100
|
+
False
|
101
|
+
|
102
|
+
:param obj: object to be tested whether it was supplied by caller or not.
|
103
|
+
:return: ``True`` if the ``obj`` is deliberatley unset by the caller, ``False`` otherwise.
|
104
|
+
"""
|
105
|
+
return obj is UNSET
|
106
|
+
|
107
|
+
|
108
|
+
def not_none_not_sentinel[T, S](val: T | None, *, sentinel: S) -> TypeGuard[T]:
|
109
|
+
"""
|
110
|
+
Returns True if ``val`` is not None and not the given sentinel.
|
111
|
+
|
112
|
+
Used to create specific type guards for `UNSET`, `MISSING`, or any custom sentinel.
|
113
|
+
|
114
|
+
>>> not_none_not_sentinel("x", sentinel=UNSET)
|
115
|
+
True
|
116
|
+
>>> not_none_not_sentinel(None, sentinel=UNSET)
|
117
|
+
False
|
118
|
+
>>> not_none_not_sentinel(UNSET, sentinel=UNSET)
|
119
|
+
False
|
120
|
+
"""
|
121
|
+
return val is not None and val is not sentinel
|
122
|
+
|
123
|
+
|
124
|
+
def not_none_not_unset[T](val: T | None | Unset) -> TypeGuard[T]:
|
125
|
+
"""
|
126
|
+
Check if the value is neither None nor UNSET.
|
127
|
+
|
128
|
+
>>> not_none_not_unset("a")
|
129
|
+
True
|
130
|
+
>>> not_none_not_unset(None)
|
131
|
+
False
|
132
|
+
>>> not_none_not_unset(UNSET)
|
133
|
+
False
|
134
|
+
|
135
|
+
>>> not_none_not_unset(0)
|
136
|
+
True
|
137
|
+
>>> not_none_not_unset("")
|
138
|
+
True
|
139
|
+
>>> not_none_not_unset(False)
|
140
|
+
True
|
141
|
+
>>> not_none_not_unset([])
|
142
|
+
True
|
143
|
+
>>> not_none_not_unset({})
|
144
|
+
True
|
145
|
+
>>> not_none_not_unset(set())
|
146
|
+
True
|
147
|
+
|
148
|
+
>>> _val: int | None | Unset = 5
|
149
|
+
>>> if not_none_not_unset(_val):
|
150
|
+
... type(_val) # Revealed type is "int"
|
151
|
+
<class 'int'>
|
152
|
+
"""
|
153
|
+
return not_none_not_sentinel(val, sentinel=UNSET)
|
154
|
+
|
155
|
+
|
156
|
+
def not_none_not_missing[T](val: T | None | Missing) -> TypeGuard[T]:
|
157
|
+
"""
|
158
|
+
Check if the value is neither None nor MISSING.
|
159
|
+
|
160
|
+
>>> not_none_not_missing("b")
|
161
|
+
True
|
162
|
+
>>> not_none_not_missing(None)
|
163
|
+
False
|
164
|
+
>>> not_none_not_missing(MISSING)
|
165
|
+
False
|
166
|
+
|
167
|
+
>>> not_none_not_missing(0)
|
168
|
+
True
|
169
|
+
>>> not_none_not_missing("")
|
170
|
+
True
|
171
|
+
>>> not_none_not_missing(False)
|
172
|
+
True
|
173
|
+
>>> not_none_not_missing([])
|
174
|
+
True
|
175
|
+
>>> not_none_not_missing({})
|
176
|
+
True
|
177
|
+
>>> not_none_not_missing(set())
|
178
|
+
True
|
179
|
+
|
180
|
+
>>> _val: str | None | Missing = "hello"
|
181
|
+
>>> if not_none_not_missing(_val):
|
182
|
+
... type(_val) # Revealed type is "str"
|
183
|
+
<class 'str'>
|
184
|
+
"""
|
185
|
+
return not_none_not_sentinel(val, sentinel=MISSING)
|
186
|
+
|
187
|
+
|
188
|
+
def _alt_if_predicate_true[T, U](
|
189
|
+
obj: Any | U, alt: T, predicate: Callable[[Any | U], bool]
|
190
|
+
) -> T:
|
191
|
+
"""
|
192
|
+
Get an alternate object ``alt`` if the queried object ``obj`` is ``MISSING``, i.e. it is not supplied by the caller.
|
193
|
+
|
194
|
+
Note::
|
195
|
+
|
196
|
+
Returned value is always of the type of alt object.
|
197
|
+
|
198
|
+
:param obj: object to be tested whether it was fulfills the ``predicate`` or not.
|
199
|
+
:param predicate: A predicate that ``obj`` needs to fulfill to be returned from this method.
|
200
|
+
:param alt: alternate object to be returned if ``obj`` does not fulfill the ``predicate``.
|
201
|
+
:return: ``obj`` if it fulfills the ``predicate`` else ``alt``.
|
202
|
+
"""
|
203
|
+
if predicate(obj):
|
204
|
+
return alt
|
205
|
+
if type(obj) is not type(alt):
|
206
|
+
raise TypeError(
|
207
|
+
f"Unexpected type: `obj` and `alt` must be of the same type. type(obj): {type(obj)}, "
|
208
|
+
f"type(alt): {type(alt)}"
|
209
|
+
)
|
210
|
+
return alt if is_missing(obj) else cast(T, obj)
|
211
|
+
|
212
|
+
|
213
|
+
def alt_if_missing[T](obj: Any | Missing, alt: T) -> T:
|
214
|
+
"""
|
215
|
+
Get an alternate object ``alt`` if the queried object ``obj`` is ``MISSING``, i.e. it is not supplied by the caller.
|
216
|
+
|
217
|
+
Note::
|
218
|
+
|
219
|
+
Returned value is always of the type of alt object.
|
220
|
+
|
221
|
+
Examples:
|
222
|
+
|
223
|
+
* Main object ``obj`` is returned if it is not ``MISSING``, i.e. it was supplied by the caller. Also, the returned
|
224
|
+
object ``obj`` is of the type of alternative ``alt`` object, test for falsy ``obj`` objects:
|
225
|
+
|
226
|
+
>>> assert alt_if_missing(None, None) is None
|
227
|
+
>>> assert alt_if_missing(0, 2) == 0
|
228
|
+
>>> assert alt_if_missing(0.0, 1.3) == 0.0
|
229
|
+
>>> assert alt_if_missing('', 'z') == ''
|
230
|
+
>>> assert alt_if_missing([], [1, 2, 3]) == []
|
231
|
+
>>> assert alt_if_missing({}, {'a': 1, 'b': 2}) == {}
|
232
|
+
>>> assert alt_if_missing(set(), {1, 2, 3}) == set()
|
233
|
+
>>> assert alt_if_missing(0j, 1+2j) == 0j
|
234
|
+
|
235
|
+
* Main object ``obj`` is returned if it is not ``MISSING``, i.e. it was supplied by the caller. Also, the returned
|
236
|
+
object ``obj`` is of the type of alternative ``alt`` object, test for truthy ``obj`` objects:
|
237
|
+
|
238
|
+
>>> assert alt_if_missing('a', 'null') == 'a'
|
239
|
+
>>> assert alt_if_missing(-1, 2) == -1
|
240
|
+
>>> assert alt_if_missing(0.9, 1.3) == 0.9
|
241
|
+
>>> assert alt_if_missing('jo', 'z') == 'jo'
|
242
|
+
>>> assert alt_if_missing([9, 8, 7], [1, 2, 3]) == [9, 8, 7]
|
243
|
+
>>> assert alt_if_missing({'z': 10, 'y': 9, 'x': 8}, {'a': 1, 'b': 2}) == {'z': 10, 'y': 9, 'x': 8}
|
244
|
+
>>> assert alt_if_missing({0, 9, 8}, {1, 2, 3}) == {0, 9, 8}
|
245
|
+
>>> assert alt_if_missing(1+0j, 1+2j) == 1+0j
|
246
|
+
|
247
|
+
* Alternate object ``alt`` is returned when main object ``obj`` is ``MISSING``, i.e. it was not supplied by
|
248
|
+
the caller. Also, the returned object ``alt`` is of the type of alternative ``alt`` object:
|
249
|
+
|
250
|
+
>>> assert alt_if_missing(MISSING, None) is None
|
251
|
+
>>> assert alt_if_missing(MISSING, 0) == 0
|
252
|
+
>>> assert alt_if_missing(MISSING, 0.0) == 0,0
|
253
|
+
>>> assert alt_if_missing(MISSING, '') == ''
|
254
|
+
>>> assert alt_if_missing(MISSING, []) == []
|
255
|
+
>>> assert alt_if_missing(MISSING, {}) == {}
|
256
|
+
>>> assert alt_if_missing(MISSING, set()) == set()
|
257
|
+
>>> assert alt_if_missing(MISSING, 0j) == 0j
|
258
|
+
|
259
|
+
* Errs if main object ``obj`` is not ``MISSING`` and hence, is supplied by the caller, but its type is different
|
260
|
+
from the type of the alternative ``alt`` object:
|
261
|
+
|
262
|
+
>>> alt_if_missing('a', 2)
|
263
|
+
Traceback (most recent call last):
|
264
|
+
TypeError: Unexpected type: `obj` and `alt` must be of the same type. type(obj): <class 'str'>, type(alt): <class 'int'>
|
265
|
+
|
266
|
+
>>> alt_if_missing([], (2, 3, 4))
|
267
|
+
Traceback (most recent call last):
|
268
|
+
TypeError: Unexpected type: `obj` and `alt` must be of the same type. type(obj): <class 'list'>, type(alt): <class 'tuple'>
|
269
|
+
|
270
|
+
:param obj: object to be tested whether it was supplied by caller or not.
|
271
|
+
:param alt: alternate object to be returned if ``obj`` was not supplied by the caller.
|
272
|
+
:return: ``obj`` if it was supplied by the caller else ``alt``.
|
273
|
+
"""
|
274
|
+
return _alt_if_predicate_true(obj, alt, is_missing)
|
275
|
+
|
276
|
+
|
277
|
+
def alt_if_unset[T](obj: Any | Unset, alt: T) -> T:
|
278
|
+
"""
|
279
|
+
Get an alternate object ``alt`` if the queried object ``obj`` is ``UNSET``, i.e. it is deliberately unset by
|
280
|
+
the caller.
|
281
|
+
|
282
|
+
Note::
|
283
|
+
|
284
|
+
Returned value is always of the type of alt object.
|
285
|
+
|
286
|
+
Examples:
|
287
|
+
|
288
|
+
* Main object ``obj`` is returned if it is not ``UNSET``, i.e. it was not deliberately unset by the caller. Also,
|
289
|
+
the returned object ``obj`` is of the type of alternative ``alt`` object, test for falsy ``obj`` objects:
|
290
|
+
|
291
|
+
>>> assert alt_if_unset(None, None) is None
|
292
|
+
>>> assert alt_if_unset(0, 2) == 0
|
293
|
+
>>> assert alt_if_unset(0.0, 1.3) == 0.0
|
294
|
+
>>> assert alt_if_unset('', 'z') == ''
|
295
|
+
>>> assert alt_if_unset([], [1, 2, 3]) == []
|
296
|
+
>>> assert alt_if_unset({}, {'a': 1, 'b': 2}) == {}
|
297
|
+
>>> assert alt_if_unset(set(), {1, 2, 3}) == set()
|
298
|
+
>>> assert alt_if_unset(0j, 1+2j) == 0j
|
299
|
+
|
300
|
+
* Main object ``obj`` is returned if it is not ``UNSET``, i.e. it was not deliberately unset by the caller. Also,
|
301
|
+
the returned object ``obj`` is of the type of alternative ``alt`` object, test for truthy ``obj`` objects:
|
302
|
+
|
303
|
+
>>> assert alt_if_unset('a', 'null') == 'a'
|
304
|
+
>>> assert alt_if_unset(-1, 2) == -1
|
305
|
+
>>> assert alt_if_unset(0.9, 1.3) == 0.9
|
306
|
+
>>> assert alt_if_unset('jo', 'z') == 'jo'
|
307
|
+
>>> assert alt_if_unset([9, 8, 7], [1, 2, 3]) == [9, 8, 7]
|
308
|
+
>>> assert alt_if_unset({'z': 10, 'y': 9, 'x': 8}, {'a': 1, 'b': 2}) == {'z': 10, 'y': 9, 'x': 8}
|
309
|
+
>>> assert alt_if_unset({0, 9, 8}, {1, 2, 3}) == {0, 9, 8}
|
310
|
+
>>> assert alt_if_unset(1+0j, 1+2j) == 1+0j
|
311
|
+
|
312
|
+
* Alternate object ``alt`` is returned when main object ``obj`` is ``UNSET``, i.e. it was deliberately unset by
|
313
|
+
the caller. Also, the returned object ``alt`` is of the type of alternative ``alt`` object:
|
314
|
+
|
315
|
+
>>> assert alt_if_unset(UNSET, None) is None
|
316
|
+
>>> assert alt_if_unset(UNSET, 0) == 0
|
317
|
+
>>> assert alt_if_unset(UNSET, 0.0) == 0,0
|
318
|
+
>>> assert alt_if_unset(UNSET, '') == ''
|
319
|
+
>>> assert alt_if_unset(UNSET, []) == []
|
320
|
+
>>> assert alt_if_unset(UNSET, {}) == {}
|
321
|
+
>>> assert alt_if_unset(UNSET, set()) == set()
|
322
|
+
>>> assert alt_if_unset(UNSET, 0j) == 0j
|
323
|
+
|
324
|
+
* Errs if main object ``obj`` is not ``UNSET`` and hence, is supplied by the caller, but its type is different
|
325
|
+
from the type of the alternative ``alt`` object:
|
326
|
+
|
327
|
+
>>> alt_if_unset('a', 2)
|
328
|
+
Traceback (most recent call last):
|
329
|
+
TypeError: Unexpected type: `obj` and `alt` must be of the same type. type(obj): <class 'str'>, type(alt): <class 'int'>
|
330
|
+
|
331
|
+
>>> alt_if_unset([], (2, 3, 4))
|
332
|
+
Traceback (most recent call last):
|
333
|
+
TypeError: Unexpected type: `obj` and `alt` must be of the same type. type(obj): <class 'list'>, type(alt): <class 'tuple'>
|
334
|
+
|
335
|
+
:param obj: object to be tested whether it was deliberately unset by the caller or not.
|
336
|
+
:param alt: alternate object to be returned if ``obj`` was not supplied by the caller.
|
337
|
+
:return: ``alt`` if ``obj`` was deliberately unset by the caller, else ``obj``.
|
338
|
+
"""
|
339
|
+
return _alt_if_predicate_true(obj, alt, is_unset)
|
340
|
+
|
341
|
+
|
342
|
+
def is_ellipses(obj: Any) -> bool:
|
343
|
+
"""
|
344
|
+
:param obj: object to be tested whether it was supplied by caller or not.
|
345
|
+
:return: ``True`` if the ``obj`` is missing and not supplied by caller, ``False`` otherwise.
|
346
|
+
"""
|
347
|
+
return obj is ...
|
348
|
+
|
349
|
+
|
350
|
+
def alt_if_ellipses[T](obj, alt: T) -> T:
|
351
|
+
"""
|
352
|
+
Get an alternate object ``alt`` if the queried object ``obj`` is ``...``, i.e. it is not supplied by the caller or
|
353
|
+
is deliberatey kept ``...`` by the caller.
|
354
|
+
|
355
|
+
Note::
|
356
|
+
|
357
|
+
Returned value is always of the type of alt object.
|
358
|
+
|
359
|
+
Examples:
|
360
|
+
|
361
|
+
* Main object ``obj`` is returned if it is not ``...``, i.e. it was supplied by the caller. Also, the returned
|
362
|
+
object ``obj`` is of the type of alternative ``alt`` object, test for falsy ``obj`` objects:
|
363
|
+
|
364
|
+
>>> assert alt_if_ellipses(None, None) is None
|
365
|
+
>>> assert alt_if_ellipses(0, 2) == 0
|
366
|
+
>>> assert alt_if_ellipses(0.0, 1.3) == 0.0
|
367
|
+
>>> assert alt_if_ellipses('', 'z') == ''
|
368
|
+
>>> assert alt_if_ellipses([], [1, 2, 3]) == []
|
369
|
+
>>> assert alt_if_ellipses({}, {'a': 1, 'b': 2}) == {}
|
370
|
+
>>> assert alt_if_ellipses(set(), {1, 2, 3}) == set()
|
371
|
+
>>> assert alt_if_ellipses(0j, 1+2j) == 0j
|
372
|
+
|
373
|
+
* Main object ``obj`` is returned if it is not ``...``, i.e. it was supplied by the caller. Also, the returned
|
374
|
+
object ``obj`` is of the type of alternative ``alt`` object, test for truthy ``obj`` objects:
|
375
|
+
|
376
|
+
>>> assert alt_if_ellipses('a', 'null') == 'a'
|
377
|
+
>>> assert alt_if_ellipses(-1, 2) == -1
|
378
|
+
>>> assert alt_if_ellipses(0.9, 1.3) == 0.9
|
379
|
+
>>> assert alt_if_ellipses('jo', 'z') == 'jo'
|
380
|
+
>>> assert alt_if_ellipses([9, 8, 7], [1, 2, 3]) == [9, 8, 7]
|
381
|
+
>>> assert alt_if_ellipses({'z': 10, 'y': 9, 'x': 8}, {'a': 1, 'b': 2}) == {'z': 10, 'y': 9, 'x': 8}
|
382
|
+
>>> assert alt_if_ellipses({0, 9, 8}, {1, 2, 3}) == {0, 9, 8}
|
383
|
+
>>> assert alt_if_ellipses(1+0j, 1+2j) == 1+0j
|
384
|
+
|
385
|
+
* Alternate object ``alt`` is returned when main object ``obj`` is ``...``, i.e. it was not supplied by
|
386
|
+
the caller or is deliberately kept ``...`` by the caller. Also, the returned object ``alt`` is of the type of
|
387
|
+
alternative ``alt`` object:
|
388
|
+
|
389
|
+
>>> assert alt_if_ellipses(..., None) is None
|
390
|
+
>>> assert alt_if_ellipses(..., 0) == 0
|
391
|
+
>>> assert alt_if_ellipses(..., 0.0) == 0,0
|
392
|
+
>>> assert alt_if_ellipses(..., '') == ''
|
393
|
+
>>> assert alt_if_ellipses(..., []) == []
|
394
|
+
>>> assert alt_if_ellipses(..., {}) == {}
|
395
|
+
>>> assert alt_if_ellipses(..., set()) == set()
|
396
|
+
>>> assert alt_if_ellipses(..., 0j) == 0j
|
397
|
+
|
398
|
+
* Errs if main object ``obj`` is not ``...`` and hence, is supplied by the caller, but its type is different
|
399
|
+
from the type of the alternative ``alt`` object:
|
400
|
+
|
401
|
+
>>> alt_if_ellipses('a', 2)
|
402
|
+
Traceback (most recent call last):
|
403
|
+
TypeError: Unexpected type: `obj` and `alt` must be of the same type. type(obj): <class 'str'>, type(alt): <class 'int'>
|
404
|
+
|
405
|
+
>>> alt_if_ellipses([], (2, 3, 4))
|
406
|
+
Traceback (most recent call last):
|
407
|
+
TypeError: Unexpected type: `obj` and `alt` must be of the same type. type(obj): <class 'list'>, type(alt): <class 'tuple'>
|
408
|
+
|
409
|
+
:param obj: object to be tested whether it was supplied as ellipses by caller or not.
|
410
|
+
:param alt: alternate object to be returned if ``obj`` is supplied as ellipses by the caller.
|
411
|
+
:return: ``obj`` if it was supplied as ellipses by the caller, else ``alt``.
|
412
|
+
"""
|
413
|
+
return _alt_if_predicate_true(obj, alt, is_ellipses)
|
414
|
+
|
415
|
+
|
416
|
+
def fallback_on_none[T](value: T | None, default_val: T | None) -> T | None:
|
417
|
+
"""
|
418
|
+
Get ``value`` if it is non-``None`` else get ``default_val``.
|
419
|
+
|
420
|
+
Examples:
|
421
|
+
|
422
|
+
>>> fallback_on_none('a', 'b')
|
423
|
+
'a'
|
424
|
+
|
425
|
+
>>> fallback_on_none(None, 'b')
|
426
|
+
'b'
|
427
|
+
|
428
|
+
>>> fallback_on_none(None, True)
|
429
|
+
True
|
430
|
+
|
431
|
+
>>> fallback_on_none(True, False)
|
432
|
+
True
|
433
|
+
|
434
|
+
* on Falsy values:
|
435
|
+
|
436
|
+
>>> fallback_on_none([], [1, 2])
|
437
|
+
[]
|
438
|
+
|
439
|
+
>>> fallback_on_none({}, {1: 2, 2: 3})
|
440
|
+
{}
|
441
|
+
|
442
|
+
>>> fallback_on_none(set(), {1, 2, 3})
|
443
|
+
set()
|
444
|
+
|
445
|
+
>>> fallback_on_none((),
|
446
|
+
... (1, 2, 3)) # noqa: some tuple warning
|
447
|
+
()
|
448
|
+
|
449
|
+
>>> fallback_on_none(False, True)
|
450
|
+
False
|
451
|
+
|
452
|
+
:param value: The main value to return if it is not ``None``.
|
453
|
+
:param default_val: returned if ``value`` is ``None``.
|
454
|
+
:return: ``default_val`` if ``value`` is ``None`` else ``value``.
|
455
|
+
"""
|
456
|
+
return default_val if value is None else value
|
457
|
+
|
458
|
+
|
459
|
+
def fallback_on_none_strict[T](value: T | None, default_val: T) -> T:
|
460
|
+
"""
|
461
|
+
Same as ``fallback_on_non_strict()`` but has an assertion guarantee that ``default_val`` is non-``None``.
|
462
|
+
|
463
|
+
Examples:
|
464
|
+
|
465
|
+
>>> fallback_on_none_strict('a', 'b')
|
466
|
+
'a'
|
467
|
+
|
468
|
+
>>> fallback_on_none_strict('a',
|
469
|
+
... None) # noqa: just for example
|
470
|
+
Traceback (most recent call last):
|
471
|
+
AssertionError: default_val must not be None.
|
472
|
+
|
473
|
+
:param value: The main value to return if it is not ``None``.
|
474
|
+
:param default_val: returned if ``value`` is ``None``.
|
475
|
+
:return: ``default_val`` if ``value`` is ``None`` else ``value``.
|
476
|
+
"""
|
477
|
+
assert default_val is not None, "default_val must not be None."
|
478
|
+
return cast(T, fallback_on_none(value, default_val))
|
479
|
+
|
480
|
+
|
481
|
+
def strictly_int(value: object) -> TypeGuard[int]:
|
482
|
+
"""
|
483
|
+
Check if a value is strictly an integer but NOT a boolean.
|
484
|
+
|
485
|
+
This utility function is designed to distinguish between `int` and `bool` types
|
486
|
+
because in Python, `bool` is a subclass of `int` and would pass an `isinstance(value, int)`
|
487
|
+
check. This function returns `True` only if the value is a genuine integer
|
488
|
+
(excluding booleans).
|
489
|
+
|
490
|
+
Usage of this function is crucial in contexts where the semantic difference between
|
491
|
+
integers and booleans must be maintained, such as argument validation for functions
|
492
|
+
that accept integers but must reject boolean values.
|
493
|
+
|
494
|
+
:param value: The value to be checked.
|
495
|
+
:return: `True` if `value` is an `int` but not a `bool`; otherwise `False`.
|
496
|
+
|
497
|
+
:rtype: TypeGuard[int]
|
498
|
+
|
499
|
+
Examples::
|
500
|
+
|
501
|
+
>>> strictly_int(5)
|
502
|
+
True
|
503
|
+
>>> strictly_int(-10)
|
504
|
+
True
|
505
|
+
>>> strictly_int(0)
|
506
|
+
True
|
507
|
+
>>> strictly_int(True)
|
508
|
+
False
|
509
|
+
>>> strictly_int(False)
|
510
|
+
False
|
511
|
+
>>> strictly_int(5.0)
|
512
|
+
False
|
513
|
+
>>> strictly_int("5")
|
514
|
+
False
|
515
|
+
>>> strictly_int(None)
|
516
|
+
False
|
517
|
+
>>> strictly_int([1, 2, 3])
|
518
|
+
False
|
519
|
+
>>> strictly_int(object())
|
520
|
+
False
|
521
|
+
|
522
|
+
This function helps static type checkers like mypy to narrow types::
|
523
|
+
|
524
|
+
>>> from typing import reveal_type
|
525
|
+
>>> def test(x: int | bool | str) -> int:
|
526
|
+
... if strictly_int(x):
|
527
|
+
... reveal_type(x) # Revealed type is 'int'
|
528
|
+
... return x + 1
|
529
|
+
... else:
|
530
|
+
... raise ValueError("Not strictly an int")
|
531
|
+
...
|
532
|
+
>>> test(10)
|
533
|
+
11
|
534
|
+
>>> test(True) # Raises ValueError
|
535
|
+
Traceback (most recent call last):
|
536
|
+
ValueError: Not strictly an int
|
537
|
+
"""
|
538
|
+
return isinstance(value, int) and not isinstance(value, bool)
|
539
|
+
|
540
|
+
|
541
|
+
# region positional args related utility methods
|
542
|
+
|
543
|
+
# region ensure_atleast_one_arg() and overloads
|
544
|
+
|
545
|
+
|
546
|
+
@overload
|
547
|
+
def ensure_atleast_one_arg[T](
|
548
|
+
first: T | None, *rest: T, falsy: bool = False, enforce_type: None = None
|
549
|
+
) -> Sequence[T]: ...
|
550
|
+
|
551
|
+
|
552
|
+
@overload
|
553
|
+
def ensure_atleast_one_arg[T](
|
554
|
+
first: object | None,
|
555
|
+
*rest: object,
|
556
|
+
falsy: bool = False,
|
557
|
+
enforce_type: Literal[False] = False,
|
558
|
+
) -> Sequence[object]: ...
|
559
|
+
|
560
|
+
|
561
|
+
@overload
|
562
|
+
def ensure_atleast_one_arg[T](
|
563
|
+
first: T | None, *rest: T, falsy: bool = False, enforce_type: type[T]
|
564
|
+
) -> Sequence[T]: ...
|
565
|
+
|
566
|
+
|
567
|
+
def ensure_atleast_one_arg[T](
|
568
|
+
first: T | object | None,
|
569
|
+
*rest: T | object,
|
570
|
+
falsy: bool = False,
|
571
|
+
enforce_type: type | Literal[False] | None = None,
|
572
|
+
) -> Sequence[T] | Sequence[object]:
|
573
|
+
"""
|
574
|
+
Ensures that at least one argument is provided (truthy or non-None),
|
575
|
+
with optional type enforcement or inference.
|
576
|
+
|
577
|
+
|
578
|
+
``enforce_type`` behavior:
|
579
|
+
|
580
|
+
* if not provided, defaults to ``None``. This defaults the method to type check all the arguments to have the same
|
581
|
+
type as the first valid argument. Invalidating this condition raises a ``TypeError``.
|
582
|
+
|
583
|
+
* if ``False`` then, no type-enforcement is performed.
|
584
|
+
|
585
|
+
* if a type is provided, like ``str``, ``int`` etc. then all the arguments are enforce to have this same type.
|
586
|
+
Invalidating this condition raises a ``TypeError``.
|
587
|
+
|
588
|
+
:param first: First argument (can be None).
|
589
|
+
:param rest: Additional arguments.
|
590
|
+
:param falsy: Whether to treat falsy values as invalid.
|
591
|
+
:param enforce_type: A specific type to enforce across all arguments.
|
592
|
+
|
593
|
+
:returns: A tuple of valid arguments.
|
594
|
+
|
595
|
+
:raises ValueError: If no valid argument is provided.
|
596
|
+
:raises TypeError: If enforce_type is given but a mismatch is found.
|
597
|
+
|
598
|
+
|
599
|
+
Examples::
|
600
|
+
|
601
|
+
>>> ensure_atleast_one_arg("foo", None)
|
602
|
+
('foo',)
|
603
|
+
|
604
|
+
>>> ensure_atleast_one_arg("foo", "bar", enforce_type=str)
|
605
|
+
('foo', 'bar')
|
606
|
+
|
607
|
+
>>> ensure_atleast_one_arg(1, "a", enforce_type=int) # type: ignore[arg-type] # expected int, provided str.
|
608
|
+
Traceback (most recent call last):
|
609
|
+
TypeError: Expected all arguments to be of type int.
|
610
|
+
|
611
|
+
>>> ensure_atleast_one_arg(1, "a")
|
612
|
+
Traceback (most recent call last):
|
613
|
+
TypeError: Expected all arguments to be of type int.
|
614
|
+
|
615
|
+
>>> ensure_atleast_one_arg(1, "a", enforce_type=False)
|
616
|
+
(1, 'a')
|
617
|
+
|
618
|
+
>>> ensure_atleast_one_arg(None, [], {}, falsy=True)
|
619
|
+
Traceback (most recent call last):
|
620
|
+
ValueError: At least one argument is required.
|
621
|
+
|
622
|
+
>>> ensure_atleast_one_arg(None, 0, "", enforce_type=False, falsy=True)
|
623
|
+
Traceback (most recent call last):
|
624
|
+
ValueError: At least one argument is required.
|
625
|
+
|
626
|
+
>>> ensure_atleast_one_arg(None, "hi", 2.3, enforce_type=False)
|
627
|
+
('hi', 2.3)
|
628
|
+
|
629
|
+
>>> ensure_atleast_one_arg(None, "hi", 2.3)
|
630
|
+
Traceback (most recent call last):
|
631
|
+
TypeError: Expected all arguments to be of type str.
|
632
|
+
|
633
|
+
>>> ensure_atleast_one_arg(None, 123, enforce_type=int)
|
634
|
+
(123,)
|
635
|
+
|
636
|
+
>>> ensure_atleast_one_arg(None, 123, enforce_type=str)
|
637
|
+
Traceback (most recent call last):
|
638
|
+
TypeError: Expected all arguments to be of type str.
|
639
|
+
|
640
|
+
>>> ensure_atleast_one_arg("hi", None, 3.0, falsy=True)
|
641
|
+
Traceback (most recent call last):
|
642
|
+
TypeError: Expected all arguments to be of type str.
|
643
|
+
|
644
|
+
>>> ensure_atleast_one_arg(None, 'null', 3.0, None, set(), enforce_type=False)
|
645
|
+
('null', 3.0, set())
|
646
|
+
"""
|
647
|
+
values = _filter_args(first, *rest, falsy=falsy)
|
648
|
+
|
649
|
+
if not values:
|
650
|
+
raise ValueError("At least one argument is required.")
|
651
|
+
|
652
|
+
if enforce_type is not False:
|
653
|
+
expected_type = enforce_type or type(values[0])
|
654
|
+
for v in values:
|
655
|
+
if not isinstance(v, expected_type):
|
656
|
+
raise TypeError(
|
657
|
+
f"Expected all arguments to be of type {expected_type.__name__}."
|
658
|
+
)
|
659
|
+
|
660
|
+
return values
|
661
|
+
|
662
|
+
|
663
|
+
# endregion
|
664
|
+
|
665
|
+
|
666
|
+
# region has_atleast_one_arg() and overloads
|
667
|
+
@overload
|
668
|
+
def has_atleast_one_arg[T](
|
669
|
+
first: T | None, *rest: T, falsy: bool = False, enforce_type: None = None
|
670
|
+
) -> bool:
|
671
|
+
"""
|
672
|
+
A ``has_atleast_one_arg()`` overload.
|
673
|
+
|
674
|
+
``first`` and ``rest`` all args must be of the same type when ``enforce_type`` is not provided or is ``None``.
|
675
|
+
"""
|
676
|
+
...
|
677
|
+
|
678
|
+
|
679
|
+
@overload
|
680
|
+
def has_atleast_one_arg(
|
681
|
+
first: object | None,
|
682
|
+
*rest: object,
|
683
|
+
falsy: bool = False,
|
684
|
+
enforce_type: Literal[False] = False,
|
685
|
+
) -> bool:
|
686
|
+
"""
|
687
|
+
A ``has_atleast_one_arg()`` overload.
|
688
|
+
|
689
|
+
``first`` and ``rest`` all args can be of any type when ``enforce_type=False``.
|
690
|
+
"""
|
691
|
+
...
|
692
|
+
|
693
|
+
|
694
|
+
@overload
|
695
|
+
def has_atleast_one_arg[T](
|
696
|
+
first: T | None, *rest: T, falsy: bool = False, enforce_type: type[T]
|
697
|
+
) -> bool:
|
698
|
+
"""
|
699
|
+
A ``has_atleast_one_arg()`` overload.
|
700
|
+
|
701
|
+
``first`` and ``rest`` all args must be of the type provided as ``enforce_type``.
|
702
|
+
|
703
|
+
For example, ``first`` and ``rest`` all args must be ``int`` when ``enforce_type=int``.
|
704
|
+
"""
|
705
|
+
...
|
706
|
+
|
707
|
+
|
708
|
+
def has_atleast_one_arg(
|
709
|
+
first: object | None,
|
710
|
+
*rest: object,
|
711
|
+
falsy: bool = False,
|
712
|
+
enforce_type: type | Literal[False] | None = None,
|
713
|
+
) -> bool:
|
714
|
+
"""
|
715
|
+
Returns True if there is at least one valid (non-None or truthy) argument,
|
716
|
+
optionally enforcing all values are of the same type.
|
717
|
+
|
718
|
+
:param first: First argument (can be None).
|
719
|
+
:param rest: Additional arguments.
|
720
|
+
:param falsy: Whether to treat falsy values as invalid.
|
721
|
+
:param enforce_type: Type to enforce across all arguments.
|
722
|
+
:returns: True if valid arguments are found and type checks pass.
|
723
|
+
|
724
|
+
>>> has_atleast_one_arg(None, [])
|
725
|
+
True
|
726
|
+
|
727
|
+
>>> has_atleast_one_arg(None, [], falsy=True)
|
728
|
+
False
|
729
|
+
|
730
|
+
>>> has_atleast_one_arg("a", "b", enforce_type=str)
|
731
|
+
True
|
732
|
+
|
733
|
+
>>> has_atleast_one_arg("a", 1, enforce_type=str) # type: ignore[arg-type] # expected str, provided int.
|
734
|
+
False
|
735
|
+
|
736
|
+
>>> has_atleast_one_arg("foo")
|
737
|
+
True
|
738
|
+
|
739
|
+
>>> has_atleast_one_arg(None, "bar")
|
740
|
+
True
|
741
|
+
|
742
|
+
>>> has_atleast_one_arg(None, None)
|
743
|
+
False
|
744
|
+
|
745
|
+
>>> has_atleast_one_arg("", [], 0)
|
746
|
+
False
|
747
|
+
|
748
|
+
>>> has_atleast_one_arg("", [], 0, enforce_type=False)
|
749
|
+
True
|
750
|
+
|
751
|
+
>>> has_atleast_one_arg("", [], 0, falsy=True)
|
752
|
+
False
|
753
|
+
|
754
|
+
>>> has_atleast_one_arg(1, 2, 3, enforce_type=int)
|
755
|
+
True
|
756
|
+
|
757
|
+
>>> has_atleast_one_arg(1, "2", enforce_type=int) # type: ignore[arg-type] # expected int, provided str.
|
758
|
+
False
|
759
|
+
|
760
|
+
>>> has_atleast_one_arg(1, "2", enforce_type=False)
|
761
|
+
True
|
762
|
+
|
763
|
+
>>> has_atleast_one_arg(None, 2.0, enforce_type=int)
|
764
|
+
False
|
765
|
+
|
766
|
+
>>> has_atleast_one_arg(None, 2.0, enforce_type=float)
|
767
|
+
True
|
768
|
+
|
769
|
+
>>> has_atleast_one_arg(None, "", 0, False, enforce_type=False, falsy=True)
|
770
|
+
False
|
771
|
+
|
772
|
+
>>> has_atleast_one_arg("abc", 123)
|
773
|
+
False
|
774
|
+
|
775
|
+
>>> has_atleast_one_arg("abc", 123, enforce_type=False)
|
776
|
+
True
|
777
|
+
|
778
|
+
>>> has_atleast_one_arg("abc", 123, enforce_type=str) # type: ignore[arg-type] # expected str, provided int.
|
779
|
+
False
|
780
|
+
|
781
|
+
>>> has_atleast_one_arg(None, None, None, falsy=True)
|
782
|
+
False
|
783
|
+
|
784
|
+
>>> has_atleast_one_arg("a", None, falsy=True)
|
785
|
+
True
|
786
|
+
|
787
|
+
>>> has_atleast_one_arg("a", None, enforce_type=str) # type: ignore[arg-type] # expected str, provided None.
|
788
|
+
True
|
789
|
+
|
790
|
+
>>> has_atleast_one_arg(None, [1, 2, 3], enforce_type=list)
|
791
|
+
True
|
792
|
+
|
793
|
+
>>> has_atleast_one_arg(None, [1, 2, 3], enforce_type=dict)
|
794
|
+
False
|
795
|
+
"""
|
796
|
+
return bool(
|
797
|
+
_collect_valid_args(first, *rest, falsy=falsy, enforce_type=enforce_type)
|
798
|
+
)
|
799
|
+
|
800
|
+
|
801
|
+
# endregion
|
802
|
+
|
803
|
+
|
804
|
+
# region Internal utility functions to handle positional variadic args.
|
805
|
+
def _filter_args(first, *rest, falsy):
|
806
|
+
args = (first, *rest)
|
807
|
+
if falsy:
|
808
|
+
return tuple(arg for arg in args if arg)
|
809
|
+
else:
|
810
|
+
return tuple(arg for arg in args if arg is not None)
|
811
|
+
|
812
|
+
|
813
|
+
def _collect_valid_args(first, *rest, falsy, enforce_type):
|
814
|
+
values = _filter_args(first, *rest, falsy=falsy)
|
815
|
+
if enforce_type is False:
|
816
|
+
return values
|
817
|
+
|
818
|
+
if values:
|
819
|
+
expected_type = enforce_type or type(values[0])
|
820
|
+
if all(isinstance(v, expected_type) for v in values):
|
821
|
+
return values
|
822
|
+
return ()
|
823
|
+
|
824
|
+
|
825
|
+
# endregion
|
826
|
+
|
827
|
+
# endregion
|