pythonwrench 0.3.0__tar.gz → 0.4.0__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.3.0/src/pythonwrench.egg-info → pythonwrench-0.4.0}/PKG-INFO +1 -1
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/pyproject.toml +4 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench/__init__.py +24 -4
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench/_core.py +4 -2
- pythonwrench-0.4.0/src/pythonwrench/argparse.py +313 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench/checksum.py +0 -37
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench/collections/__init__.py +1 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench/collections/collections.py +23 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench/collections/reducers.py +6 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench/csv.py +295 -119
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench/difflib.py +2 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench/disk_cache.py +52 -10
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench/entries.py +7 -6
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench/enum.py +11 -6
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench/functools.py +24 -1
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench/importlib.py +51 -4
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench/inspect.py +17 -7
- pythonwrench-0.4.0/src/pythonwrench/json.py +170 -0
- pythonwrench-0.4.0/src/pythonwrench/jsonl.py +198 -0
- pythonwrench-0.4.0/src/pythonwrench/pickle.py +150 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench/semver.py +29 -24
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench/warnings.py +30 -3
- {pythonwrench-0.3.0 → pythonwrench-0.4.0/src/pythonwrench.egg-info}/PKG-INFO +1 -1
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench.egg-info/SOURCES.txt +4 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench.egg-info/entry_points.txt +3 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/tests/test_argparse.py +29 -1
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/tests/test_cast.py +2 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/tests/test_checksum.py +3 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/tests/test_collections.py +9 -1
- pythonwrench-0.4.0/tests/test_csv.py +38 -0
- pythonwrench-0.4.0/tests/test_enum.py +29 -0
- pythonwrench-0.4.0/tests/test_importlib.py +54 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/tests/test_inspect.py +19 -3
- pythonwrench-0.4.0/tests/test_json.py +24 -0
- pythonwrench-0.4.0/tests/test_jsonl.py +18 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/tests/test_semver.py +4 -1
- pythonwrench-0.3.0/src/pythonwrench/argparse.py +0 -119
- pythonwrench-0.3.0/src/pythonwrench/json.py +0 -88
- pythonwrench-0.3.0/src/pythonwrench/pickle.py +0 -72
- pythonwrench-0.3.0/tests/test_csv.py +0 -25
- pythonwrench-0.3.0/tests/test_importlib.py +0 -30
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/LICENSE +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/README.md +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/setup.cfg +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/setup.py +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench/__main__.py +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench/abc.py +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench/cast.py +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench/collections/prop.py +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench/dataclasses.py +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench/datetime.py +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench/hashlib.py +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench/io.py +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench/logging.py +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench/math.py +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench/os.py +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench/random.py +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench/re.py +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench/typing/__init__.py +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench/typing/checks.py +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench/typing/classes.py +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench.egg-info/dependency_links.txt +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench.egg-info/requires.txt +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/src/pythonwrench.egg-info/top_level.txt +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/tests/test_abc.py +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/tests/test_dataclasses.py +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/tests/test_difflib.py +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/tests/test_entries.py +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/tests/test_functools.py +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/tests/test_hashlib.py +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/tests/test_logging.py +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/tests/test_math.py +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/tests/test_os.py +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/tests/test_random.py +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/tests/test_readme.py +0 -0
- {pythonwrench-0.3.0 → pythonwrench-0.4.0}/tests/test_typing.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pythonwrench
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Set of tools for Python that could be in the standard library.
|
|
5
5
|
Author-email: "Étienne Labbé (Labbeti)" <labbeti.pub@gmail.com>
|
|
6
6
|
Maintainer-email: "Étienne Labbé (Labbeti)" <labbeti.pub@gmail.com>
|
|
@@ -8,6 +8,7 @@ readme = "README.md"
|
|
|
8
8
|
requires-python = ">=3.8"
|
|
9
9
|
keywords = ["python", "tools", "utilities"]
|
|
10
10
|
license = {file = "LICENSE"}
|
|
11
|
+
# license-files = ["LICENSE"] # unsupported by python 3.8, but will be required in 2026
|
|
11
12
|
classifiers = [
|
|
12
13
|
"Intended Audience :: Developers",
|
|
13
14
|
"Intended Audience :: Science/Research",
|
|
@@ -40,6 +41,9 @@ Tracker = "https://github.com/Labbeti/pythonwrench/issues"
|
|
|
40
41
|
pythonwrench-info = "pythonwrench.entries:print_install_info"
|
|
41
42
|
pythonwrench-tree = "pythonwrench.entries:main_tree"
|
|
42
43
|
pythonwrench-safe-rmdir = "pythonwrench.entries:main_safe_rmdir"
|
|
44
|
+
pyw-info = "pythonwrench.entries:print_install_info"
|
|
45
|
+
pyw-tree = "pythonwrench.entries:main_tree"
|
|
46
|
+
pyw-safe-rmdir = "pythonwrench.entries:main_safe_rmdir"
|
|
43
47
|
|
|
44
48
|
[build-system]
|
|
45
49
|
requires = ["setuptools >= 61.0"]
|
|
@@ -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.
|
|
12
|
+
__version__ = "0.4.0"
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
# Re-import for language servers
|
|
@@ -40,11 +40,14 @@ from . import warnings as warnings
|
|
|
40
40
|
# Global library imports
|
|
41
41
|
from .abc import Singleton
|
|
42
42
|
from .argparse import (
|
|
43
|
+
parse_to,
|
|
43
44
|
str_to_bool,
|
|
45
|
+
str_to_none,
|
|
44
46
|
str_to_optional_bool,
|
|
45
47
|
str_to_optional_float,
|
|
46
48
|
str_to_optional_int,
|
|
47
49
|
str_to_optional_str,
|
|
50
|
+
str_to_type,
|
|
48
51
|
)
|
|
49
52
|
from .cast import as_builtin
|
|
50
53
|
from .checksum import checksum_any, register_checksum_fn
|
|
@@ -54,6 +57,7 @@ from .collections import (
|
|
|
54
57
|
contained,
|
|
55
58
|
dict_list_to_list_dict,
|
|
56
59
|
dump_dict,
|
|
60
|
+
duplicate_list,
|
|
57
61
|
filter_iterable,
|
|
58
62
|
find,
|
|
59
63
|
flat_dict_of_dict,
|
|
@@ -81,7 +85,7 @@ from .collections import (
|
|
|
81
85
|
union_lists,
|
|
82
86
|
unzip,
|
|
83
87
|
)
|
|
84
|
-
from .csv import dump_csv, load_csv
|
|
88
|
+
from .csv import dump_csv, dumps_csv, load_csv, loads_csv, read_csv, save_csv
|
|
85
89
|
from .dataclasses import get_defaults_values
|
|
86
90
|
from .datetime import get_now, get_now_iso8601
|
|
87
91
|
from .difflib import find_closest_in_list, sequence_matcher_ratio
|
|
@@ -93,6 +97,7 @@ from .functools import (
|
|
|
93
97
|
filter_and_call,
|
|
94
98
|
function_alias,
|
|
95
99
|
identity,
|
|
100
|
+
repeat_fn,
|
|
96
101
|
)
|
|
97
102
|
from .hashlib import hash_file
|
|
98
103
|
from .importlib import (
|
|
@@ -103,7 +108,15 @@ from .importlib import (
|
|
|
103
108
|
search_submodules,
|
|
104
109
|
)
|
|
105
110
|
from .inspect import get_argnames, get_current_fn_name, get_fullname
|
|
106
|
-
from .json import dump_json, load_json
|
|
111
|
+
from .json import dump_json, dumps_json, load_json, loads_json, read_json, save_json
|
|
112
|
+
from .jsonl import (
|
|
113
|
+
dump_jsonl,
|
|
114
|
+
dumps_jsonl,
|
|
115
|
+
load_jsonl,
|
|
116
|
+
loads_jsonl,
|
|
117
|
+
read_jsonl,
|
|
118
|
+
save_jsonl,
|
|
119
|
+
)
|
|
107
120
|
from .logging import (
|
|
108
121
|
VERBOSE_DEBUG,
|
|
109
122
|
VERBOSE_ERROR,
|
|
@@ -122,7 +135,14 @@ from .logging import (
|
|
|
122
135
|
)
|
|
123
136
|
from .math import argmax, argmin, argsort, clamp, clip
|
|
124
137
|
from .os import get_num_cpus_available, safe_rmdir, tree_iter
|
|
125
|
-
from .pickle import
|
|
138
|
+
from .pickle import (
|
|
139
|
+
dump_pickle,
|
|
140
|
+
dumps_pickle,
|
|
141
|
+
load_pickle,
|
|
142
|
+
loads_pickle,
|
|
143
|
+
read_pickle,
|
|
144
|
+
save_pickle,
|
|
145
|
+
)
|
|
126
146
|
from .random import randstr
|
|
127
147
|
from .re import (
|
|
128
148
|
PatternLike,
|
|
@@ -37,7 +37,7 @@ def _decorator_factory(
|
|
|
37
37
|
*,
|
|
38
38
|
pre_fn: Callable[..., Any] = return_none,
|
|
39
39
|
post_fn: Callable[..., Any] = return_none,
|
|
40
|
-
) -> Callable[
|
|
40
|
+
) -> Callable[[Callable[P, U]], Callable[P, U]]:
|
|
41
41
|
"""Deprecated decorator for function aliases."""
|
|
42
42
|
|
|
43
43
|
def wrapper_factory(fn: Callable[P, U]) -> Callable[P, U]:
|
|
@@ -77,7 +77,9 @@ class _FunctionRegistry(Generic[T_Output]):
|
|
|
77
77
|
priority: int = 0,
|
|
78
78
|
) -> Callable[[T], T_Output]:
|
|
79
79
|
return self.register_decorator(
|
|
80
|
-
class_or_tuple,
|
|
80
|
+
class_or_tuple,
|
|
81
|
+
custom_predicate=custom_predicate,
|
|
82
|
+
priority=priority,
|
|
81
83
|
)(fn)
|
|
82
84
|
|
|
83
85
|
def register_decorator(
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
from functools import partial
|
|
5
|
+
from typing import (
|
|
6
|
+
Any,
|
|
7
|
+
Callable,
|
|
8
|
+
Iterable,
|
|
9
|
+
List,
|
|
10
|
+
Optional,
|
|
11
|
+
Type,
|
|
12
|
+
TypeVar,
|
|
13
|
+
Union,
|
|
14
|
+
get_args,
|
|
15
|
+
get_origin,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from pythonwrench.typing.classes import NoneType
|
|
19
|
+
|
|
20
|
+
T = TypeVar("T")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
DEFAULT_TRUE_VALUES = ("True", "t", "yes", "y", "1")
|
|
24
|
+
DEFAULT_FALSE_VALUES = ("False", "f", "no", "n", "0")
|
|
25
|
+
DEFAULT_NONE_VALUES = ("None", "null")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def parse_to(
|
|
29
|
+
target_type: Type[T],
|
|
30
|
+
*,
|
|
31
|
+
case_sensitive: bool = False,
|
|
32
|
+
true_values: Union[str, Iterable[str]] = DEFAULT_TRUE_VALUES,
|
|
33
|
+
false_values: Union[str, Iterable[str]] = DEFAULT_FALSE_VALUES,
|
|
34
|
+
none_values: Union[str, Iterable[str]] = DEFAULT_NONE_VALUES,
|
|
35
|
+
) -> Callable[[str], T]:
|
|
36
|
+
"""Returns a callable that convert string value to target type safely.
|
|
37
|
+
|
|
38
|
+
Intended for argparse boolean arguments.
|
|
39
|
+
"""
|
|
40
|
+
return partial(
|
|
41
|
+
str_to_type,
|
|
42
|
+
target_type=target_type,
|
|
43
|
+
case_sensitive=case_sensitive,
|
|
44
|
+
true_values=true_values,
|
|
45
|
+
false_values=false_values,
|
|
46
|
+
none_values=none_values,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def str_to_type(
|
|
51
|
+
x: str,
|
|
52
|
+
target_type: Type[T],
|
|
53
|
+
*,
|
|
54
|
+
case_sensitive: bool = False,
|
|
55
|
+
true_values: Union[str, Iterable[str]] = DEFAULT_TRUE_VALUES,
|
|
56
|
+
false_values: Union[str, Iterable[str]] = DEFAULT_FALSE_VALUES,
|
|
57
|
+
none_values: Union[str, Iterable[str]] = DEFAULT_NONE_VALUES,
|
|
58
|
+
) -> T:
|
|
59
|
+
"""Convert string values to target type safely. Intended for argparse boolean arguments.
|
|
60
|
+
|
|
61
|
+
- True values: 'True', 'T', 'yes', 'y', '1'.
|
|
62
|
+
- False values: 'False', 'F', 'no', 'n', '0'.
|
|
63
|
+
- None values: 'None', 'null'
|
|
64
|
+
- Other raises ValueError.
|
|
65
|
+
"""
|
|
66
|
+
result = _str_to_type_impl(
|
|
67
|
+
x,
|
|
68
|
+
target_type,
|
|
69
|
+
case_sensitive=case_sensitive,
|
|
70
|
+
true_values=true_values,
|
|
71
|
+
false_values=false_values,
|
|
72
|
+
none_values=none_values,
|
|
73
|
+
)
|
|
74
|
+
if isinstance(result, Exception):
|
|
75
|
+
raise result
|
|
76
|
+
else:
|
|
77
|
+
return result
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def str_to_bool(
|
|
81
|
+
x: str,
|
|
82
|
+
*,
|
|
83
|
+
case_sensitive: bool = False,
|
|
84
|
+
true_values: Union[str, Iterable[str]] = DEFAULT_TRUE_VALUES,
|
|
85
|
+
false_values: Union[str, Iterable[str]] = DEFAULT_FALSE_VALUES,
|
|
86
|
+
) -> bool:
|
|
87
|
+
"""Convert string values to bool safely. Intended for argparse boolean arguments.
|
|
88
|
+
|
|
89
|
+
- True values: 'True', 'T', 'yes', 'y', '1'.
|
|
90
|
+
- False values: 'False', 'F', 'no', 'n', '0'.
|
|
91
|
+
- Other raises ValueError.
|
|
92
|
+
"""
|
|
93
|
+
return str_to_type(
|
|
94
|
+
x,
|
|
95
|
+
bool,
|
|
96
|
+
case_sensitive=case_sensitive,
|
|
97
|
+
true_values=true_values,
|
|
98
|
+
false_values=false_values,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def str_to_none(
|
|
103
|
+
x: str,
|
|
104
|
+
*,
|
|
105
|
+
case_sensitive: bool = False,
|
|
106
|
+
none_values: Union[str, Iterable[str]] = DEFAULT_NONE_VALUES,
|
|
107
|
+
) -> None:
|
|
108
|
+
"""Convert string values to None safely. Intended for argparse boolean arguments.
|
|
109
|
+
|
|
110
|
+
- None values: 'None', 'null'
|
|
111
|
+
- Other raises ValueError.
|
|
112
|
+
"""
|
|
113
|
+
return str_to_type(
|
|
114
|
+
x, NoneType, case_sensitive=case_sensitive, none_values=none_values
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def str_to_optional_bool(
|
|
119
|
+
x: str,
|
|
120
|
+
*,
|
|
121
|
+
case_sensitive: bool = False,
|
|
122
|
+
true_values: Union[str, Iterable[str]] = DEFAULT_TRUE_VALUES,
|
|
123
|
+
false_values: Union[str, Iterable[str]] = DEFAULT_FALSE_VALUES,
|
|
124
|
+
none_values: Union[str, Iterable[str]] = DEFAULT_NONE_VALUES,
|
|
125
|
+
) -> Optional[bool]:
|
|
126
|
+
"""Convert string values to optional bool safely. Intended for argparse boolean arguments.
|
|
127
|
+
|
|
128
|
+
- True values: 'True', 'T', 'yes', 'y', '1'.
|
|
129
|
+
- False values: 'False', 'F', 'no', 'n', '0'.
|
|
130
|
+
- None values: 'None', 'null'
|
|
131
|
+
- Other raises ValueError.
|
|
132
|
+
"""
|
|
133
|
+
return str_to_type(
|
|
134
|
+
x, Optional[bool], case_sensitive=case_sensitive, none_values=none_values
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def str_to_optional_float(
|
|
139
|
+
x: str,
|
|
140
|
+
*,
|
|
141
|
+
case_sensitive: bool = False,
|
|
142
|
+
none_values: Union[str, Iterable[str]] = DEFAULT_NONE_VALUES,
|
|
143
|
+
) -> Optional[float]:
|
|
144
|
+
"""Convert string values to optional float safely. Intended for argparse boolean arguments."""
|
|
145
|
+
return str_to_type(
|
|
146
|
+
x, Optional[float], case_sensitive=case_sensitive, none_values=none_values
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def str_to_optional_int(
|
|
151
|
+
x: str,
|
|
152
|
+
*,
|
|
153
|
+
case_sensitive: bool = False,
|
|
154
|
+
none_values: Union[str, Iterable[str]] = DEFAULT_NONE_VALUES,
|
|
155
|
+
) -> Optional[int]:
|
|
156
|
+
"""Convert string values to optional int safely. Intended for argparse boolean arguments."""
|
|
157
|
+
return str_to_type(
|
|
158
|
+
x, Optional[int], case_sensitive=case_sensitive, none_values=none_values
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def str_to_optional_str(
|
|
163
|
+
x: str,
|
|
164
|
+
*,
|
|
165
|
+
case_sensitive: bool = False,
|
|
166
|
+
none_values: Union[str, Iterable[str]] = DEFAULT_NONE_VALUES,
|
|
167
|
+
) -> Optional[str]:
|
|
168
|
+
"""Convert string values to optional str safely. Intended for argparse boolean arguments."""
|
|
169
|
+
return str_to_type(
|
|
170
|
+
x, Optional[str], case_sensitive=case_sensitive, none_values=none_values
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _str_to_type_impl(
|
|
175
|
+
x: str,
|
|
176
|
+
target_type: Type[T],
|
|
177
|
+
*,
|
|
178
|
+
case_sensitive: bool = False,
|
|
179
|
+
true_values: Union[str, Iterable[str]] = DEFAULT_TRUE_VALUES,
|
|
180
|
+
false_values: Union[str, Iterable[str]] = DEFAULT_FALSE_VALUES,
|
|
181
|
+
none_values: Union[str, Iterable[str]] = DEFAULT_NONE_VALUES,
|
|
182
|
+
) -> Union[T, Exception]:
|
|
183
|
+
if target_type in (str, int, float, None, NoneType, bool):
|
|
184
|
+
return _str_to_scalar_impl(
|
|
185
|
+
x,
|
|
186
|
+
target_type,
|
|
187
|
+
case_sensitive=case_sensitive,
|
|
188
|
+
true_values=true_values,
|
|
189
|
+
false_values=false_values,
|
|
190
|
+
none_values=none_values,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
origin = get_origin(target_type)
|
|
194
|
+
if getattr(target_type, "__name__", None) == "Optional":
|
|
195
|
+
args = (None,) + get_args(target_type)
|
|
196
|
+
elif origin == Union or origin.__name__ in ("Union", "UnionType"): # type: ignore
|
|
197
|
+
args = get_args(target_type)
|
|
198
|
+
else:
|
|
199
|
+
msg = f"Invalid argument {target_type=}. (unsupported type)"
|
|
200
|
+
raise ValueError(msg)
|
|
201
|
+
|
|
202
|
+
# str is always at the end
|
|
203
|
+
def key_fn(xi: Any) -> int:
|
|
204
|
+
if xi is str:
|
|
205
|
+
return 1
|
|
206
|
+
else:
|
|
207
|
+
return 0
|
|
208
|
+
|
|
209
|
+
args = sorted(args, key=key_fn)
|
|
210
|
+
|
|
211
|
+
for arg in args:
|
|
212
|
+
result = _str_to_type_impl(
|
|
213
|
+
x,
|
|
214
|
+
arg, # type: ignore
|
|
215
|
+
case_sensitive=case_sensitive,
|
|
216
|
+
true_values=true_values,
|
|
217
|
+
false_values=false_values,
|
|
218
|
+
)
|
|
219
|
+
if not isinstance(result, Exception):
|
|
220
|
+
return result
|
|
221
|
+
|
|
222
|
+
return ValueError(f"Invalid argument {x=} with {target_type=}.")
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _str_to_scalar_impl(
|
|
226
|
+
x: str,
|
|
227
|
+
target_type: Type[T],
|
|
228
|
+
*,
|
|
229
|
+
case_sensitive: bool = False,
|
|
230
|
+
true_values: Union[str, Iterable[str]] = DEFAULT_TRUE_VALUES,
|
|
231
|
+
false_values: Union[str, Iterable[str]] = DEFAULT_FALSE_VALUES,
|
|
232
|
+
none_values: Union[str, Iterable[str]] = DEFAULT_NONE_VALUES,
|
|
233
|
+
) -> Any:
|
|
234
|
+
if target_type is str:
|
|
235
|
+
return x
|
|
236
|
+
elif target_type is int:
|
|
237
|
+
try:
|
|
238
|
+
return int(x)
|
|
239
|
+
except ValueError as err:
|
|
240
|
+
return err
|
|
241
|
+
elif target_type is float:
|
|
242
|
+
try:
|
|
243
|
+
return float(x)
|
|
244
|
+
except ValueError as err:
|
|
245
|
+
return err
|
|
246
|
+
elif target_type in (None, NoneType):
|
|
247
|
+
return _str_to_none_impl(
|
|
248
|
+
x, case_sensitive=case_sensitive, none_values=none_values
|
|
249
|
+
)
|
|
250
|
+
elif target_type is bool:
|
|
251
|
+
return _str_to_bool_impl(
|
|
252
|
+
x,
|
|
253
|
+
case_sensitive=case_sensitive,
|
|
254
|
+
true_values=true_values,
|
|
255
|
+
false_values=false_values,
|
|
256
|
+
)
|
|
257
|
+
else:
|
|
258
|
+
raise ValueError(f"Invalid argument {target_type=}. (unsupported type)")
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _str_to_bool_impl(
|
|
262
|
+
x: str,
|
|
263
|
+
*,
|
|
264
|
+
case_sensitive: bool = False,
|
|
265
|
+
true_values: Union[str, Iterable[str]] = DEFAULT_TRUE_VALUES,
|
|
266
|
+
false_values: Union[str, Iterable[str]] = DEFAULT_FALSE_VALUES,
|
|
267
|
+
) -> Union[bool, Exception]:
|
|
268
|
+
true_values = _sanitize_values(true_values)
|
|
269
|
+
if _str_in(x, true_values, case_sensitive):
|
|
270
|
+
return True
|
|
271
|
+
|
|
272
|
+
false_values = _sanitize_values(false_values)
|
|
273
|
+
if _str_in(x, false_values, case_sensitive):
|
|
274
|
+
return False
|
|
275
|
+
|
|
276
|
+
values = tuple(true_values + false_values)
|
|
277
|
+
err = ValueError(f"Invalid argument '{x}'. (expected one of {values})")
|
|
278
|
+
return err
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _str_to_none_impl(
|
|
282
|
+
x: str,
|
|
283
|
+
*,
|
|
284
|
+
case_sensitive: bool = False,
|
|
285
|
+
none_values: Union[str, Iterable[str]] = DEFAULT_NONE_VALUES,
|
|
286
|
+
) -> Union[None, Exception]:
|
|
287
|
+
"""Convert string values to None safely. Intended for argparse boolean arguments.
|
|
288
|
+
|
|
289
|
+
- None values: 'None', 'null'
|
|
290
|
+
- Other raises ValueError.
|
|
291
|
+
"""
|
|
292
|
+
none_values = _sanitize_values(none_values)
|
|
293
|
+
if _str_in(x, none_values, case_sensitive):
|
|
294
|
+
return None
|
|
295
|
+
|
|
296
|
+
values = tuple(none_values)
|
|
297
|
+
err = ValueError(f"Invalid argument '{x}'. (expected one of {values})")
|
|
298
|
+
return err
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _sanitize_values(values: Union[str, Iterable[str]]) -> List[str]:
|
|
302
|
+
if isinstance(values, str):
|
|
303
|
+
values = [values]
|
|
304
|
+
else:
|
|
305
|
+
values = list(values)
|
|
306
|
+
return values
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _str_in(x: str, values: List[str], case_sensitive: bool) -> bool:
|
|
310
|
+
if case_sensitive:
|
|
311
|
+
return x in values
|
|
312
|
+
else:
|
|
313
|
+
return x.lower() in map(str.lower, values)
|
|
@@ -18,20 +18,15 @@ from typing import (
|
|
|
18
18
|
Optional,
|
|
19
19
|
TypeVar,
|
|
20
20
|
Union,
|
|
21
|
-
get_args,
|
|
22
21
|
overload,
|
|
23
22
|
)
|
|
24
23
|
|
|
25
24
|
from pythonwrench._core import ClassOrTuple, Predicate, _FunctionRegistry
|
|
26
25
|
from pythonwrench.inspect import get_fullname
|
|
27
26
|
from pythonwrench.typing import (
|
|
28
|
-
BuiltinNumber,
|
|
29
|
-
BuiltinScalar,
|
|
30
27
|
DataclassInstance,
|
|
31
28
|
NamedTupleInstance,
|
|
32
29
|
NoneType,
|
|
33
|
-
is_builtin_number,
|
|
34
|
-
is_builtin_scalar,
|
|
35
30
|
)
|
|
36
31
|
|
|
37
32
|
T = TypeVar("T")
|
|
@@ -127,38 +122,6 @@ def checksum_int(x: int, **kwargs) -> int:
|
|
|
127
122
|
|
|
128
123
|
|
|
129
124
|
# Intermediate functions
|
|
130
|
-
@register_checksum_fn(None, custom_predicate=is_builtin_scalar)
|
|
131
|
-
def checksum_builtin_scalar(x: BuiltinScalar, **kwargs) -> int:
|
|
132
|
-
if is_builtin_number(x):
|
|
133
|
-
return checksum_builtin_number(x, **kwargs)
|
|
134
|
-
elif isinstance(x, bytes):
|
|
135
|
-
return checksum_bytes(x, **kwargs)
|
|
136
|
-
elif x is None:
|
|
137
|
-
return checksum_none(x, **kwargs)
|
|
138
|
-
elif isinstance(x, str):
|
|
139
|
-
return checksum_str(x, **kwargs)
|
|
140
|
-
else:
|
|
141
|
-
msg = f"Invalid argument type {type(x)}. (expected one of {get_args(BuiltinScalar)})"
|
|
142
|
-
raise TypeError(msg)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
@register_checksum_fn(None, custom_predicate=is_builtin_number)
|
|
146
|
-
def checksum_builtin_number(x: BuiltinNumber, **kwargs) -> int:
|
|
147
|
-
"""Compute a simple checksum of a builtin scalar number."""
|
|
148
|
-
# Note: instance check must follow this order: bool, int, float, complex, because isinstance(True, int) returns True !
|
|
149
|
-
if isinstance(x, bool):
|
|
150
|
-
return checksum_bool(x, **kwargs)
|
|
151
|
-
elif isinstance(x, int):
|
|
152
|
-
return checksum_int(x, **kwargs)
|
|
153
|
-
elif isinstance(x, float):
|
|
154
|
-
return checksum_float(x, **kwargs)
|
|
155
|
-
elif isinstance(x, complex):
|
|
156
|
-
return checksum_complex(x, **kwargs)
|
|
157
|
-
else:
|
|
158
|
-
msg = f"Invalid argument type {type(x)}. (expected one of {get_args(BuiltinNumber)})"
|
|
159
|
-
raise TypeError(msg)
|
|
160
|
-
|
|
161
|
-
|
|
162
125
|
@register_checksum_fn(bytearray)
|
|
163
126
|
def checksum_bytearray(x: bytearray, **kwargs) -> int:
|
|
164
127
|
kwargs["accumulator"] = kwargs.get("accumulator", 0) + _cached_checksum_str(
|
|
@@ -736,3 +736,26 @@ def unzip(lst):
|
|
|
736
736
|
... [1, 2, 3, 4], [5, 6, 7, 8]
|
|
737
737
|
"""
|
|
738
738
|
return tuple(map(list, zip(*lst)))
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
def duplicate_list(lst: List[T], sizes: List[int]) -> List[T]:
|
|
742
|
+
"""Duplicate elements elements of a list with the corresponding sizes.
|
|
743
|
+
|
|
744
|
+
Example 1
|
|
745
|
+
----------
|
|
746
|
+
>>> lst = ["a", "b", "c", "d", "e"]
|
|
747
|
+
>>> sizes = [1, 0, 2, 1, 3]
|
|
748
|
+
>>> duplicate_list(lst, sizes)
|
|
749
|
+
... ["a", "c", "c", "d", "e", "e", "e"]
|
|
750
|
+
"""
|
|
751
|
+
if len(lst) != len(sizes):
|
|
752
|
+
msg = f"Invalid arguments lengths. (found {len(lst)=} != {len(sizes)=})"
|
|
753
|
+
raise ValueError(msg)
|
|
754
|
+
|
|
755
|
+
out_size = sum(sizes)
|
|
756
|
+
out: List[T] = [None for _ in range(out_size)] # type: ignore
|
|
757
|
+
curidx = 0
|
|
758
|
+
for size, elt in zip(sizes, lst):
|
|
759
|
+
out[curidx : curidx + size] = [elt] * size
|
|
760
|
+
curidx += size
|
|
761
|
+
return out
|
|
@@ -56,6 +56,7 @@ def reduce_add(
|
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
def reduce_add(*args, start=None):
|
|
59
|
+
"""Reduce elements using "add" operator (+)."""
|
|
59
60
|
return _reduce(*args, start=start, op_fn=operator.add, type_=SupportsAdd)
|
|
60
61
|
|
|
61
62
|
|
|
@@ -85,6 +86,7 @@ def reduce_and(
|
|
|
85
86
|
|
|
86
87
|
|
|
87
88
|
def reduce_and(*args, start=None):
|
|
89
|
+
"""Reduce elements using "and" operator (&)."""
|
|
88
90
|
return _reduce(*args, start=start, op_fn=operator.and_, type_=SupportsAnd)
|
|
89
91
|
|
|
90
92
|
|
|
@@ -114,6 +116,7 @@ def reduce_mul(
|
|
|
114
116
|
|
|
115
117
|
|
|
116
118
|
def reduce_mul(*args, start=None):
|
|
119
|
+
"""Reduce elements using "mul" operator (*)."""
|
|
117
120
|
return _reduce(*args, start=start, op_fn=operator.mul, type_=SupportsMul)
|
|
118
121
|
|
|
119
122
|
|
|
@@ -143,6 +146,7 @@ def reduce_or(
|
|
|
143
146
|
|
|
144
147
|
|
|
145
148
|
def reduce_or(*args, start=None):
|
|
149
|
+
"""Reduce elements using "or" operator (|)."""
|
|
146
150
|
return _reduce(*args, start=start, op_fn=operator.or_, type_=SupportsOr)
|
|
147
151
|
|
|
148
152
|
|
|
@@ -204,6 +208,7 @@ def sum(
|
|
|
204
208
|
|
|
205
209
|
|
|
206
210
|
def sum(*args, start: Any = 0):
|
|
211
|
+
"""Compute sum of elements."""
|
|
207
212
|
return reduce_add(*args, start=start)
|
|
208
213
|
|
|
209
214
|
|
|
@@ -233,6 +238,7 @@ def prod(
|
|
|
233
238
|
|
|
234
239
|
|
|
235
240
|
def prod(*args, start: Any = 1):
|
|
241
|
+
"""Compute product of elements."""
|
|
236
242
|
return reduce_mul(*args, start=start)
|
|
237
243
|
|
|
238
244
|
|