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,551 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
import inspect
|
|
5
|
+
import logging
|
|
6
|
+
import sys
|
|
7
|
+
import typing
|
|
8
|
+
from collections.abc import Callable as _RuntimeCallable
|
|
9
|
+
from dataclasses import is_dataclass
|
|
10
|
+
from numbers import Integral
|
|
11
|
+
from types import FunctionType, MethodType
|
|
12
|
+
from typing import (
|
|
13
|
+
Any,
|
|
14
|
+
Callable,
|
|
15
|
+
Dict,
|
|
16
|
+
Generator,
|
|
17
|
+
Iterable,
|
|
18
|
+
Literal,
|
|
19
|
+
Mapping,
|
|
20
|
+
Sequence,
|
|
21
|
+
Tuple,
|
|
22
|
+
Type,
|
|
23
|
+
TypedDict,
|
|
24
|
+
Union,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
import typing_extensions
|
|
28
|
+
from typing_extensions import (
|
|
29
|
+
NotRequired,
|
|
30
|
+
ParamSpec,
|
|
31
|
+
Required,
|
|
32
|
+
TypeGuard,
|
|
33
|
+
TypeIs,
|
|
34
|
+
TypeVar,
|
|
35
|
+
get_args,
|
|
36
|
+
get_origin,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
from pythonwrench.typing.classes import (
|
|
40
|
+
BuiltinCollection,
|
|
41
|
+
BuiltinNumber,
|
|
42
|
+
BuiltinScalar,
|
|
43
|
+
DataclassInstance,
|
|
44
|
+
NamedTupleInstance,
|
|
45
|
+
NoneType,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
T = TypeVar("T")
|
|
49
|
+
P = ParamSpec("P")
|
|
50
|
+
|
|
51
|
+
logger = logging.getLogger(__name__)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def check_args_types(fn: Callable[P, T]) -> Callable[P, T]:
|
|
55
|
+
"""Decorator to check argument types before call to a function.
|
|
56
|
+
|
|
57
|
+
Example
|
|
58
|
+
-------
|
|
59
|
+
>>> import pythonwrench as pw
|
|
60
|
+
>>> @pw.check_args_types
|
|
61
|
+
>>> def f(a: int, b: str) -> str:
|
|
62
|
+
>>> return a * b
|
|
63
|
+
>>> f(1, "a") # pass check
|
|
64
|
+
>>> f(1, 2) # raises TypeError from decorator
|
|
65
|
+
"""
|
|
66
|
+
if not isinstance(fn, (FunctionType, MethodType)):
|
|
67
|
+
msg = f"Invalid argument type {type(fn)}. (expected function or method)"
|
|
68
|
+
raise TypeError(msg)
|
|
69
|
+
|
|
70
|
+
parameters = inspect.signature(fn).parameters
|
|
71
|
+
annotations = {k: v.annotation for k, v in parameters.items()}
|
|
72
|
+
argnames = list(annotations.keys())
|
|
73
|
+
|
|
74
|
+
def _wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
|
|
75
|
+
"""Perform the wrapper operation."""
|
|
76
|
+
num_positional = len(args)
|
|
77
|
+
given_kwargs = dict(zip(argnames[:num_positional], args))
|
|
78
|
+
given_kwargs.update(kwargs)
|
|
79
|
+
|
|
80
|
+
msgs = []
|
|
81
|
+
for i, (k, v) in enumerate(given_kwargs.items()):
|
|
82
|
+
if isinstance_generic(v, annotations[k]):
|
|
83
|
+
continue
|
|
84
|
+
|
|
85
|
+
if i < num_positional:
|
|
86
|
+
msg = f" - invalid argument n°{i + 1} with value {v!r}; expected an instance of {annotations[k]}."
|
|
87
|
+
else:
|
|
88
|
+
msg = f" - invalid argument '{k}' with value {v!r}; expected an instance of {annotations[k]}."
|
|
89
|
+
msgs.append(msg)
|
|
90
|
+
|
|
91
|
+
if len(msgs) > 0:
|
|
92
|
+
msgs = [
|
|
93
|
+
f"{fn.__name__}() has {len(msgs)}/{len(given_kwargs)} invalid argument(s):",
|
|
94
|
+
] + msgs
|
|
95
|
+
msg = "\n".join(msgs)
|
|
96
|
+
raise TypeError(msg)
|
|
97
|
+
|
|
98
|
+
result = fn(*args, **kwargs)
|
|
99
|
+
return result
|
|
100
|
+
|
|
101
|
+
return _wrapper
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def isinstance_generic(
|
|
105
|
+
obj: Any,
|
|
106
|
+
class_or_tuple: Union[Type[T], None, Tuple[Type[T], ...], Any],
|
|
107
|
+
*,
|
|
108
|
+
check_only_first: bool = False,
|
|
109
|
+
) -> TypeIs[T]:
|
|
110
|
+
"""Improved isinstance(...) function that supports parametrized Union, TypedDict, Literal, Mapping or Iterable.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
obj: Object to check.
|
|
114
|
+
class_or_tuple: Type to check. Can be a parametrized type from `typing`.
|
|
115
|
+
check_only_first: If True, check only if first element when checking for Iterable[type]. defaults to False.
|
|
116
|
+
|
|
117
|
+
Example 1
|
|
118
|
+
---------
|
|
119
|
+
>>> isinstance_generic({"a": 1, "b": 2}, dict)
|
|
120
|
+
... True
|
|
121
|
+
>>> isinstance_generic({"a": 1, "b": 2}, dict[str, int])
|
|
122
|
+
... True
|
|
123
|
+
>>> isinstance_generic({"a": 1, "b": 2}, dict[str, str])
|
|
124
|
+
... False
|
|
125
|
+
>>> from typing import Literal
|
|
126
|
+
>>> isinstance_generic({"a": 1, "b": 2}, dict[str, Literal[1, 2]])
|
|
127
|
+
... True
|
|
128
|
+
|
|
129
|
+
"""
|
|
130
|
+
if class_or_tuple is Any or class_or_tuple is typing_extensions.Any:
|
|
131
|
+
return True
|
|
132
|
+
if class_or_tuple is None:
|
|
133
|
+
return obj is None
|
|
134
|
+
if isinstance(class_or_tuple, tuple):
|
|
135
|
+
return any(
|
|
136
|
+
isinstance_generic(obj, target_type_i) for target_type_i in class_or_tuple
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
if is_typed_dict(class_or_tuple):
|
|
140
|
+
return _isinstance_generic_typed_dict(obj, class_or_tuple)
|
|
141
|
+
|
|
142
|
+
origin = get_origin(class_or_tuple)
|
|
143
|
+
if origin is None:
|
|
144
|
+
return isinstance(obj, class_or_tuple) # type: ignore
|
|
145
|
+
|
|
146
|
+
# Special case for empty tuple because get_args(Tuple[()]) returns () and not ((),) in python >= 3.11
|
|
147
|
+
# More info at https://github.com/python/cpython/issues/91137
|
|
148
|
+
if class_or_tuple == Tuple[()]:
|
|
149
|
+
return obj == ()
|
|
150
|
+
|
|
151
|
+
args = get_args(class_or_tuple)
|
|
152
|
+
|
|
153
|
+
if _is_callable_type(origin):
|
|
154
|
+
if not callable(obj):
|
|
155
|
+
return False
|
|
156
|
+
if len(args) == 0:
|
|
157
|
+
return True
|
|
158
|
+
elif len(args) != 2:
|
|
159
|
+
msg = f"Invalid number of parameters in Callable. (found {len(args)} but expected 0 or 2)"
|
|
160
|
+
raise RuntimeError(msg)
|
|
161
|
+
|
|
162
|
+
sign = inspect.signature(obj)
|
|
163
|
+
type_params_annots, type_return_annot = args
|
|
164
|
+
obj_return_annot = sign.return_annotation
|
|
165
|
+
|
|
166
|
+
if obj_return_annot != type_return_annot:
|
|
167
|
+
return False
|
|
168
|
+
|
|
169
|
+
obj_params_annots = [param.annotation for param in sign.parameters.values()]
|
|
170
|
+
if type_params_annots is ...:
|
|
171
|
+
return True
|
|
172
|
+
|
|
173
|
+
if len(obj_params_annots) != len(type_params_annots):
|
|
174
|
+
return False
|
|
175
|
+
|
|
176
|
+
for obj_param_annot, type_param_annot in zip(
|
|
177
|
+
obj_params_annots, type_params_annots
|
|
178
|
+
):
|
|
179
|
+
if obj_param_annot != type_param_annot:
|
|
180
|
+
return False
|
|
181
|
+
|
|
182
|
+
return True
|
|
183
|
+
|
|
184
|
+
if len(args) == 0:
|
|
185
|
+
return isinstance_generic(obj, origin)
|
|
186
|
+
|
|
187
|
+
if origin is Union:
|
|
188
|
+
return any(isinstance_generic(obj, arg) for arg in args)
|
|
189
|
+
|
|
190
|
+
if origin is Literal:
|
|
191
|
+
return obj in args
|
|
192
|
+
|
|
193
|
+
if isinstance(obj, Generator):
|
|
194
|
+
msg = f"Invalid argument type {type(obj)}. (cannot check elements in generator)"
|
|
195
|
+
raise TypeError(msg)
|
|
196
|
+
|
|
197
|
+
if issubclass(origin, Generator):
|
|
198
|
+
msg = f"Invalid argument type {origin}. (cannot check generator type)"
|
|
199
|
+
raise TypeError(msg)
|
|
200
|
+
|
|
201
|
+
if issubclass(origin, Mapping):
|
|
202
|
+
assert len(args) == 2, f"{args=}"
|
|
203
|
+
if not isinstance_generic(obj, origin):
|
|
204
|
+
return False
|
|
205
|
+
|
|
206
|
+
return all(isinstance_generic(k, args[0]) for k in obj.keys()) and all(
|
|
207
|
+
isinstance_generic(v, args[1]) for v in obj.values()
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
if issubclass(origin, Tuple):
|
|
211
|
+
if not isinstance_generic(obj, origin):
|
|
212
|
+
return False
|
|
213
|
+
elif len(args) == 1 and args[0] == ():
|
|
214
|
+
return len(obj) == 0
|
|
215
|
+
elif len(args) == 2 and args[1] is ...:
|
|
216
|
+
if check_only_first:
|
|
217
|
+
args = (args[0],)
|
|
218
|
+
else:
|
|
219
|
+
args = tuple([args[0]] * len(obj))
|
|
220
|
+
elif len(obj) != len(args):
|
|
221
|
+
return False
|
|
222
|
+
return all(isinstance_generic(xi, ti) for xi, ti in zip(obj, args))
|
|
223
|
+
|
|
224
|
+
if issubclass(origin, Iterable):
|
|
225
|
+
if not isinstance_generic(obj, origin):
|
|
226
|
+
return False
|
|
227
|
+
|
|
228
|
+
if check_only_first:
|
|
229
|
+
try:
|
|
230
|
+
return isinstance_generic(next(iter(obj)), args[0])
|
|
231
|
+
except StopIteration:
|
|
232
|
+
return True
|
|
233
|
+
else:
|
|
234
|
+
return all(isinstance_generic(xi, args[0]) for xi in obj)
|
|
235
|
+
|
|
236
|
+
msg = f"Unsupported type {class_or_tuple}. (expected unparametrized type or parametrized Union, TypedDict, Literal, Mapping or Iterable)"
|
|
237
|
+
raise NotImplementedError(msg)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _isinstance_generic_typed_dict(x: Any, target_type: type) -> bool:
|
|
241
|
+
"""Perform the isinstance generic typed dict operation."""
|
|
242
|
+
if not isinstance_generic(x, Dict[str, Any]):
|
|
243
|
+
return False
|
|
244
|
+
|
|
245
|
+
total: bool = target_type.__total__
|
|
246
|
+
annotations = target_type.__annotations__
|
|
247
|
+
|
|
248
|
+
required_annotations = {}
|
|
249
|
+
optional_annotations = {}
|
|
250
|
+
for k, v in annotations.items():
|
|
251
|
+
origin = get_origin(v)
|
|
252
|
+
if origin is Required:
|
|
253
|
+
required_annotations[k] = v
|
|
254
|
+
elif origin is NotRequired:
|
|
255
|
+
optional_annotations[k] = v
|
|
256
|
+
elif total:
|
|
257
|
+
required_annotations[k] = v
|
|
258
|
+
else:
|
|
259
|
+
optional_annotations[k] = v
|
|
260
|
+
|
|
261
|
+
if not set(required_annotations.keys()).issubset(x.keys()):
|
|
262
|
+
return False
|
|
263
|
+
|
|
264
|
+
annotations_set = set(required_annotations.keys()) | set(
|
|
265
|
+
optional_annotations.keys()
|
|
266
|
+
)
|
|
267
|
+
if not annotations_set.issuperset(x.keys()):
|
|
268
|
+
return False
|
|
269
|
+
|
|
270
|
+
for k, v in required_annotations.items():
|
|
271
|
+
origin = get_origin(v)
|
|
272
|
+
if origin is Required:
|
|
273
|
+
v = get_args(v)[0]
|
|
274
|
+
|
|
275
|
+
if not isinstance_generic(x[k], v):
|
|
276
|
+
return False
|
|
277
|
+
|
|
278
|
+
for k, v in optional_annotations.items():
|
|
279
|
+
if k not in x:
|
|
280
|
+
continue
|
|
281
|
+
origin = get_origin(v)
|
|
282
|
+
if origin is NotRequired:
|
|
283
|
+
v = get_args(v)[0]
|
|
284
|
+
if not isinstance_generic(x[k], v):
|
|
285
|
+
return False
|
|
286
|
+
|
|
287
|
+
return True
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def is_builtin_collection(x: Any, *, strict: bool = False) -> TypeIs[BuiltinCollection]:
|
|
291
|
+
"""Returns True if x is an instance of a builtin collection type (list, tuple, dict, set, frozenset).
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
x: Object to check.
|
|
295
|
+
strict: If True, it will not consider custom subtypes of builtins as builtin collections. defaults to False.
|
|
296
|
+
"""
|
|
297
|
+
if strict and not is_builtin_obj(x):
|
|
298
|
+
return False
|
|
299
|
+
return isinstance(x, (list, tuple, dict, set, frozenset))
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def is_builtin_number(x: Any, *, strict: bool = False) -> TypeIs[BuiltinNumber]:
|
|
303
|
+
"""Returns True if x is an instance of a builtin number type (int, float, bool, complex).
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
x: Object to check.
|
|
307
|
+
strict: If True, it will not consider custom subtypes of builtins as builtin numbers. defaults to False.
|
|
308
|
+
"""
|
|
309
|
+
if strict and not is_builtin_obj(x):
|
|
310
|
+
return False
|
|
311
|
+
return isinstance(x, (int, float, bool, complex))
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def is_builtin_obj(x: Any) -> bool:
|
|
315
|
+
"""Returns True if object is an instance of a builtin object.
|
|
316
|
+
|
|
317
|
+
Note: If the object is an instance of a custom subtype of a builtin object, this function returns False.
|
|
318
|
+
"""
|
|
319
|
+
return x.__class__.__module__ == "builtins" and not isinstance(x, type)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def is_builtin_scalar(x: Any, *, strict: bool = False) -> TypeIs[BuiltinScalar]:
|
|
323
|
+
"""Returns True if x is an instance of a builtin scalar type (int, float, bool, complex, NoneType, str, bytes).
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
x: Object to check.
|
|
327
|
+
strict: If True, it will not consider subtypes of builtins as builtin scalars. defaults to False.
|
|
328
|
+
"""
|
|
329
|
+
if strict and not is_builtin_obj(x):
|
|
330
|
+
return False
|
|
331
|
+
return isinstance(x, (int, float, bool, complex, NoneType, str, bytes))
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def is_dataclass_instance(x: Any) -> TypeIs[DataclassInstance]:
|
|
335
|
+
"""Returns True if argument is a dataclass.
|
|
336
|
+
|
|
337
|
+
Unlike function `dataclasses.is_dataclass`, this function returns False for a dataclass type.
|
|
338
|
+
"""
|
|
339
|
+
return not isinstance(x, type) and isinstance_generic(x, DataclassInstance)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def is_dataclass_type(x: Any) -> TypeIs[Type[DataclassInstance]]:
|
|
343
|
+
"""Return whether dataclass type."""
|
|
344
|
+
return isinstance(x, type) and is_dataclass(x)
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def is_iterable_bool(
|
|
348
|
+
x: Any,
|
|
349
|
+
*,
|
|
350
|
+
accept_generator: bool = True,
|
|
351
|
+
) -> TypeIs[Iterable[bool]]:
|
|
352
|
+
"""Return whether iterable bool."""
|
|
353
|
+
if not accept_generator and isinstance(x, Generator):
|
|
354
|
+
return False
|
|
355
|
+
return isinstance_generic(x, Iterable[bool])
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def is_iterable_bytes_or_list(
|
|
359
|
+
x: Any,
|
|
360
|
+
*,
|
|
361
|
+
accept_generator: bool = True,
|
|
362
|
+
) -> TypeIs[Iterable[Union[bytes, list]]]:
|
|
363
|
+
"""Return whether iterable bytes or list."""
|
|
364
|
+
if not accept_generator and isinstance(x, Generator):
|
|
365
|
+
return False
|
|
366
|
+
return isinstance_generic(x, Iterable[Union[bytes, list]])
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def is_iterable_float(
|
|
370
|
+
x: Any,
|
|
371
|
+
*,
|
|
372
|
+
accept_generator: bool = True,
|
|
373
|
+
) -> TypeIs[Iterable[float]]:
|
|
374
|
+
"""Return whether iterable float."""
|
|
375
|
+
if not accept_generator and isinstance(x, Generator):
|
|
376
|
+
return False
|
|
377
|
+
return isinstance_generic(x, Iterable[float])
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def is_iterable_int(
|
|
381
|
+
x: Any,
|
|
382
|
+
*,
|
|
383
|
+
accept_bool: bool = True,
|
|
384
|
+
accept_generator: bool = True,
|
|
385
|
+
) -> TypeIs[Iterable[int]]:
|
|
386
|
+
"""Return whether iterable int."""
|
|
387
|
+
if not accept_generator and isinstance(x, Generator):
|
|
388
|
+
return False
|
|
389
|
+
return isinstance_generic(x, Iterable[int]) and (
|
|
390
|
+
accept_bool or not isinstance_generic(x, Iterable[bool])
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def is_iterable_integral(
|
|
395
|
+
x: Any,
|
|
396
|
+
*,
|
|
397
|
+
accept_generator: bool = True,
|
|
398
|
+
) -> TypeIs[Iterable[Integral]]:
|
|
399
|
+
"""Return whether iterable integral."""
|
|
400
|
+
if not accept_generator and isinstance(x, Generator):
|
|
401
|
+
return False
|
|
402
|
+
return isinstance_generic(x, Iterable[Integral])
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def is_iterable_str(
|
|
406
|
+
x: Any,
|
|
407
|
+
*,
|
|
408
|
+
accept_str: bool = True,
|
|
409
|
+
accept_generator: bool = True,
|
|
410
|
+
) -> TypeGuard[Iterable[str]]:
|
|
411
|
+
"""Return whether iterable str."""
|
|
412
|
+
if isinstance(x, str):
|
|
413
|
+
return accept_str
|
|
414
|
+
if isinstance(x, Generator):
|
|
415
|
+
return accept_generator and all(isinstance(xi, str) for xi in x)
|
|
416
|
+
return isinstance_generic(x, Iterable[str])
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def is_namedtuple_instance(x: Any) -> TypeIs[NamedTupleInstance]:
|
|
420
|
+
"""Returns True if argument is a NamedTuple."""
|
|
421
|
+
return not isinstance(x, type) and isinstance_generic(x, NamedTupleInstance)
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def is_sequence_str(
|
|
425
|
+
x: Any,
|
|
426
|
+
*,
|
|
427
|
+
accept_str: bool = True,
|
|
428
|
+
) -> TypeGuard[Sequence[str]]:
|
|
429
|
+
"""Return whether sequence str."""
|
|
430
|
+
return (accept_str and isinstance(x, str)) or (
|
|
431
|
+
not isinstance(x, str)
|
|
432
|
+
and isinstance(x, Sequence)
|
|
433
|
+
and all(isinstance(xi, str) for xi in x)
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def is_typed_dict(x: Any) -> TypeGuard[type]:
|
|
438
|
+
"""Return whether typed dict."""
|
|
439
|
+
if sys.version_info.major == 3 and sys.version_info.minor < 9:
|
|
440
|
+
return x.__class__.__name__ == "_TypedDictMeta"
|
|
441
|
+
else:
|
|
442
|
+
return hasattr(x, "__orig_bases__") and TypedDict in x.__orig_bases__
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def is_parameterized(x: Any) -> bool:
|
|
446
|
+
"""Returns True if object is a parametrized object like `Iterable[int]`, `Mapping[str, float]`, etc."""
|
|
447
|
+
return get_origin(x) is not None and len(get_args(x)) > 0
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
_COLLECTION_ALIASES = [
|
|
451
|
+
typing.Iterable,
|
|
452
|
+
typing.Iterator,
|
|
453
|
+
typing.Reversible,
|
|
454
|
+
typing.Generator,
|
|
455
|
+
typing.AsyncIterable,
|
|
456
|
+
typing.AsyncIterator,
|
|
457
|
+
typing.AsyncGenerator,
|
|
458
|
+
typing.Collection,
|
|
459
|
+
typing.Container,
|
|
460
|
+
typing.Sized,
|
|
461
|
+
typing.Sequence,
|
|
462
|
+
typing.MutableSequence,
|
|
463
|
+
typing.Set,
|
|
464
|
+
typing.MutableSet,
|
|
465
|
+
typing.Mapping,
|
|
466
|
+
typing.MutableMapping,
|
|
467
|
+
typing.MappingView,
|
|
468
|
+
typing.KeysView,
|
|
469
|
+
typing.ItemsView,
|
|
470
|
+
typing.ValuesView,
|
|
471
|
+
typing.Awaitable,
|
|
472
|
+
typing.Coroutine,
|
|
473
|
+
typing.Callable,
|
|
474
|
+
typing.Hashable,
|
|
475
|
+
typing_extensions.Iterable,
|
|
476
|
+
typing_extensions.Iterator,
|
|
477
|
+
typing_extensions.Reversible,
|
|
478
|
+
typing_extensions.Generator,
|
|
479
|
+
typing_extensions.AsyncIterable,
|
|
480
|
+
typing_extensions.AsyncIterator,
|
|
481
|
+
typing_extensions.AsyncGenerator,
|
|
482
|
+
typing_extensions.Collection,
|
|
483
|
+
typing_extensions.Container,
|
|
484
|
+
typing_extensions.Sized,
|
|
485
|
+
typing_extensions.Sequence,
|
|
486
|
+
typing_extensions.MutableSequence,
|
|
487
|
+
typing_extensions.Set,
|
|
488
|
+
typing_extensions.MutableSet,
|
|
489
|
+
typing_extensions.Mapping,
|
|
490
|
+
typing_extensions.MutableMapping,
|
|
491
|
+
typing_extensions.MappingView,
|
|
492
|
+
typing_extensions.KeysView,
|
|
493
|
+
typing_extensions.ItemsView,
|
|
494
|
+
typing_extensions.ValuesView,
|
|
495
|
+
typing_extensions.Awaitable,
|
|
496
|
+
typing_extensions.Coroutine,
|
|
497
|
+
typing_extensions.Callable,
|
|
498
|
+
typing_extensions.Hashable,
|
|
499
|
+
]
|
|
500
|
+
|
|
501
|
+
_SPECIAL_FORMS = [] + [
|
|
502
|
+
getattr(module, candidate_name, None)
|
|
503
|
+
for module in (typing, typing_extensions)
|
|
504
|
+
for candidate_name in [
|
|
505
|
+
"Any",
|
|
506
|
+
"NoReturn",
|
|
507
|
+
"Union",
|
|
508
|
+
"Optional",
|
|
509
|
+
"Literal",
|
|
510
|
+
"Final",
|
|
511
|
+
"ClassVar",
|
|
512
|
+
"TypeVar",
|
|
513
|
+
"Annotated",
|
|
514
|
+
"Never",
|
|
515
|
+
"Self",
|
|
516
|
+
"Required",
|
|
517
|
+
"NotRequired",
|
|
518
|
+
"TypeGuard",
|
|
519
|
+
"TypeIs",
|
|
520
|
+
"Concatenate",
|
|
521
|
+
"ParamSpec",
|
|
522
|
+
"ParamSpecArgs",
|
|
523
|
+
"ParamSpecKwargs",
|
|
524
|
+
"TypeVarTuple",
|
|
525
|
+
"Unpack",
|
|
526
|
+
]
|
|
527
|
+
if getattr(module, candidate_name, None) is not None
|
|
528
|
+
]
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def is_collection_alias(x: Any) -> bool:
|
|
532
|
+
"""Returns True if object is a non-parameterized collection alias type."""
|
|
533
|
+
return _safe_isin(x, _COLLECTION_ALIASES)
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def is_special_form(x: Any) -> bool:
|
|
537
|
+
"""Returns True if object is a typing special form like `Any`."""
|
|
538
|
+
return _safe_isin(x, _SPECIAL_FORMS)
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def _safe_isin(x: Any, targets: Iterable) -> bool:
|
|
542
|
+
"""Perform the safe isin operation."""
|
|
543
|
+
for alias in targets:
|
|
544
|
+
if (x == alias) is True:
|
|
545
|
+
return True
|
|
546
|
+
return False
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def _is_callable_type(x: Any) -> bool:
|
|
550
|
+
"""Perform the is callable type operation."""
|
|
551
|
+
return x in (Callable, _RuntimeCallable)
|