dycw-utilities 0.156.0__py3-none-any.whl → 0.157.0__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.156.0.dist-info → dycw_utilities-0.157.0.dist-info}/METADATA +1 -1
- {dycw_utilities-0.156.0.dist-info → dycw_utilities-0.157.0.dist-info}/RECORD +14 -14
- utilities/__init__.py +1 -1
- utilities/asyncio.py +1 -45
- utilities/eventkit.py +5 -2
- utilities/logging.py +12 -4
- utilities/os.py +10 -1
- utilities/pottery.py +6 -90
- utilities/pytest.py +0 -9
- utilities/redis.py +1 -2
- utilities/sqlalchemy.py +1 -2
- {dycw_utilities-0.156.0.dist-info → dycw_utilities-0.157.0.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.156.0.dist-info → dycw_utilities-0.157.0.dist-info}/entry_points.txt +0 -0
- {dycw_utilities-0.156.0.dist-info → dycw_utilities-0.157.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,6 @@
|
|
1
|
-
utilities/__init__.py,sha256=
|
1
|
+
utilities/__init__.py,sha256=T1D5frjma6r3zBtDh4rvvHMOorqu5R_6WaB8xxZsqNU,60
|
2
2
|
utilities/altair.py,sha256=92E2lCdyHY4Zb-vCw6rEJIsWdKipuu-Tu2ab1ufUfAk,9079
|
3
|
-
utilities/asyncio.py,sha256=
|
3
|
+
utilities/asyncio.py,sha256=PUedzQ5deqlSECQ33sam9cRzI9TnygHz3FdOqWJWPTM,15288
|
4
4
|
utilities/atomicwrites.py,sha256=tPo6r-Rypd9u99u66B9z86YBPpnLrlHtwox_8Z7T34Y,5790
|
5
5
|
utilities/atools.py,sha256=6neeCcgXxK2dlsc0xp15Za7nSucbCgFtAJepGI_-WXU,2549
|
6
6
|
utilities/cachetools.py,sha256=v1-9sXHLdOLiwmkq6NB0OUbxeKBuVVN6wmAWefWoaHI,2744
|
@@ -13,7 +13,7 @@ utilities/cvxpy.py,sha256=Rv1-fD-XYerosCavRF8Pohop2DBkU3AlFaGTfD8AEAA,13776
|
|
13
13
|
utilities/dataclasses.py,sha256=MXrvIPSZHlpV4msRdVVDRZZo7MC3gX5C9jDUSoNOdpE,32478
|
14
14
|
utilities/enum.py,sha256=5l6pwZD1cjSlVW4ss-zBPspWvrbrYrdtJWcg6f5_J5w,5781
|
15
15
|
utilities/errors.py,sha256=mFlDGSM0LI1jZ1pbqwLAH3ttLZ2JVIxyZLojw8tGVZU,1479
|
16
|
-
utilities/eventkit.py,sha256=
|
16
|
+
utilities/eventkit.py,sha256=wjqrSlqHSe7chAcAROXt9h1IVt8grzX_8jp8PGoeRAs,12804
|
17
17
|
utilities/fastapi.py,sha256=3wpd63Tw9paSyy7STpAD7GGe8fLkLaRC6TPCwIGm1BU,1361
|
18
18
|
utilities/fpdf2.py,sha256=HgM8JSvoioDXrjC0UR3HVLjnMnnb_mML7nL2EmkTwGI,1854
|
19
19
|
utilities/functions.py,sha256=RNVAoLeT_sl-gXaBv2VI_U_EB-d-nSVosYR4gTeeojE,28261
|
@@ -31,7 +31,7 @@ utilities/json.py,sha256=-WcGtSsCr9Y42wHZzAMnfvU6ihAfVftylFfRUORaDFo,2102
|
|
31
31
|
utilities/jupyter.py,sha256=ft5JA7fBxXKzP-L9W8f2-wbF0QeYc_2uLQNFDVk4Z-M,2917
|
32
32
|
utilities/libcst.py,sha256=TKgKN4bNmtBNEE-TUfhTyd1BrTncfsl_7tTuhpesGYY,5585
|
33
33
|
utilities/lightweight_charts.py,sha256=YM3ojBvJxuCSUBu_KrhFBmaMCvRPvupKC3qkm-UVZq4,2751
|
34
|
-
utilities/logging.py,sha256=
|
34
|
+
utilities/logging.py,sha256=NBxAafc9q0KwCqZRHbxI_OEnYADiEGOleLxDsapS7g4,18379
|
35
35
|
utilities/math.py,sha256=7ve4RxX3g-FGGVnWV0K9bBeGnKUEjnTbH13VxdvFtGE,26847
|
36
36
|
utilities/memory_profiler.py,sha256=XzN56jDCa5aqXS_DxEjb_K4L6aIWh_5zyKi6OhcIxw0,853
|
37
37
|
utilities/modules.py,sha256=iuvLluJya-hvl1Q25-Jk3dLgx2Es3ck4SjJiEkAlVTs,3195
|
@@ -40,7 +40,7 @@ utilities/numpy.py,sha256=Xn23sA2ZbVNqwUYEgNJD3XBYH6IbCri_WkHSNhg3NkY,26122
|
|
40
40
|
utilities/operator.py,sha256=nhxn5q6CFNzUm1wpTwWPCu9JGCqVHSlaJf0o1-efoII,3616
|
41
41
|
utilities/optuna.py,sha256=C-fhWYiXHVPo1l8QctYkFJ4DyhbSrGorzP1dJb_qvd8,1933
|
42
42
|
utilities/orjson.py,sha256=Ll0U172ITMqOJc3kjV90C0eI-EWzSXlMHSdUBaUSe80,41499
|
43
|
-
utilities/os.py,sha256=
|
43
|
+
utilities/os.py,sha256=Zwznb1Y0cHHIPG7t0UfEWeo0VeDzCnBcwI0mTV-xh2M,3877
|
44
44
|
utilities/parse.py,sha256=JcJn5yXKhIWXBCwgBdPsyu7Hvcuw6kyEdqvaebCaI9k,17951
|
45
45
|
utilities/pathlib.py,sha256=qGuU8XPmdgGpy8tOMUgelfXx3kxI8h9IaV3TI_06QGE,8428
|
46
46
|
utilities/pickle.py,sha256=MBT2xZCsv0pH868IXLGKnlcqNx2IRVKYNpRcqiQQqxw,653
|
@@ -48,23 +48,23 @@ utilities/platform.py,sha256=pTn7gw6N4T6LdKrf0virwarof_mze9WtoQlrGMzhGVI,2798
|
|
48
48
|
utilities/polars.py,sha256=JOZjSpj9jitDijX044mKc-N00C5N_On3TJYJKJRhdcE,78494
|
49
49
|
utilities/polars_ols.py,sha256=Uc9V5kvlWZ5cU93lKZ-cfAKdVFFw81tqwLW9PxtUvMs,5618
|
50
50
|
utilities/postgres.py,sha256=ynCTTaF-bVEOSW-KEAR-dlLh_hYjeVVjm__-4pEU8Zk,12269
|
51
|
-
utilities/pottery.py,sha256=
|
51
|
+
utilities/pottery.py,sha256=ggMN72Y7wx7Js8VN6eyNyodpm8TIYqZHGghkDPXIVWk,3949
|
52
52
|
utilities/pqdm.py,sha256=idv2seRVP2f6NeSfpeEnT5A-tQezaHZKDyeu16g2-0E,3091
|
53
53
|
utilities/psutil.py,sha256=KUlu4lrUw9Zg1V7ZGetpWpGb9DB8l_SSDWGbANFNCPU,2104
|
54
54
|
utilities/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
55
55
|
utilities/pyinstrument.py,sha256=NZCZz2nBo0BLJ9DTf7H_Q_KGxvsf2S2M3h0qYoYh2kw,804
|
56
|
-
utilities/pytest.py,sha256=
|
56
|
+
utilities/pytest.py,sha256=M-Om6b3hpF9W_bEB7UFY2IzBCubSxzVQleGrgRXHtxY,7741
|
57
57
|
utilities/pytest_regressions.py,sha256=8by5DWEL89Y469TI5AzX1pMy3NJWVtjEg2xQdOOdYuM,4169
|
58
58
|
utilities/random.py,sha256=hZlH4gnAtoaofWswuJYjcygejrY8db4CzP-z_adO2Mo,4165
|
59
59
|
utilities/re.py,sha256=S4h-DLL6ScMPqjboZ_uQ1BVTJajrqV06r_81D--_HCE,4573
|
60
|
-
utilities/redis.py,sha256=
|
60
|
+
utilities/redis.py,sha256=pqzl5A08vaRS4Gfjxob3LWWH9c-vwlsKbvVMTjWMSh8,28364
|
61
61
|
utilities/reprlib.py,sha256=ssYTcBW-TeRh3fhCJv57sopTZHF5FrPyyUg9yp5XBlo,3953
|
62
62
|
utilities/scipy.py,sha256=wZJM7fEgBAkLSYYvSmsg5ac-QuwAI0BGqHVetw1_Hb0,947
|
63
63
|
utilities/sentinel.py,sha256=A_p5jX2K0Yc5XBfoYHyBLqHsEWzE1ByOdDuzzA2pZnE,1434
|
64
64
|
utilities/shelve.py,sha256=4OzjQI6kGuUbJciqf535rwnao-_IBv66gsT6tRGiUt0,759
|
65
65
|
utilities/slack_sdk.py,sha256=ppFBvKgfg5IRWiIoKPtpTyzBtBF4XmwEvU3I5wLJikM,2140
|
66
66
|
utilities/socket.py,sha256=K77vfREvzoVTrpYKo6MZakol0EYu2q1sWJnnZqL0So0,118
|
67
|
-
utilities/sqlalchemy.py,sha256=
|
67
|
+
utilities/sqlalchemy.py,sha256=wtbIp6XDjKwfrvl-wfoY4FQXo_a9vSoHq5K_dYeBBeY,40541
|
68
68
|
utilities/sqlalchemy_polars.py,sha256=5Q9HReETYg0qB6E6WQhFh4QAZlKE-IWlogj2BVif_-w,14246
|
69
69
|
utilities/statsmodels.py,sha256=koyiBHvpMcSiBfh99wFUfSggLNx7cuAw3rwyfAhoKpQ,3410
|
70
70
|
utilities/string.py,sha256=shmBK87zZwzGyixuNuXCiUbqzfeZ9xlrFwz6JTaRvDk,582
|
@@ -87,8 +87,8 @@ utilities/zoneinfo.py,sha256=FBMcUQ4662Aq8SsuCL1OAhDQiyANmVjtb-C30DRrWoE,1966
|
|
87
87
|
utilities/pytest_plugins/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
|
88
88
|
utilities/pytest_plugins/pytest_randomly.py,sha256=B1qYVlExGOxTywq2r1SMi5o7btHLk2PNdY_b1p98dkE,409
|
89
89
|
utilities/pytest_plugins/pytest_regressions.py,sha256=9v8kAXDM2ycIXJBimoiF4EgrwbUvxTycFWJiGR_GHhM,1466
|
90
|
-
dycw_utilities-0.
|
91
|
-
dycw_utilities-0.
|
92
|
-
dycw_utilities-0.
|
93
|
-
dycw_utilities-0.
|
94
|
-
dycw_utilities-0.
|
90
|
+
dycw_utilities-0.157.0.dist-info/METADATA,sha256=I1kXqyTgNiYkQ5eip4vqyC8d-JA3uZCxNWB7Dxpn5JM,1643
|
91
|
+
dycw_utilities-0.157.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
92
|
+
dycw_utilities-0.157.0.dist-info/entry_points.txt,sha256=BOD_SoDxwsfJYOLxhrSXhHP_T7iw-HXI9f2WVkzYxvQ,135
|
93
|
+
dycw_utilities-0.157.0.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
|
94
|
+
dycw_utilities-0.157.0.dist-info/RECORD,,
|
utilities/__init__.py
CHANGED
utilities/asyncio.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import asyncio
|
4
|
-
import sys
|
5
4
|
from asyncio import (
|
6
5
|
Lock,
|
7
6
|
Queue,
|
@@ -36,9 +35,8 @@ from typing import (
|
|
36
35
|
override,
|
37
36
|
)
|
38
37
|
|
39
|
-
from utilities.errors import ImpossibleCaseError, is_instance_error
|
40
38
|
from utilities.functions import ensure_int, ensure_not_none
|
41
|
-
from utilities.
|
39
|
+
from utilities.os import is_pytest
|
42
40
|
from utilities.random import SYSTEM_RANDOM
|
43
41
|
from utilities.sentinel import Sentinel, sentinel
|
44
42
|
from utilities.shelve import yield_shelf
|
@@ -70,8 +68,6 @@ if TYPE_CHECKING:
|
|
70
68
|
from utilities.types import (
|
71
69
|
Coro,
|
72
70
|
Delta,
|
73
|
-
ExceptionTypeLike,
|
74
|
-
LoggerLike,
|
75
71
|
MaybeCallableBoolLike,
|
76
72
|
MaybeType,
|
77
73
|
PathLike,
|
@@ -369,8 +365,6 @@ async def get_items[T](queue: Queue[T], /, *, max_size: int | None = None) -> li
|
|
369
365
|
try:
|
370
366
|
items = [await queue.get()]
|
371
367
|
except RuntimeError as error: # pragma: no cover
|
372
|
-
from utilities.pytest import is_pytest
|
373
|
-
|
374
368
|
if (not is_pytest()) or (error.args[0] != "Event loop is closed"):
|
375
369
|
raise
|
376
370
|
return []
|
@@ -400,43 +394,6 @@ def get_items_nowait[T](queue: Queue[T], /, *, max_size: int | None = None) -> l
|
|
400
394
|
##
|
401
395
|
|
402
396
|
|
403
|
-
async def loop_until_succeed(
|
404
|
-
func: Callable[[], Coro[None]],
|
405
|
-
/,
|
406
|
-
*,
|
407
|
-
logger: LoggerLike | None = None,
|
408
|
-
errors: ExceptionTypeLike[Exception] | None = None,
|
409
|
-
sleep: Delta | None = None,
|
410
|
-
) -> bool:
|
411
|
-
"""Repeatedly call a coroutine until it succeeds."""
|
412
|
-
name = get_coroutine_name(func)
|
413
|
-
while True:
|
414
|
-
try:
|
415
|
-
await func()
|
416
|
-
except Exception as error: # noqa: BLE001
|
417
|
-
if logger is not None:
|
418
|
-
to_logger(logger).error("Error running %r", name, exc_info=True)
|
419
|
-
exc_type, exc_value, traceback = sys.exc_info()
|
420
|
-
if (exc_type is None) or (exc_value is None): # pragma: no cover
|
421
|
-
raise ImpossibleCaseError(
|
422
|
-
case=[f"{exc_type=}", f"{exc_value=}"]
|
423
|
-
) from None
|
424
|
-
sys.excepthook(exc_type, exc_value, traceback)
|
425
|
-
if (errors is not None) and is_instance_error(error, errors):
|
426
|
-
return False
|
427
|
-
if sleep is not None:
|
428
|
-
if logger is not None:
|
429
|
-
to_logger(logger).info("Sleeping for %s...", sleep)
|
430
|
-
await sleep_td(sleep)
|
431
|
-
if logger is not None:
|
432
|
-
to_logger(logger).info("Retrying %r...", name)
|
433
|
-
else:
|
434
|
-
return True
|
435
|
-
|
436
|
-
|
437
|
-
##
|
438
|
-
|
439
|
-
|
440
397
|
async def put_items[T](items: Iterable[T], queue: Queue[T], /) -> None:
|
441
398
|
"""Put items into a queue; if full then wait."""
|
442
399
|
for item in items:
|
@@ -589,7 +546,6 @@ __all__ = [
|
|
589
546
|
"get_coroutine_name",
|
590
547
|
"get_items",
|
591
548
|
"get_items_nowait",
|
592
|
-
"loop_until_succeed",
|
593
549
|
"put_items",
|
594
550
|
"put_items_nowait",
|
595
551
|
"sleep_max",
|
utilities/eventkit.py
CHANGED
@@ -48,6 +48,7 @@ def add_listener[E: Event, F: Callable](
|
|
48
48
|
error: Callable[[Event, BaseException], MaybeCoro[None]] | None = None,
|
49
49
|
ignore: TypeLike[BaseException] | None = None,
|
50
50
|
logger: LoggerLike | None = None,
|
51
|
+
logger_allow_pytest: bool = False,
|
51
52
|
decorators: MaybeIterable[Callable[[F], F]] | None = None,
|
52
53
|
done: Callable[..., MaybeCoro[None]] | None = None,
|
53
54
|
keep_ref: bool = False,
|
@@ -59,6 +60,7 @@ def add_listener[E: Event, F: Callable](
|
|
59
60
|
error=error,
|
60
61
|
ignore=ignore,
|
61
62
|
logger=logger,
|
63
|
+
logger_allow_pytest=logger_allow_pytest,
|
62
64
|
decorators=decorators,
|
63
65
|
)
|
64
66
|
return cast("E", event.connect(lifted, done=done, keep_ref=keep_ref))
|
@@ -284,6 +286,7 @@ def lift_listener[F1: Callable[..., MaybeCoro[None]], F2: Callable](
|
|
284
286
|
error: Callable[[Event, BaseException], MaybeCoro[None]] | None = None,
|
285
287
|
ignore: TypeLike[BaseException] | None = None,
|
286
288
|
logger: LoggerLike | None = None,
|
289
|
+
logger_allow_pytest: bool = False,
|
287
290
|
decorators: MaybeIterable[Callable[[F2], F2]] | None = None,
|
288
291
|
) -> F1:
|
289
292
|
match error, bool(iscoroutinefunction(listener)):
|
@@ -297,7 +300,7 @@ def lift_listener[F1: Callable[..., MaybeCoro[None]], F2: Callable](
|
|
297
300
|
except Exception as exc: # noqa: BLE001
|
298
301
|
if (ignore is not None) and isinstance(exc, ignore):
|
299
302
|
return
|
300
|
-
to_logger(logger).exception("")
|
303
|
+
to_logger(logger, allow_pytest=logger_allow_pytest).exception("")
|
301
304
|
|
302
305
|
lifted = listener_no_error_sync
|
303
306
|
|
@@ -311,7 +314,7 @@ def lift_listener[F1: Callable[..., MaybeCoro[None]], F2: Callable](
|
|
311
314
|
except Exception as exc: # noqa: BLE001
|
312
315
|
if (ignore is not None) and isinstance(exc, ignore):
|
313
316
|
return
|
314
|
-
to_logger(logger).exception("")
|
317
|
+
to_logger(logger, allow_pytest=logger_allow_pytest).exception("")
|
315
318
|
|
316
319
|
lifted = listener_no_error_async
|
317
320
|
case _, _:
|
utilities/logging.py
CHANGED
@@ -37,6 +37,7 @@ from utilities.atomicwrites import move_many
|
|
37
37
|
from utilities.dataclasses import replace_non_sentinel
|
38
38
|
from utilities.errors import ImpossibleCaseError
|
39
39
|
from utilities.iterables import OneEmptyError, always_iterable, one
|
40
|
+
from utilities.os import is_pytest
|
40
41
|
from utilities.pathlib import ensure_suffix, to_path
|
41
42
|
from utilities.re import (
|
42
43
|
ExtractGroupError,
|
@@ -95,6 +96,7 @@ def basic_config(
|
|
95
96
|
filters: MaybeIterable[_FilterType] | None = None,
|
96
97
|
plain: bool = False,
|
97
98
|
color_field_styles: Mapping[str, _FieldStyleKeys] | None = None,
|
99
|
+
allow_pytest: bool = False,
|
98
100
|
) -> None:
|
99
101
|
"""Do the basic config."""
|
100
102
|
match obj:
|
@@ -120,7 +122,7 @@ def basic_config(
|
|
120
122
|
)
|
121
123
|
case str() as name:
|
122
124
|
basic_config(
|
123
|
-
obj=to_logger(name),
|
125
|
+
obj=to_logger(name, allow_pytest=allow_pytest),
|
124
126
|
format_=format_,
|
125
127
|
prefix=prefix,
|
126
128
|
hostname=hostname,
|
@@ -260,6 +262,7 @@ def setup_logging(
|
|
260
262
|
console_level: LogLevel = "INFO",
|
261
263
|
console_prefix: str = "❯", # noqa: RUF001
|
262
264
|
console_filters: MaybeIterable[_FilterType] | None = None,
|
265
|
+
allow_pytest: bool = False,
|
263
266
|
files_dir: MaybeCallablePathLike = Path.cwd,
|
264
267
|
files_max_bytes: int = _DEFAULT_MAX_BYTES,
|
265
268
|
files_when: _When = _DEFAULT_WHEN,
|
@@ -276,7 +279,7 @@ def setup_logging(
|
|
276
279
|
level=console_level,
|
277
280
|
filters=console_filters,
|
278
281
|
)
|
279
|
-
logger_use = to_logger(logger)
|
282
|
+
logger_use = to_logger(logger, allow_pytest=allow_pytest)
|
280
283
|
name = logger_use.name
|
281
284
|
levels: list[LogLevel] = ["DEBUG", "INFO", "ERROR"]
|
282
285
|
for level in levels:
|
@@ -575,13 +578,18 @@ class _Rotation:
|
|
575
578
|
##
|
576
579
|
|
577
580
|
|
578
|
-
def to_logger(
|
581
|
+
def to_logger(
|
582
|
+
logger: LoggerLike | None = None, /, *, allow_pytest: bool = False
|
583
|
+
) -> Logger:
|
579
584
|
"""Convert to a logger."""
|
580
585
|
match logger:
|
581
586
|
case Logger():
|
582
587
|
return logger
|
583
588
|
case str() | None:
|
584
|
-
|
589
|
+
logger = getLogger(logger)
|
590
|
+
if not allow_pytest:
|
591
|
+
_ = logger.addFilter(lambda _: not is_pytest())
|
592
|
+
return logger
|
585
593
|
case never:
|
586
594
|
assert_never(never)
|
587
595
|
|
utilities/os.py
CHANGED
@@ -126,7 +126,15 @@ class GetEnvVarError(Exception):
|
|
126
126
|
|
127
127
|
def is_debug() -> bool:
|
128
128
|
"""Check if we are in `DEBUG` mode."""
|
129
|
-
return
|
129
|
+
return "DEBUG" in environ
|
130
|
+
|
131
|
+
|
132
|
+
##
|
133
|
+
|
134
|
+
|
135
|
+
def is_pytest() -> bool:
|
136
|
+
"""Check if `pytest` is running."""
|
137
|
+
return "PYTEST_VERSION" in environ
|
130
138
|
|
131
139
|
|
132
140
|
##
|
@@ -164,5 +172,6 @@ __all__ = [
|
|
164
172
|
"get_cpu_use",
|
165
173
|
"get_env_var",
|
166
174
|
"is_debug",
|
175
|
+
"is_pytest",
|
167
176
|
"temp_environ",
|
168
177
|
]
|
utilities/pottery.py
CHANGED
@@ -1,32 +1,27 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
from contextlib import
|
3
|
+
from contextlib import suppress
|
4
4
|
from dataclasses import dataclass
|
5
|
-
from functools import wraps
|
6
5
|
from sys import maxsize
|
7
6
|
from typing import TYPE_CHECKING, override
|
8
7
|
|
9
|
-
from pottery import AIORedlock
|
8
|
+
from pottery import AIORedlock
|
10
9
|
from pottery.exceptions import ReleaseUnlockedLock
|
11
10
|
from redis.asyncio import Redis
|
12
11
|
|
13
|
-
from utilities.asyncio import
|
12
|
+
from utilities.asyncio import sleep_td, timeout_td
|
14
13
|
from utilities.contextlib import enhanced_async_context_manager
|
15
|
-
from utilities.contextvars import yield_set_context
|
16
14
|
from utilities.iterables import always_iterable
|
17
|
-
from utilities.logging import to_logger
|
18
15
|
from utilities.whenever import MILLISECOND, SECOND, to_seconds
|
19
16
|
|
20
17
|
if TYPE_CHECKING:
|
21
|
-
from collections.abc import AsyncIterator,
|
22
|
-
from contextvars import ContextVar
|
18
|
+
from collections.abc import AsyncIterator, Iterable
|
23
19
|
|
24
20
|
from whenever import Delta
|
25
21
|
|
26
|
-
from utilities.types import
|
22
|
+
from utilities.types import MaybeIterable
|
27
23
|
|
28
24
|
_NUM: int = 1
|
29
|
-
_TIMEOUT_TRY_ACQUIRE: Delta = SECOND
|
30
25
|
_TIMEOUT_RELEASE: Delta = 10 * SECOND
|
31
26
|
_SLEEP: Delta = MILLISECOND
|
32
27
|
|
@@ -45,79 +40,6 @@ async def extend_lock(
|
|
45
40
|
##
|
46
41
|
|
47
42
|
|
48
|
-
@enhanced_async_context_manager
|
49
|
-
async def try_yield_coroutine_looper(
|
50
|
-
redis: MaybeIterable[Redis],
|
51
|
-
key: str,
|
52
|
-
/,
|
53
|
-
*,
|
54
|
-
num: int = _NUM,
|
55
|
-
timeout_release: Delta = _TIMEOUT_RELEASE,
|
56
|
-
num_extensions: int | None = None,
|
57
|
-
timeout_acquire: Delta = _TIMEOUT_TRY_ACQUIRE,
|
58
|
-
sleep_acquire: Delta = _SLEEP,
|
59
|
-
throttle: Delta | None = None,
|
60
|
-
logger: LoggerLike | None = None,
|
61
|
-
sleep_error: Delta | None = None,
|
62
|
-
context: ContextVar[bool] | None = None,
|
63
|
-
) -> AsyncIterator[CoroutineLooper | None]:
|
64
|
-
"""Try acquire access to a coroutine looper."""
|
65
|
-
try: # skipif-ci-and-not-linux
|
66
|
-
async with yield_access(
|
67
|
-
redis,
|
68
|
-
key,
|
69
|
-
num=num,
|
70
|
-
timeout_release=timeout_release,
|
71
|
-
num_extensions=num_extensions,
|
72
|
-
timeout_acquire=timeout_acquire,
|
73
|
-
sleep=sleep_acquire,
|
74
|
-
throttle=throttle,
|
75
|
-
) as lock:
|
76
|
-
looper = CoroutineLooper(lock=lock, logger=logger, sleep=sleep_error)
|
77
|
-
if context is None:
|
78
|
-
yield looper
|
79
|
-
else:
|
80
|
-
with yield_set_context(context):
|
81
|
-
yield looper
|
82
|
-
except _YieldAccessUnableToAcquireLockError as error: # skipif-ci-and-not-linux
|
83
|
-
if logger is not None:
|
84
|
-
to_logger(logger).info("%s", error)
|
85
|
-
async with nullcontext():
|
86
|
-
yield
|
87
|
-
|
88
|
-
|
89
|
-
@dataclass(order=True, unsafe_hash=True, kw_only=True)
|
90
|
-
class CoroutineLooper:
|
91
|
-
"""Looper, guarded by a lock, to repeatedly call a coroutine until it succeeds."""
|
92
|
-
|
93
|
-
lock: AIORedlock
|
94
|
-
logger: LoggerLike | None = None
|
95
|
-
sleep: Delta | None = None
|
96
|
-
|
97
|
-
async def __call__[**P](
|
98
|
-
self, func: Callable[P, Coro[None]], *args: P.args, **kwargs: P.kwargs
|
99
|
-
) -> bool:
|
100
|
-
return await loop_until_succeed(
|
101
|
-
lambda: self._make_coro(func, *args, **kwargs),
|
102
|
-
logger=self.logger,
|
103
|
-
errors=ExtendUnlockedLock,
|
104
|
-
sleep=self.sleep,
|
105
|
-
)
|
106
|
-
|
107
|
-
def _make_coro[**P](
|
108
|
-
self, func: Callable[P, Coro[None]], /, *args: P.args, **kwargs: P.kwargs
|
109
|
-
) -> Coro[None]:
|
110
|
-
@wraps(func)
|
111
|
-
async def wrapped() -> None:
|
112
|
-
await extend_lock(lock=self.lock)
|
113
|
-
return await func(*args, **kwargs)
|
114
|
-
|
115
|
-
return wrapped()
|
116
|
-
|
117
|
-
|
118
|
-
##
|
119
|
-
|
120
|
-
|
121
43
|
@enhanced_async_context_manager
|
122
44
|
async def yield_access(
|
123
45
|
redis: MaybeIterable[Redis],
|
@@ -212,10 +134,4 @@ class _YieldAccessUnableToAcquireLockError(YieldAccessError):
|
|
212
134
|
return f"Unable to acquire any 1 of {self.num} locks for {self.key!r} after {self.timeout}" # skipif-ci-and-not-linux
|
213
135
|
|
214
136
|
|
215
|
-
__all__ = [
|
216
|
-
"CoroutineLooper",
|
217
|
-
"YieldAccessError",
|
218
|
-
"extend_lock",
|
219
|
-
"try_yield_coroutine_looper",
|
220
|
-
"yield_access",
|
221
|
-
]
|
137
|
+
__all__ = ["YieldAccessError", "extend_lock", "yield_access"]
|
utilities/pytest.py
CHANGED
@@ -111,14 +111,6 @@ def add_pytest_configure(config: Config, options: Iterable[tuple[str, str]], /)
|
|
111
111
|
##
|
112
112
|
|
113
113
|
|
114
|
-
def is_pytest() -> bool:
|
115
|
-
"""Check if `pytest` is running."""
|
116
|
-
return "PYTEST_VERSION" in environ
|
117
|
-
|
118
|
-
|
119
|
-
##
|
120
|
-
|
121
|
-
|
122
114
|
def node_id_path(
|
123
115
|
node_id: str, /, *, root: PathLike | None = None, suffix: str | None = None
|
124
116
|
) -> Path:
|
@@ -257,7 +249,6 @@ __all__ = [
|
|
257
249
|
"add_pytest_addoption",
|
258
250
|
"add_pytest_collection_modifyitems",
|
259
251
|
"add_pytest_configure",
|
260
|
-
"is_pytest",
|
261
252
|
"node_id_path",
|
262
253
|
"skipif_linux",
|
263
254
|
"skipif_mac",
|
utilities/redis.py
CHANGED
@@ -25,6 +25,7 @@ from utilities.contextlib import enhanced_async_context_manager
|
|
25
25
|
from utilities.errors import ImpossibleCaseError
|
26
26
|
from utilities.functions import ensure_int, identity
|
27
27
|
from utilities.iterables import always_iterable, one
|
28
|
+
from utilities.os import is_pytest
|
28
29
|
from utilities.whenever import MILLISECOND, SECOND, to_milliseconds, to_seconds
|
29
30
|
|
30
31
|
if TYPE_CHECKING:
|
@@ -730,8 +731,6 @@ async def subscribe[T](
|
|
730
731
|
try:
|
731
732
|
_ = task.cancel()
|
732
733
|
except RuntimeError as error: # pragma: no cover
|
733
|
-
from utilities.pytest import is_pytest
|
734
|
-
|
735
734
|
if (not is_pytest()) or (error.args[0] != "Event loop is closed"):
|
736
735
|
raise
|
737
736
|
with suppress(CancelledError):
|
utilities/sqlalchemy.py
CHANGED
@@ -95,6 +95,7 @@ from utilities.iterables import (
|
|
95
95
|
merge_str_mappings,
|
96
96
|
one,
|
97
97
|
)
|
98
|
+
from utilities.os import is_pytest
|
98
99
|
from utilities.reprlib import get_repr
|
99
100
|
from utilities.text import secret_str, snake_case
|
100
101
|
from utilities.types import (
|
@@ -976,8 +977,6 @@ async def yield_connection(
|
|
976
977
|
async with timeout_td(timeout, error=error), engine.begin() as conn:
|
977
978
|
yield conn
|
978
979
|
except GeneratorExit: # pragma: no cover
|
979
|
-
from utilities.pytest import is_pytest
|
980
|
-
|
981
980
|
if not is_pytest():
|
982
981
|
raise
|
983
982
|
return
|
File without changes
|
File without changes
|
File without changes
|