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.
@@ -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