pythonwrench 0.6.4__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.
Files changed (131) hide show
  1. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/CHANGELOG.md +9 -0
  2. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/CITATION.cff +2 -2
  3. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/PKG-INFO +1 -1
  4. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/__init__.py +1 -1
  5. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/argparse/dataclass_.py +24 -1
  6. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/logging.py +51 -17
  7. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/time.py +17 -0
  8. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/tests/test_argparse.py +16 -2
  9. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/.github/workflows/test.yaml +0 -0
  10. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/.gitignore +0 -0
  11. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/.pre-commit-config.yaml +0 -0
  12. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/.readthedocs.yaml +0 -0
  13. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/LICENSE +0 -0
  14. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/README.md +0 -0
  15. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/Makefile +0 -0
  16. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/conf.py +0 -0
  17. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/index.rst +0 -0
  18. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/make.bat +0 -0
  19. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/modules.rst +0 -0
  20. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.abc.rst +0 -0
  21. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.argparse.dataclass_.rst +0 -0
  22. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.argparse.parsers.rst +0 -0
  23. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.argparse.rst +0 -0
  24. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.cast.rst +0 -0
  25. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.checksum.rst +0 -0
  26. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.collections.collections.rst +0 -0
  27. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.collections.prop.rst +0 -0
  28. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.collections.reducers.rst +0 -0
  29. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.collections.rst +0 -0
  30. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.concurrent.rst +0 -0
  31. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.csv.rst +0 -0
  32. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.dataclasses.rst +0 -0
  33. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.datetime.rst +0 -0
  34. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.difflib.rst +0 -0
  35. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.disk_cache.rst +0 -0
  36. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.enum.rst +0 -0
  37. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.functools.rst +0 -0
  38. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.hashlib.rst +0 -0
  39. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.importlib.rst +0 -0
  40. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.inspect.rst +0 -0
  41. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.json.rst +0 -0
  42. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.jsonl.rst +0 -0
  43. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.logging.rst +0 -0
  44. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.math.rst +0 -0
  45. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.os.rst +0 -0
  46. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.pickle.rst +0 -0
  47. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.random.rst +0 -0
  48. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.re.rst +0 -0
  49. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.rst +0 -0
  50. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.semver.rst +0 -0
  51. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.serialization.csv.rst +0 -0
  52. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.serialization.json.rst +0 -0
  53. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.serialization.jsonl.rst +0 -0
  54. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.serialization.pickle.rst +0 -0
  55. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.serialization.rst +0 -0
  56. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.time.rst +0 -0
  57. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.typing.checks.rst +0 -0
  58. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.typing.classes.rst +0 -0
  59. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.typing.rst +0 -0
  60. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/pythonwrench.warnings.rst +0 -0
  61. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/docs/update.bash +0 -0
  62. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/pyproject.toml +0 -0
  63. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/setup.py +0 -0
  64. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/__main__.py +0 -0
  65. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/_core.py +0 -0
  66. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/abc.py +0 -0
  67. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/argparse/__init__.py +0 -0
  68. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/argparse/parsers.py +0 -0
  69. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/cast.py +0 -0
  70. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/checksum.py +0 -0
  71. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/collections/__init__.py +0 -0
  72. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/collections/collections.py +0 -0
  73. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/collections/prop.py +0 -0
  74. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/collections/reducers.py +0 -0
  75. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/concurrent.py +0 -0
  76. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/csv.py +0 -0
  77. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/dataclasses.py +0 -0
  78. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/datetime.py +0 -0
  79. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/difflib.py +0 -0
  80. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/disk_cache.py +0 -0
  81. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/entrypoints/info.py +0 -0
  82. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/entrypoints/safe_rmdir.py +0 -0
  83. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/entrypoints/tree.py +0 -0
  84. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/enum.py +0 -0
  85. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/functools.py +0 -0
  86. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/hashlib.py +0 -0
  87. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/importlib.py +0 -0
  88. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/inspect.py +0 -0
  89. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/json.py +0 -0
  90. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/jsonl.py +0 -0
  91. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/math.py +0 -0
  92. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/os.py +0 -0
  93. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/pickle.py +0 -0
  94. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/random.py +0 -0
  95. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/re.py +0 -0
  96. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/semver.py +0 -0
  97. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/serialization/__init__.py +0 -0
  98. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/serialization/_core.py +0 -0
  99. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/serialization/csv.py +0 -0
  100. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/serialization/json.py +0 -0
  101. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/serialization/jsonl.py +0 -0
  102. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/serialization/pickle.py +0 -0
  103. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/typing/__init__.py +0 -0
  104. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/typing/checks.py +0 -0
  105. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/typing/classes.py +0 -0
  106. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/src/pythonwrench/warnings.py +0 -0
  107. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/tests/test_abc.py +0 -0
  108. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/tests/test_cast.py +0 -0
  109. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/tests/test_checksum.py +0 -0
  110. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/tests/test_collections.py +0 -0
  111. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/tests/test_core.py +0 -0
  112. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/tests/test_csv.py +0 -0
  113. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/tests/test_dataclasses.py +0 -0
  114. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/tests/test_difflib.py +0 -0
  115. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/tests/test_disk_cache.py +0 -0
  116. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/tests/test_entrypoints.py +0 -0
  117. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/tests/test_enum.py +0 -0
  118. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/tests/test_functools.py +0 -0
  119. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/tests/test_hashlib.py +0 -0
  120. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/tests/test_importlib.py +0 -0
  121. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/tests/test_inspect.py +0 -0
  122. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/tests/test_json.py +0 -0
  123. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/tests/test_jsonl.py +0 -0
  124. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/tests/test_logging.py +0 -0
  125. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/tests/test_math.py +0 -0
  126. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/tests/test_os.py +0 -0
  127. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/tests/test_random.py +0 -0
  128. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/tests/test_readme.py +0 -0
  129. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/tests/test_semver.py +0 -0
  130. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/tests/test_typing.py +0 -0
  131. {pythonwrench-0.6.4 → pythonwrench-0.6.5}/uv.lock +0 -0
@@ -2,6 +2,15 @@
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
+
5
14
  ## [0.6.4] 2026-08-17
6
15
  ### Added
7
16
  - Function `parse_args_using_dataclass` can now handle multiple dataclass at once.
@@ -17,5 +17,5 @@ keywords:
17
17
  - tools
18
18
  - utilities
19
19
  license: MIT
20
- version: 0.6.4
21
- date-released: '2026-08-17'
20
+ version: 0.6.5
21
+ date-released: '2026-08-20'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pythonwrench
3
- Version: 0.6.4
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/
@@ -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.4"
12
+ __version__ = "0.6.5"
13
13
 
14
14
 
15
15
  from typing import TYPE_CHECKING
@@ -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:
@@ -8,13 +8,13 @@ from functools import lru_cache
8
8
  from logging import FileHandler, Formatter, Logger, StreamHandler
9
9
  from pathlib import Path
10
10
  from types import ModuleType
11
- from typing import IO, List, Literal, Optional, TypeVar, Union
11
+ from typing import IO, Iterable, List, Literal, Optional, TypeVar, Union
12
12
 
13
13
  from typing_extensions import TypeAlias
14
14
 
15
15
  from pythonwrench.importlib import reload_submodules
16
16
  from pythonwrench.semver import Version
17
- from pythonwrench.typing import SupportsIterLen
17
+ from pythonwrench.typing import SupportsIterLen, isinstance_generic
18
18
 
19
19
  T = TypeVar("T", covariant=True)
20
20
 
@@ -29,6 +29,9 @@ PackageOrLoggerList: TypeAlias = Union[
29
29
  PackageOrLogger,
30
30
  SupportsIterLen[PackageOrLogger],
31
31
  ]
32
+ TargetStream = Union[
33
+ IO[str], Iterable[IO[str]], Literal["auto", "root_logger_streams"], None
34
+ ]
32
35
 
33
36
  _PARENT_FILE_KEY = "__parent_file__"
34
37
  DEFAULT_FMT = "[%(asctime)s][%(name)s][%(levelname)s] - %(message)s"
@@ -58,7 +61,7 @@ def setup_logging_verbose(
58
61
  verbose: Optional[int] = VERBOSE_INFO,
59
62
  *,
60
63
  fmt: Union[str, None, Formatter] = DEFAULT_FMT,
61
- stream: Union[IO[str], Literal["auto"]] = "auto",
64
+ stream: TargetStream = "auto",
62
65
  set_fmt: bool = True,
63
66
  capture_warnings: bool = True,
64
67
  autoreload: bool = True,
@@ -88,7 +91,7 @@ def setup_logging_level(
88
91
  level: Optional[int] = logging.INFO,
89
92
  *,
90
93
  fmt: Union[str, None, Formatter] = DEFAULT_FMT,
91
- stream: Union[IO[str], Literal["auto"]] = "auto",
94
+ stream: TargetStream = "auto",
92
95
  set_fmt: bool = True,
93
96
  capture_warnings: bool = True,
94
97
  autoreload: bool = True,
@@ -105,28 +108,59 @@ def setup_logging_level(
105
108
 
106
109
  if stream == "auto":
107
110
  if running_on_interpreter():
108
- stream = sys.stdout
111
+ streams = [sys.stdout]
109
112
  else:
110
- stream = sys.stderr
113
+ streams = [sys.stderr]
114
+ elif stream == "root_logger_streams":
115
+ streams = [
116
+ handler.stream
117
+ for handler in logging.getLogger().handlers
118
+ if isinstance(handler, StreamHandler)
119
+ ]
120
+ elif isinstance(stream, IO):
121
+ streams = [stream]
122
+ elif stream is None:
123
+ streams = stream
124
+ elif isinstance_generic(stream, Iterable[IO]):
125
+ streams = list(stream)
126
+ else:
127
+ raise TypeError(f"Invalid argument type {type(stream)=}.")
128
+ del stream
111
129
 
112
130
  for logger in logger_lst:
113
- if set_fmt:
114
- found = False
131
+ if level is not None:
132
+ logger.setLevel(level)
115
133
 
134
+ if not set_fmt:
135
+ continue
136
+
137
+ if streams is None:
116
138
  for handler in logger.handlers:
117
- if isinstance(handler, StreamHandler) and handler.stream is stream:
118
- handler.setFormatter(fmt)
119
- found = True
120
- break
139
+ handler.setFormatter(fmt)
140
+ continue
121
141
 
122
- if not found:
123
- handler = StreamHandler(stream) # type: ignore
142
+ found = [False] * len(streams)
143
+
144
+ for handler in logger.handlers:
145
+ if not isinstance(handler, StreamHandler):
146
+ continue
147
+
148
+ try:
149
+ index = streams.index(handler.stream)
150
+ except IndexError:
151
+ continue
152
+
153
+ handler.setFormatter(fmt)
154
+ found[index] = True
155
+
156
+ if not all(found):
157
+ for stream_i, found_i in zip(streams, found):
158
+ if found_i:
159
+ continue
160
+ handler = StreamHandler(stream_i)
124
161
  handler.setFormatter(fmt)
125
162
  logger.addHandler(handler)
126
163
 
127
- if level is not None:
128
- logger.setLevel(level)
129
-
130
164
  if autoreload:
131
165
  for logger in logger_lst:
132
166
  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:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes