pythonwrench 0.6.6__tar.gz → 0.6.8__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.6 → pythonwrench-0.6.8}/CHANGELOG.md +21 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/CITATION.cff +2 -2
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/PKG-INFO +1 -1
- pythonwrench-0.6.8/pythonwrench.code-workspace +13 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/__init__.py +14 -3
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/_core.py +135 -1
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/argparse/dataclass_.py +151 -27
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/cast.py +7 -1
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/collections/collections.py +26 -15
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/collections/reducers.py +2 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/functools.py +8 -29
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/importlib.py +1 -1
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/logging.py +1 -1
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/os.py +42 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/re.py +70 -3
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/semver.py +2 -2
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/serialization/_core.py +3 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/serialization/csv.py +6 -2
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/serialization/json.py +47 -9
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/serialization/jsonl.py +34 -9
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/serialization/pickle.py +50 -10
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/typing/__init__.py +2 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/typing/checks.py +135 -68
- pythonwrench-0.6.8/src/pythonwrench/warnings.py +8 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/tests/test_argparse.py +90 -4
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/tests/test_collections.py +9 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/tests/test_typing.py +52 -20
- pythonwrench-0.6.6/src/pythonwrench/warnings.py +0 -118
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/.github/workflows/test.yaml +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/.gitignore +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/.pre-commit-config.yaml +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/.readthedocs.yaml +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/LICENSE +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/README.md +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/Makefile +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/conf.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/index.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/make.bat +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/modules.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.abc.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.argparse.dataclass_.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.argparse.parsers.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.argparse.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.cast.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.checksum.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.collections.collections.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.collections.prop.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.collections.reducers.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.collections.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.concurrent.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.csv.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.dataclasses.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.datetime.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.difflib.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.disk_cache.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.enum.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.functools.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.hashlib.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.importlib.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.inspect.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.json.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.jsonl.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.logging.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.math.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.os.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.pickle.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.random.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.re.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.semver.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.serialization.csv.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.serialization.json.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.serialization.jsonl.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.serialization.pickle.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.serialization.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.time.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.typing.checks.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.typing.classes.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.typing.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/pythonwrench.warnings.rst +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/docs/update.bash +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/pyproject.toml +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/setup.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/__main__.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/abc.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/argparse/__init__.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/argparse/parsers.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/checksum.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/collections/__init__.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/collections/prop.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/concurrent.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/csv.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/dataclasses.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/datetime.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/difflib.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/disk_cache.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/entrypoints/info.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/entrypoints/safe_rmdir.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/entrypoints/tree.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/enum.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/hashlib.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/inspect.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/json.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/jsonl.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/math.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/pickle.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/random.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/serialization/__init__.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/time.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/src/pythonwrench/typing/classes.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/tests/test_abc.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/tests/test_cast.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/tests/test_checksum.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/tests/test_core.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/tests/test_csv.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/tests/test_dataclasses.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/tests/test_difflib.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/tests/test_disk_cache.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/tests/test_entrypoints.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/tests/test_enum.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/tests/test_functools.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/tests/test_hashlib.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/tests/test_importlib.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/tests/test_inspect.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/tests/test_json.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/tests/test_jsonl.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/tests/test_logging.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/tests/test_math.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/tests/test_os.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/tests/test_random.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/tests/test_readme.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/tests/test_semver.py +0 -0
- {pythonwrench-0.6.6 → pythonwrench-0.6.8}/uv.lock +0 -0
|
@@ -2,10 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.6.8] 2026-10-26
|
|
6
|
+
### Added
|
|
7
|
+
- `remove_children` function to remove all content of a directory.
|
|
8
|
+
- `filter_dict_with_patterns` function.
|
|
9
|
+
- `parse_args_using_dataclass` and `add_dataclass_fields_to_parser` functions now supports nested dataclasses with subparsers.
|
|
10
|
+
|
|
11
|
+
### Modified
|
|
12
|
+
- `Version` is now converted to str by default in `as_builtin`.
|
|
13
|
+
- `isinstance_generic` now raises explicit exception when forwarded ref is given.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## [0.6.7] 2026-09-07
|
|
17
|
+
### Added
|
|
18
|
+
- `on_error` and `default` arguments to `load_json`, `load_pickle`, `load_jsonl` functions.
|
|
19
|
+
- `posonly_for_required` argument for `parse_args_using_dataclass`.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- `setup_logging_level` when stderr is not set in root logger.
|
|
23
|
+
|
|
24
|
+
|
|
5
25
|
## [0.6.6] 2026-08-20
|
|
6
26
|
### Fixed
|
|
7
27
|
- `setup_logging_level` isinstance check.
|
|
8
28
|
|
|
29
|
+
|
|
9
30
|
## [0.6.5] 2026-08-20
|
|
10
31
|
### Added
|
|
11
32
|
- `return_single_namespace` option for `parse_args_using_dataclass` function.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pythonwrench
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.8
|
|
4
4
|
Summary: Python library with tools for typing, manipulating collections, and more!
|
|
5
5
|
Project-URL: Homepage, https://pypi.org/project/pythonwrench/
|
|
6
6
|
Project-URL: Documentation, https://pythonwrench.readthedocs.io/
|
|
@@ -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.8"
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
from typing import TYPE_CHECKING
|
|
@@ -157,12 +157,14 @@ if TYPE_CHECKING or lazy is None:
|
|
|
157
157
|
setup_logging_verbose,
|
|
158
158
|
)
|
|
159
159
|
from .math import argmax, argmin, argsort, clamp, clip
|
|
160
|
-
from .os import get_num_cpus_available, safe_rmdir, tree_iter
|
|
160
|
+
from .os import get_num_cpus_available, remove_children, safe_rmdir, tree_iter
|
|
161
161
|
from .random import randstr
|
|
162
162
|
from .re import (
|
|
163
163
|
PatternLike,
|
|
164
164
|
PatternListLike,
|
|
165
165
|
compile_patterns,
|
|
166
|
+
filter_dict_with_patterns,
|
|
167
|
+
filter_iter_with_patterns,
|
|
166
168
|
filter_with_patterns,
|
|
167
169
|
find_patterns,
|
|
168
170
|
get_key_fn,
|
|
@@ -245,6 +247,7 @@ if TYPE_CHECKING or lazy is None:
|
|
|
245
247
|
is_special_form,
|
|
246
248
|
is_typed_dict,
|
|
247
249
|
isinstance_generic,
|
|
250
|
+
isinstance_parametrized,
|
|
248
251
|
)
|
|
249
252
|
from .warnings import deprecated_alias, deprecated_function, warn_once
|
|
250
253
|
|
|
@@ -414,13 +417,20 @@ else:
|
|
|
414
417
|
"setup_logging_verbose",
|
|
415
418
|
],
|
|
416
419
|
"math": ["argmax", "argmin", "argsort", "clamp", "clip"],
|
|
417
|
-
"os": [
|
|
420
|
+
"os": [
|
|
421
|
+
"get_num_cpus_available",
|
|
422
|
+
"remove_children",
|
|
423
|
+
"safe_rmdir",
|
|
424
|
+
"tree_iter",
|
|
425
|
+
],
|
|
418
426
|
"random": ["randstr"],
|
|
419
427
|
"re": [
|
|
420
428
|
"PatternLike",
|
|
421
429
|
"PatternListLike",
|
|
422
430
|
"compile_patterns",
|
|
423
431
|
"filter_with_patterns",
|
|
432
|
+
"filter_iter_with_patterns",
|
|
433
|
+
"filter_dict_with_patterns",
|
|
424
434
|
"find_patterns",
|
|
425
435
|
"get_key_fn",
|
|
426
436
|
"match_patterns",
|
|
@@ -476,6 +486,7 @@ else:
|
|
|
476
486
|
"is_special_form",
|
|
477
487
|
"is_typed_dict",
|
|
478
488
|
"isinstance_generic",
|
|
489
|
+
"isinstance_parametrized",
|
|
479
490
|
],
|
|
480
491
|
"warnings": ["deprecated_alias", "deprecated_function", "warn_once"],
|
|
481
492
|
},
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
import warnings
|
|
5
|
+
from functools import lru_cache, partial, wraps
|
|
5
6
|
from typing import (
|
|
6
7
|
Any,
|
|
7
8
|
Callable,
|
|
@@ -11,8 +12,10 @@ from typing import (
|
|
|
11
12
|
Optional,
|
|
12
13
|
Protocol,
|
|
13
14
|
Tuple,
|
|
15
|
+
Type,
|
|
14
16
|
Union,
|
|
15
17
|
get_args,
|
|
18
|
+
overload,
|
|
16
19
|
runtime_checkable,
|
|
17
20
|
)
|
|
18
21
|
|
|
@@ -21,6 +24,7 @@ from typing_extensions import ParamSpec, TypeVar
|
|
|
21
24
|
P = ParamSpec("P")
|
|
22
25
|
T = TypeVar("T", covariant=True)
|
|
23
26
|
U = TypeVar("U", covariant=True)
|
|
27
|
+
|
|
24
28
|
T_Output = TypeVar("T_Output", default=Any)
|
|
25
29
|
T_PredicateArg = TypeVar("T_PredicateArg", contravariant=True, default=Any)
|
|
26
30
|
T_Function = TypeVar("T_Function", bound=Callable[..., Any])
|
|
@@ -41,6 +45,31 @@ def return_none(*args, **kwargs) -> None:
|
|
|
41
45
|
return None
|
|
42
46
|
|
|
43
47
|
|
|
48
|
+
def function_alias(
|
|
49
|
+
alternative: T_Function,
|
|
50
|
+
*,
|
|
51
|
+
pre_fn: Optional[Callable[..., Any]] = None,
|
|
52
|
+
post_fn: Optional[Callable[..., Any]] = None,
|
|
53
|
+
) -> Callable[..., T_Function]:
|
|
54
|
+
"""Decorator to wrap function aliases.
|
|
55
|
+
|
|
56
|
+
Unlike setting directly an alias, this preserve the nature of the alias for language server.
|
|
57
|
+
|
|
58
|
+
Example
|
|
59
|
+
-------
|
|
60
|
+
>>> def f(a: int, b: str) -> str:
|
|
61
|
+
>>> return a * b
|
|
62
|
+
>>> @function_alias(f)
|
|
63
|
+
>>> def g(*args, **kwargs): ...
|
|
64
|
+
>>> f(2, "a")
|
|
65
|
+
... "aa"
|
|
66
|
+
>>> g(3, "b") # calls function f() internally.
|
|
67
|
+
... "bbb"
|
|
68
|
+
|
|
69
|
+
"""
|
|
70
|
+
return _decorator_factory(alternative, pre_fn=pre_fn, post_fn=post_fn)
|
|
71
|
+
|
|
72
|
+
|
|
44
73
|
def _decorator_factory(
|
|
45
74
|
inner_fn: Optional[T_Function],
|
|
46
75
|
*,
|
|
@@ -190,3 +219,108 @@ def _get_insertion_index(lst: list, priority: int, priority_key: int) -> int:
|
|
|
190
219
|
if priority >= other_priority:
|
|
191
220
|
return i
|
|
192
221
|
return len(lst)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
@overload
|
|
225
|
+
def warn_once(
|
|
226
|
+
message: str,
|
|
227
|
+
category: Optional[Type[Warning]] = None,
|
|
228
|
+
stacklevel: int = 1,
|
|
229
|
+
source: Any = None,
|
|
230
|
+
) -> None:
|
|
231
|
+
"""Warn message once using warnings module."""
|
|
232
|
+
...
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
@overload
|
|
236
|
+
def warn_once(
|
|
237
|
+
message: Warning,
|
|
238
|
+
category: Any = None,
|
|
239
|
+
stacklevel: int = 1,
|
|
240
|
+
source: Any = None,
|
|
241
|
+
) -> None:
|
|
242
|
+
"""Warn message once using warnings module."""
|
|
243
|
+
...
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@lru_cache(maxsize=None)
|
|
247
|
+
def warn_once(
|
|
248
|
+
message: Union[str, Warning],
|
|
249
|
+
category: Optional[Type[Warning]] = None,
|
|
250
|
+
stacklevel: int = 1,
|
|
251
|
+
source: Any = None,
|
|
252
|
+
) -> None:
|
|
253
|
+
"""Warn message once using warnings module."""
|
|
254
|
+
warnings.warn(message, category, stacklevel, source)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def deprecated_alias(
|
|
258
|
+
alternative: T_Function,
|
|
259
|
+
msg_fmt: str = "Deprecated call to '{fn_name}', use '{alternative_name}' instead.",
|
|
260
|
+
warn_fn: Callable[[str], Any] = partial(warn_once, category=DeprecationWarning),
|
|
261
|
+
*,
|
|
262
|
+
pre_fn: Optional[Callable[..., Any]] = None,
|
|
263
|
+
post_fn: Optional[Callable[..., Any]] = None,
|
|
264
|
+
) -> Callable[..., T_Function]:
|
|
265
|
+
"""Decorator to wrap deprecated function aliases."""
|
|
266
|
+
alternative_name = alternative.__name__ if alternative is not None else "None"
|
|
267
|
+
if pre_fn is None:
|
|
268
|
+
pre_fn = return_none
|
|
269
|
+
|
|
270
|
+
def inner_pre_fn(fn, *args, **kwargs) -> None:
|
|
271
|
+
"""Perform the inner pre fn operation."""
|
|
272
|
+
msg = msg_fmt.format(fn_name=fn.__name__, alternative_name=alternative_name)
|
|
273
|
+
warn_fn(msg)
|
|
274
|
+
pre_fn(fn, *args, **kwargs)
|
|
275
|
+
|
|
276
|
+
return _decorator_factory(alternative, pre_fn=inner_pre_fn, post_fn=post_fn)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
@overload
|
|
280
|
+
def deprecated_function(
|
|
281
|
+
fn: None = None,
|
|
282
|
+
/,
|
|
283
|
+
*,
|
|
284
|
+
msg_fmt: str = "Deprecated call to '{fn_name}'.",
|
|
285
|
+
warn_fn: Callable[[str], Any] = partial(warn_once, category=DeprecationWarning),
|
|
286
|
+
) -> Callable[[T_Function], T_Function]:
|
|
287
|
+
"""Perform the deprecated function operation."""
|
|
288
|
+
...
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
@overload
|
|
292
|
+
def deprecated_function(
|
|
293
|
+
fn: T_Function,
|
|
294
|
+
/,
|
|
295
|
+
*,
|
|
296
|
+
msg_fmt: str = "Deprecated call to '{fn_name}'.",
|
|
297
|
+
warn_fn: Callable[[str], Any] = partial(warn_once, category=DeprecationWarning),
|
|
298
|
+
) -> T_Function:
|
|
299
|
+
"""Perform the deprecated function operation."""
|
|
300
|
+
...
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def deprecated_function(
|
|
304
|
+
fn: Optional[T_Function] = None,
|
|
305
|
+
/,
|
|
306
|
+
*,
|
|
307
|
+
msg_fmt: str = "Deprecated call to '{fn_name}'.",
|
|
308
|
+
warn_fn: Callable[[str], Any] = partial(warn_once, category=DeprecationWarning),
|
|
309
|
+
pre_fn: Optional[Callable[..., Any]] = None,
|
|
310
|
+
post_fn: Optional[Callable[..., Any]] = None,
|
|
311
|
+
) -> Union[Callable[[T_Function], T_Function], T_Function]:
|
|
312
|
+
"""Decorator to wrap deprecated functions."""
|
|
313
|
+
if pre_fn is None:
|
|
314
|
+
pre_fn = return_none
|
|
315
|
+
|
|
316
|
+
def inner_pre_fn(fn, *args, **kwargs):
|
|
317
|
+
"""Perform the inner pre fn operation."""
|
|
318
|
+
msg = msg_fmt.format(fn_name=fn.__qualname__)
|
|
319
|
+
warn_fn(msg)
|
|
320
|
+
pre_fn(fn, *args, **kwargs)
|
|
321
|
+
|
|
322
|
+
decorator = _decorator_factory(None, pre_fn=inner_pre_fn, post_fn=post_fn)
|
|
323
|
+
if fn is None:
|
|
324
|
+
return decorator
|
|
325
|
+
else:
|
|
326
|
+
return decorator(fn)
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
|
|
4
|
+
import copy
|
|
4
5
|
from argparse import ArgumentParser, Namespace
|
|
5
6
|
from dataclasses import MISSING, fields
|
|
6
7
|
from typing import (
|
|
7
8
|
Any,
|
|
8
9
|
Dict,
|
|
9
10
|
Iterable,
|
|
11
|
+
List,
|
|
10
12
|
Literal,
|
|
11
13
|
Optional,
|
|
12
14
|
Tuple,
|
|
@@ -21,9 +23,11 @@ from pythonwrench.argparse.parsers import (
|
|
|
21
23
|
ListParsing,
|
|
22
24
|
_is_iterable_type_like,
|
|
23
25
|
_is_literal_type,
|
|
26
|
+
_is_union_type,
|
|
24
27
|
_search_parse_fn,
|
|
25
28
|
)
|
|
26
29
|
from pythonwrench.functools import filter_and_call
|
|
30
|
+
from pythonwrench.typing.checks import is_dataclass_type
|
|
27
31
|
from pythonwrench.typing.classes import (
|
|
28
32
|
Dataclass,
|
|
29
33
|
DataclassInstance,
|
|
@@ -58,6 +62,7 @@ def parse_args_using_dataclass(
|
|
|
58
62
|
bool_action: BoolAction = "store",
|
|
59
63
|
add_dashed_arg: bool = True,
|
|
60
64
|
return_single_namespace: Literal[True],
|
|
65
|
+
posonly_for_required: bool = False,
|
|
61
66
|
) -> Namespace: ...
|
|
62
67
|
|
|
63
68
|
|
|
@@ -71,6 +76,7 @@ def parse_args_using_dataclass(
|
|
|
71
76
|
bool_action: BoolAction = "store",
|
|
72
77
|
add_dashed_arg: bool = True,
|
|
73
78
|
return_single_namespace: Literal[False] = False,
|
|
79
|
+
posonly_for_required: bool = False,
|
|
74
80
|
) -> T_DataclassInstance: ...
|
|
75
81
|
|
|
76
82
|
|
|
@@ -86,6 +92,7 @@ def parse_args_using_dataclass(
|
|
|
86
92
|
bool_action: BoolAction = "store",
|
|
87
93
|
add_dashed_arg: bool = True,
|
|
88
94
|
return_single_namespace: Literal[False] = False,
|
|
95
|
+
posonly_for_required: bool = False,
|
|
89
96
|
) -> Tuple[
|
|
90
97
|
T_DataclassInstance,
|
|
91
98
|
T_DataclassInstance_2,
|
|
@@ -105,6 +112,7 @@ def parse_args_using_dataclass(
|
|
|
105
112
|
bool_action: BoolAction = "store",
|
|
106
113
|
add_dashed_arg: bool = True,
|
|
107
114
|
return_single_namespace: Literal[False] = False,
|
|
115
|
+
posonly_for_required: bool = False,
|
|
108
116
|
) -> Tuple[
|
|
109
117
|
T_DataclassInstance,
|
|
110
118
|
T_DataclassInstance_2,
|
|
@@ -126,6 +134,7 @@ def parse_args_using_dataclass(
|
|
|
126
134
|
bool_action: BoolAction = "store",
|
|
127
135
|
add_dashed_arg: bool = True,
|
|
128
136
|
return_single_namespace: Literal[False] = False,
|
|
137
|
+
posonly_for_required: bool = False,
|
|
129
138
|
) -> Tuple[
|
|
130
139
|
T_DataclassInstance,
|
|
131
140
|
T_DataclassInstance_2,
|
|
@@ -149,6 +158,7 @@ def parse_args_using_dataclass(
|
|
|
149
158
|
bool_action: BoolAction = "store",
|
|
150
159
|
add_dashed_arg: bool = True,
|
|
151
160
|
return_single_namespace: Literal[False] = False,
|
|
161
|
+
posonly_for_required: bool = False,
|
|
152
162
|
) -> Tuple[
|
|
153
163
|
T_DataclassInstance,
|
|
154
164
|
T_DataclassInstance_2,
|
|
@@ -159,14 +169,15 @@ def parse_args_using_dataclass(
|
|
|
159
169
|
|
|
160
170
|
|
|
161
171
|
def parse_args_using_dataclass(
|
|
162
|
-
dataclass_type:
|
|
163
|
-
*dataclass_types:
|
|
172
|
+
dataclass_type: Dataclass,
|
|
173
|
+
*dataclass_types: Dataclass,
|
|
164
174
|
args: Optional[Iterable[str]] = None,
|
|
165
175
|
parser: Optional[ArgumentParser] = None,
|
|
166
176
|
list_parsing: ListParsing = "argparse",
|
|
167
177
|
bool_action: BoolAction = "store",
|
|
168
178
|
add_dashed_arg: bool = True,
|
|
169
179
|
return_single_namespace: bool = False,
|
|
180
|
+
posonly_for_required: bool = False,
|
|
170
181
|
) -> Union[
|
|
171
182
|
DataclassInstance,
|
|
172
183
|
Tuple[DataclassInstance, ...],
|
|
@@ -187,28 +198,25 @@ def parse_args_using_dataclass(
|
|
|
187
198
|
list_parsing=list_parsing,
|
|
188
199
|
bool_action=bool_action,
|
|
189
200
|
add_dashed_arg=add_dashed_arg,
|
|
201
|
+
posonly_for_required=posonly_for_required,
|
|
190
202
|
)
|
|
191
203
|
assert parser is not None
|
|
192
204
|
|
|
193
|
-
parsed,
|
|
194
|
-
if len(
|
|
195
|
-
msg = f"Found {len(
|
|
205
|
+
parsed, other_args = parser.parse_known_args(args)
|
|
206
|
+
if len(other_args) > 0:
|
|
207
|
+
msg = f"Found {len(other_args)} unknown arguments: {other_args}. (with source {args=})"
|
|
196
208
|
raise ValueError(msg)
|
|
197
209
|
|
|
198
210
|
if return_single_namespace:
|
|
199
211
|
return parsed
|
|
200
212
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
_fill_all_arguments=True,
|
|
209
|
-
**parsed.__dict__,
|
|
210
|
-
)
|
|
211
|
-
dataclass_insts.append(instance)
|
|
213
|
+
accept_more_args = (init_parser is not None) or (len(dataclass_types) != 1)
|
|
214
|
+
# breakpoint() # TODO
|
|
215
|
+
dataclass_insts = _instantiate_dataclasses(
|
|
216
|
+
dataclass_types,
|
|
217
|
+
parsed,
|
|
218
|
+
accept_more_args=accept_more_args,
|
|
219
|
+
)
|
|
212
220
|
|
|
213
221
|
if len(dataclass_insts) == 1:
|
|
214
222
|
return dataclass_insts[0]
|
|
@@ -216,6 +224,79 @@ def parse_args_using_dataclass(
|
|
|
216
224
|
return tuple(dataclass_insts)
|
|
217
225
|
|
|
218
226
|
|
|
227
|
+
def _instantiate_dataclasses(
|
|
228
|
+
dcls_types: Tuple[Dataclass, ...],
|
|
229
|
+
parsed: Union[Namespace, Dict[str, Any]],
|
|
230
|
+
accept_more_args: bool,
|
|
231
|
+
) -> List[DataclassInstance]:
|
|
232
|
+
if isinstance(parsed, Namespace):
|
|
233
|
+
parsed = parsed.__dict__
|
|
234
|
+
|
|
235
|
+
subparser_fields = {
|
|
236
|
+
field.name
|
|
237
|
+
for dcls_type in dcls_types
|
|
238
|
+
for field in fields(dcls_type)
|
|
239
|
+
if _is_dataclass_or_union_of_dataclasses(field.type)
|
|
240
|
+
}
|
|
241
|
+
base_kwds = {
|
|
242
|
+
k: v for k, v in parsed.items() if "." not in k and k not in subparser_fields
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
dcls_insts = []
|
|
246
|
+
|
|
247
|
+
for dcls_type_i in dcls_types:
|
|
248
|
+
sub_dclasses = {
|
|
249
|
+
field.name: field.type
|
|
250
|
+
for field in fields(dcls_type_i)
|
|
251
|
+
if _is_dataclass_or_union_of_dataclasses(field.type)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
kwds_i = copy.copy(base_kwds)
|
|
255
|
+
for name, dcls_union in sub_dclasses.items():
|
|
256
|
+
selected = parsed.get(name)
|
|
257
|
+
if selected is None:
|
|
258
|
+
# note: an optional subparser that was not selected is None,
|
|
259
|
+
# it must not replace the dataclass field's default/default_factory.
|
|
260
|
+
continue
|
|
261
|
+
|
|
262
|
+
if _is_union_type(dcls_union):
|
|
263
|
+
dclasses = get_args(dcls_union)
|
|
264
|
+
dclasses_names = {dcls.__name__: dcls for dcls in dclasses}
|
|
265
|
+
try:
|
|
266
|
+
dclass_type = dclasses_names[selected]
|
|
267
|
+
except KeyError as err:
|
|
268
|
+
msg = f"Invalid subparser {selected!r} for field {name!r}."
|
|
269
|
+
raise ValueError(msg) from err
|
|
270
|
+
else:
|
|
271
|
+
dclass_type = dcls_union
|
|
272
|
+
|
|
273
|
+
field_prefix = f"{name}."
|
|
274
|
+
field_kwds = {
|
|
275
|
+
key[len(field_prefix) :]: value
|
|
276
|
+
for key, value in parsed.items()
|
|
277
|
+
if key.startswith(field_prefix)
|
|
278
|
+
}
|
|
279
|
+
dclass_inst = _instantiate_dataclasses(
|
|
280
|
+
(dclass_type,), # type: ignore
|
|
281
|
+
parsed=field_kwds,
|
|
282
|
+
accept_more_args=True,
|
|
283
|
+
)[0]
|
|
284
|
+
kwds_i[name] = dclass_inst
|
|
285
|
+
|
|
286
|
+
if accept_more_args:
|
|
287
|
+
instance = filter_and_call(
|
|
288
|
+
dcls_type_i,
|
|
289
|
+
_fill_all_arguments=True,
|
|
290
|
+
**kwds_i,
|
|
291
|
+
)
|
|
292
|
+
else:
|
|
293
|
+
instance = dcls_type_i(**kwds_i)
|
|
294
|
+
|
|
295
|
+
dcls_insts.append(instance)
|
|
296
|
+
|
|
297
|
+
return dcls_insts
|
|
298
|
+
|
|
299
|
+
|
|
219
300
|
def add_dataclass_fields_to_parser(
|
|
220
301
|
dataclass_type: Type[T_DataclassInstance],
|
|
221
302
|
*,
|
|
@@ -223,17 +304,24 @@ def add_dataclass_fields_to_parser(
|
|
|
223
304
|
list_parsing: ListParsing = "argparse",
|
|
224
305
|
bool_action: BoolAction = "store",
|
|
225
306
|
add_dashed_arg: bool = True,
|
|
307
|
+
posonly_for_required: bool = False,
|
|
308
|
+
argprefix: str = "",
|
|
226
309
|
) -> ArgumentParser:
|
|
227
310
|
"""Perform the add dataclass fields to parser operation."""
|
|
311
|
+
if posonly_for_required:
|
|
312
|
+
prefix = argprefix
|
|
313
|
+
else:
|
|
314
|
+
prefix = f"--{argprefix}"
|
|
315
|
+
|
|
228
316
|
if parser is None:
|
|
229
317
|
parser = ArgumentParser()
|
|
230
318
|
|
|
231
319
|
for field in fields(dataclass_type):
|
|
232
320
|
kwds = {}
|
|
233
|
-
posargs = [f"
|
|
321
|
+
posargs = [f"{prefix}{field.name}"]
|
|
234
322
|
if add_dashed_arg and "_" in field.name:
|
|
235
323
|
dashed_arg_name = field.name.replace("_", "-")
|
|
236
|
-
posargs.append(f"
|
|
324
|
+
posargs.append(f"{prefix}{dashed_arg_name}")
|
|
237
325
|
|
|
238
326
|
if field.default is MISSING and field.default_factory is MISSING:
|
|
239
327
|
if bool_action != "store" and field.type is bool:
|
|
@@ -244,19 +332,45 @@ def add_dataclass_fields_to_parser(
|
|
|
244
332
|
elif field.default is not MISSING:
|
|
245
333
|
kwds["default"] = field.default
|
|
246
334
|
elif field.default_factory is not MISSING:
|
|
247
|
-
kwds["default"] = field.default_factory()
|
|
335
|
+
kwds["default"] = field.default_factory()
|
|
248
336
|
else:
|
|
249
|
-
msg = f"Invalid field {field.name}: found values for default and default_factory."
|
|
337
|
+
msg = f"Invalid field '{field.name}': found values for default and default_factory."
|
|
250
338
|
raise ValueError(msg)
|
|
251
339
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
340
|
+
if is_dataclass_type(field.type):
|
|
341
|
+
subclasses = (field.type,)
|
|
342
|
+
elif _is_union_of_dataclasses(field.type):
|
|
343
|
+
subclasses = get_args(field.type)
|
|
344
|
+
else:
|
|
345
|
+
subclasses = None
|
|
257
346
|
|
|
258
|
-
|
|
259
|
-
|
|
347
|
+
if subclasses is not None:
|
|
348
|
+
subparsers = parser.add_subparsers(
|
|
349
|
+
dest=f"{argprefix}{field.name}",
|
|
350
|
+
required=kwds.get("required", False),
|
|
351
|
+
)
|
|
352
|
+
for subcls in subclasses:
|
|
353
|
+
subparser = subparsers.add_parser(subcls.__name__)
|
|
354
|
+
add_dataclass_fields_to_parser(
|
|
355
|
+
subcls,
|
|
356
|
+
parser=subparser,
|
|
357
|
+
list_parsing=list_parsing,
|
|
358
|
+
bool_action=bool_action,
|
|
359
|
+
add_dashed_arg=add_dashed_arg,
|
|
360
|
+
posonly_for_required=posonly_for_required,
|
|
361
|
+
argprefix=f"{argprefix}{field.name}.",
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
else:
|
|
365
|
+
try:
|
|
366
|
+
inner_kwds = _get_kwds_for_type(field.type, list_parsing, bool_action)
|
|
367
|
+
except (ValueError, TypeError, RuntimeError) as err:
|
|
368
|
+
msg = f"Invalid field '{field.name}': field type '{field.type}' is not supported."
|
|
369
|
+
raise type(err)(msg) from err
|
|
370
|
+
|
|
371
|
+
kwds.update(inner_kwds)
|
|
372
|
+
|
|
373
|
+
parser.add_argument(*posargs, **kwds)
|
|
260
374
|
|
|
261
375
|
return parser
|
|
262
376
|
|
|
@@ -300,6 +414,16 @@ def _get_kwds_for_type(
|
|
|
300
414
|
raise TypeError(msg)
|
|
301
415
|
|
|
302
416
|
|
|
417
|
+
def _is_dataclass_or_union_of_dataclasses(x: Any) -> bool:
|
|
418
|
+
return is_dataclass_type(x) or _is_union_of_dataclasses(x)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def _is_union_of_dataclasses(x: Any) -> bool:
|
|
422
|
+
return _is_union_type(x) and all(
|
|
423
|
+
is_dataclass_type(subcls) for subcls in get_args(x)
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
|
|
303
427
|
# ALIASES
|
|
304
428
|
@deprecated_alias(add_dataclass_fields_to_parser)
|
|
305
429
|
def new_parser_from_dataclass(*args, **kwargs):
|
|
@@ -22,6 +22,7 @@ from typing import (
|
|
|
22
22
|
|
|
23
23
|
from pythonwrench._core import ClassOrTuple, Predicate, _FunctionRegistry
|
|
24
24
|
from pythonwrench.functools import identity
|
|
25
|
+
from pythonwrench.semver import Version
|
|
25
26
|
from pythonwrench.typing import (
|
|
26
27
|
DataclassInstance,
|
|
27
28
|
NamedTupleInstance,
|
|
@@ -136,11 +137,16 @@ def _dataclass_to_builtin(x: DataclassInstance, **kwargs) -> Any:
|
|
|
136
137
|
|
|
137
138
|
|
|
138
139
|
@register_as_builtin_fn(NamedTupleInstance)
|
|
139
|
-
def _namedtuple_to_builtin(x: NamedTupleInstance, **kwargs) -> Any:
|
|
140
|
+
def _namedtuple_to_builtin(x: NamedTupleInstance, **kwargs) -> Dict[str, Any]:
|
|
140
141
|
"""Perform the namedtuple to builtin operation."""
|
|
141
142
|
return as_builtin(x._asdict(), **kwargs)
|
|
142
143
|
|
|
143
144
|
|
|
145
|
+
@register_as_builtin_fn(Version)
|
|
146
|
+
def _version_to_str(x: Version, **kwargs) -> str:
|
|
147
|
+
return x.to_str()
|
|
148
|
+
|
|
149
|
+
|
|
144
150
|
@register_as_builtin_fn(Mapping, priority=-100)
|
|
145
151
|
def _mapping_to_builtin(x: Mapping, **kwargs) -> Any:
|
|
146
152
|
"""Perform the mapping to builtin operation."""
|