pythonwrench 0.6.0__tar.gz → 0.6.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pythonwrench-0.6.0/src/pythonwrench.egg-info → pythonwrench-0.6.1}/PKG-INFO +1 -1
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/pyproject.toml +3 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/__init__.py +7 -1
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/_core.py +6 -2
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/argparse.py +131 -23
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/concurrent.py +6 -2
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/difflib.py +9 -6
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/entrypoints/info.py +9 -11
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/functools.py +11 -3
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/importlib.py +39 -21
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/typing/checks.py +4 -1
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/warnings.py +16 -5
- {pythonwrench-0.6.0 → pythonwrench-0.6.1/src/pythonwrench.egg-info}/PKG-INFO +1 -1
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench.egg-info/entry_points.txt +3 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/tests/test_argparse.py +19 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/tests/test_typing.py +11 -2
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/LICENSE +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/README.md +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/setup.cfg +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/setup.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/__main__.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/abc.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/cast.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/checksum.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/collections/__init__.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/collections/collections.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/collections/prop.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/collections/reducers.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/csv.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/dataclasses.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/datetime.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/disk_cache.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/entrypoints/safe_rmdir.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/entrypoints/tree.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/enum.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/hashlib.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/inspect.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/json.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/jsonl.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/logging.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/math.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/os.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/pickle.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/random.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/re.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/semver.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/serialization/__init__.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/serialization/_core.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/serialization/csv.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/serialization/json.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/serialization/jsonl.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/serialization/pickle.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/time.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/typing/__init__.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench/typing/classes.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench.egg-info/SOURCES.txt +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench.egg-info/dependency_links.txt +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench.egg-info/requires.txt +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/src/pythonwrench.egg-info/top_level.txt +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/tests/test_abc.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/tests/test_cast.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/tests/test_checksum.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/tests/test_collections.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/tests/test_csv.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/tests/test_dataclasses.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/tests/test_difflib.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/tests/test_disk_cache.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/tests/test_entrypoints.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/tests/test_enum.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/tests/test_functools.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/tests/test_hashlib.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/tests/test_importlib.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/tests/test_inspect.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/tests/test_json.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/tests/test_jsonl.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/tests/test_logging.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/tests/test_math.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/tests/test_os.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/tests/test_random.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/tests/test_readme.py +0 -0
- {pythonwrench-0.6.0 → pythonwrench-0.6.1}/tests/test_semver.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pythonwrench
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.1
|
|
4
4
|
Summary: Python library with tools for typing, manipulating collections, and more!
|
|
5
5
|
Author-email: "Étienne Labbé (Labbeti)" <labbeti.pub@gmail.com>
|
|
6
6
|
Maintainer-email: "Étienne Labbé (Labbeti)" <labbeti.pub@gmail.com>
|
|
@@ -45,6 +45,9 @@ Tracker = "https://github.com/Labbeti/pythonwrench/issues"
|
|
|
45
45
|
pythonwrench-info = "pythonwrench.entrypoints.info:main_info"
|
|
46
46
|
pythonwrench-tree = "pythonwrench.entrypoints.tree:main_tree"
|
|
47
47
|
pythonwrench-safe-rmdir = "pythonwrench.entrypoints.safe_rmdir:main_safe_rmdir"
|
|
48
|
+
pw-info = "pythonwrench.entrypoints.info:main_info"
|
|
49
|
+
pw-tree = "pythonwrench.entrypoints.tree:main_tree"
|
|
50
|
+
pw-safe-rmdir = "pythonwrench.entrypoints.safe_rmdir:main_safe_rmdir"
|
|
48
51
|
pyw-info = "pythonwrench.entrypoints.info:main_info"
|
|
49
52
|
pyw-tree = "pythonwrench.entrypoints.tree:main_tree"
|
|
50
53
|
pyw-safe-rmdir = "pythonwrench.entrypoints.safe_rmdir:main_safe_rmdir"
|
|
@@ -9,7 +9,7 @@ __author_email__ = "labbeti.pub@gmail.com"
|
|
|
9
9
|
__license__ = "MIT"
|
|
10
10
|
__maintainer__ = "Étienne Labbé (Labbeti)"
|
|
11
11
|
__status__ = "Development"
|
|
12
|
-
__version__ = "0.6.
|
|
12
|
+
__version__ = "0.6.1"
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
from typing import TYPE_CHECKING
|
|
@@ -51,6 +51,7 @@ if TYPE_CHECKING or lazy is None:
|
|
|
51
51
|
# Global library imports
|
|
52
52
|
from .abc import Singleton
|
|
53
53
|
from .argparse import (
|
|
54
|
+
add_dataclass_fields_to_parser,
|
|
54
55
|
new_parser_from_dataclass,
|
|
55
56
|
parse_args_using_dataclass,
|
|
56
57
|
parse_to,
|
|
@@ -115,7 +116,9 @@ if TYPE_CHECKING or lazy is None:
|
|
|
115
116
|
)
|
|
116
117
|
from .hashlib import hash_file
|
|
117
118
|
from .importlib import (
|
|
119
|
+
ModulePlaceholder,
|
|
118
120
|
Placeholder,
|
|
121
|
+
import_if_available,
|
|
119
122
|
is_available_package,
|
|
120
123
|
is_editable_package,
|
|
121
124
|
reload_editable_packages,
|
|
@@ -264,6 +267,7 @@ else:
|
|
|
264
267
|
],
|
|
265
268
|
submod_attrs={
|
|
266
269
|
"argparse": [
|
|
270
|
+
"add_dataclass_fields_to_parser",
|
|
267
271
|
"new_parser_from_dataclass",
|
|
268
272
|
"parse_args_using_dataclass",
|
|
269
273
|
"parse_to",
|
|
@@ -355,6 +359,8 @@ else:
|
|
|
355
359
|
],
|
|
356
360
|
"hashlib": ["hash_file"],
|
|
357
361
|
"importlib": [
|
|
362
|
+
"import_if_available",
|
|
363
|
+
"ModulePlaceholder",
|
|
358
364
|
"is_available_package",
|
|
359
365
|
"is_editable_package",
|
|
360
366
|
"reload_editable_packages",
|
|
@@ -39,10 +39,14 @@ def return_none(*args, **kwargs) -> None:
|
|
|
39
39
|
def _decorator_factory(
|
|
40
40
|
inner_fn: Optional[Callable[P, U]],
|
|
41
41
|
*,
|
|
42
|
-
pre_fn: Callable[..., Any] =
|
|
43
|
-
post_fn: Callable[..., Any] =
|
|
42
|
+
pre_fn: Optional[Callable[..., Any]] = None,
|
|
43
|
+
post_fn: Optional[Callable[..., Any]] = None,
|
|
44
44
|
) -> Callable[[Callable[P, U]], Callable[P, U]]:
|
|
45
45
|
"""Deprecated decorator for function aliases."""
|
|
46
|
+
if pre_fn is None:
|
|
47
|
+
pre_fn = return_none
|
|
48
|
+
if post_fn is None:
|
|
49
|
+
post_fn = return_none
|
|
46
50
|
|
|
47
51
|
def wrapper_factory(fn: Callable[P, U]) -> Callable[P, U]:
|
|
48
52
|
if inner_fn is None:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
|
|
4
|
+
import re
|
|
4
5
|
from argparse import ArgumentParser
|
|
5
6
|
from dataclasses import MISSING, fields
|
|
6
7
|
from functools import partial
|
|
@@ -25,13 +26,15 @@ except ImportError:
|
|
|
25
26
|
# support older python versions
|
|
26
27
|
UnionType = Any
|
|
27
28
|
|
|
29
|
+
from pythonwrench.functools import filter_and_call
|
|
28
30
|
from pythonwrench.typing.classes import Dataclass, DataclassInstance, NoneType
|
|
31
|
+
from pythonwrench.warnings import deprecated_alias
|
|
29
32
|
|
|
30
33
|
T = TypeVar("T")
|
|
31
34
|
T_Dataclass = TypeVar("T_Dataclass", bound=Dataclass)
|
|
32
35
|
T_DataclassInstance = TypeVar("T_DataclassInstance", bound=DataclassInstance)
|
|
33
|
-
TargetType = Union[Type[T], UnionType, "Type[Literal]"]
|
|
34
|
-
|
|
36
|
+
TargetType = Union[Type[T], UnionType, "Type[Literal]", "Type[Optional]"]
|
|
37
|
+
ListParsing = Literal["argparse", "brackets"]
|
|
35
38
|
|
|
36
39
|
DEFAULT_TRUE_VALUES = ("True", "t", "yes", "y", "1")
|
|
37
40
|
DEFAULT_FALSE_VALUES = ("False", "f", "no", "n", "0")
|
|
@@ -42,24 +45,41 @@ _SCALARS_TARGET_TYPES = (str, int, float, None, NoneType, bool)
|
|
|
42
45
|
|
|
43
46
|
def parse_args_using_dataclass(
|
|
44
47
|
dataclass_type: Type[T_DataclassInstance],
|
|
48
|
+
*,
|
|
45
49
|
args: Optional[Iterable[str]] = None,
|
|
50
|
+
parser: Optional[ArgumentParser] = None,
|
|
51
|
+
list_parsing: ListParsing = "argparse",
|
|
46
52
|
) -> T_DataclassInstance:
|
|
47
53
|
"""Converts prog args to a typed dataclass using argparse.
|
|
48
54
|
|
|
49
55
|
Currently only supports dataclasses that contains only builtin scalars: str, int, float, None, bool OR list of builtin scalars.
|
|
50
56
|
"""
|
|
51
|
-
|
|
52
|
-
parser =
|
|
57
|
+
init_parser = parser
|
|
58
|
+
parser = add_dataclass_fields_to_parser(
|
|
59
|
+
dataclass_type,
|
|
60
|
+
parser=parser,
|
|
61
|
+
list_parsing=list_parsing,
|
|
62
|
+
)
|
|
53
63
|
parsed, argv = parser.parse_known_args(args)
|
|
54
64
|
if len(argv) > 0:
|
|
55
65
|
raise ValueError(f"Found {len(argv)} unknown arguments: {argv}.")
|
|
56
|
-
|
|
66
|
+
|
|
67
|
+
if init_parser is None:
|
|
68
|
+
instance = dataclass_type(**parsed.__dict__)
|
|
69
|
+
else:
|
|
70
|
+
instance = filter_and_call(
|
|
71
|
+
dataclass_type,
|
|
72
|
+
_fill_all_arguments=True,
|
|
73
|
+
**parsed.__dict__,
|
|
74
|
+
)
|
|
57
75
|
return instance
|
|
58
76
|
|
|
59
77
|
|
|
60
|
-
def
|
|
78
|
+
def add_dataclass_fields_to_parser(
|
|
61
79
|
dataclass_type: Type[T_DataclassInstance],
|
|
80
|
+
*,
|
|
62
81
|
parser: Optional[ArgumentParser],
|
|
82
|
+
list_parsing: ListParsing = "argparse",
|
|
63
83
|
) -> ArgumentParser:
|
|
64
84
|
if parser is None:
|
|
65
85
|
parser = ArgumentParser()
|
|
@@ -79,13 +99,21 @@ def new_parser_from_dataclass(
|
|
|
79
99
|
msg = f"Invalid field {field.name}: found values for default and default_factory."
|
|
80
100
|
raise ValueError(msg)
|
|
81
101
|
|
|
82
|
-
|
|
102
|
+
inner_kwds = _get_kwds_for_type(field.type, list_parsing)
|
|
103
|
+
kwds.update(inner_kwds)
|
|
83
104
|
parser.add_argument(*posargs, **kwds)
|
|
84
105
|
|
|
85
106
|
return parser
|
|
86
107
|
|
|
87
108
|
|
|
88
|
-
|
|
109
|
+
@deprecated_alias(add_dataclass_fields_to_parser)
|
|
110
|
+
def new_parser_from_dataclass(*args, **kwargs): ...
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _get_kwds_for_type(
|
|
114
|
+
field_type: Any,
|
|
115
|
+
list_parsing: ListParsing = "argparse",
|
|
116
|
+
) -> Dict[str, Any]:
|
|
89
117
|
kwds = {}
|
|
90
118
|
|
|
91
119
|
type_origin = get_origin(field_type)
|
|
@@ -101,17 +129,49 @@ def _get_kwds_for_type(field_type: Any) -> Dict[str, Any]:
|
|
|
101
129
|
msg = f"Invalid argument {field_type=}. (expected homogeneous types in {type_origin})"
|
|
102
130
|
raise TypeError(msg)
|
|
103
131
|
|
|
104
|
-
if
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
132
|
+
if (
|
|
133
|
+
(field_type in _SCALARS_TARGET_TYPES)
|
|
134
|
+
or (
|
|
135
|
+
type_origin
|
|
136
|
+
in (
|
|
137
|
+
Literal,
|
|
138
|
+
Optional,
|
|
139
|
+
UnionType,
|
|
140
|
+
Union,
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
or (type_origin in (list, Iterable) and list_parsing == "brackets")
|
|
109
144
|
):
|
|
110
|
-
|
|
111
|
-
|
|
145
|
+
inner_kwds = _get_kwds_for_scalar_type(field_type, field_type, list_parsing)
|
|
146
|
+
kwds.update(inner_kwds)
|
|
147
|
+
|
|
148
|
+
elif type_origin in (list, Iterable):
|
|
112
149
|
item_type = type_args[0]
|
|
113
|
-
|
|
114
|
-
|
|
150
|
+
inner_kwds = _get_kwds_for_scalar_type(item_type, field_type, list_parsing)
|
|
151
|
+
inner_kwds["nargs"] = "*"
|
|
152
|
+
|
|
153
|
+
# TODO: rm
|
|
154
|
+
# if list_parsing == "argparse":
|
|
155
|
+
# kwds["nargs"] = "*"
|
|
156
|
+
# elif list_parsing == "brackets":
|
|
157
|
+
# parse_fn = inner_kwds.pop("type")
|
|
158
|
+
|
|
159
|
+
# def brackets_parse(x: str) -> Any:
|
|
160
|
+
# x = (
|
|
161
|
+
# x.strip()
|
|
162
|
+
# .removeprefix("[")
|
|
163
|
+
# .removesuffix("]")
|
|
164
|
+
# .removesuffix(",")
|
|
165
|
+
# .strip()
|
|
166
|
+
# )
|
|
167
|
+
# return list(map(parse_fn, x.split(",")))
|
|
168
|
+
|
|
169
|
+
# inner_kwds["type"] = brackets_parse
|
|
170
|
+
# else:
|
|
171
|
+
# msg = f"Invalid argument {list_parsing=}. (expected one of {get_args(ListParsing)})"
|
|
172
|
+
# raise ValueError(msg)
|
|
173
|
+
|
|
174
|
+
kwds.update(inner_kwds)
|
|
115
175
|
else:
|
|
116
176
|
msg = f"Unsupported type {field_type}."
|
|
117
177
|
raise TypeError(msg)
|
|
@@ -119,11 +179,20 @@ def _get_kwds_for_type(field_type: Any) -> Dict[str, Any]:
|
|
|
119
179
|
return kwds
|
|
120
180
|
|
|
121
181
|
|
|
122
|
-
def _get_kwds_for_scalar_type(
|
|
182
|
+
def _get_kwds_for_scalar_type(
|
|
183
|
+
type_: Any, from_field_type: Any, list_parsing: ListParsing
|
|
184
|
+
) -> Dict[str, Any]:
|
|
123
185
|
type_origin = get_origin(type_)
|
|
124
186
|
kwds = {}
|
|
125
187
|
|
|
126
|
-
if
|
|
188
|
+
if (
|
|
189
|
+
type_ in _SCALARS_TARGET_TYPES
|
|
190
|
+
or type_origin in (UnionType, Union, Optional)
|
|
191
|
+
or (
|
|
192
|
+
get_origin(from_field_type) in (list, Iterable)
|
|
193
|
+
and list_parsing == "brackets"
|
|
194
|
+
)
|
|
195
|
+
):
|
|
127
196
|
pass
|
|
128
197
|
elif type_origin is Literal:
|
|
129
198
|
type_args = get_args(type_)
|
|
@@ -132,7 +201,7 @@ def _get_kwds_for_scalar_type(type_: Any, from_field_type: Any) -> Dict[str, Any
|
|
|
132
201
|
msg = f"Unsupported dataclass member type {type_} from {from_field_type}."
|
|
133
202
|
raise TypeError(msg)
|
|
134
203
|
|
|
135
|
-
kwds["type"] = parse_to(type_) # type: ignore
|
|
204
|
+
kwds["type"] = parse_to(type_, list_parsing=list_parsing) # type: ignore
|
|
136
205
|
return kwds
|
|
137
206
|
|
|
138
207
|
|
|
@@ -143,6 +212,7 @@ def parse_to(
|
|
|
143
212
|
true_values: Union[str, Iterable[str]] = DEFAULT_TRUE_VALUES,
|
|
144
213
|
false_values: Union[str, Iterable[str]] = DEFAULT_FALSE_VALUES,
|
|
145
214
|
none_values: Union[str, Iterable[str]] = DEFAULT_NONE_VALUES,
|
|
215
|
+
list_parsing: ListParsing = "argparse",
|
|
146
216
|
) -> Callable[[str], T]:
|
|
147
217
|
"""Returns a callable that convert string value to target type safely.
|
|
148
218
|
|
|
@@ -155,6 +225,7 @@ def parse_to(
|
|
|
155
225
|
true_values=true_values,
|
|
156
226
|
false_values=false_values,
|
|
157
227
|
none_values=none_values,
|
|
228
|
+
list_parsing=list_parsing,
|
|
158
229
|
)
|
|
159
230
|
|
|
160
231
|
|
|
@@ -166,6 +237,7 @@ def str_to_type(
|
|
|
166
237
|
true_values: Union[str, Iterable[str]] = DEFAULT_TRUE_VALUES,
|
|
167
238
|
false_values: Union[str, Iterable[str]] = DEFAULT_FALSE_VALUES,
|
|
168
239
|
none_values: Union[str, Iterable[str]] = DEFAULT_NONE_VALUES,
|
|
240
|
+
list_parsing: ListParsing = "argparse",
|
|
169
241
|
) -> T:
|
|
170
242
|
"""Convert string values to target type safely. Intended for argparse arguments.
|
|
171
243
|
|
|
@@ -181,6 +253,7 @@ def str_to_type(
|
|
|
181
253
|
true_values=true_values,
|
|
182
254
|
false_values=false_values,
|
|
183
255
|
none_values=none_values,
|
|
256
|
+
list_parsing=list_parsing,
|
|
184
257
|
)
|
|
185
258
|
if isinstance(result, Exception):
|
|
186
259
|
raise result
|
|
@@ -303,12 +376,14 @@ def _str_to_type_impl(
|
|
|
303
376
|
true_values: Union[str, Iterable[str]] = DEFAULT_TRUE_VALUES,
|
|
304
377
|
false_values: Union[str, Iterable[str]] = DEFAULT_FALSE_VALUES,
|
|
305
378
|
none_values: Union[str, Iterable[str]] = DEFAULT_NONE_VALUES,
|
|
379
|
+
list_parsing: ListParsing = "argparse",
|
|
306
380
|
) -> Union[T, Exception]:
|
|
307
381
|
kwds: Dict[str, Any] = dict(
|
|
308
382
|
case_sensitive=case_sensitive,
|
|
309
383
|
true_values=true_values,
|
|
310
384
|
false_values=false_values,
|
|
311
385
|
none_values=none_values,
|
|
386
|
+
list_parsing=list_parsing,
|
|
312
387
|
)
|
|
313
388
|
if target_type in _SCALARS_TARGET_TYPES:
|
|
314
389
|
return _str_to_scalar_impl(x, target_type, **kwds)
|
|
@@ -329,7 +404,31 @@ def _str_to_type_impl(
|
|
|
329
404
|
raise ValueError(msg)
|
|
330
405
|
return scalar
|
|
331
406
|
|
|
332
|
-
|
|
407
|
+
if origin in (list, Iterable):
|
|
408
|
+
if list_parsing != "brackets":
|
|
409
|
+
raise ValueError
|
|
410
|
+
|
|
411
|
+
args = get_args(target_type)
|
|
412
|
+
|
|
413
|
+
if len(args) == 0:
|
|
414
|
+
target_item_type = str
|
|
415
|
+
elif len(args) == 1:
|
|
416
|
+
target_item_type = args[0]
|
|
417
|
+
else:
|
|
418
|
+
raise ValueError
|
|
419
|
+
|
|
420
|
+
x = re.sub(r"^\s*\[\s*(|.*[^,\s])(|\s*,)\s*\]\s*$", r"\1", x)
|
|
421
|
+
x_list = x.split(",")
|
|
422
|
+
|
|
423
|
+
y_list = []
|
|
424
|
+
for xi in x_list:
|
|
425
|
+
yi = _str_to_type_impl(xi, target_item_type, **kwds) # type: ignore
|
|
426
|
+
if isinstance(yi, Exception):
|
|
427
|
+
return yi
|
|
428
|
+
y_list.append(yi)
|
|
429
|
+
return y_list # type: ignore
|
|
430
|
+
|
|
431
|
+
if getattr(target_type, "__name__", None) == "Optional":
|
|
333
432
|
args = (None,) + get_args(target_type)
|
|
334
433
|
elif origin == Union or origin.__name__ in ("Union", "UnionType"): # type: ignore
|
|
335
434
|
args = get_args(target_type)
|
|
@@ -362,23 +461,31 @@ def _str_to_scalar_impl(
|
|
|
362
461
|
true_values: Union[str, Iterable[str]] = DEFAULT_TRUE_VALUES,
|
|
363
462
|
false_values: Union[str, Iterable[str]] = DEFAULT_FALSE_VALUES,
|
|
364
463
|
none_values: Union[str, Iterable[str]] = DEFAULT_NONE_VALUES,
|
|
464
|
+
list_parsing: ListParsing = "argparse",
|
|
365
465
|
) -> Any:
|
|
466
|
+
del list_parsing
|
|
366
467
|
if target_type is str:
|
|
367
468
|
return x
|
|
469
|
+
|
|
368
470
|
elif target_type is int:
|
|
369
471
|
try:
|
|
370
472
|
return int(x)
|
|
371
473
|
except ValueError as err:
|
|
372
474
|
return err
|
|
475
|
+
|
|
373
476
|
elif target_type is float:
|
|
374
477
|
try:
|
|
375
478
|
return float(x)
|
|
376
479
|
except ValueError as err:
|
|
377
480
|
return err
|
|
481
|
+
|
|
378
482
|
elif target_type in (None, NoneType):
|
|
379
483
|
return _str_to_none_impl(
|
|
380
|
-
x,
|
|
484
|
+
x,
|
|
485
|
+
case_sensitive=case_sensitive,
|
|
486
|
+
none_values=none_values,
|
|
381
487
|
)
|
|
488
|
+
|
|
382
489
|
elif target_type is bool:
|
|
383
490
|
return _str_to_bool_impl(
|
|
384
491
|
x,
|
|
@@ -387,7 +494,8 @@ def _str_to_scalar_impl(
|
|
|
387
494
|
false_values=false_values,
|
|
388
495
|
)
|
|
389
496
|
else:
|
|
390
|
-
|
|
497
|
+
msg = f"Invalid argument {target_type=}. (unsupported type)"
|
|
498
|
+
raise ValueError(msg)
|
|
391
499
|
|
|
392
500
|
|
|
393
501
|
def _str_to_bool_impl(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
|
|
4
|
+
import copy
|
|
4
5
|
import logging
|
|
5
6
|
from concurrent.futures import Future, ThreadPoolExecutor
|
|
6
7
|
from typing import Any, Callable, Dict, Generic, Iterable, List, Optional, TypeVar
|
|
@@ -42,8 +43,11 @@ class ThreadPoolExecutorHelper(Generic[P, T]):
|
|
|
42
43
|
executor_kwds = {}
|
|
43
44
|
self.executor = ThreadPoolExecutor(**executor_kwds)
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
default_kwargs = copy.copy(self.default_kwargs)
|
|
47
|
+
default_kwargs.update(kwargs)
|
|
48
|
+
del kwargs
|
|
49
|
+
|
|
50
|
+
future = self.executor.submit(self.fn, *args, **default_kwargs)
|
|
47
51
|
self.futures.append(future)
|
|
48
52
|
return future
|
|
49
53
|
|
|
@@ -6,18 +6,16 @@ from difflib import SequenceMatcher
|
|
|
6
6
|
from typing import Callable, Iterable, Optional
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
def sequence_matcher_ratio(a: str, b: str) -> float:
|
|
10
|
-
"""Compute distance ratio of two strings."""
|
|
11
|
-
return SequenceMatcher(None, a, b).ratio()
|
|
12
|
-
|
|
13
|
-
|
|
14
9
|
def find_closest_in_list(
|
|
15
10
|
x: str,
|
|
16
11
|
lst: Iterable[str],
|
|
17
|
-
sim_fn: Callable[[str, str], float] =
|
|
12
|
+
sim_fn: Optional[Callable[[str, str], float]] = None,
|
|
18
13
|
higher_is_closer: bool = True,
|
|
19
14
|
) -> Optional[str]:
|
|
20
15
|
"""Find closest element in a list based on matches ratio."""
|
|
16
|
+
if sim_fn is None:
|
|
17
|
+
sim_fn = sequence_matcher_ratio
|
|
18
|
+
|
|
21
19
|
best_sim = -int(higher_is_closer) * math.inf
|
|
22
20
|
closest = None
|
|
23
21
|
|
|
@@ -34,3 +32,8 @@ def find_closest_in_list(
|
|
|
34
32
|
raise ValueError(msg)
|
|
35
33
|
|
|
36
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()
|
|
@@ -6,7 +6,7 @@ import platform
|
|
|
6
6
|
import sys
|
|
7
7
|
import warnings
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from typing import
|
|
9
|
+
from typing import Any, Dict
|
|
10
10
|
|
|
11
11
|
import pythonwrench
|
|
12
12
|
from pythonwrench.os import get_num_cpus_available
|
|
@@ -17,27 +17,25 @@ logger = logging.getLogger(__name__)
|
|
|
17
17
|
|
|
18
18
|
def main_info() -> None:
|
|
19
19
|
"""Show main packages versions."""
|
|
20
|
-
warnings.
|
|
21
|
-
|
|
22
|
-
warnings.filterwarnings("default", category=UserWarning)
|
|
20
|
+
with warnings.catch_warnings():
|
|
21
|
+
install_info = get_install_info()
|
|
23
22
|
dumped = dumps_json(install_info, to_builtins=True)
|
|
24
23
|
print(dumped)
|
|
25
24
|
|
|
26
25
|
|
|
27
|
-
def get_install_info() -> Dict[str,
|
|
26
|
+
def get_install_info() -> Dict[str, Any]:
|
|
28
27
|
"""Returns current installation information. Meant for debugging."""
|
|
29
|
-
|
|
30
|
-
"pythonwrench": pythonwrench.__version__,
|
|
31
|
-
"python": f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
|
|
28
|
+
return {
|
|
32
29
|
"os": platform.system(),
|
|
33
30
|
"architecture": platform.architecture()[0],
|
|
34
31
|
"num_cpus": get_num_cpus_available(),
|
|
35
|
-
"
|
|
32
|
+
"python": f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
|
|
33
|
+
"pythonwrench": pythonwrench.__version__,
|
|
34
|
+
"pythonwrench_path": get_pythonwrench_repository_path(),
|
|
36
35
|
}
|
|
37
|
-
return install_info
|
|
38
36
|
|
|
39
37
|
|
|
40
|
-
def
|
|
38
|
+
def get_pythonwrench_repository_path() -> str:
|
|
41
39
|
"""Return the absolute path where the source code of this package is installed."""
|
|
42
40
|
return str(Path(__file__).parent.parent.parent)
|
|
43
41
|
|
|
@@ -8,6 +8,7 @@ from typing import (
|
|
|
8
8
|
Callable,
|
|
9
9
|
Generic,
|
|
10
10
|
Iterable,
|
|
11
|
+
Optional,
|
|
11
12
|
Tuple,
|
|
12
13
|
TypeVar,
|
|
13
14
|
overload,
|
|
@@ -113,7 +114,9 @@ compose = Compose # type: ignore
|
|
|
113
114
|
|
|
114
115
|
|
|
115
116
|
def filter_and_call(
|
|
116
|
-
fn: Callable[..., T],
|
|
117
|
+
fn: Callable[..., T],
|
|
118
|
+
_fill_all_arguments: bool = False,
|
|
119
|
+
**kwargs: Any,
|
|
117
120
|
) -> T:
|
|
118
121
|
"""Call object only with the valid keyword arguments. Non-valid arguments are ignored.
|
|
119
122
|
|
|
@@ -158,7 +161,12 @@ def filter_and_call(
|
|
|
158
161
|
return result
|
|
159
162
|
|
|
160
163
|
|
|
161
|
-
def function_alias(
|
|
164
|
+
def function_alias(
|
|
165
|
+
alternative: Callable[P, U],
|
|
166
|
+
*,
|
|
167
|
+
pre_fn: Optional[Callable[..., Any]] = None,
|
|
168
|
+
post_fn: Optional[Callable[..., Any]] = None,
|
|
169
|
+
) -> Callable[..., Callable[P, U]]:
|
|
162
170
|
"""Decorator to wrap function aliases.
|
|
163
171
|
|
|
164
172
|
Example
|
|
@@ -173,7 +181,7 @@ def function_alias(alternative: Callable[P, U]) -> Callable[..., Callable[P, U]]
|
|
|
173
181
|
... "bbb"
|
|
174
182
|
|
|
175
183
|
"""
|
|
176
|
-
return _decorator_factory(alternative)
|
|
184
|
+
return _decorator_factory(alternative, pre_fn=pre_fn, post_fn=post_fn)
|
|
177
185
|
|
|
178
186
|
|
|
179
187
|
def identity(x: T, **kwargs) -> T:
|
|
@@ -153,27 +153,6 @@ def reload_editable_packages(*, verbose: int = 0) -> List[ModuleType]:
|
|
|
153
153
|
)
|
|
154
154
|
|
|
155
155
|
|
|
156
|
-
class Placeholder:
|
|
157
|
-
"""Placeholder object. All instances attributes always returns the object itself."""
|
|
158
|
-
|
|
159
|
-
def __init__(self, *args, **kwargs) -> None: ...
|
|
160
|
-
|
|
161
|
-
def __getattr__(self, name: str) -> Any:
|
|
162
|
-
return self
|
|
163
|
-
|
|
164
|
-
def __call__(self, *args, **kwargs) -> Any:
|
|
165
|
-
return self
|
|
166
|
-
|
|
167
|
-
def __getitem__(self, *args, **kwargs) -> Any:
|
|
168
|
-
return self
|
|
169
|
-
|
|
170
|
-
def __eq__(self, other) -> Any:
|
|
171
|
-
return self == other
|
|
172
|
-
|
|
173
|
-
def __ne__(self, other) -> Any:
|
|
174
|
-
return self != other
|
|
175
|
-
|
|
176
|
-
|
|
177
156
|
def requires_packages(
|
|
178
157
|
arg0: Union[Iterable[str], str],
|
|
179
158
|
/,
|
|
@@ -213,3 +192,42 @@ def requires_packages(
|
|
|
213
192
|
return _impl
|
|
214
193
|
|
|
215
194
|
return _wrap
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class Placeholder:
|
|
198
|
+
"""Placeholder object. All instances attributes always returns the object itself."""
|
|
199
|
+
|
|
200
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
201
|
+
super().__init__()
|
|
202
|
+
self.__excluded_self_attrs = [
|
|
203
|
+
"__file__",
|
|
204
|
+
"__eq__",
|
|
205
|
+
"__ne__",
|
|
206
|
+
"__str__",
|
|
207
|
+
"__repr__",
|
|
208
|
+
]
|
|
209
|
+
|
|
210
|
+
def __getattr__(self, name: str) -> Any:
|
|
211
|
+
if name in self.__excluded_self_attrs:
|
|
212
|
+
return self.__getattribute__(name)
|
|
213
|
+
else:
|
|
214
|
+
return self
|
|
215
|
+
|
|
216
|
+
def __call__(self, *args, **kwargs) -> Any:
|
|
217
|
+
return self
|
|
218
|
+
|
|
219
|
+
def __getitem__(self, *args, **kwargs) -> Any:
|
|
220
|
+
return self
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class ModulePlaceholder(ModuleType, Placeholder):
|
|
224
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
225
|
+
Placeholder.__init__(self, *args, **kwargs)
|
|
226
|
+
ModuleType.__init__(self, *args, **kwargs)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def import_if_available(name: str) -> ModuleType:
|
|
230
|
+
if is_available_package(name):
|
|
231
|
+
return __import__(name)
|
|
232
|
+
else:
|
|
233
|
+
return ModulePlaceholder(name)
|
|
@@ -199,7 +199,10 @@ def isinstance_generic(
|
|
|
199
199
|
return False
|
|
200
200
|
|
|
201
201
|
if check_only_first:
|
|
202
|
-
|
|
202
|
+
try:
|
|
203
|
+
return isinstance_generic(next(iter(obj)), args[0])
|
|
204
|
+
except StopIteration:
|
|
205
|
+
return True
|
|
203
206
|
else:
|
|
204
207
|
return all(isinstance_generic(xi, args[0]) for xi in obj)
|
|
205
208
|
|
|
@@ -7,7 +7,7 @@ from typing import Any, Callable, Optional, Type, TypeVar, Union, overload
|
|
|
7
7
|
|
|
8
8
|
from typing_extensions import ParamSpec
|
|
9
9
|
|
|
10
|
-
from pythonwrench._core import _decorator_factory
|
|
10
|
+
from pythonwrench._core import _decorator_factory, return_none
|
|
11
11
|
|
|
12
12
|
P = ParamSpec("P")
|
|
13
13
|
U = TypeVar("U")
|
|
@@ -46,15 +46,21 @@ def deprecated_alias(
|
|
|
46
46
|
alternative: Callable[P, U],
|
|
47
47
|
msg_fmt: str = "Deprecated call to '{fn_name}', use '{alternative_name}' instead.",
|
|
48
48
|
warn_fn: Callable[[str], Any] = partial(warn_once, category=DeprecationWarning),
|
|
49
|
+
*,
|
|
50
|
+
pre_fn: Optional[Callable[..., Any]] = None,
|
|
51
|
+
post_fn: Optional[Callable[..., Any]] = None,
|
|
49
52
|
) -> Callable[..., Callable[P, U]]:
|
|
50
53
|
"""Decorator to wrap deprecated function aliases."""
|
|
51
54
|
alternative_name = alternative.__name__ if alternative is not None else "None"
|
|
55
|
+
if pre_fn is None:
|
|
56
|
+
pre_fn = return_none
|
|
52
57
|
|
|
53
|
-
def
|
|
58
|
+
def inner_pre_fn(fn, *args, **kwargs) -> None:
|
|
54
59
|
msg = msg_fmt.format(fn_name=fn.__name__, alternative_name=alternative_name)
|
|
55
60
|
warn_fn(msg)
|
|
61
|
+
pre_fn(fn, *args, **kwargs)
|
|
56
62
|
|
|
57
|
-
return _decorator_factory(alternative, pre_fn=
|
|
63
|
+
return _decorator_factory(alternative, pre_fn=inner_pre_fn, post_fn=post_fn)
|
|
58
64
|
|
|
59
65
|
|
|
60
66
|
@overload
|
|
@@ -83,14 +89,19 @@ def deprecated_function(
|
|
|
83
89
|
*,
|
|
84
90
|
msg_fmt: str = "Deprecated call to '{fn_name}'.",
|
|
85
91
|
warn_fn: Callable[[str], Any] = partial(warn_once, category=DeprecationWarning),
|
|
92
|
+
pre_fn: Optional[Callable[..., Any]] = None,
|
|
93
|
+
post_fn: Optional[Callable[..., Any]] = None,
|
|
86
94
|
) -> Callable:
|
|
87
95
|
"""Decorator to wrap deprecated functions."""
|
|
96
|
+
if pre_fn is None:
|
|
97
|
+
pre_fn = return_none
|
|
88
98
|
|
|
89
|
-
def
|
|
99
|
+
def inner_pre_fn(fn, *args, **kwargs):
|
|
90
100
|
msg = msg_fmt.format(fn_name=fn.__qualname__)
|
|
91
101
|
warn_fn(msg)
|
|
102
|
+
pre_fn(fn, *args, **kwargs)
|
|
92
103
|
|
|
93
|
-
decorator = _decorator_factory(None, pre_fn=
|
|
104
|
+
decorator = _decorator_factory(None, pre_fn=inner_pre_fn, post_fn=post_fn)
|
|
94
105
|
if fn is None:
|
|
95
106
|
return decorator
|
|
96
107
|
else:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pythonwrench
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.1
|
|
4
4
|
Summary: Python library with tools for typing, manipulating collections, and more!
|
|
5
5
|
Author-email: "Étienne Labbé (Labbeti)" <labbeti.pub@gmail.com>
|
|
6
6
|
Maintainer-email: "Étienne Labbé (Labbeti)" <labbeti.pub@gmail.com>
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
[console_scripts]
|
|
2
|
+
pw-info = pythonwrench.entrypoints.info:main_info
|
|
3
|
+
pw-safe-rmdir = pythonwrench.entrypoints.safe_rmdir:main_safe_rmdir
|
|
4
|
+
pw-tree = pythonwrench.entrypoints.tree:main_tree
|
|
2
5
|
pythonwrench-info = pythonwrench.entrypoints.info:main_info
|
|
3
6
|
pythonwrench-safe-rmdir = pythonwrench.entrypoints.safe_rmdir:main_safe_rmdir
|
|
4
7
|
pythonwrench-tree = pythonwrench.entrypoints.tree:main_tree
|
|
@@ -130,6 +130,25 @@ class TestArgparse(TestCase):
|
|
|
130
130
|
with self.assertRaises(SystemExit):
|
|
131
131
|
_ = parse_args_using_dataclass(D, args=["--arg_d", "1.1"])
|
|
132
132
|
|
|
133
|
+
def test_parse_args_using_dataclass_example_4(self) -> None:
|
|
134
|
+
@dataclass
|
|
135
|
+
class A:
|
|
136
|
+
nums: List[int] = field(default_factory=list)
|
|
137
|
+
|
|
138
|
+
target = A(nums=[1, -1, 10])
|
|
139
|
+
|
|
140
|
+
nums_args = list(map(str, target.nums))
|
|
141
|
+
result = parse_args_using_dataclass(
|
|
142
|
+
A, args=["--nums"] + nums_args, list_parsing="argparse"
|
|
143
|
+
)
|
|
144
|
+
assert target == result
|
|
145
|
+
|
|
146
|
+
nums_args = [f"[{','.join(map(str, target.nums))}]"]
|
|
147
|
+
result = parse_args_using_dataclass(
|
|
148
|
+
A, args=["--nums"] + nums_args, list_parsing="brackets"
|
|
149
|
+
)
|
|
150
|
+
assert target == result
|
|
151
|
+
|
|
133
152
|
|
|
134
153
|
if __name__ == "__main__":
|
|
135
154
|
unittest.main()
|
|
@@ -22,10 +22,10 @@ from typing import (
|
|
|
22
22
|
)
|
|
23
23
|
from unittest import TestCase
|
|
24
24
|
|
|
25
|
-
import pandas as pd
|
|
26
25
|
import typing_extensions
|
|
27
26
|
from typing_extensions import NotRequired
|
|
28
27
|
|
|
28
|
+
from pythonwrench.importlib import import_if_available, is_available_package
|
|
29
29
|
from pythonwrench.typing import (
|
|
30
30
|
NoneType,
|
|
31
31
|
check_args_types,
|
|
@@ -39,6 +39,8 @@ from pythonwrench.typing import (
|
|
|
39
39
|
isinstance_generic,
|
|
40
40
|
)
|
|
41
41
|
|
|
42
|
+
pd = import_if_available("pandas")
|
|
43
|
+
|
|
42
44
|
|
|
43
45
|
class ExampleDict(TypedDict):
|
|
44
46
|
a: int
|
|
@@ -125,7 +127,8 @@ class TestChecks(TestCase):
|
|
|
125
127
|
assert is_special_form(Any)
|
|
126
128
|
assert is_special_form(typing_extensions.Any)
|
|
127
129
|
|
|
128
|
-
|
|
130
|
+
if is_available_package("pandas"):
|
|
131
|
+
assert not is_special_form(pd.DataFrame({"a": [1, 2, 3]}))
|
|
129
132
|
|
|
130
133
|
|
|
131
134
|
class TestIsInstanceGuard(TestCase):
|
|
@@ -314,6 +317,12 @@ class TestIsInstanceGuard(TestCase):
|
|
|
314
317
|
with self.assertRaises(NotImplementedError):
|
|
315
318
|
assert isinstance_generic(Path, Callable[[str], Path])
|
|
316
319
|
|
|
320
|
+
def test_check_only_first(self) -> None:
|
|
321
|
+
assert isinstance_generic([1, 2], List[int], check_only_first=True)
|
|
322
|
+
assert isinstance_generic([1, "2", 1], List[int], check_only_first=True)
|
|
323
|
+
assert isinstance_generic([], List[int], check_only_first=True)
|
|
324
|
+
assert isinstance_generic([1, 2], List, check_only_first=True)
|
|
325
|
+
|
|
317
326
|
|
|
318
327
|
class TestCheckArgsType(TestCase):
|
|
319
328
|
def test_example_1(self) -> None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|