thds.core 1.33.20250402145013__py3-none-any.whl → 1.33.20250407153127__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/fretry.py CHANGED
@@ -1,5 +1,6 @@
1
1
  """A more composable retry decorator."""
2
2
 
3
+ import asyncio
3
4
  import random
4
5
  import time
5
6
  import typing as ty
@@ -12,7 +13,19 @@ F = ty.TypeVar("F", bound=ty.Callable)
12
13
 
13
14
  IsRetryable = ty.Callable[[Exception], bool]
14
15
  RetryStrategy = ty.Iterable[IsRetryable]
16
+ AsyncRetryStrategy = ty.AsyncIterable[IsRetryable]
15
17
  RetryStrategyFactory = ty.Callable[[], RetryStrategy]
18
+ AsyncRetryStrategyFactory = ty.Callable[[], AsyncRetryStrategy]
19
+
20
+
21
+ def n_times(retries: int) -> ty.Callable[[], ty.Iterator[None]]:
22
+ """End iteration after yielding 'retries' times."""
23
+
24
+ def n_times_() -> ty.Iterator[None]:
25
+ for _ in range(retries):
26
+ yield None
27
+
28
+ return n_times_
16
29
 
17
30
 
18
31
  def expo(
@@ -113,3 +126,84 @@ def is_exc(*exc_types: ty.Type[Exception]) -> IsRetryable:
113
126
  return isinstance(exc, exc_types)
114
127
 
115
128
  return _is_exc_retryable
129
+
130
+
131
+ # some async reimplementations of everything :P
132
+ T = ty.TypeVar("T")
133
+
134
+
135
+ async def aenumerate(
136
+ asequence: ty.AsyncIterable[T], start: int = 0
137
+ ) -> ty.AsyncGenerator[ty.Tuple[int, T], None]:
138
+ # https://stackoverflow.com/questions/48070863/is-python3-6-new-change-async-for-not-compatible-with-enumerate
139
+ """Asynchronously enumerate an async iterator from a given start value"""
140
+ n = start
141
+ async for elem in asequence:
142
+ yield n, elem
143
+ n += 1
144
+
145
+
146
+ def iter_to_async(
147
+ sync_iter_factory: ty.Callable[[], ty.Iterable[T]]
148
+ ) -> ty.Callable[[], ty.AsyncIterator[T]]:
149
+ """Convert a synchronous iterable factory to an async iterator factory."""
150
+
151
+ async def async_wrapper() -> ty.AsyncIterator[T]:
152
+ for item in sync_iter_factory():
153
+ yield item
154
+
155
+ return async_wrapper
156
+
157
+
158
+ def retry_async(retry_strategy_factory: AsyncRetryStrategyFactory) -> ty.Callable[[F], F]:
159
+ def _retry_decorator(func: F) -> F:
160
+ @wraps(func)
161
+ async def retry_wrapper(*args, **kwargs):
162
+ async for i, is_retryable in aenumerate(retry_strategy_factory(), start=1):
163
+ try:
164
+ return await func(*args, **kwargs)
165
+ except Exception as ex:
166
+ if not is_retryable(ex):
167
+ raise ex
168
+ getLogger(__name__).info("Retry #%d for %s due to exception %s", i, func, ex)
169
+ # one final retry that, if it fails, will not get caught and retried.
170
+ return await func(*args, **kwargs)
171
+
172
+ return ty.cast(F, retry_wrapper)
173
+
174
+ return _retry_decorator
175
+
176
+
177
+ def retry_regular_async(
178
+ is_retryable: IsRetryable,
179
+ intervals_factory: ty.Callable[[], ty.AsyncIterable[ty.Any]],
180
+ ) -> ty.Callable[[F], F]:
181
+ async def _retry_internal() -> ty.AsyncIterable[IsRetryable]:
182
+ async for _ in intervals_factory():
183
+ yield is_retryable
184
+
185
+ return retry_async(_retry_internal)
186
+
187
+
188
+ def sleep_async(
189
+ mk_seconds_iter: ty.Callable[[], ty.Iterable[float]],
190
+ sleeper: ty.Callable[[float], ty.Awaitable[ty.Any]] = asyncio.sleep,
191
+ ) -> ty.Callable[[], ty.AsyncGenerator[str, None]]:
192
+ async def sleep_() -> ty.AsyncGenerator[str, None]:
193
+ start = default_timer()
194
+
195
+ so_far = 0.0
196
+ for i, secs in enumerate(mk_seconds_iter(), start=1):
197
+ yield f"attempt {i} after {so_far:.2f}s"
198
+ so_far = default_timer() - start
199
+ await sleeper(secs)
200
+
201
+ return sleep_
202
+
203
+
204
+ def retry_sleep_async(
205
+ is_retryable: IsRetryable,
206
+ seconds_iter: ty.Callable[[], ty.Iterable[float]],
207
+ ) -> ty.Callable[[F], F]:
208
+ """E.g. retry_sleep(expo(retries=5)) to get max 6 calls to the function."""
209
+ return retry_regular_async(is_retryable, sleep_async(seconds_iter))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: thds.core
3
- Version: 1.33.20250402145013
3
+ Version: 1.33.20250407153127
4
4
  Summary: Core utilities.
5
5
  Author-email: Trilliant Health <info@trillianthealth.com>
6
6
  License: MIT
@@ -11,7 +11,7 @@ thds/core/dict_utils.py,sha256=MatsjZC9lchfdaDqNAzL2mkTZytDnCAqg56sMm71wbE,6364
11
11
  thds/core/env.py,sha256=HkuyFmGpCgdQUB1r2GbpCqB3cs1lCsvp47Ghk1DHBo8,1083
12
12
  thds/core/exit_after.py,sha256=0lz63nz2NTiIdyBDYyRa9bQShxQKe7eISy8VhXeW4HU,3485
13
13
  thds/core/files.py,sha256=NJlPXj7BejKd_Pa06MOywVv_YapT4bVedfsJHrWX8nI,4579
14
- thds/core/fretry.py,sha256=Tui2q6vXV6c7mjTa1czLrXiugHUEwQp-sZdiwXfxvmM,3829
14
+ thds/core/fretry.py,sha256=PKgOxCMjcF4zsFfXFvPXpomv5J6KU6llB1EaKukugig,6942
15
15
  thds/core/generators.py,sha256=rcdFpPj0NMJWSaSZTnBfTeZxTTORNB633Lng-BW1284,1939
16
16
  thds/core/git.py,sha256=I6kaEvwcvVxCLYHhTTfnHle-GkmgOR9_fHs03QxgBfI,2792
17
17
  thds/core/hash_cache.py,sha256=bkdV8HZOl66zNffnIjRvMQH1A0_xNqowMDLBtRXGFl8,3723
@@ -67,8 +67,8 @@ thds/core/sqlite/structured.py,sha256=SvZ67KcVcVdmpR52JSd52vMTW2ALUXmlHEeD-VrzWV
67
67
  thds/core/sqlite/types.py,sha256=oUkfoKRYNGDPZRk29s09rc9ha3SCk2SKr_K6WKebBFs,1308
68
68
  thds/core/sqlite/upsert.py,sha256=BmKK6fsGVedt43iY-Lp7dnAu8aJ1e9CYlPVEQR2pMj4,5827
69
69
  thds/core/sqlite/write.py,sha256=z0219vDkQDCnsV0WLvsj94keItr7H4j7Y_evbcoBrWU,3458
70
- thds_core-1.33.20250402145013.dist-info/METADATA,sha256=FQVWtsRPSLrLz69_uo_5HdGxGarUh08vGSfafoNQDSo,2277
71
- thds_core-1.33.20250402145013.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
72
- thds_core-1.33.20250402145013.dist-info/entry_points.txt,sha256=bOCOVhKZv7azF3FvaWX6uxE6yrjK6FcjqhtxXvLiFY8,161
73
- thds_core-1.33.20250402145013.dist-info/top_level.txt,sha256=LTZaE5SkWJwv9bwOlMbIhiS-JWQEEIcjVYnJrt-CriY,5
74
- thds_core-1.33.20250402145013.dist-info/RECORD,,
70
+ thds_core-1.33.20250407153127.dist-info/METADATA,sha256=s72b-zOILq-OIy-GfQ7rMtTfP373m9tsHcUtgdcqX3c,2277
71
+ thds_core-1.33.20250407153127.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
72
+ thds_core-1.33.20250407153127.dist-info/entry_points.txt,sha256=bOCOVhKZv7azF3FvaWX6uxE6yrjK6FcjqhtxXvLiFY8,161
73
+ thds_core-1.33.20250407153127.dist-info/top_level.txt,sha256=LTZaE5SkWJwv9bwOlMbIhiS-JWQEEIcjVYnJrt-CriY,5
74
+ thds_core-1.33.20250407153127.dist-info/RECORD,,