thds.core 1.47.20251023235801__py3-none-any.whl → 1.47.20251024053252__py3-none-any.whl
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.
Potentially problematic release.
This version of thds.core might be problematic. Click here for more details.
- thds/core/inspect.py +1 -26
- thds/core/log/__init__.py +1 -1
- thds/core/log/kw_logger.py +0 -10
- thds/core/parallel.py +23 -38
- {thds_core-1.47.20251023235801.dist-info → thds_core-1.47.20251024053252.dist-info}/METADATA +1 -1
- {thds_core-1.47.20251023235801.dist-info → thds_core-1.47.20251024053252.dist-info}/RECORD +9 -9
- {thds_core-1.47.20251023235801.dist-info → thds_core-1.47.20251024053252.dist-info}/WHEEL +0 -0
- {thds_core-1.47.20251023235801.dist-info → thds_core-1.47.20251024053252.dist-info}/entry_points.txt +0 -0
- {thds_core-1.47.20251023235801.dist-info → thds_core-1.47.20251024053252.dist-info}/top_level.txt +0 -0
thds/core/inspect.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
import typing as ty
|
|
3
3
|
from dataclasses import dataclass
|
|
4
|
-
from types import ModuleType
|
|
5
4
|
|
|
6
5
|
|
|
7
6
|
@dataclass(frozen=True)
|
|
@@ -33,7 +32,7 @@ def get_caller_info(skip: int = 2) -> CallerInfo:
|
|
|
33
32
|
start = 0 + skip
|
|
34
33
|
if len(stack) < start + 1:
|
|
35
34
|
raise RuntimeError(f"The stack has less than f{skip} + 1 frames in it.")
|
|
36
|
-
parentframe = stack[start]
|
|
35
|
+
parentframe = stack[start][0]
|
|
37
36
|
|
|
38
37
|
# full dotted name of caller module
|
|
39
38
|
module_info = inspect.getmodule(parentframe)
|
|
@@ -69,27 +68,3 @@ def bind_arguments(
|
|
|
69
68
|
|
|
70
69
|
def get_argument(arg_name: str, bound_arguments: inspect.BoundArguments) -> ty.Any:
|
|
71
70
|
return bound_arguments.arguments[arg_name]
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def yield_caller_modules_and_frames(*skip: str) -> ty.Iterator[tuple[ModuleType, inspect.FrameInfo]]:
|
|
75
|
-
"""Yields caller modules and their frame info, skipping any modules in the skip list."""
|
|
76
|
-
stack = inspect.stack()
|
|
77
|
-
skip = set(skip) | {__name__} # type: ignore
|
|
78
|
-
for frame_info in stack[1:]: # don't bother with the current frame, obviously
|
|
79
|
-
module = inspect.getmodule(frame_info.frame)
|
|
80
|
-
if module:
|
|
81
|
-
module_name = module.__name__
|
|
82
|
-
if module_name not in skip:
|
|
83
|
-
yield module, frame_info
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def caller_module_name(*skip: str) -> str:
|
|
87
|
-
"""
|
|
88
|
-
Find the first caller module that is not in the skip list.
|
|
89
|
-
:param skip: module names to skip
|
|
90
|
-
:return: the first caller module name not in skip, or empty string if no module can be found
|
|
91
|
-
"""
|
|
92
|
-
for module, _frame in yield_caller_modules_and_frames(*skip):
|
|
93
|
-
return module.__name__
|
|
94
|
-
|
|
95
|
-
return "" # this is trivially distinguishable from a module name, so no need to force people to handle None
|
thds/core/log/__init__.py
CHANGED
|
@@ -26,4 +26,4 @@ logger.info("testing 5")
|
|
|
26
26
|
|
|
27
27
|
from .basic_config import DuplicateFilter, set_logger_to_console_level # noqa: F401
|
|
28
28
|
from .kw_formatter import ThdsCompactFormatter # noqa: F401
|
|
29
|
-
from .kw_logger import KwLogger,
|
|
29
|
+
from .kw_logger import KwLogger, getLogger, logger_context, make_th_formatters_safe # noqa: F401
|
thds/core/log/kw_logger.py
CHANGED
|
@@ -80,16 +80,6 @@ def getLogger(name: Optional[str] = None) -> logging.LoggerAdapter:
|
|
|
80
80
|
return KwLogger(logging.getLogger(name), dict())
|
|
81
81
|
|
|
82
82
|
|
|
83
|
-
def auto(*skip: str) -> logging.LoggerAdapter:
|
|
84
|
-
from .. import inspect
|
|
85
|
-
|
|
86
|
-
module_name = inspect.caller_module_name(__name__, *skip)
|
|
87
|
-
if not module_name:
|
|
88
|
-
raise ValueError("Cannot automatically determine caller module name for logger.")
|
|
89
|
-
|
|
90
|
-
return getLogger(module_name)
|
|
91
|
-
|
|
92
|
-
|
|
93
83
|
def make_th_formatters_safe(logger: logging.Logger):
|
|
94
84
|
"""Non-adapted loggers may still run into our root format string,
|
|
95
85
|
which expects _TH_REC_CTXT to be present on every LogRecord.
|
thds/core/parallel.py
CHANGED
|
@@ -8,7 +8,7 @@ from collections import defaultdict
|
|
|
8
8
|
from dataclasses import dataclass
|
|
9
9
|
from uuid import uuid4
|
|
10
10
|
|
|
11
|
-
from thds.core import concurrency, config, files,
|
|
11
|
+
from thds.core import concurrency, config, files, log
|
|
12
12
|
|
|
13
13
|
PARALLEL_OFF = config.item("off", default=False, parse=config.tobool)
|
|
14
14
|
# if you want to simplify a stack trace, this may be your friend
|
|
@@ -17,6 +17,9 @@ R = ty.TypeVar("R")
|
|
|
17
17
|
T_co = ty.TypeVar("T_co", covariant=True)
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
logger = log.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
20
23
|
class IterableWithLen(ty.Protocol[T_co]):
|
|
21
24
|
def __iter__(self) -> ty.Iterator[T_co]: ... # pragma: no cover
|
|
22
25
|
|
|
@@ -66,21 +69,13 @@ class Error:
|
|
|
66
69
|
H = ty.TypeVar("H", bound=ty.Hashable)
|
|
67
70
|
|
|
68
71
|
|
|
69
|
-
def _get_caller_logger(named: str) -> ty.Callable[[str], ty.Any]:
|
|
70
|
-
module_name = inspect.caller_module_name(__name__)
|
|
71
|
-
if module_name:
|
|
72
|
-
return log.getLogger(module_name).info if named else log.getLogger(module_name).debug
|
|
73
|
-
return log.getLogger(__name__).debug # if not named, we default to debug level
|
|
74
|
-
|
|
75
|
-
|
|
76
72
|
def yield_all(
|
|
77
73
|
thunks: ty.Iterable[ty.Tuple[H, ty.Callable[[], R]]],
|
|
78
74
|
*,
|
|
79
75
|
executor_cm: ty.Optional[ty.ContextManager[concurrent.futures.Executor]] = None,
|
|
80
76
|
fmt: ty.Callable[[str], str] = lambda x: x,
|
|
81
|
-
error_fmt: ty.Callable[[str], str] = lambda x: x,
|
|
82
77
|
named: str = "",
|
|
83
|
-
progress_logger: ty.
|
|
78
|
+
progress_logger: ty.Callable[[str], ty.Any] = logger.info,
|
|
84
79
|
) -> ty.Iterator[ty.Tuple[H, ty.Union[R, Error]]]:
|
|
85
80
|
"""Stream your results so that you don't have to load them all into memory at the same
|
|
86
81
|
time (necessarily). Also, yield (rather than raise) Exceptions, wrapped as Errors.
|
|
@@ -109,8 +104,6 @@ def yield_all(
|
|
|
109
104
|
yield key, Error(e)
|
|
110
105
|
return # we're done here
|
|
111
106
|
|
|
112
|
-
progress_logger = progress_logger or _get_caller_logger(named)
|
|
113
|
-
|
|
114
107
|
executor_cm = executor_cm or concurrent.futures.ThreadPoolExecutor(
|
|
115
108
|
max_workers=len_or_none or None, **concurrency.initcontext()
|
|
116
109
|
) # if len_or_none turns out to be zero, swap in a None which won't kill the executor
|
|
@@ -120,12 +113,8 @@ def yield_all(
|
|
|
120
113
|
# While concurrent.futures.as_completed accepts an iterable as input, it
|
|
121
114
|
# does not yield any completed futures until the input iterable is
|
|
122
115
|
# exhausted.
|
|
123
|
-
num_exceptions = 0
|
|
124
116
|
for i, future in enumerate(concurrent.futures.as_completed(keys_onto_futures.values()), start=1):
|
|
125
117
|
thunk_key = future_ids_onto_keys[id(future)]
|
|
126
|
-
error_suffix = (
|
|
127
|
-
error_fmt(f"; {num_exceptions} tasks have raised exceptions") if num_exceptions else ""
|
|
128
|
-
)
|
|
129
118
|
try:
|
|
130
119
|
result = future.result()
|
|
131
120
|
yielder: tuple[H, ty.Union[R, Error]] = thunk_key, ty.cast(R, result)
|
|
@@ -134,7 +123,7 @@ def yield_all(
|
|
|
134
123
|
yielder = thunk_key, Error(e)
|
|
135
124
|
name = named or e.__class__.__name__
|
|
136
125
|
finally:
|
|
137
|
-
progress_logger(fmt(f"Yielding {name} {i}{num_tasks_log}")
|
|
126
|
+
progress_logger(fmt(f"Yielding {name} {i}{num_tasks_log}"))
|
|
138
127
|
yield yielder
|
|
139
128
|
|
|
140
129
|
|
|
@@ -148,10 +137,8 @@ def failfast(results: ty.Iterable[ty.Tuple[H, ty.Union[R, Error]]]) -> ty.Iterat
|
|
|
148
137
|
yield key, res
|
|
149
138
|
|
|
150
139
|
|
|
151
|
-
def xf_mapping(
|
|
152
|
-
thunks
|
|
153
|
-
) -> ty.Iterator[ty.Tuple[H, R]]:
|
|
154
|
-
return failfast(yield_all(IteratorWithLen(len(thunks), thunks.items()), named=named))
|
|
140
|
+
def xf_mapping(thunks: ty.Mapping[H, ty.Callable[[], R]]) -> ty.Iterator[ty.Tuple[H, R]]:
|
|
141
|
+
return failfast(yield_all(IteratorWithLen(len(thunks), thunks.items())))
|
|
155
142
|
|
|
156
143
|
|
|
157
144
|
def create_keys(iterable: ty.Iterable[R]) -> ty.Iterator[ty.Tuple[str, R]]:
|
|
@@ -166,9 +153,6 @@ def create_keys(iterable: ty.Iterable[R]) -> ty.Iterator[ty.Tuple[str, R]]:
|
|
|
166
153
|
return iter(with_keys)
|
|
167
154
|
|
|
168
155
|
|
|
169
|
-
ERROR_LOGGER = log.getLogger(__name__)
|
|
170
|
-
|
|
171
|
-
|
|
172
156
|
def yield_results(
|
|
173
157
|
thunks: ty.Iterable[ty.Callable[[], R]],
|
|
174
158
|
*,
|
|
@@ -176,9 +160,9 @@ def yield_results(
|
|
|
176
160
|
error_fmt: ty.Callable[[str], str] = lambda x: x,
|
|
177
161
|
success_fmt: ty.Callable[[str], str] = lambda x: x,
|
|
178
162
|
named: str = "",
|
|
179
|
-
progress_logger: ty.
|
|
163
|
+
progress_logger: ty.Callable[[str], ty.Any] = logger.info,
|
|
180
164
|
) -> ty.Iterator[R]:
|
|
181
|
-
"""Yield only the successful results of your Callables/Thunks.
|
|
165
|
+
"""Yield only the successful results of your Callables/Thunks.
|
|
182
166
|
|
|
183
167
|
If your iterable has a length, we will be able to log progress
|
|
184
168
|
information. In most cases, this will be advantageous for you.
|
|
@@ -193,26 +177,27 @@ def yield_results(
|
|
|
193
177
|
|
|
194
178
|
exceptions: ty.List[Exception] = list()
|
|
195
179
|
|
|
180
|
+
num_tasks = try_len(thunks)
|
|
181
|
+
num_tasks_log = "" if not num_tasks else f" of {num_tasks}"
|
|
182
|
+
named = f" {named} " if named else " result "
|
|
183
|
+
|
|
196
184
|
for i, (_key, res) in enumerate(
|
|
197
|
-
yield_all(
|
|
198
|
-
create_keys(thunks),
|
|
199
|
-
executor_cm=executor_cm,
|
|
200
|
-
named=named,
|
|
201
|
-
progress_logger=progress_logger,
|
|
202
|
-
fmt=success_fmt,
|
|
203
|
-
error_fmt=error_fmt,
|
|
204
|
-
),
|
|
185
|
+
yield_all(create_keys(thunks), executor_cm=executor_cm),
|
|
205
186
|
start=1,
|
|
206
187
|
):
|
|
207
188
|
if not isinstance(res, Error):
|
|
189
|
+
errors = error_fmt(f"; {len(exceptions)} tasks have raised exceptions") if exceptions else ""
|
|
190
|
+
progress_logger(success_fmt(f"Yielding{named}{i}{num_tasks_log} {errors}"))
|
|
208
191
|
yield res
|
|
209
192
|
else:
|
|
210
193
|
exceptions.append(res.error)
|
|
211
194
|
# print tracebacks as we go, so as not to defer potentially-helpful
|
|
212
195
|
# debugging information while a long run is ongoing.
|
|
213
196
|
traceback.print_exception(type(res.error), res.error, res.error.__traceback__)
|
|
214
|
-
|
|
215
|
-
error_fmt(
|
|
197
|
+
logger.error( # should only use logger.exception from an except block
|
|
198
|
+
error_fmt(
|
|
199
|
+
f"Task {i}{num_tasks_log} errored with {type(res.error).__name__}({res.error})"
|
|
200
|
+
)
|
|
216
201
|
)
|
|
217
202
|
|
|
218
203
|
summarize_exceptions(error_fmt, exceptions)
|
|
@@ -233,10 +218,10 @@ def summarize_exceptions(
|
|
|
233
218
|
most_common_type = None
|
|
234
219
|
max_count = 0
|
|
235
220
|
for _type, excs in by_type.items():
|
|
236
|
-
|
|
221
|
+
logger.error(error_fmt(f"{len(excs)} tasks failed with exception: " + _type.__name__))
|
|
237
222
|
if len(excs) > max_count:
|
|
238
223
|
max_count = len(excs)
|
|
239
224
|
most_common_type = _type
|
|
240
225
|
|
|
241
|
-
|
|
226
|
+
logger.info("Raising one of the most common exception type.")
|
|
242
227
|
raise by_type[most_common_type][0] # type: ignore
|
|
@@ -22,14 +22,14 @@ thds/core/hashing.py,sha256=dR4HEWcAdU8O-9ASGkl8naKs6I1Sd7aps4EcVefvVLQ,4246
|
|
|
22
22
|
thds/core/home.py,sha256=tTClL_AarIKeri1aNCpuIC6evD7qr83ESGD173B81hU,470
|
|
23
23
|
thds/core/hostname.py,sha256=canFGr-JaaG7nUfsQlyL0JT-2tnZoT1BvXzyaOMK1vA,208
|
|
24
24
|
thds/core/imports.py,sha256=0LVegY8I8_XKZPcqiIp2OVVzEDtyqYA3JETf9OAKNKs,568
|
|
25
|
-
thds/core/inspect.py,sha256=
|
|
25
|
+
thds/core/inspect.py,sha256=IXzJYkc8XGx9W9Oym9Bus2fGMcWHLAtlzvmQ1PwDKmk,2371
|
|
26
26
|
thds/core/iterators.py,sha256=h0JBu2-rYhKMfJTDlZWfyHQWzgtIO8vp_Sp0gENFo7g,645
|
|
27
27
|
thds/core/lazy.py,sha256=e1WvG4LsbEydV0igEr_Vl1cq05zlQNIE8MFYT90yglE,3289
|
|
28
28
|
thds/core/link.py,sha256=4-9d22l_oSkKoSzlYEO-rwxO1hvvj6VETY7LwvGcX6M,5534
|
|
29
29
|
thds/core/logical_root.py,sha256=gWkIYRv9kNQfzbpxJaYiwNXVz1neZ2NvnvProtOn9d8,1399
|
|
30
30
|
thds/core/merge_args.py,sha256=7oj7dtO1-XVkfTM3aBlq3QlZbo8tb6X7E3EVIR-60t8,5781
|
|
31
31
|
thds/core/meta.py,sha256=Df0DxV5UzHcEsu5UCYaE1BWipMPTEXycn9Ug4cdquMk,12114
|
|
32
|
-
thds/core/parallel.py,sha256=
|
|
32
|
+
thds/core/parallel.py,sha256=FojyyUtJ3jrYcsPiHlotWqUUybP_HtVgAN59_ipYf2g,8597
|
|
33
33
|
thds/core/pickle_visit.py,sha256=QNMWIi5buvk2zsvx1-D-FKL7tkrFUFDs387vxgGebgU,833
|
|
34
34
|
thds/core/prof.py,sha256=5ViolfPsAPwUTHuhAe-bon7IArPGXydpGoB5uZmObDk,8264
|
|
35
35
|
thds/core/progress.py,sha256=tY8tc_6CMnu_O8DVisnsRoDpFJOw5vqyYzLhQDxsLn8,4361
|
|
@@ -46,11 +46,11 @@ thds/core/thunks.py,sha256=LxwqUsu3YPVDleGbNk5JWZIncDYwvM8wUBNOS2L09zs,1056
|
|
|
46
46
|
thds/core/timer.py,sha256=aOpNP-wHKaKs6ONK5fOtIOgx00FChVZquG4PeaEYH_k,5376
|
|
47
47
|
thds/core/tmp.py,sha256=jA8FwDbXo3hx8o4kRjAlkwpcI77X86GY4Sktkps29ho,3166
|
|
48
48
|
thds/core/types.py,sha256=sFqI_8BsB1u85PSizjBZw8PBtplC7U54E19wZZWCEvI,152
|
|
49
|
-
thds/core/log/__init__.py,sha256=
|
|
49
|
+
thds/core/log/__init__.py,sha256=bDbZvlxyymY6VrQzD8lCn0egniLEiA9hpNMAXZ7e7wY,1348
|
|
50
50
|
thds/core/log/basic_config.py,sha256=2Y9U_c4PTrIsCmaN7Ps6Xr90AhJPzdYjeUzUMqO7oFU,6704
|
|
51
51
|
thds/core/log/json_formatter.py,sha256=C5bRsSbAqaQqfTm88jc3mYe3vwKZZLAxET8s7_u7aN0,1757
|
|
52
52
|
thds/core/log/kw_formatter.py,sha256=9-MVOd2r5NEkYNne9qWyFMeR5lac3w7mjHXsDa681i0,3379
|
|
53
|
-
thds/core/log/kw_logger.py,sha256=
|
|
53
|
+
thds/core/log/kw_logger.py,sha256=CyZVPnkUMtrUL2Lyk261AIEPmoP-buf_suFAhQlU1io,4063
|
|
54
54
|
thds/core/log/logfmt.py,sha256=i66zoG2oERnE1P_0TVXdlfJ1YgUmvtMjqRtdV5u2SvU,10366
|
|
55
55
|
thds/core/source/__init__.py,sha256=e-cRoLl1HKY3YrDjpV5p_i7zvr1L4q51-t1ISTxdig4,543
|
|
56
56
|
thds/core/source/_construct.py,sha256=jtsh0Du67TslWjCLASZ3pAMeaiowfgm7Bt50zIhwx7k,4330
|
|
@@ -74,8 +74,8 @@ thds/core/sqlite/structured.py,sha256=8t1B6XbM5NnudKEeBLsdjRVbSXXSr6iHOW0HwEAqtX
|
|
|
74
74
|
thds/core/sqlite/types.py,sha256=oq8m0UrvSn1IqWWcQ4FPptfAhdj6DllnCe7puVqSHlY,1297
|
|
75
75
|
thds/core/sqlite/upsert.py,sha256=BmKK6fsGVedt43iY-Lp7dnAu8aJ1e9CYlPVEQR2pMj4,5827
|
|
76
76
|
thds/core/sqlite/write.py,sha256=z0219vDkQDCnsV0WLvsj94keItr7H4j7Y_evbcoBrWU,3458
|
|
77
|
-
thds_core-1.47.
|
|
78
|
-
thds_core-1.47.
|
|
79
|
-
thds_core-1.47.
|
|
80
|
-
thds_core-1.47.
|
|
81
|
-
thds_core-1.47.
|
|
77
|
+
thds_core-1.47.20251024053252.dist-info/METADATA,sha256=yNcSdU59kVnfCMtS6Q3XwfMr6Ak3-PPnRYRS9ItJGc4,2216
|
|
78
|
+
thds_core-1.47.20251024053252.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
79
|
+
thds_core-1.47.20251024053252.dist-info/entry_points.txt,sha256=bOCOVhKZv7azF3FvaWX6uxE6yrjK6FcjqhtxXvLiFY8,161
|
|
80
|
+
thds_core-1.47.20251024053252.dist-info/top_level.txt,sha256=LTZaE5SkWJwv9bwOlMbIhiS-JWQEEIcjVYnJrt-CriY,5
|
|
81
|
+
thds_core-1.47.20251024053252.dist-info/RECORD,,
|
|
File without changes
|
{thds_core-1.47.20251023235801.dist-info → thds_core-1.47.20251024053252.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{thds_core-1.47.20251023235801.dist-info → thds_core-1.47.20251024053252.dist-info}/top_level.txt
RENAMED
|
File without changes
|