thds.core 1.47.20251023213000__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 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].frame
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, auto, getLogger, logger_context, make_th_formatters_safe # noqa: F401
29
+ from .kw_logger import KwLogger, getLogger, logger_context, make_th_formatters_safe # noqa: F401
@@ -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, inspect, log
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.Optional[ty.Callable[[str], ty.Any]] = None,
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}") + error_suffix)
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: ty.Mapping[H, ty.Callable[[], R]], named: str = ""
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.Optional[ty.Callable[[str], ty.Any]] = None,
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. Continue despite errors.
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
- ERROR_LOGGER.error( # should only use logger.exception from an except block
215
- error_fmt(f"Task {i} errored with {type(res.error).__name__}({res.error})")
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
- ERROR_LOGGER.error(error_fmt(f"{len(excs)} tasks failed with exception: " + _type.__name__))
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
- ERROR_LOGGER.info("Raising one of the most common exception type.")
226
+ logger.info("Raising one of the most common exception type.")
242
227
  raise by_type[most_common_type][0] # type: ignore
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: thds.core
3
- Version: 1.47.20251023213000
3
+ Version: 1.47.20251024053252
4
4
  Summary: Core utilities.
5
5
  Author-email: Trilliant Health <info@trillianthealth.com>
6
6
  License: MIT
@@ -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=vBuVJ9aKR_WT0W8SZ59UfZ3NXVS330WCeef1uUe546w,3426
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=is12mjELQwueUfrpTyu9d4Ow20641nGozw0Qib5bmWA,9108
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=wbHgzqHln74wfqM7gLzRURgOn9V_-2dOnoOBi37xczU,1354
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=bXajphFJXOtiIOix7a4uOs_gzqTiPu-76uVGIZK7A5E,4351
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.20251023213000.dist-info/METADATA,sha256=erTFVMgIzDBSmObLbSJ4-Gn-NpraPzD4RAWYdaAYLto,2216
78
- thds_core-1.47.20251023213000.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
79
- thds_core-1.47.20251023213000.dist-info/entry_points.txt,sha256=bOCOVhKZv7azF3FvaWX6uxE6yrjK6FcjqhtxXvLiFY8,161
80
- thds_core-1.47.20251023213000.dist-info/top_level.txt,sha256=LTZaE5SkWJwv9bwOlMbIhiS-JWQEEIcjVYnJrt-CriY,5
81
- thds_core-1.47.20251023213000.dist-info/RECORD,,
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,,