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.
Files changed (131) hide show
  1. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/CHANGELOG.md +13 -0
  2. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/CITATION.cff +2 -2
  3. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/PKG-INFO +1 -1
  4. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/__init__.py +1 -1
  5. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/argparse/dataclass_.py +24 -1
  6. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/logging.py +52 -17
  7. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/time.py +17 -0
  8. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_argparse.py +16 -2
  9. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_typing.py +15 -0
  10. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/.github/workflows/test.yaml +0 -0
  11. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/.gitignore +0 -0
  12. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/.pre-commit-config.yaml +0 -0
  13. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/.readthedocs.yaml +0 -0
  14. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/LICENSE +0 -0
  15. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/README.md +0 -0
  16. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/Makefile +0 -0
  17. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/conf.py +0 -0
  18. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/index.rst +0 -0
  19. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/make.bat +0 -0
  20. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/modules.rst +0 -0
  21. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.abc.rst +0 -0
  22. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.argparse.dataclass_.rst +0 -0
  23. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.argparse.parsers.rst +0 -0
  24. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.argparse.rst +0 -0
  25. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.cast.rst +0 -0
  26. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.checksum.rst +0 -0
  27. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.collections.collections.rst +0 -0
  28. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.collections.prop.rst +0 -0
  29. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.collections.reducers.rst +0 -0
  30. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.collections.rst +0 -0
  31. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.concurrent.rst +0 -0
  32. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.csv.rst +0 -0
  33. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.dataclasses.rst +0 -0
  34. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.datetime.rst +0 -0
  35. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.difflib.rst +0 -0
  36. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.disk_cache.rst +0 -0
  37. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.enum.rst +0 -0
  38. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.functools.rst +0 -0
  39. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.hashlib.rst +0 -0
  40. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.importlib.rst +0 -0
  41. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.inspect.rst +0 -0
  42. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.json.rst +0 -0
  43. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.jsonl.rst +0 -0
  44. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.logging.rst +0 -0
  45. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.math.rst +0 -0
  46. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.os.rst +0 -0
  47. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.pickle.rst +0 -0
  48. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.random.rst +0 -0
  49. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.re.rst +0 -0
  50. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.rst +0 -0
  51. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.semver.rst +0 -0
  52. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.serialization.csv.rst +0 -0
  53. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.serialization.json.rst +0 -0
  54. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.serialization.jsonl.rst +0 -0
  55. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.serialization.pickle.rst +0 -0
  56. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.serialization.rst +0 -0
  57. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.time.rst +0 -0
  58. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.typing.checks.rst +0 -0
  59. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.typing.classes.rst +0 -0
  60. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.typing.rst +0 -0
  61. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/pythonwrench.warnings.rst +0 -0
  62. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/docs/update.bash +0 -0
  63. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/pyproject.toml +0 -0
  64. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/setup.py +0 -0
  65. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/__main__.py +0 -0
  66. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/_core.py +0 -0
  67. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/abc.py +0 -0
  68. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/argparse/__init__.py +0 -0
  69. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/argparse/parsers.py +0 -0
  70. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/cast.py +0 -0
  71. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/checksum.py +0 -0
  72. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/collections/__init__.py +0 -0
  73. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/collections/collections.py +0 -0
  74. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/collections/prop.py +0 -0
  75. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/collections/reducers.py +0 -0
  76. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/concurrent.py +0 -0
  77. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/csv.py +0 -0
  78. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/dataclasses.py +0 -0
  79. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/datetime.py +0 -0
  80. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/difflib.py +0 -0
  81. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/disk_cache.py +0 -0
  82. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/entrypoints/info.py +0 -0
  83. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/entrypoints/safe_rmdir.py +0 -0
  84. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/entrypoints/tree.py +0 -0
  85. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/enum.py +0 -0
  86. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/functools.py +0 -0
  87. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/hashlib.py +0 -0
  88. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/importlib.py +0 -0
  89. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/inspect.py +0 -0
  90. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/json.py +0 -0
  91. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/jsonl.py +0 -0
  92. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/math.py +0 -0
  93. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/os.py +0 -0
  94. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/pickle.py +0 -0
  95. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/random.py +0 -0
  96. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/re.py +0 -0
  97. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/semver.py +0 -0
  98. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/serialization/__init__.py +0 -0
  99. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/serialization/_core.py +0 -0
  100. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/serialization/csv.py +0 -0
  101. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/serialization/json.py +0 -0
  102. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/serialization/jsonl.py +0 -0
  103. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/serialization/pickle.py +0 -0
  104. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/typing/__init__.py +0 -0
  105. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/typing/checks.py +0 -0
  106. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/typing/classes.py +0 -0
  107. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/src/pythonwrench/warnings.py +0 -0
  108. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_abc.py +0 -0
  109. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_cast.py +0 -0
  110. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_checksum.py +0 -0
  111. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_collections.py +0 -0
  112. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_core.py +0 -0
  113. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_csv.py +0 -0
  114. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_dataclasses.py +0 -0
  115. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_difflib.py +0 -0
  116. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_disk_cache.py +0 -0
  117. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_entrypoints.py +0 -0
  118. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_enum.py +0 -0
  119. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_functools.py +0 -0
  120. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_hashlib.py +0 -0
  121. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_importlib.py +0 -0
  122. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_inspect.py +0 -0
  123. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_json.py +0 -0
  124. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_jsonl.py +0 -0
  125. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_logging.py +0 -0
  126. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_math.py +0 -0
  127. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_os.py +0 -0
  128. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_random.py +0 -0
  129. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_readme.py +0 -0
  130. {pythonwrench-0.6.4 → pythonwrench-0.6.6}/tests/test_semver.py +0 -0
  131. {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.
@@ -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.6
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.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/
@@ -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.6"
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:
@@ -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: Union[IO[str], Literal["auto"]] = "auto",
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: Union[IO[str], Literal["auto"]] = "auto",
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
- stream = sys.stdout
112
+ streams = [sys.stdout]
109
113
  else:
110
- stream = sys.stderr
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 set_fmt:
114
- found = False
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
- if isinstance(handler, StreamHandler) and handler.stream is stream:
118
- handler.setFormatter(fmt)
119
- found = True
120
- break
140
+ handler.setFormatter(fmt)
141
+ continue
121
142
 
122
- if not found:
123
- handler = StreamHandler(stream) # type: ignore
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