dycw-utilities 0.121.1__py3-none-any.whl → 0.122.1__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.
- {dycw_utilities-0.121.1.dist-info → dycw_utilities-0.122.1.dist-info}/METADATA +1 -1
- {dycw_utilities-0.121.1.dist-info → dycw_utilities-0.122.1.dist-info}/RECORD +9 -9
- utilities/__init__.py +1 -1
- utilities/asyncio.py +155 -29
- utilities/fastapi.py +4 -0
- utilities/redis.py +1 -1
- utilities/sqlalchemy.py +1 -1
- {dycw_utilities-0.121.1.dist-info → dycw_utilities-0.122.1.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.121.1.dist-info → dycw_utilities-0.122.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,7 +1,7 @@
|
|
1
|
-
utilities/__init__.py,sha256=
|
1
|
+
utilities/__init__.py,sha256=HQ1CqsUZ8X8-X0sFT3wvjjj0NMYqAnzvjRBDmvWB2xU,60
|
2
2
|
utilities/altair.py,sha256=Gpja-flOo-Db0PIPJLJsgzAlXWoKUjPU1qY-DQ829ek,9156
|
3
3
|
utilities/astor.py,sha256=xuDUkjq0-b6fhtwjhbnebzbqQZAjMSHR1IIS5uOodVg,777
|
4
|
-
utilities/asyncio.py,sha256=
|
4
|
+
utilities/asyncio.py,sha256=cjXpSezv95ZG3t-D9sXLOn6BGBj7w-Wf4udvE5gU4vU,23508
|
5
5
|
utilities/atomicwrites.py,sha256=geFjn9Pwn-tTrtoGjDDxWli9NqbYfy3gGL6ZBctiqSo,5393
|
6
6
|
utilities/atools.py,sha256=IYMuFSFGSKyuQmqD6v5IUtDlz8PPw0Sr87Cub_gRU3M,1168
|
7
7
|
utilities/cachetools.py,sha256=C1zqOg7BYz0IfQFK8e3qaDDgEZxDpo47F15RTfJM37Q,2910
|
@@ -16,7 +16,7 @@ utilities/datetime.py,sha256=uYoaOi_C1YtNXGfTN9xlTrW62Re2b1_4Skuv14_MeYQ,38985
|
|
16
16
|
utilities/enum.py,sha256=HoRwVCWzsnH0vpO9ZEcAAIZLMv0Sn2vJxxA4sYMQgDs,5793
|
17
17
|
utilities/errors.py,sha256=gxsaa7eq7jbYl41Of40-ivjXqJB5gt4QAcJ0smZZMJE,829
|
18
18
|
utilities/eventkit.py,sha256=6M5Xu1SzN-juk9PqBHwy5dS-ta7T0qA6SMpDsakOJ0E,13039
|
19
|
-
utilities/fastapi.py,sha256=
|
19
|
+
utilities/fastapi.py,sha256=LG1-Q8RDi7wsyVN6v74qptPYX8WGXPkFOQFniMvtzjc,2439
|
20
20
|
utilities/fpdf2.py,sha256=y1NGXR5chWqLXWpewGV3hlRGMr_5yV1lVRkPBhPEgJI,1843
|
21
21
|
utilities/functions.py,sha256=jgt592voaHNtX56qX0SRvFveVCRmSIxCZmqvpLZCnY8,27305
|
22
22
|
utilities/functools.py,sha256=WrpHt7NLNWSUn9A1Q_ZIWlNaYZOEI4IFKyBG9HO3BC4,1643
|
@@ -59,7 +59,7 @@ utilities/pytest_regressions.py,sha256=-SVT9647Dg6-JcdsiaDKXe3NdOmmrvGevLKWwGjxq
|
|
59
59
|
utilities/python_dotenv.py,sha256=iWcnpXbH7S6RoXHiLlGgyuH6udCupAcPd_gQ0eAenQ0,3190
|
60
60
|
utilities/random.py,sha256=lYdjgxB7GCfU_fwFVl5U-BIM_HV3q6_urL9byjrwDM8,4157
|
61
61
|
utilities/re.py,sha256=5J4d8VwIPFVrX2Eb8zfoxImDv7IwiN_U7mJ07wR2Wvs,3958
|
62
|
-
utilities/redis.py,sha256=
|
62
|
+
utilities/redis.py,sha256=8ELnXiISVHY1m9elJWhekhLXg6NQSaNIIPvZ_EaGw3s,26624
|
63
63
|
utilities/reprlib.py,sha256=Re9bk3n-kC__9DxQmRlevqFA86pE6TtVfWjUgpbVOv0,1849
|
64
64
|
utilities/rich.py,sha256=t50MwwVBsoOLxzmeVFSVpjno4OW6Ufum32skXbV8-Bs,1911
|
65
65
|
utilities/scipy.py,sha256=X6ROnHwiUhAmPhM0jkfEh0-Fd9iRvwiqtCQMOLmOQF8,945
|
@@ -67,7 +67,7 @@ utilities/sentinel.py,sha256=3jIwgpMekWgDAxPDA_hXMP2St43cPhciKN3LWiZ7kv0,1248
|
|
67
67
|
utilities/shelve.py,sha256=HZsMwK4tcIfg3sh0gApx4-yjQnrY4o3V3ZRimvRhoW0,738
|
68
68
|
utilities/slack_sdk.py,sha256=A2f7-DYOngRoUP6ZdLIaUQ6Lfzgru5Xp3U3k5JfEkQE,3301
|
69
69
|
utilities/socket.py,sha256=K77vfREvzoVTrpYKo6MZakol0EYu2q1sWJnnZqL0So0,118
|
70
|
-
utilities/sqlalchemy.py,sha256=
|
70
|
+
utilities/sqlalchemy.py,sha256=yCUCDhg0wFOCdEh6wwBD7Ma979OksLpz4arck1s653s,35447
|
71
71
|
utilities/sqlalchemy_polars.py,sha256=wjJpoUo-yO9E2ujpG_06vV5r2OdvBiQ4yvV6wKCa2Tk,15605
|
72
72
|
utilities/statsmodels.py,sha256=koyiBHvpMcSiBfh99wFUfSggLNx7cuAw3rwyfAhoKpQ,3410
|
73
73
|
utilities/streamlit.py,sha256=U9PJBaKP1IdSykKhPZhIzSPTZsmLsnwbEPZWzNhJPKk,2955
|
@@ -88,7 +88,7 @@ utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
|
|
88
88
|
utilities/whenever.py,sha256=jS31ZAY5OMxFxLja_Yo5Fidi87Pd-GoVZ7Vi_teqVDA,16743
|
89
89
|
utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
|
90
90
|
utilities/zoneinfo.py,sha256=-5j7IQ9nb7gR43rdgA7ms05im-XuqhAk9EJnQBXxCoQ,1874
|
91
|
-
dycw_utilities-0.
|
92
|
-
dycw_utilities-0.
|
93
|
-
dycw_utilities-0.
|
94
|
-
dycw_utilities-0.
|
91
|
+
dycw_utilities-0.122.1.dist-info/METADATA,sha256=9QN9Ulm7O-TsU1Zg2xrl5ylNadZMkHMp9ktRVFxpFwQ,12943
|
92
|
+
dycw_utilities-0.122.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
93
|
+
dycw_utilities-0.122.1.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
|
94
|
+
dycw_utilities-0.122.1.dist-info/RECORD,,
|
utilities/__init__.py
CHANGED
utilities/asyncio.py
CHANGED
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
3
3
|
import datetime as dt
|
4
4
|
from abc import ABC, abstractmethod
|
5
5
|
from asyncio import (
|
6
|
+
CancelledError,
|
6
7
|
Event,
|
7
8
|
PriorityQueue,
|
8
9
|
Queue,
|
@@ -12,11 +13,18 @@ from asyncio import (
|
|
12
13
|
Task,
|
13
14
|
TaskGroup,
|
14
15
|
create_subprocess_shell,
|
16
|
+
create_task,
|
15
17
|
sleep,
|
16
18
|
timeout,
|
17
19
|
)
|
18
20
|
from collections.abc import Callable, Hashable, Iterable, Iterator, Mapping
|
19
|
-
from contextlib import
|
21
|
+
from contextlib import (
|
22
|
+
AbstractAsyncContextManager,
|
23
|
+
AsyncExitStack,
|
24
|
+
_AsyncGeneratorContextManager,
|
25
|
+
asynccontextmanager,
|
26
|
+
suppress,
|
27
|
+
)
|
20
28
|
from dataclasses import dataclass, field
|
21
29
|
from io import StringIO
|
22
30
|
from logging import getLogger
|
@@ -27,6 +35,7 @@ from typing import (
|
|
27
35
|
Any,
|
28
36
|
Generic,
|
29
37
|
NoReturn,
|
38
|
+
Self,
|
30
39
|
TextIO,
|
31
40
|
TypeVar,
|
32
41
|
assert_never,
|
@@ -42,7 +51,7 @@ from utilities.datetime import (
|
|
42
51
|
get_now,
|
43
52
|
round_datetime,
|
44
53
|
)
|
45
|
-
from utilities.errors import repr_error
|
54
|
+
from utilities.errors import ImpossibleCaseError, repr_error
|
46
55
|
from utilities.functions import ensure_int, ensure_not_none, get_class_name
|
47
56
|
from utilities.reprlib import get_repr
|
48
57
|
from utilities.sentinel import Sentinel, sentinel
|
@@ -60,6 +69,7 @@ if TYPE_CHECKING:
|
|
60
69
|
from asyncio.subprocess import Process
|
61
70
|
from collections.abc import AsyncIterator, Sequence
|
62
71
|
from contextvars import Context
|
72
|
+
from types import TracebackType
|
63
73
|
|
64
74
|
from utilities.types import Duration
|
65
75
|
|
@@ -76,6 +86,8 @@ class EnhancedTaskGroup(TaskGroup):
|
|
76
86
|
_semaphore: Semaphore | None
|
77
87
|
_timeout: Duration | None
|
78
88
|
_error: type[Exception]
|
89
|
+
_stack: AsyncExitStack
|
90
|
+
_stack_entered: bool
|
79
91
|
_timeout_cm: _AsyncGeneratorContextManager[None] | None
|
80
92
|
|
81
93
|
@override
|
@@ -90,8 +102,25 @@ class EnhancedTaskGroup(TaskGroup):
|
|
90
102
|
self._semaphore = None if max_tasks is None else Semaphore(max_tasks)
|
91
103
|
self._timeout = timeout
|
92
104
|
self._error = error
|
105
|
+
self._stack = AsyncExitStack()
|
106
|
+
self._stack_entered = False # TOOD: no need
|
93
107
|
self._timeout_cm = None
|
94
108
|
|
109
|
+
@override
|
110
|
+
async def __aenter__(self) -> Self:
|
111
|
+
_ = await self._stack.__aenter__()
|
112
|
+
return await super().__aenter__()
|
113
|
+
|
114
|
+
@override
|
115
|
+
async def __aexit__(
|
116
|
+
self,
|
117
|
+
et: type[BaseException] | None,
|
118
|
+
exc: BaseException | None,
|
119
|
+
tb: TracebackType | None,
|
120
|
+
) -> None:
|
121
|
+
_ = await self._stack.__aexit__(et, exc, tb)
|
122
|
+
_ = await super().__aexit__(et, exc, tb)
|
123
|
+
|
95
124
|
@override
|
96
125
|
def create_task(
|
97
126
|
self,
|
@@ -107,6 +136,9 @@ class EnhancedTaskGroup(TaskGroup):
|
|
107
136
|
coroutine = self._wrap_with_timeout(coroutine)
|
108
137
|
return super().create_task(coroutine, name=name, context=context)
|
109
138
|
|
139
|
+
async def enter_async_context(self, cm: AbstractAsyncContextManager[_T], /) -> _T:
|
140
|
+
return await self._stack.enter_async_context(cm)
|
141
|
+
|
110
142
|
async def _wrap_with_semaphore(
|
111
143
|
self, semaphore: Semaphore, coroutine: _CoroutineLike[_T], /
|
112
144
|
) -> _T:
|
@@ -125,26 +157,94 @@ class EnhancedTaskGroup(TaskGroup):
|
|
125
157
|
class InfiniteLooper(ABC, Generic[THashable]):
|
126
158
|
"""An infinite loop which can throw exceptions by setting events."""
|
127
159
|
|
128
|
-
sleep_core: DurationOrEveryDuration = SECOND
|
129
|
-
sleep_restart: DurationOrEveryDuration = MINUTE
|
130
|
-
|
160
|
+
sleep_core: DurationOrEveryDuration = field(default=SECOND, repr=False)
|
161
|
+
sleep_restart: DurationOrEveryDuration = field(default=MINUTE, repr=False)
|
162
|
+
duration: Duration | None = field(default=None, repr=False)
|
163
|
+
logger: str | None = field(default=None, repr=False)
|
164
|
+
_await_upon_aenter: bool = field(default=True, init=False, repr=False)
|
165
|
+
_depth: int = field(default=0, init=False, repr=False)
|
131
166
|
_events: Mapping[THashable | None, Event] = field(
|
132
167
|
default_factory=dict, init=False, repr=False, hash=False
|
133
168
|
)
|
169
|
+
_stack: AsyncExitStack = field(
|
170
|
+
default_factory=AsyncExitStack, init=False, repr=False
|
171
|
+
)
|
172
|
+
_task: Task[None] | None = field(default=None, init=False, repr=False)
|
134
173
|
|
135
174
|
def __post_init__(self) -> None:
|
136
175
|
self._events = {
|
137
176
|
event: Event() for event, _ in self._yield_events_and_exceptions()
|
138
177
|
}
|
139
178
|
|
140
|
-
async def
|
141
|
-
"""
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
179
|
+
async def __aenter__(self) -> Self:
|
180
|
+
"""Context manager entry."""
|
181
|
+
if (self._task is None) and (self._depth == 0):
|
182
|
+
_ = await self._stack.__aenter__()
|
183
|
+
self._task = create_task(self._run_looper())
|
184
|
+
if self._await_upon_aenter:
|
185
|
+
with suppress(CancelledError):
|
186
|
+
await self._task
|
187
|
+
elif (self._task is not None) and (self._depth >= 1):
|
188
|
+
...
|
189
|
+
else:
|
190
|
+
raise ImpossibleCaseError( # pragma: no cover
|
191
|
+
case=[f"{self._task=}", f"{self._depth=}"]
|
192
|
+
)
|
193
|
+
self._depth += 1
|
194
|
+
return self
|
195
|
+
|
196
|
+
async def __aexit__(
|
197
|
+
self,
|
198
|
+
exc_type: type[BaseException] | None = None,
|
199
|
+
exc_value: BaseException | None = None,
|
200
|
+
traceback: TracebackType | None = None,
|
201
|
+
) -> None:
|
202
|
+
"""Context manager exit."""
|
203
|
+
_ = (exc_type, exc_value, traceback)
|
204
|
+
if (self._task is None) or (self._depth == 0):
|
205
|
+
raise ImpossibleCaseError( # pragma: no cover
|
206
|
+
case=[f"{self._task=}", f"{self._depth=}"]
|
207
|
+
)
|
208
|
+
self._depth -= 1
|
209
|
+
if self._depth == 0:
|
210
|
+
_ = await self._stack.__aexit__(exc_type, exc_value, traceback)
|
211
|
+
with suppress(CancelledError):
|
212
|
+
await self._task
|
213
|
+
self._task = None
|
214
|
+
with suppress(Exception):
|
215
|
+
await self._teardown()
|
216
|
+
|
217
|
+
async def stop(self) -> None:
|
218
|
+
"""Stop the service."""
|
219
|
+
if self._task is None:
|
220
|
+
raise ImpossibleCaseError(case=[f"{self._task=}"]) # pragma: no cover
|
221
|
+
with suppress(CancelledError):
|
222
|
+
_ = self._task.cancel()
|
146
223
|
|
147
224
|
async def _run_looper(self) -> None:
|
225
|
+
"""Run the looper."""
|
226
|
+
match self.duration:
|
227
|
+
case None:
|
228
|
+
await self._run_looper_without_timeout()
|
229
|
+
case int() | float() | dt.timedelta() as duration:
|
230
|
+
try:
|
231
|
+
async with timeout_dur(duration=duration):
|
232
|
+
return await self._run_looper_without_timeout()
|
233
|
+
except TimeoutError:
|
234
|
+
await self.stop()
|
235
|
+
case _ as never:
|
236
|
+
assert_never(never)
|
237
|
+
return None
|
238
|
+
|
239
|
+
async def _run_looper_without_timeout(self) -> None:
|
240
|
+
"""Run the looper without a timeout."""
|
241
|
+
coroutines = list(self._yield_coroutines())
|
242
|
+
loopers = list(self._yield_loopers())
|
243
|
+
if (len(coroutines) == 0) and (len(loopers) == 0):
|
244
|
+
return await self._run_looper_by_itself()
|
245
|
+
return await self._run_looper_with_others(coroutines, loopers)
|
246
|
+
|
247
|
+
async def _run_looper_by_itself(self) -> None:
|
148
248
|
"""Run the looper by itself."""
|
149
249
|
while True:
|
150
250
|
try:
|
@@ -171,20 +271,31 @@ class InfiniteLooper(ABC, Generic[THashable]):
|
|
171
271
|
raise
|
172
272
|
except Exception as error: # noqa: BLE001
|
173
273
|
self._error_upon_core(error)
|
174
|
-
|
274
|
+
try:
|
275
|
+
await self._teardown()
|
276
|
+
except Exception as error: # noqa: BLE001
|
277
|
+
self._error_upon_teardown(error)
|
278
|
+
finally:
|
279
|
+
await self._run_sleep(self.sleep_restart)
|
175
280
|
|
176
|
-
async def
|
177
|
-
self,
|
281
|
+
async def _run_looper_with_others(
|
282
|
+
self,
|
283
|
+
coroutines: Iterable[Callable[[], Coroutine1[None]]],
|
284
|
+
loopers: Iterable[InfiniteLooper[Any]],
|
285
|
+
/,
|
178
286
|
) -> None:
|
179
287
|
"""Run multiple loopers."""
|
180
288
|
while True:
|
181
289
|
self._reset_events()
|
182
290
|
try:
|
183
|
-
async with TaskGroup() as tg:
|
184
|
-
_ = tg.create_task(self.
|
291
|
+
async with TaskGroup() as tg, AsyncExitStack() as stack:
|
292
|
+
_ = tg.create_task(self._run_looper_by_itself())
|
185
293
|
_ = [tg.create_task(c()) for c in coroutines]
|
294
|
+
_ = [
|
295
|
+
tg.create_task(stack.enter_async_context(lo)) for lo in loopers
|
296
|
+
]
|
186
297
|
except ExceptionGroup as error:
|
187
|
-
self.
|
298
|
+
self._error_group_upon_others(error)
|
188
299
|
await self._run_sleep(self.sleep_restart)
|
189
300
|
|
190
301
|
async def _initialize(self) -> None:
|
@@ -193,6 +304,9 @@ class InfiniteLooper(ABC, Generic[THashable]):
|
|
193
304
|
async def _core(self) -> None:
|
194
305
|
"""Run the core part of the loop."""
|
195
306
|
|
307
|
+
async def _teardown(self) -> None:
|
308
|
+
"""Tear down the loop."""
|
309
|
+
|
196
310
|
def _error_upon_initialize(self, error: Exception, /) -> None:
|
197
311
|
"""Handle any errors upon initializing the looper."""
|
198
312
|
if self.logger is not None:
|
@@ -213,7 +327,17 @@ class InfiniteLooper(ABC, Generic[THashable]):
|
|
213
327
|
self._sleep_restart_desc,
|
214
328
|
)
|
215
329
|
|
216
|
-
def
|
330
|
+
def _error_upon_teardown(self, error: Exception, /) -> None:
|
331
|
+
"""Handle any errors upon tearing down the looper."""
|
332
|
+
if self.logger is not None:
|
333
|
+
getLogger(name=self.logger).error(
|
334
|
+
"%r encountered %r whilst tearing down; sleeping %s...",
|
335
|
+
get_class_name(self),
|
336
|
+
repr_error(error),
|
337
|
+
self._sleep_restart_desc,
|
338
|
+
)
|
339
|
+
|
340
|
+
def _error_group_upon_others(self, group: ExceptionGroup, /) -> None:
|
217
341
|
"""Handle any errors upon running the core function."""
|
218
342
|
if self.logger is not None:
|
219
343
|
errors = group.exceptions
|
@@ -269,15 +393,19 @@ class InfiniteLooper(ABC, Generic[THashable]):
|
|
269
393
|
raise _InfiniteLooperNoSuchEventError(looper=self, event=event) from None
|
270
394
|
event_obj.set()
|
271
395
|
|
396
|
+
def _yield_events_and_exceptions(
|
397
|
+
self,
|
398
|
+
) -> Iterator[tuple[THashable | None, MaybeType[Exception]]]:
|
399
|
+
"""Yield the events & exceptions."""
|
400
|
+
yield (None, _InfiniteLooperDefaultEventError(looper=self))
|
401
|
+
|
272
402
|
def _yield_coroutines(self) -> Iterator[Callable[[], Coroutine1[None]]]:
|
273
403
|
"""Yield any other coroutines which must also be run."""
|
274
404
|
yield from []
|
275
405
|
|
276
|
-
def
|
277
|
-
|
278
|
-
|
279
|
-
"""Yield the events & exceptions."""
|
280
|
-
yield (None, _InfiniteLooperDefaultEventError)
|
406
|
+
def _yield_loopers(self) -> Iterator[InfiniteLooper[Any]]:
|
407
|
+
"""Yield any other loopers which must also be run."""
|
408
|
+
yield from []
|
281
409
|
|
282
410
|
|
283
411
|
@dataclass(kw_only=True, slots=True)
|
@@ -309,6 +437,7 @@ class InfiniteQueueLooper(InfiniteLooper[THashable], Generic[THashable, _T]):
|
|
309
437
|
"""An infinite loop which processes a queue."""
|
310
438
|
|
311
439
|
queue_type: type[Queue[_T]] = field(default=Queue, repr=False)
|
440
|
+
_await_upon_aenter: bool = field(default=False, init=False, repr=False)
|
312
441
|
_queue: Queue[_T] = field(init=False)
|
313
442
|
|
314
443
|
@override
|
@@ -346,6 +475,7 @@ class InfiniteQueueLooper(InfiniteLooper[THashable], Generic[THashable, _T]):
|
|
346
475
|
"""Run until the queue is empty."""
|
347
476
|
while not self.empty():
|
348
477
|
await self._process_items(*get_items_nowait(self._queue))
|
478
|
+
await self.stop()
|
349
479
|
|
350
480
|
@override
|
351
481
|
def _error_upon_core(self, error: Exception, /) -> None:
|
@@ -353,12 +483,12 @@ class InfiniteQueueLooper(InfiniteLooper[THashable], Generic[THashable, _T]):
|
|
353
483
|
if self.logger is not None:
|
354
484
|
if isinstance(error, InfiniteQueueLooperError):
|
355
485
|
getLogger(name=self.logger).error(
|
356
|
-
"%r encountered %s whilst processing %d item(s) %s; sleeping
|
486
|
+
"%r encountered %s whilst processing %d item(s) %s; sleeping %s...",
|
357
487
|
get_class_name(self),
|
358
488
|
repr_error(error.error),
|
359
489
|
len(error.items),
|
360
490
|
get_repr(error.items),
|
361
|
-
self.
|
491
|
+
self._sleep_restart_desc,
|
362
492
|
)
|
363
493
|
else:
|
364
494
|
super()._error_upon_core(error) # pragma: no cover
|
@@ -370,10 +500,6 @@ class InfiniteQueueLooperError(Exception, Generic[_T]):
|
|
370
500
|
items: Sequence[_T]
|
371
501
|
error: Exception
|
372
502
|
|
373
|
-
@override
|
374
|
-
def __str__(self) -> str:
|
375
|
-
return f"{get_class_name(self.looper)!r} encountered {repr_error(self.error)} whilst processing {len(self.items)} item(s): {get_repr(self.items)}"
|
376
|
-
|
377
503
|
|
378
504
|
##
|
379
505
|
|
utilities/fastapi.py
CHANGED
utilities/redis.py
CHANGED
@@ -611,7 +611,7 @@ class Publisher(InfiniteQueueLooper[None, tuple[str, EncodableT]]):
|
|
611
611
|
@override
|
612
612
|
def _yield_events_and_exceptions(
|
613
613
|
self,
|
614
|
-
) -> Iterator[tuple[None, MaybeType[
|
614
|
+
) -> Iterator[tuple[None, MaybeType[Exception]]]:
|
615
615
|
yield (None, PublisherError) # skipif-ci-and-not-linux
|
616
616
|
|
617
617
|
|
utilities/sqlalchemy.py
CHANGED
@@ -640,7 +640,7 @@ class Upserter(InfiniteQueueLooper[None, _InsertItem]):
|
|
640
640
|
@override
|
641
641
|
def _yield_events_and_exceptions(
|
642
642
|
self,
|
643
|
-
) -> Iterator[tuple[None, MaybeType[
|
643
|
+
) -> Iterator[tuple[None, MaybeType[Exception]]]:
|
644
644
|
yield (None, UpserterError)
|
645
645
|
|
646
646
|
|
File without changes
|
File without changes
|