pythonwrench 0.6.3__tar.gz → 0.6.5__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.5/.github/workflows/test.yaml +194 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/CHANGELOG.md +15 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/CITATION.cff +2 -2
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/PKG-INFO +2 -2
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/__init__.py +1 -1
- pythonwrench-0.6.5/src/pythonwrench/argparse/dataclass_.py +307 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/argparse/parsers.py +50 -20
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/checksum.py +9 -9
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/functools.py +1 -2
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/logging.py +51 -17
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/re.py +1 -1
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/time.py +17 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/typing/checks.py +0 -26
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/tests/test_argparse.py +73 -5
- pythonwrench-0.6.3/.github/workflows/test.yaml +0 -69
- pythonwrench-0.6.3/src/pythonwrench/argparse/dataclass_.py +0 -190
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/.gitignore +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/.pre-commit-config.yaml +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/.readthedocs.yaml +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/LICENSE +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/README.md +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/Makefile +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/conf.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/index.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/make.bat +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/modules.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.abc.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.argparse.dataclass_.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.argparse.parsers.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.argparse.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.cast.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.checksum.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.collections.collections.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.collections.prop.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.collections.reducers.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.collections.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.concurrent.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.csv.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.dataclasses.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.datetime.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.difflib.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.disk_cache.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.enum.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.functools.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.hashlib.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.importlib.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.inspect.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.json.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.jsonl.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.logging.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.math.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.os.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.pickle.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.random.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.re.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.semver.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.serialization.csv.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.serialization.json.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.serialization.jsonl.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.serialization.pickle.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.serialization.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.time.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.typing.checks.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.typing.classes.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.typing.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/pythonwrench.warnings.rst +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/docs/update.bash +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/pyproject.toml +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/setup.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/__main__.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/_core.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/abc.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/argparse/__init__.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/cast.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/collections/__init__.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/collections/collections.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/collections/prop.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/collections/reducers.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/concurrent.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/csv.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/dataclasses.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/datetime.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/difflib.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/disk_cache.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/entrypoints/info.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/entrypoints/safe_rmdir.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/entrypoints/tree.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/enum.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/hashlib.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/importlib.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/inspect.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/json.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/jsonl.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/math.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/os.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/pickle.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/random.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/semver.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/serialization/__init__.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/serialization/_core.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/serialization/csv.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/serialization/json.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/serialization/jsonl.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/serialization/pickle.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/typing/__init__.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/typing/classes.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/src/pythonwrench/warnings.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/tests/test_abc.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/tests/test_cast.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/tests/test_checksum.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/tests/test_collections.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/tests/test_core.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/tests/test_csv.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/tests/test_dataclasses.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/tests/test_difflib.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/tests/test_disk_cache.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/tests/test_entrypoints.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/tests/test_enum.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/tests/test_functools.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/tests/test_hashlib.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/tests/test_importlib.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/tests/test_inspect.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/tests/test_json.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/tests/test_jsonl.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/tests/test_logging.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/tests/test_math.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/tests/test_os.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/tests/test_random.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/tests/test_readme.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/tests/test_semver.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/tests/test_typing.py +0 -0
- {pythonwrench-0.6.3 → pythonwrench-0.6.5}/uv.lock +0 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Template: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
|
|
2
|
+
|
|
3
|
+
name: Python package using Pip
|
|
4
|
+
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
branches: [ main, dev** ]
|
|
8
|
+
pull_request:
|
|
9
|
+
branches: [ main, dev** ]
|
|
10
|
+
|
|
11
|
+
env:
|
|
12
|
+
CACHE_NUMBER: 0 # increase to reset cache manually
|
|
13
|
+
UV_NO_SYNC: 1
|
|
14
|
+
|
|
15
|
+
# Cancel workflow if a new push occurs
|
|
16
|
+
concurrency:
|
|
17
|
+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
18
|
+
# Never interrupt a main-branch publication between PyPI and GitHub.
|
|
19
|
+
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
build-and-test:
|
|
23
|
+
runs-on: ${{ matrix.os }}
|
|
24
|
+
|
|
25
|
+
strategy:
|
|
26
|
+
matrix:
|
|
27
|
+
os: [ubuntu-24.04]
|
|
28
|
+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
29
|
+
include:
|
|
30
|
+
- os: windows-2022
|
|
31
|
+
python-version: "3.8"
|
|
32
|
+
- os: windows-latest
|
|
33
|
+
python-version: "3.14"
|
|
34
|
+
- os: macos-15
|
|
35
|
+
python-version: "3.8"
|
|
36
|
+
- os: macos-latest
|
|
37
|
+
python-version: "3.14"
|
|
38
|
+
|
|
39
|
+
steps:
|
|
40
|
+
# --- INSTALLATIONS ---
|
|
41
|
+
- name: Checkout repository
|
|
42
|
+
uses: actions/checkout@v5
|
|
43
|
+
|
|
44
|
+
- name: Install uv and set Python ${{ matrix.python-version }}
|
|
45
|
+
uses: astral-sh/setup-uv@v9.0.0
|
|
46
|
+
with:
|
|
47
|
+
python-version: ${{ matrix.python-version }}
|
|
48
|
+
enable-cache: true
|
|
49
|
+
cache-dependency-glob: "pyproject.toml"
|
|
50
|
+
|
|
51
|
+
- name: Install the project
|
|
52
|
+
run: |
|
|
53
|
+
uv sync --extra tests
|
|
54
|
+
|
|
55
|
+
# --- TESTS ---
|
|
56
|
+
- name: Print install info
|
|
57
|
+
run: |
|
|
58
|
+
uv run pythonwrench-info
|
|
59
|
+
|
|
60
|
+
- name: Check format with Ruff
|
|
61
|
+
run: |
|
|
62
|
+
uv run ruff check
|
|
63
|
+
|
|
64
|
+
- name: Test with coverage + pytest
|
|
65
|
+
run: |
|
|
66
|
+
uv run coverage run -m pytest -v
|
|
67
|
+
|
|
68
|
+
- name: Show coverage results
|
|
69
|
+
run: |
|
|
70
|
+
uv run coverage report -m
|
|
71
|
+
|
|
72
|
+
release-check:
|
|
73
|
+
name: Check whether a release is needed
|
|
74
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
75
|
+
needs: build-and-test
|
|
76
|
+
runs-on: ubuntu-24.04
|
|
77
|
+
permissions:
|
|
78
|
+
contents: read
|
|
79
|
+
outputs:
|
|
80
|
+
version: ${{ steps.release.outputs.version }}
|
|
81
|
+
should-publish: ${{ steps.release.outputs.should-publish }}
|
|
82
|
+
|
|
83
|
+
steps:
|
|
84
|
+
- name: Checkout repository and tags
|
|
85
|
+
uses: actions/checkout@v5
|
|
86
|
+
with:
|
|
87
|
+
fetch-depth: 0
|
|
88
|
+
|
|
89
|
+
- name: Validate release metadata
|
|
90
|
+
id: release
|
|
91
|
+
shell: python
|
|
92
|
+
run: |
|
|
93
|
+
import os
|
|
94
|
+
import pathlib
|
|
95
|
+
import re
|
|
96
|
+
|
|
97
|
+
init_text = pathlib.Path("src/pythonwrench/__init__.py").read_text(encoding="utf-8")
|
|
98
|
+
version_match = re.search(
|
|
99
|
+
r'^__version__\s*=\s*["\'](\d+\.\d+\.\d+)["\']\s*$',
|
|
100
|
+
init_text,
|
|
101
|
+
flags=re.MULTILINE,
|
|
102
|
+
)
|
|
103
|
+
if version_match is None:
|
|
104
|
+
raise SystemExit(
|
|
105
|
+
"__version__ must be a stable X.Y.Z release in "
|
|
106
|
+
"src/pythonwrench/__init__.py"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
version = version_match.group(1)
|
|
110
|
+
changelog = pathlib.Path("CHANGELOG.md").read_text(encoding="utf-8")
|
|
111
|
+
heading = rf"^## \[{re.escape(version)}\](?:[ \t]+[^\r\n]*)?$"
|
|
112
|
+
if re.search(heading, changelog, flags=re.MULTILINE) is None:
|
|
113
|
+
raise SystemExit(f"CHANGELOG.md has no release section for {version}")
|
|
114
|
+
|
|
115
|
+
tag_ref = pathlib.Path(".git/refs/tags") / f"v{version}"
|
|
116
|
+
tag_exists = tag_ref.exists()
|
|
117
|
+
if not tag_exists:
|
|
118
|
+
import subprocess
|
|
119
|
+
|
|
120
|
+
tag_exists = subprocess.run(
|
|
121
|
+
["git", "rev-parse", "--verify", "--quiet", f"refs/tags/v{version}"],
|
|
122
|
+
check=False,
|
|
123
|
+
).returncode == 0
|
|
124
|
+
|
|
125
|
+
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output:
|
|
126
|
+
print(f"version={version}", file=output)
|
|
127
|
+
print(f"should-publish={'false' if tag_exists else 'true'}", file=output)
|
|
128
|
+
|
|
129
|
+
if tag_exists:
|
|
130
|
+
print(f"v{version} already exists; publication will be skipped.")
|
|
131
|
+
else:
|
|
132
|
+
print(f"v{version} is ready to publish.")
|
|
133
|
+
|
|
134
|
+
publish:
|
|
135
|
+
name: Publish to PyPI and GitHub Releases
|
|
136
|
+
if: needs.release-check.outputs.should-publish == 'true'
|
|
137
|
+
needs: release-check
|
|
138
|
+
runs-on: ubuntu-24.04
|
|
139
|
+
environment:
|
|
140
|
+
name: pypi
|
|
141
|
+
url: https://pypi.org/project/pythonwrench/${{ needs.release-check.outputs.version }}/
|
|
142
|
+
permissions:
|
|
143
|
+
contents: write
|
|
144
|
+
id-token: write
|
|
145
|
+
env:
|
|
146
|
+
RELEASE_VERSION: ${{ needs.release-check.outputs.version }}
|
|
147
|
+
|
|
148
|
+
steps:
|
|
149
|
+
- name: Checkout tested commit
|
|
150
|
+
uses: actions/checkout@v5
|
|
151
|
+
|
|
152
|
+
- name: Install uv
|
|
153
|
+
uses: astral-sh/setup-uv@v9.0.0
|
|
154
|
+
|
|
155
|
+
- name: Build distributions
|
|
156
|
+
run: uv build
|
|
157
|
+
|
|
158
|
+
- name: Validate distributions
|
|
159
|
+
run: uvx twine check dist/*
|
|
160
|
+
|
|
161
|
+
- name: Extract release notes from changelog
|
|
162
|
+
shell: python
|
|
163
|
+
run: |
|
|
164
|
+
import os
|
|
165
|
+
import pathlib
|
|
166
|
+
import re
|
|
167
|
+
|
|
168
|
+
version = os.environ["RELEASE_VERSION"]
|
|
169
|
+
changelog = pathlib.Path("CHANGELOG.md").read_text(encoding="utf-8")
|
|
170
|
+
section = re.search(
|
|
171
|
+
rf"^## \[{re.escape(version)}\](?:[ \t]+[^\r\n]*)?\n(.*?)(?=^## \[|\Z)",
|
|
172
|
+
changelog,
|
|
173
|
+
flags=re.MULTILINE | re.DOTALL,
|
|
174
|
+
)
|
|
175
|
+
if section is None:
|
|
176
|
+
raise SystemExit(f"Could not extract changelog notes for {version}")
|
|
177
|
+
pathlib.Path("release-notes.md").write_text(
|
|
178
|
+
section.group(1).strip() + "\n",
|
|
179
|
+
encoding="utf-8",
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
- name: Publish distributions to PyPI
|
|
183
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
184
|
+
with:
|
|
185
|
+
skip-existing: true
|
|
186
|
+
|
|
187
|
+
- name: Create GitHub Release and tag
|
|
188
|
+
env:
|
|
189
|
+
GH_TOKEN: ${{ github.token }}
|
|
190
|
+
run: |
|
|
191
|
+
gh release create "v${RELEASE_VERSION}" dist/* \
|
|
192
|
+
--target "${GITHUB_SHA}" \
|
|
193
|
+
--title "v${RELEASE_VERSION}" \
|
|
194
|
+
--notes-file release-notes.md
|
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.6.5] 2026-08-20
|
|
6
|
+
### Added
|
|
7
|
+
- `return_single_namespace` option for `parse_args_using_dataclass` function.
|
|
8
|
+
- Context manager for `Ticker` class.
|
|
9
|
+
|
|
10
|
+
### Modified
|
|
11
|
+
- Allow argument `stream` to be None or root_logging_stream customize handlers set in setup_logging functions.
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## [0.6.4] 2026-08-17
|
|
15
|
+
### Added
|
|
16
|
+
- Function `parse_args_using_dataclass` can now handle multiple dataclass at once.
|
|
17
|
+
- Function `parse_args_using_dataclass` now has option `bool_action`.
|
|
18
|
+
|
|
19
|
+
|
|
5
20
|
## [0.6.3] 2026-08-06
|
|
6
21
|
### Added
|
|
7
22
|
- Handle `Enum`, `Path`, and add `register_parser_fn` decorator for `parse_to_type`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: pythonwrench
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.5
|
|
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/
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
from argparse import ArgumentParser, Namespace
|
|
5
|
+
from dataclasses import MISSING, fields
|
|
6
|
+
from typing import (
|
|
7
|
+
Any,
|
|
8
|
+
Dict,
|
|
9
|
+
Iterable,
|
|
10
|
+
Literal,
|
|
11
|
+
Optional,
|
|
12
|
+
Tuple,
|
|
13
|
+
Type,
|
|
14
|
+
TypeVar,
|
|
15
|
+
Union,
|
|
16
|
+
get_args,
|
|
17
|
+
overload,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from pythonwrench.argparse.parsers import (
|
|
21
|
+
ListParsing,
|
|
22
|
+
_is_iterable_type_like,
|
|
23
|
+
_is_literal_type,
|
|
24
|
+
_search_parse_fn,
|
|
25
|
+
)
|
|
26
|
+
from pythonwrench.functools import filter_and_call
|
|
27
|
+
from pythonwrench.typing.classes import (
|
|
28
|
+
Dataclass,
|
|
29
|
+
DataclassInstance,
|
|
30
|
+
)
|
|
31
|
+
from pythonwrench.warnings import deprecated_alias
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
from argparse import BooleanOptionalAction # type: ignore
|
|
35
|
+
except ImportError:
|
|
36
|
+
|
|
37
|
+
class BooleanOptionalAction: ...
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
T_Dataclass = TypeVar("T_Dataclass", bound=Dataclass)
|
|
41
|
+
T_DataclassInstance = TypeVar("T_DataclassInstance", bound=DataclassInstance)
|
|
42
|
+
T_DataclassInstance_2 = TypeVar("T_DataclassInstance_2", bound=DataclassInstance)
|
|
43
|
+
T_DataclassInstance_3 = TypeVar("T_DataclassInstance_3", bound=DataclassInstance)
|
|
44
|
+
T_DataclassInstance_4 = TypeVar("T_DataclassInstance_4", bound=DataclassInstance)
|
|
45
|
+
T_DataclassInstance_5 = TypeVar("T_DataclassInstance_5", bound=DataclassInstance)
|
|
46
|
+
|
|
47
|
+
_BoolActionName = Literal["store", "store_true", "store_false", "bool_optional"]
|
|
48
|
+
BoolAction = Union[_BoolActionName, Type[BooleanOptionalAction]]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@overload
|
|
52
|
+
def parse_args_using_dataclass(
|
|
53
|
+
dataclass_type: Type[DataclassInstance],
|
|
54
|
+
*dataclass_types: Type[DataclassInstance],
|
|
55
|
+
args: Optional[Iterable[str]] = None,
|
|
56
|
+
parser: Optional[ArgumentParser] = None,
|
|
57
|
+
list_parsing: ListParsing = "argparse",
|
|
58
|
+
bool_action: BoolAction = "store",
|
|
59
|
+
add_dashed_arg: bool = True,
|
|
60
|
+
return_single_namespace: Literal[True],
|
|
61
|
+
) -> Namespace: ...
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@overload
|
|
65
|
+
def parse_args_using_dataclass(
|
|
66
|
+
dataclass_type: Type[T_DataclassInstance],
|
|
67
|
+
*,
|
|
68
|
+
args: Optional[Iterable[str]] = None,
|
|
69
|
+
parser: Optional[ArgumentParser] = None,
|
|
70
|
+
list_parsing: ListParsing = "argparse",
|
|
71
|
+
bool_action: BoolAction = "store",
|
|
72
|
+
add_dashed_arg: bool = True,
|
|
73
|
+
return_single_namespace: Literal[False] = False,
|
|
74
|
+
) -> T_DataclassInstance: ...
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@overload
|
|
78
|
+
def parse_args_using_dataclass(
|
|
79
|
+
dataclass_type: Type[T_DataclassInstance],
|
|
80
|
+
dataclass_type_2: Type[T_DataclassInstance_2],
|
|
81
|
+
/,
|
|
82
|
+
*,
|
|
83
|
+
args: Optional[Iterable[str]] = None,
|
|
84
|
+
parser: Optional[ArgumentParser] = None,
|
|
85
|
+
list_parsing: ListParsing = "argparse",
|
|
86
|
+
bool_action: BoolAction = "store",
|
|
87
|
+
add_dashed_arg: bool = True,
|
|
88
|
+
return_single_namespace: Literal[False] = False,
|
|
89
|
+
) -> Tuple[
|
|
90
|
+
T_DataclassInstance,
|
|
91
|
+
T_DataclassInstance_2,
|
|
92
|
+
]: ...
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@overload
|
|
96
|
+
def parse_args_using_dataclass(
|
|
97
|
+
dataclass_type: Type[T_DataclassInstance],
|
|
98
|
+
dataclass_type_2: Type[T_DataclassInstance_2],
|
|
99
|
+
dataclass_type_3: Type[T_DataclassInstance_3],
|
|
100
|
+
/,
|
|
101
|
+
*,
|
|
102
|
+
args: Optional[Iterable[str]] = None,
|
|
103
|
+
parser: Optional[ArgumentParser] = None,
|
|
104
|
+
list_parsing: ListParsing = "argparse",
|
|
105
|
+
bool_action: BoolAction = "store",
|
|
106
|
+
add_dashed_arg: bool = True,
|
|
107
|
+
return_single_namespace: Literal[False] = False,
|
|
108
|
+
) -> Tuple[
|
|
109
|
+
T_DataclassInstance,
|
|
110
|
+
T_DataclassInstance_2,
|
|
111
|
+
T_DataclassInstance_3,
|
|
112
|
+
]: ...
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@overload
|
|
116
|
+
def parse_args_using_dataclass(
|
|
117
|
+
dataclass_type: Type[T_DataclassInstance],
|
|
118
|
+
dataclass_type_2: Type[T_DataclassInstance_2],
|
|
119
|
+
dataclass_type_3: Type[T_DataclassInstance_3],
|
|
120
|
+
dataclass_type_4: Type[T_DataclassInstance_4],
|
|
121
|
+
/,
|
|
122
|
+
*,
|
|
123
|
+
args: Optional[Iterable[str]] = None,
|
|
124
|
+
parser: Optional[ArgumentParser] = None,
|
|
125
|
+
list_parsing: ListParsing = "argparse",
|
|
126
|
+
bool_action: BoolAction = "store",
|
|
127
|
+
add_dashed_arg: bool = True,
|
|
128
|
+
return_single_namespace: Literal[False] = False,
|
|
129
|
+
) -> Tuple[
|
|
130
|
+
T_DataclassInstance,
|
|
131
|
+
T_DataclassInstance_2,
|
|
132
|
+
T_DataclassInstance_3,
|
|
133
|
+
T_DataclassInstance_4,
|
|
134
|
+
]: ...
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@overload
|
|
138
|
+
def parse_args_using_dataclass(
|
|
139
|
+
dataclass_type: Type[T_DataclassInstance],
|
|
140
|
+
dataclass_type_2: Type[T_DataclassInstance_2],
|
|
141
|
+
dataclass_type_3: Type[T_DataclassInstance_3],
|
|
142
|
+
dataclass_type_4: Type[T_DataclassInstance_4],
|
|
143
|
+
dataclass_type_5: Type[T_DataclassInstance_5],
|
|
144
|
+
/,
|
|
145
|
+
*,
|
|
146
|
+
args: Optional[Iterable[str]] = None,
|
|
147
|
+
parser: Optional[ArgumentParser] = None,
|
|
148
|
+
list_parsing: ListParsing = "argparse",
|
|
149
|
+
bool_action: BoolAction = "store",
|
|
150
|
+
add_dashed_arg: bool = True,
|
|
151
|
+
return_single_namespace: Literal[False] = False,
|
|
152
|
+
) -> Tuple[
|
|
153
|
+
T_DataclassInstance,
|
|
154
|
+
T_DataclassInstance_2,
|
|
155
|
+
T_DataclassInstance_3,
|
|
156
|
+
T_DataclassInstance_4,
|
|
157
|
+
T_DataclassInstance_5,
|
|
158
|
+
]: ...
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def parse_args_using_dataclass(
|
|
162
|
+
dataclass_type: Type[DataclassInstance],
|
|
163
|
+
*dataclass_types: Type[DataclassInstance],
|
|
164
|
+
args: Optional[Iterable[str]] = None,
|
|
165
|
+
parser: Optional[ArgumentParser] = None,
|
|
166
|
+
list_parsing: ListParsing = "argparse",
|
|
167
|
+
bool_action: BoolAction = "store",
|
|
168
|
+
add_dashed_arg: bool = True,
|
|
169
|
+
return_single_namespace: bool = False,
|
|
170
|
+
) -> Union[
|
|
171
|
+
DataclassInstance,
|
|
172
|
+
Tuple[DataclassInstance, ...],
|
|
173
|
+
Namespace,
|
|
174
|
+
]:
|
|
175
|
+
"""Converts prog args to a typed dataclass using argparse.
|
|
176
|
+
|
|
177
|
+
Currently only supports dataclasses that contains only builtin scalars: str, int, float, None, bool OR list of builtin scalars.
|
|
178
|
+
"""
|
|
179
|
+
init_parser = parser
|
|
180
|
+
dataclass_types = (dataclass_type,) + dataclass_types
|
|
181
|
+
del dataclass_type
|
|
182
|
+
|
|
183
|
+
for dataclass_type_i in dataclass_types:
|
|
184
|
+
parser = add_dataclass_fields_to_parser(
|
|
185
|
+
dataclass_type_i,
|
|
186
|
+
parser=parser,
|
|
187
|
+
list_parsing=list_parsing,
|
|
188
|
+
bool_action=bool_action,
|
|
189
|
+
add_dashed_arg=add_dashed_arg,
|
|
190
|
+
)
|
|
191
|
+
assert parser is not None
|
|
192
|
+
|
|
193
|
+
parsed, argv = parser.parse_known_args(args)
|
|
194
|
+
if len(argv) > 0:
|
|
195
|
+
msg = f"Found {len(argv)} unknown arguments: {argv}."
|
|
196
|
+
raise ValueError(msg)
|
|
197
|
+
|
|
198
|
+
if return_single_namespace:
|
|
199
|
+
return parsed
|
|
200
|
+
|
|
201
|
+
dataclass_insts = []
|
|
202
|
+
for dataclass_type_i in dataclass_types:
|
|
203
|
+
if init_parser is None and len(dataclass_types) == 1:
|
|
204
|
+
instance = dataclass_type_i(**parsed.__dict__)
|
|
205
|
+
else:
|
|
206
|
+
instance = filter_and_call(
|
|
207
|
+
dataclass_type_i,
|
|
208
|
+
_fill_all_arguments=True,
|
|
209
|
+
**parsed.__dict__,
|
|
210
|
+
)
|
|
211
|
+
dataclass_insts.append(instance)
|
|
212
|
+
|
|
213
|
+
if len(dataclass_insts) == 1:
|
|
214
|
+
return dataclass_insts[0]
|
|
215
|
+
else:
|
|
216
|
+
return tuple(dataclass_insts)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def add_dataclass_fields_to_parser(
|
|
220
|
+
dataclass_type: Type[T_DataclassInstance],
|
|
221
|
+
*,
|
|
222
|
+
parser: Optional[ArgumentParser],
|
|
223
|
+
list_parsing: ListParsing = "argparse",
|
|
224
|
+
bool_action: BoolAction = "store",
|
|
225
|
+
add_dashed_arg: bool = True,
|
|
226
|
+
) -> ArgumentParser:
|
|
227
|
+
"""Perform the add dataclass fields to parser operation."""
|
|
228
|
+
if parser is None:
|
|
229
|
+
parser = ArgumentParser()
|
|
230
|
+
|
|
231
|
+
for field in fields(dataclass_type):
|
|
232
|
+
kwds = {}
|
|
233
|
+
posargs = [f"--{field.name}"]
|
|
234
|
+
if add_dashed_arg and "_" in field.name:
|
|
235
|
+
dashed_arg_name = field.name.replace("_", "-")
|
|
236
|
+
posargs.append(f"--{dashed_arg_name}")
|
|
237
|
+
|
|
238
|
+
if field.default is MISSING and field.default_factory is MISSING:
|
|
239
|
+
if bool_action != "store" and field.type is bool:
|
|
240
|
+
msg = f"Invalid arguments: boolean '{field.name}' without default value is incompatible with {bool_action=}."
|
|
241
|
+
raise RuntimeError(msg)
|
|
242
|
+
kwds["required"] = True
|
|
243
|
+
|
|
244
|
+
elif field.default is not MISSING:
|
|
245
|
+
kwds["default"] = field.default
|
|
246
|
+
elif field.default_factory is not MISSING:
|
|
247
|
+
kwds["default"] = field.default_factory() # type: ignore
|
|
248
|
+
else:
|
|
249
|
+
msg = f"Invalid field {field.name}: found values for default and default_factory."
|
|
250
|
+
raise ValueError(msg)
|
|
251
|
+
|
|
252
|
+
try:
|
|
253
|
+
inner_kwds = _get_kwds_for_type(field.type, list_parsing, bool_action)
|
|
254
|
+
except (ValueError, TypeError, RuntimeError) as err:
|
|
255
|
+
msg = f"Invalid field {field.name}: field type '{field.type}' is not supported."
|
|
256
|
+
raise type(err)(msg) from err
|
|
257
|
+
|
|
258
|
+
kwds.update(inner_kwds)
|
|
259
|
+
parser.add_argument(*posargs, **kwds)
|
|
260
|
+
|
|
261
|
+
return parser
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _get_kwds_for_type(
|
|
265
|
+
field_type: Any,
|
|
266
|
+
list_parsing: Optional[ListParsing],
|
|
267
|
+
bool_action: BoolAction,
|
|
268
|
+
) -> Dict[str, Any]:
|
|
269
|
+
"""Perform the get kwds for type operation."""
|
|
270
|
+
if bool_action == "bool_optional":
|
|
271
|
+
bool_action = BooleanOptionalAction
|
|
272
|
+
kwds = {}
|
|
273
|
+
|
|
274
|
+
if bool_action != "store" and field_type is bool:
|
|
275
|
+
kwds["action"] = bool_action
|
|
276
|
+
return kwds
|
|
277
|
+
|
|
278
|
+
elif list_parsing == "argparse" and _is_iterable_type_like(field_type):
|
|
279
|
+
type_args = get_args(field_type)
|
|
280
|
+
if isinstance(type_args, tuple) and len(type_args) == 1:
|
|
281
|
+
item_type = type_args[0]
|
|
282
|
+
kwds = _get_kwds_for_type(
|
|
283
|
+
item_type, list_parsing=None, bool_action=bool_action
|
|
284
|
+
)
|
|
285
|
+
kwds["nargs"] = "*"
|
|
286
|
+
return kwds
|
|
287
|
+
|
|
288
|
+
parse_fn = _search_parse_fn(field_type, list_parsing=list_parsing)
|
|
289
|
+
|
|
290
|
+
if parse_fn is not None:
|
|
291
|
+
kwds["type"] = parse_fn
|
|
292
|
+
if _is_literal_type(field_type):
|
|
293
|
+
kwds["choices"] = get_args(field_type)
|
|
294
|
+
return kwds
|
|
295
|
+
|
|
296
|
+
else:
|
|
297
|
+
msg = (
|
|
298
|
+
f"Unsupported type {field_type}. (with {list_parsing=} and {bool_action=})"
|
|
299
|
+
)
|
|
300
|
+
raise TypeError(msg)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
# ALIASES
|
|
304
|
+
@deprecated_alias(add_dataclass_fields_to_parser)
|
|
305
|
+
def new_parser_from_dataclass(*args, **kwargs):
|
|
306
|
+
"""Perform the new parser from dataclass operation."""
|
|
307
|
+
...
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
|
|
4
4
|
import re
|
|
5
|
+
from collections.abc import Iterable as _RuntimeIterable
|
|
5
6
|
from enum import Enum
|
|
6
7
|
from functools import partial
|
|
7
8
|
from pathlib import Path
|
|
@@ -17,16 +18,11 @@ from typing import (
|
|
|
17
18
|
TypeVar,
|
|
18
19
|
Union,
|
|
19
20
|
get_args,
|
|
21
|
+
get_origin,
|
|
20
22
|
overload,
|
|
21
23
|
)
|
|
22
24
|
|
|
23
25
|
from pythonwrench._core import Predicate
|
|
24
|
-
from pythonwrench.typing.checks import (
|
|
25
|
-
_is_iterable_type_like,
|
|
26
|
-
_is_literal_type,
|
|
27
|
-
_is_optional_type,
|
|
28
|
-
_is_union_type,
|
|
29
|
-
)
|
|
30
26
|
from pythonwrench.typing.classes import NoneType, UnionType
|
|
31
27
|
from pythonwrench.warnings import deprecated_alias
|
|
32
28
|
|
|
@@ -38,7 +34,6 @@ TargetType = Union[
|
|
|
38
34
|
UnionType,
|
|
39
35
|
"Type[Literal]",
|
|
40
36
|
"Type[Optional]",
|
|
41
|
-
Tuple[type, ...],
|
|
42
37
|
]
|
|
43
38
|
|
|
44
39
|
ListParsing = Literal["argparse", "brackets"]
|
|
@@ -51,12 +46,9 @@ DEFAULT_NONE_VALUES = ("None", "null")
|
|
|
51
46
|
_PARSER_REGISTRY: List[Tuple[Union[TargetType, Predicate], Callable]] = []
|
|
52
47
|
|
|
53
48
|
|
|
54
|
-
class ParseError(ValueError): ...
|
|
55
|
-
|
|
56
|
-
|
|
57
49
|
@overload
|
|
58
50
|
def register_parser_fn(
|
|
59
|
-
type_: Union[TargetType, Predicate, None],
|
|
51
|
+
type_: Union[TargetType[T], Predicate, None],
|
|
60
52
|
fn: None = None,
|
|
61
53
|
) -> Callable[[T_Callable], T_Callable]:
|
|
62
54
|
"""Perform the register parser fn operation."""
|
|
@@ -65,7 +57,7 @@ def register_parser_fn(
|
|
|
65
57
|
|
|
66
58
|
@overload
|
|
67
59
|
def register_parser_fn(
|
|
68
|
-
type_: Union[TargetType, Predicate, None],
|
|
60
|
+
type_: Union[TargetType[T], Predicate, None],
|
|
69
61
|
fn: T_Callable,
|
|
70
62
|
) -> T_Callable:
|
|
71
63
|
"""Perform the register parser fn operation."""
|
|
@@ -73,7 +65,7 @@ def register_parser_fn(
|
|
|
73
65
|
|
|
74
66
|
|
|
75
67
|
def register_parser_fn(
|
|
76
|
-
type_: Union[TargetType, Predicate, None],
|
|
68
|
+
type_: Union[TargetType[T], Predicate, None],
|
|
77
69
|
fn: Optional[Callable] = None,
|
|
78
70
|
) -> Callable:
|
|
79
71
|
"""Perform the register parser fn operation."""
|
|
@@ -107,7 +99,7 @@ def parse_to_type(
|
|
|
107
99
|
- True values: 'True', 'T', 'yes', 'y', '1'.
|
|
108
100
|
- False values: 'False', 'F', 'no', 'n', '0'.
|
|
109
101
|
- None values: 'None', 'null'
|
|
110
|
-
- Other raises
|
|
102
|
+
- Other raises ValueError.
|
|
111
103
|
"""
|
|
112
104
|
parse_fn = get_parse_fn(
|
|
113
105
|
target_type,
|
|
@@ -144,6 +136,16 @@ def get_parse_fn(
|
|
|
144
136
|
list_parsing=list_parsing,
|
|
145
137
|
handle_exception=handle_exception,
|
|
146
138
|
)
|
|
139
|
+
parse_fn = _search_parse_fn(type_, **kwds)
|
|
140
|
+
|
|
141
|
+
if parse_fn is None:
|
|
142
|
+
msg = f"Invalid argument {type_=}. (no valid type or typing found in registry)"
|
|
143
|
+
raise ValueError(msg)
|
|
144
|
+
|
|
145
|
+
return parse_fn
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _search_parse_fn(type_: TargetType[T], **kwds) -> Optional[Callable[[str], T]]:
|
|
147
149
|
if type_ is None:
|
|
148
150
|
type_ = NoneType
|
|
149
151
|
|
|
@@ -161,11 +163,9 @@ def get_parse_fn(
|
|
|
161
163
|
msg = f"Invalid argument {type_or_pred_i=}. (excepted type or predicate function)"
|
|
162
164
|
raise ValueError(msg)
|
|
163
165
|
|
|
164
|
-
if parse_fn is None:
|
|
165
|
-
|
|
166
|
-
raise ValueError(msg)
|
|
166
|
+
if parse_fn is not None:
|
|
167
|
+
parse_fn = partial(parse_fn, **kwds)
|
|
167
168
|
|
|
168
|
-
parse_fn = partial(parse_fn, **kwds)
|
|
169
169
|
return parse_fn
|
|
170
170
|
|
|
171
171
|
|
|
@@ -189,7 +189,7 @@ def parse_to_bool(
|
|
|
189
189
|
return False
|
|
190
190
|
|
|
191
191
|
values = tuple(true_values + false_values)
|
|
192
|
-
output =
|
|
192
|
+
output = ValueError(f"Invalid argument '{x}'. (expected one of {values})")
|
|
193
193
|
return _handle_output(x, handle_exception, output)
|
|
194
194
|
|
|
195
195
|
|
|
@@ -251,9 +251,39 @@ def _parse_to_str(x: str, **kwds) -> str:
|
|
|
251
251
|
return x
|
|
252
252
|
|
|
253
253
|
|
|
254
|
+
def _is_enum_type(x: Any, **kwds) -> bool:
|
|
255
|
+
"""Perform the is enum type operation."""
|
|
256
|
+
return isinstance(x, type) and issubclass(x, Enum)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _is_iterable_type_like(x: Any) -> bool:
|
|
260
|
+
"""Perform the is iterable type like operation."""
|
|
261
|
+
return any(xi in (list, Iterable, _RuntimeIterable) for xi in (x, get_origin(x)))
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _is_literal_type(x: Any) -> bool:
|
|
265
|
+
"""Perform the is literal type operation."""
|
|
266
|
+
origin = get_origin(x)
|
|
267
|
+
return origin is Literal
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _is_optional_type(x: Any) -> bool:
|
|
271
|
+
"""Perform the is optional type operation."""
|
|
272
|
+
return getattr(x, "__name__", None) == "Optional"
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _is_union_type(x: Any) -> bool:
|
|
276
|
+
"""Perform the is union type operation."""
|
|
277
|
+
origin = get_origin(x)
|
|
278
|
+
return origin == Union or getattr(origin, "__name__", None) in (
|
|
279
|
+
"Union",
|
|
280
|
+
"UnionType",
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
|
|
254
284
|
def _is_enum_for_parsing(x: Any, **kwds) -> bool:
|
|
255
285
|
"""Perform the is enum for parsing operation."""
|
|
256
|
-
return
|
|
286
|
+
return _is_enum_type(x)
|
|
257
287
|
|
|
258
288
|
|
|
259
289
|
def _is_iterable_type_like_for_parsing(
|