pythonwrench 0.6.4__tar.gz → 0.6.6__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.4 → pythonwrench-0.6.6}/CHANGELOG.md +13 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/CITATION.cff +2 -2
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/PKG-INFO +1 -1
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/__init__.py +1 -1
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/argparse/dataclass_.py +24 -1
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/logging.py +52 -17
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/time.py +17 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_argparse.py +16 -2
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_typing.py +15 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/.github/workflows/test.yaml +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/.gitignore +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/.pre-commit-config.yaml +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/.readthedocs.yaml +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/LICENSE +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/README.md +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/Makefile +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/conf.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/index.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/make.bat +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/modules.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.abc.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.argparse.dataclass_.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.argparse.parsers.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.argparse.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.cast.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.checksum.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.collections.collections.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.collections.prop.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.collections.reducers.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.collections.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.concurrent.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.csv.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.dataclasses.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.datetime.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.difflib.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.disk_cache.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.enum.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.functools.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.hashlib.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.importlib.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.inspect.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.json.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.jsonl.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.logging.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.math.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.os.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.pickle.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.random.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.re.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.semver.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.serialization.csv.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.serialization.json.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.serialization.jsonl.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.serialization.pickle.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.serialization.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.time.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.typing.checks.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.typing.classes.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.typing.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.warnings.rst +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/update.bash +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/pyproject.toml +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/setup.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/__main__.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/_core.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/abc.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/argparse/__init__.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/argparse/parsers.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/cast.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/checksum.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/collections/__init__.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/collections/collections.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/collections/prop.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/collections/reducers.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/concurrent.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/csv.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/dataclasses.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/datetime.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/difflib.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/disk_cache.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/entrypoints/info.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/entrypoints/safe_rmdir.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/entrypoints/tree.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/enum.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/functools.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/hashlib.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/importlib.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/inspect.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/json.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/jsonl.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/math.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/os.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/pickle.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/random.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/re.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/semver.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/serialization/__init__.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/serialization/_core.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/serialization/csv.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/serialization/json.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/serialization/jsonl.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/serialization/pickle.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/typing/__init__.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/typing/checks.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/typing/classes.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/warnings.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_abc.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_cast.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_checksum.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_collections.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_core.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_csv.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_dataclasses.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_difflib.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_disk_cache.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_entrypoints.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_enum.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_functools.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_hashlib.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_importlib.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_inspect.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_json.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_jsonl.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_logging.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_math.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_os.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_random.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_readme.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_semver.py +0 -0
- {pythonwrench-0.6.4 → pythonwrench-0.6.6}/uv.lock +0 -0
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.6.6] 2026-08-20
|
|
6
|
+
### Fixed
|
|
7
|
+
- `setup_logging_level` isinstance check.
|
|
8
|
+
|
|
9
|
+
## [0.6.5] 2026-08-20
|
|
10
|
+
### Added
|
|
11
|
+
- `return_single_namespace` option for `parse_args_using_dataclass` function.
|
|
12
|
+
- Context manager for `Ticker` class.
|
|
13
|
+
|
|
14
|
+
### Modified
|
|
15
|
+
- Allow argument `stream` to be None or root_logging_stream customize handlers set in setup_logging functions.
|
|
16
|
+
|
|
17
|
+
|
|
5
18
|
## [0.6.4] 2026-08-17
|
|
6
19
|
### Added
|
|
7
20
|
- Function `parse_args_using_dataclass` can now handle multiple dataclass at once.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pythonwrench
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.6
|
|
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/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
|
|
4
|
-
from argparse import ArgumentParser
|
|
4
|
+
from argparse import ArgumentParser, Namespace
|
|
5
5
|
from dataclasses import MISSING, fields
|
|
6
6
|
from typing import (
|
|
7
7
|
Any,
|
|
@@ -48,6 +48,19 @@ _BoolActionName = Literal["store", "store_true", "store_false", "bool_optional"]
|
|
|
48
48
|
BoolAction = Union[_BoolActionName, Type[BooleanOptionalAction]]
|
|
49
49
|
|
|
50
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
|
+
|
|
51
64
|
@overload
|
|
52
65
|
def parse_args_using_dataclass(
|
|
53
66
|
dataclass_type: Type[T_DataclassInstance],
|
|
@@ -57,6 +70,7 @@ def parse_args_using_dataclass(
|
|
|
57
70
|
list_parsing: ListParsing = "argparse",
|
|
58
71
|
bool_action: BoolAction = "store",
|
|
59
72
|
add_dashed_arg: bool = True,
|
|
73
|
+
return_single_namespace: Literal[False] = False,
|
|
60
74
|
) -> T_DataclassInstance: ...
|
|
61
75
|
|
|
62
76
|
|
|
@@ -71,6 +85,7 @@ def parse_args_using_dataclass(
|
|
|
71
85
|
list_parsing: ListParsing = "argparse",
|
|
72
86
|
bool_action: BoolAction = "store",
|
|
73
87
|
add_dashed_arg: bool = True,
|
|
88
|
+
return_single_namespace: Literal[False] = False,
|
|
74
89
|
) -> Tuple[
|
|
75
90
|
T_DataclassInstance,
|
|
76
91
|
T_DataclassInstance_2,
|
|
@@ -89,6 +104,7 @@ def parse_args_using_dataclass(
|
|
|
89
104
|
list_parsing: ListParsing = "argparse",
|
|
90
105
|
bool_action: BoolAction = "store",
|
|
91
106
|
add_dashed_arg: bool = True,
|
|
107
|
+
return_single_namespace: Literal[False] = False,
|
|
92
108
|
) -> Tuple[
|
|
93
109
|
T_DataclassInstance,
|
|
94
110
|
T_DataclassInstance_2,
|
|
@@ -109,6 +125,7 @@ def parse_args_using_dataclass(
|
|
|
109
125
|
list_parsing: ListParsing = "argparse",
|
|
110
126
|
bool_action: BoolAction = "store",
|
|
111
127
|
add_dashed_arg: bool = True,
|
|
128
|
+
return_single_namespace: Literal[False] = False,
|
|
112
129
|
) -> Tuple[
|
|
113
130
|
T_DataclassInstance,
|
|
114
131
|
T_DataclassInstance_2,
|
|
@@ -131,6 +148,7 @@ def parse_args_using_dataclass(
|
|
|
131
148
|
list_parsing: ListParsing = "argparse",
|
|
132
149
|
bool_action: BoolAction = "store",
|
|
133
150
|
add_dashed_arg: bool = True,
|
|
151
|
+
return_single_namespace: Literal[False] = False,
|
|
134
152
|
) -> Tuple[
|
|
135
153
|
T_DataclassInstance,
|
|
136
154
|
T_DataclassInstance_2,
|
|
@@ -148,9 +166,11 @@ def parse_args_using_dataclass(
|
|
|
148
166
|
list_parsing: ListParsing = "argparse",
|
|
149
167
|
bool_action: BoolAction = "store",
|
|
150
168
|
add_dashed_arg: bool = True,
|
|
169
|
+
return_single_namespace: bool = False,
|
|
151
170
|
) -> Union[
|
|
152
171
|
DataclassInstance,
|
|
153
172
|
Tuple[DataclassInstance, ...],
|
|
173
|
+
Namespace,
|
|
154
174
|
]:
|
|
155
175
|
"""Converts prog args to a typed dataclass using argparse.
|
|
156
176
|
|
|
@@ -175,6 +195,9 @@ def parse_args_using_dataclass(
|
|
|
175
195
|
msg = f"Found {len(argv)} unknown arguments: {argv}."
|
|
176
196
|
raise ValueError(msg)
|
|
177
197
|
|
|
198
|
+
if return_single_namespace:
|
|
199
|
+
return parsed
|
|
200
|
+
|
|
178
201
|
dataclass_insts = []
|
|
179
202
|
for dataclass_type_i in dataclass_types:
|
|
180
203
|
if init_parser is None and len(dataclass_types) == 1:
|
|
@@ -5,16 +5,17 @@ import inspect
|
|
|
5
5
|
import logging
|
|
6
6
|
import sys
|
|
7
7
|
from functools import lru_cache
|
|
8
|
+
from io import TextIOBase
|
|
8
9
|
from logging import FileHandler, Formatter, Logger, StreamHandler
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
from types import ModuleType
|
|
11
|
-
from typing import IO, List, Literal, Optional, TypeVar, Union
|
|
12
|
+
from typing import IO, Iterable, List, Literal, Optional, TextIO, TypeVar, Union
|
|
12
13
|
|
|
13
14
|
from typing_extensions import TypeAlias
|
|
14
15
|
|
|
15
16
|
from pythonwrench.importlib import reload_submodules
|
|
16
17
|
from pythonwrench.semver import Version
|
|
17
|
-
from pythonwrench.typing import SupportsIterLen
|
|
18
|
+
from pythonwrench.typing import SupportsIterLen, isinstance_generic
|
|
18
19
|
|
|
19
20
|
T = TypeVar("T", covariant=True)
|
|
20
21
|
|
|
@@ -29,6 +30,9 @@ PackageOrLoggerList: TypeAlias = Union[
|
|
|
29
30
|
PackageOrLogger,
|
|
30
31
|
SupportsIterLen[PackageOrLogger],
|
|
31
32
|
]
|
|
33
|
+
TargetStream = Union[
|
|
34
|
+
IO[str], Iterable[IO[str]], Literal["auto", "root_logger_streams"], None
|
|
35
|
+
]
|
|
32
36
|
|
|
33
37
|
_PARENT_FILE_KEY = "__parent_file__"
|
|
34
38
|
DEFAULT_FMT = "[%(asctime)s][%(name)s][%(levelname)s] - %(message)s"
|
|
@@ -58,7 +62,7 @@ def setup_logging_verbose(
|
|
|
58
62
|
verbose: Optional[int] = VERBOSE_INFO,
|
|
59
63
|
*,
|
|
60
64
|
fmt: Union[str, None, Formatter] = DEFAULT_FMT,
|
|
61
|
-
stream:
|
|
65
|
+
stream: TargetStream = "auto",
|
|
62
66
|
set_fmt: bool = True,
|
|
63
67
|
capture_warnings: bool = True,
|
|
64
68
|
autoreload: bool = True,
|
|
@@ -88,7 +92,7 @@ def setup_logging_level(
|
|
|
88
92
|
level: Optional[int] = logging.INFO,
|
|
89
93
|
*,
|
|
90
94
|
fmt: Union[str, None, Formatter] = DEFAULT_FMT,
|
|
91
|
-
stream:
|
|
95
|
+
stream: TargetStream = "auto",
|
|
92
96
|
set_fmt: bool = True,
|
|
93
97
|
capture_warnings: bool = True,
|
|
94
98
|
autoreload: bool = True,
|
|
@@ -105,28 +109,59 @@ def setup_logging_level(
|
|
|
105
109
|
|
|
106
110
|
if stream == "auto":
|
|
107
111
|
if running_on_interpreter():
|
|
108
|
-
|
|
112
|
+
streams = [sys.stdout]
|
|
109
113
|
else:
|
|
110
|
-
|
|
114
|
+
streams = [sys.stderr]
|
|
115
|
+
elif stream == "root_logger_streams":
|
|
116
|
+
streams = [
|
|
117
|
+
handler.stream
|
|
118
|
+
for handler in logging.getLogger().handlers
|
|
119
|
+
if isinstance(handler, StreamHandler)
|
|
120
|
+
]
|
|
121
|
+
elif isinstance(stream, (TextIOBase, TextIO)):
|
|
122
|
+
streams = [stream]
|
|
123
|
+
elif stream is None:
|
|
124
|
+
streams = stream
|
|
125
|
+
elif isinstance_generic(stream, Iterable[Union[TextIOBase, TextIO]]):
|
|
126
|
+
streams = list(stream)
|
|
127
|
+
else:
|
|
128
|
+
raise TypeError(f"Invalid argument type {type(stream)=}.")
|
|
129
|
+
del stream
|
|
111
130
|
|
|
112
131
|
for logger in logger_lst:
|
|
113
|
-
if
|
|
114
|
-
|
|
132
|
+
if level is not None:
|
|
133
|
+
logger.setLevel(level)
|
|
115
134
|
|
|
135
|
+
if not set_fmt:
|
|
136
|
+
continue
|
|
137
|
+
|
|
138
|
+
if streams is None:
|
|
116
139
|
for handler in logger.handlers:
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
found = True
|
|
120
|
-
break
|
|
140
|
+
handler.setFormatter(fmt)
|
|
141
|
+
continue
|
|
121
142
|
|
|
122
|
-
|
|
123
|
-
|
|
143
|
+
found = [False] * len(streams)
|
|
144
|
+
|
|
145
|
+
for handler in logger.handlers:
|
|
146
|
+
if not isinstance(handler, StreamHandler):
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
index = streams.index(handler.stream)
|
|
151
|
+
except IndexError:
|
|
152
|
+
continue
|
|
153
|
+
|
|
154
|
+
handler.setFormatter(fmt)
|
|
155
|
+
found[index] = True
|
|
156
|
+
|
|
157
|
+
if not all(found):
|
|
158
|
+
for stream_i, found_i in zip(streams, found):
|
|
159
|
+
if found_i:
|
|
160
|
+
continue
|
|
161
|
+
handler = StreamHandler(stream_i)
|
|
124
162
|
handler.setFormatter(fmt)
|
|
125
163
|
logger.addHandler(handler)
|
|
126
164
|
|
|
127
|
-
if level is not None:
|
|
128
|
-
logger.setLevel(level)
|
|
129
|
-
|
|
130
165
|
if autoreload:
|
|
131
166
|
for logger in logger_lst:
|
|
132
167
|
if logger.name not in sys.modules:
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
import time
|
|
5
5
|
from typing import Callable, Optional
|
|
6
6
|
|
|
7
|
+
from typing_extensions import Self
|
|
8
|
+
|
|
7
9
|
|
|
8
10
|
class Ticker:
|
|
9
11
|
def __init__(
|
|
@@ -32,3 +34,18 @@ class Ticker:
|
|
|
32
34
|
if prev_tick is None:
|
|
33
35
|
prev_tick = self._get_time_fn()
|
|
34
36
|
self._prev_tick = prev_tick
|
|
37
|
+
|
|
38
|
+
def get_elapsed_duration(self) -> float:
|
|
39
|
+
"""Returns elapsed time since last tick. If no tick has been set, returns 0."""
|
|
40
|
+
if self._prev_tick is None:
|
|
41
|
+
return 0.0
|
|
42
|
+
now = self._get_time_fn()
|
|
43
|
+
duration = now - self._prev_tick
|
|
44
|
+
return duration
|
|
45
|
+
|
|
46
|
+
def __enter__(self) -> Self:
|
|
47
|
+
self.tick()
|
|
48
|
+
return self
|
|
49
|
+
|
|
50
|
+
def __exit__(self, exc_type, exc, tb):
|
|
51
|
+
pass
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
|
|
4
4
|
import unittest
|
|
5
|
-
from argparse import ArgumentParser
|
|
6
|
-
from dataclasses import dataclass, field
|
|
5
|
+
from argparse import ArgumentParser, Namespace
|
|
6
|
+
from dataclasses import asdict, dataclass, field
|
|
7
7
|
from enum import Enum, auto
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from typing import Iterable, List, Literal, Optional, Tuple, Union
|
|
@@ -302,6 +302,20 @@ class TestDataclassParser(TestCase):
|
|
|
302
302
|
assert cfg1 == expected_cfg1
|
|
303
303
|
assert cfg2 == expected_cfg2
|
|
304
304
|
|
|
305
|
+
args = parse_args_using_dataclass(
|
|
306
|
+
Cfg2,
|
|
307
|
+
Cfg1,
|
|
308
|
+
args=[
|
|
309
|
+
"--path",
|
|
310
|
+
str(expected_cfg1.path),
|
|
311
|
+
"--b",
|
|
312
|
+
expected_cfg2.b,
|
|
313
|
+
],
|
|
314
|
+
return_single_namespace=True,
|
|
315
|
+
)
|
|
316
|
+
expected_args = Namespace(**asdict(expected_cfg1), **asdict(expected_cfg2))
|
|
317
|
+
assert args == expected_args
|
|
318
|
+
|
|
305
319
|
def test_parse_store_bool(self) -> None:
|
|
306
320
|
@dataclass
|
|
307
321
|
class Cfg:
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
|
|
4
|
+
import logging
|
|
4
5
|
import unittest
|
|
5
6
|
from dataclasses import dataclass
|
|
7
|
+
from io import TextIOBase
|
|
8
|
+
from logging import StreamHandler
|
|
6
9
|
from numbers import Number
|
|
7
10
|
from pathlib import Path
|
|
8
11
|
from typing import (
|
|
@@ -331,6 +334,18 @@ class TestIsInstanceGuard(TestCase):
|
|
|
331
334
|
assert isinstance_generic([], List[int], check_only_first=True)
|
|
332
335
|
assert isinstance_generic([1, 2], List, check_only_first=True)
|
|
333
336
|
|
|
337
|
+
def test_io_check(self) -> None:
|
|
338
|
+
assert isinstance_generic([], Iterable[TextIOBase])
|
|
339
|
+
assert not isinstance_generic([1], Iterable[TextIOBase])
|
|
340
|
+
|
|
341
|
+
logger = logging.getLogger()
|
|
342
|
+
streams = [
|
|
343
|
+
handler.stream
|
|
344
|
+
for handler in logger.handlers
|
|
345
|
+
if isinstance(handler, StreamHandler)
|
|
346
|
+
]
|
|
347
|
+
assert isinstance_generic(streams, Iterable[TextIOBase])
|
|
348
|
+
|
|
334
349
|
|
|
335
350
|
class TestCheckArgsType(TestCase):
|
|
336
351
|
def test_example_1(self) -> None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|