dycw-utilities 0.148.5__py3-none-any.whl → 0.174.12__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 dycw-utilities might be problematic. Click here for more details.
- dycw_utilities-0.174.12.dist-info/METADATA +41 -0
- dycw_utilities-0.174.12.dist-info/RECORD +104 -0
- dycw_utilities-0.174.12.dist-info/WHEEL +4 -0
- {dycw_utilities-0.148.5.dist-info → dycw_utilities-0.174.12.dist-info}/entry_points.txt +3 -0
- utilities/__init__.py +1 -1
- utilities/{eventkit.py → aeventkit.py} +12 -11
- utilities/altair.py +7 -6
- utilities/asyncio.py +113 -64
- utilities/atomicwrites.py +1 -1
- utilities/atools.py +64 -4
- utilities/cachetools.py +9 -6
- utilities/click.py +145 -49
- utilities/concurrent.py +1 -1
- utilities/contextlib.py +4 -2
- utilities/contextvars.py +20 -1
- utilities/cryptography.py +3 -3
- utilities/dataclasses.py +15 -28
- utilities/docker.py +292 -0
- utilities/enum.py +2 -2
- utilities/errors.py +1 -1
- utilities/fastapi.py +8 -3
- utilities/fpdf2.py +2 -2
- utilities/functions.py +20 -297
- utilities/git.py +19 -0
- utilities/grp.py +28 -0
- utilities/hypothesis.py +360 -78
- utilities/inflect.py +1 -1
- utilities/iterables.py +12 -58
- utilities/jinja2.py +148 -0
- utilities/json.py +1 -1
- utilities/libcst.py +7 -7
- utilities/logging.py +74 -85
- utilities/math.py +8 -4
- utilities/more_itertools.py +4 -6
- utilities/operator.py +1 -1
- utilities/orjson.py +86 -34
- utilities/os.py +49 -2
- utilities/parse.py +2 -2
- utilities/pathlib.py +66 -34
- utilities/permissions.py +297 -0
- utilities/platform.py +5 -5
- utilities/polars.py +932 -420
- utilities/polars_ols.py +1 -1
- utilities/postgres.py +296 -174
- utilities/pottery.py +8 -73
- utilities/pqdm.py +3 -3
- utilities/pwd.py +28 -0
- utilities/pydantic.py +11 -0
- utilities/pydantic_settings.py +240 -0
- utilities/pydantic_settings_sops.py +76 -0
- utilities/pyinstrument.py +5 -5
- utilities/pytest.py +155 -46
- utilities/pytest_plugins/pytest_randomly.py +1 -1
- utilities/pytest_plugins/pytest_regressions.py +7 -3
- utilities/pytest_regressions.py +2 -3
- utilities/random.py +11 -6
- utilities/re.py +1 -1
- utilities/redis.py +101 -64
- utilities/sentinel.py +10 -0
- utilities/shelve.py +4 -1
- utilities/shutil.py +25 -0
- utilities/slack_sdk.py +8 -3
- utilities/sqlalchemy.py +422 -352
- utilities/sqlalchemy_polars.py +28 -52
- utilities/string.py +1 -1
- utilities/subprocess.py +864 -0
- utilities/tempfile.py +62 -4
- utilities/testbook.py +50 -0
- utilities/text.py +165 -42
- utilities/timer.py +2 -2
- utilities/traceback.py +46 -36
- utilities/types.py +62 -23
- utilities/typing.py +479 -19
- utilities/uuid.py +42 -5
- utilities/version.py +27 -26
- utilities/whenever.py +661 -151
- utilities/zoneinfo.py +80 -22
- dycw_utilities-0.148.5.dist-info/METADATA +0 -41
- dycw_utilities-0.148.5.dist-info/RECORD +0 -95
- dycw_utilities-0.148.5.dist-info/WHEEL +0 -4
- dycw_utilities-0.148.5.dist-info/licenses/LICENSE +0 -21
- utilities/period.py +0 -237
- utilities/typed_settings.py +0 -144
utilities/redis.py
CHANGED
|
@@ -25,7 +25,9 @@ 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.
|
|
28
|
+
from utilities.os import is_pytest
|
|
29
|
+
from utilities.typing import is_instance_gen
|
|
30
|
+
from utilities.whenever import MILLISECOND, SECOND, to_milliseconds, to_nanoseconds
|
|
29
31
|
|
|
30
32
|
if TYPE_CHECKING:
|
|
31
33
|
from collections.abc import AsyncIterator, Awaitable, Collection, Iterable
|
|
@@ -35,7 +37,7 @@ if TYPE_CHECKING:
|
|
|
35
37
|
from redis.typing import EncodableT
|
|
36
38
|
|
|
37
39
|
from utilities.iterables import MaybeIterable
|
|
38
|
-
from utilities.types import Delta,
|
|
40
|
+
from utilities.types import Delta, MaybeSequence, MaybeType, TypeLike
|
|
39
41
|
|
|
40
42
|
|
|
41
43
|
_PUBLISH_TIMEOUT: Delta = SECOND
|
|
@@ -187,7 +189,7 @@ def redis_hash_map_key[K, V](
|
|
|
187
189
|
value_serializer: Callable[[V], bytes] | None = None,
|
|
188
190
|
value_deserializer: Callable[[bytes], V] | None = None,
|
|
189
191
|
timeout: Delta | None = None,
|
|
190
|
-
error:
|
|
192
|
+
error: MaybeType[BaseException] = TimeoutError,
|
|
191
193
|
ttl: Delta | None = None,
|
|
192
194
|
) -> RedisHashMapKey[K, V]: ...
|
|
193
195
|
@overload
|
|
@@ -202,7 +204,7 @@ def redis_hash_map_key[K, V1, V2](
|
|
|
202
204
|
value_serializer: Callable[[V1 | V2], bytes] | None = None,
|
|
203
205
|
value_deserializer: Callable[[bytes], V1 | V2] | None = None,
|
|
204
206
|
timeout: Delta | None = None,
|
|
205
|
-
error:
|
|
207
|
+
error: MaybeType[BaseException] = TimeoutError,
|
|
206
208
|
ttl: Delta | None = None,
|
|
207
209
|
) -> RedisHashMapKey[K, V1 | V2]: ...
|
|
208
210
|
@overload
|
|
@@ -217,7 +219,7 @@ def redis_hash_map_key[K, V1, V2, V3](
|
|
|
217
219
|
value_serializer: Callable[[V1 | V2 | V3], bytes] | None = None,
|
|
218
220
|
value_deserializer: Callable[[bytes], V1 | V2 | V3] | None = None,
|
|
219
221
|
timeout: Delta | None = None,
|
|
220
|
-
error:
|
|
222
|
+
error: MaybeType[BaseException] = TimeoutError,
|
|
221
223
|
ttl: Delta | None = None,
|
|
222
224
|
) -> RedisHashMapKey[K, V1 | V2 | V3]: ...
|
|
223
225
|
@overload
|
|
@@ -232,7 +234,7 @@ def redis_hash_map_key[K1, K2, V](
|
|
|
232
234
|
value_serializer: Callable[[V], bytes] | None = None,
|
|
233
235
|
value_deserializer: Callable[[bytes], V] | None = None,
|
|
234
236
|
timeout: Delta | None = None,
|
|
235
|
-
error:
|
|
237
|
+
error: MaybeType[BaseException] = TimeoutError,
|
|
236
238
|
ttl: Delta | None = None,
|
|
237
239
|
) -> RedisHashMapKey[K1 | K2, V]: ...
|
|
238
240
|
@overload
|
|
@@ -247,7 +249,7 @@ def redis_hash_map_key[K1, K2, V1, V2](
|
|
|
247
249
|
value_serializer: Callable[[V1 | V2], bytes] | None = None,
|
|
248
250
|
value_deserializer: Callable[[bytes], V1 | V2] | None = None,
|
|
249
251
|
timeout: Delta | None = None,
|
|
250
|
-
error:
|
|
252
|
+
error: MaybeType[BaseException] = TimeoutError,
|
|
251
253
|
ttl: Delta | None = None,
|
|
252
254
|
) -> RedisHashMapKey[K1 | K2, V1 | V2]: ...
|
|
253
255
|
@overload
|
|
@@ -262,7 +264,7 @@ def redis_hash_map_key[K1, K2, V1, V2, V3](
|
|
|
262
264
|
value_serializer: Callable[[V1 | V2 | V3], bytes] | None = None,
|
|
263
265
|
value_deserializer: Callable[[bytes], V1 | V2 | V3] | None = None,
|
|
264
266
|
timeout: Delta | None = None,
|
|
265
|
-
error:
|
|
267
|
+
error: MaybeType[BaseException] = TimeoutError,
|
|
266
268
|
ttl: Delta | None = None,
|
|
267
269
|
) -> RedisHashMapKey[K1 | K2, V1 | V2 | V3]: ...
|
|
268
270
|
@overload
|
|
@@ -277,7 +279,7 @@ def redis_hash_map_key[K1, K2, K3, V](
|
|
|
277
279
|
value_serializer: Callable[[V], bytes] | None = None,
|
|
278
280
|
value_deserializer: Callable[[bytes], V] | None = None,
|
|
279
281
|
timeout: Delta | None = None,
|
|
280
|
-
error:
|
|
282
|
+
error: MaybeType[BaseException] = TimeoutError,
|
|
281
283
|
ttl: Delta | None = None,
|
|
282
284
|
) -> RedisHashMapKey[K1 | K2 | K3, V]: ...
|
|
283
285
|
@overload
|
|
@@ -292,7 +294,7 @@ def redis_hash_map_key[K1, K2, K3, V1, V2](
|
|
|
292
294
|
value_serializer: Callable[[V1 | V2], bytes] | None = None,
|
|
293
295
|
value_deserializer: Callable[[bytes], V1 | V2] | None = None,
|
|
294
296
|
timeout: Delta | None = None,
|
|
295
|
-
error:
|
|
297
|
+
error: MaybeType[BaseException] = TimeoutError,
|
|
296
298
|
ttl: Delta | None = None,
|
|
297
299
|
) -> RedisHashMapKey[K1 | K2 | K3, V1 | V2]: ...
|
|
298
300
|
@overload
|
|
@@ -307,7 +309,7 @@ def redis_hash_map_key[K1, K2, K3, V1, V2, V3](
|
|
|
307
309
|
value_serializer: Callable[[V1 | V2 | V3], bytes] | None = None,
|
|
308
310
|
value_deserializer: Callable[[bytes], V1 | V2 | V3] | None = None,
|
|
309
311
|
timeout: Delta | None = None,
|
|
310
|
-
error:
|
|
312
|
+
error: MaybeType[BaseException] = TimeoutError,
|
|
311
313
|
ttl: Delta | None = None,
|
|
312
314
|
) -> RedisHashMapKey[K1 | K2 | K3, V1 | V2 | V3]: ...
|
|
313
315
|
@overload
|
|
@@ -322,7 +324,7 @@ def redis_hash_map_key[K, K1, K2, K3, V, V1, V2, V3](
|
|
|
322
324
|
value_serializer: Callable[[V1 | V2 | V3], bytes] | None = None,
|
|
323
325
|
value_deserializer: Callable[[bytes], V1 | V2 | V3] | None = None,
|
|
324
326
|
timeout: Delta | None = None,
|
|
325
|
-
error:
|
|
327
|
+
error: MaybeType[BaseException] = TimeoutError,
|
|
326
328
|
ttl: Delta | None = None,
|
|
327
329
|
) -> RedisHashMapKey[K, V]: ...
|
|
328
330
|
def redis_hash_map_key[K, V](
|
|
@@ -337,7 +339,7 @@ def redis_hash_map_key[K, V](
|
|
|
337
339
|
value_deserializer: Callable[[bytes], Any] | None = None,
|
|
338
340
|
timeout: Delta | None = None,
|
|
339
341
|
ttl: Delta | None = None,
|
|
340
|
-
error:
|
|
342
|
+
error: MaybeType[BaseException] = TimeoutError,
|
|
341
343
|
) -> RedisHashMapKey[K, V]:
|
|
342
344
|
"""Create a redis key."""
|
|
343
345
|
return RedisHashMapKey( # skipif-ci-and-not-linux
|
|
@@ -386,7 +388,7 @@ class RedisKey[T]:
|
|
|
386
388
|
match result: # skipif-ci-and-not-linux
|
|
387
389
|
case 0 | 1 as value:
|
|
388
390
|
return bool(value)
|
|
389
|
-
case
|
|
391
|
+
case never:
|
|
390
392
|
assert_never(never)
|
|
391
393
|
|
|
392
394
|
async def get(self, redis: Redis, /) -> T:
|
|
@@ -425,7 +427,7 @@ def redis_key[T](
|
|
|
425
427
|
serializer: Callable[[T], bytes] | None = None,
|
|
426
428
|
deserializer: Callable[[bytes], T] | None = None,
|
|
427
429
|
timeout: Delta | None = None,
|
|
428
|
-
error:
|
|
430
|
+
error: MaybeType[BaseException] = TimeoutError,
|
|
429
431
|
ttl: Delta | None = None,
|
|
430
432
|
) -> RedisKey[T]: ...
|
|
431
433
|
@overload
|
|
@@ -437,7 +439,7 @@ def redis_key[T1, T2](
|
|
|
437
439
|
serializer: Callable[[T1 | T2], bytes] | None = None,
|
|
438
440
|
deserializer: Callable[[bytes], T1 | T2] | None = None,
|
|
439
441
|
timeout: Delta | None = None,
|
|
440
|
-
error:
|
|
442
|
+
error: MaybeType[BaseException] = TimeoutError,
|
|
441
443
|
ttl: Delta | None = None,
|
|
442
444
|
) -> RedisKey[T1 | T2]: ...
|
|
443
445
|
@overload
|
|
@@ -449,7 +451,7 @@ def redis_key[T1, T2, T3](
|
|
|
449
451
|
serializer: Callable[[T1 | T2 | T3], bytes] | None = None,
|
|
450
452
|
deserializer: Callable[[bytes], T1 | T2 | T3] | None = None,
|
|
451
453
|
timeout: Delta | None = None,
|
|
452
|
-
error:
|
|
454
|
+
error: MaybeType[BaseException] = TimeoutError,
|
|
453
455
|
ttl: Delta | None = None,
|
|
454
456
|
) -> RedisKey[T1 | T2 | T3]: ...
|
|
455
457
|
@overload
|
|
@@ -461,7 +463,7 @@ def redis_key[T1, T2, T3, T4](
|
|
|
461
463
|
serializer: Callable[[T1 | T2 | T3 | T4], bytes] | None = None,
|
|
462
464
|
deserializer: Callable[[bytes], T1 | T2 | T3 | T4] | None = None,
|
|
463
465
|
timeout: Delta | None = None,
|
|
464
|
-
error:
|
|
466
|
+
error: MaybeType[BaseException] = TimeoutError,
|
|
465
467
|
ttl: Delta | None = None,
|
|
466
468
|
) -> RedisKey[T1 | T2 | T3 | T4]: ...
|
|
467
469
|
@overload
|
|
@@ -473,7 +475,7 @@ def redis_key[T1, T2, T3, T4, T5](
|
|
|
473
475
|
serializer: Callable[[T1 | T2 | T3 | T4 | T5], bytes] | None = None,
|
|
474
476
|
deserializer: Callable[[bytes], T1 | T2 | T3 | T4 | T5] | None = None,
|
|
475
477
|
timeout: Delta | None = None,
|
|
476
|
-
error:
|
|
478
|
+
error: MaybeType[BaseException] = TimeoutError,
|
|
477
479
|
ttl: Delta | None = None,
|
|
478
480
|
) -> RedisKey[T1 | T2 | T3 | T4 | T5]: ...
|
|
479
481
|
@overload
|
|
@@ -485,7 +487,7 @@ def redis_key[T, T1, T2, T3, T4, T5](
|
|
|
485
487
|
serializer: Callable[[T1 | T2 | T3 | T4 | T5], bytes] | None = None,
|
|
486
488
|
deserializer: Callable[[bytes], T1 | T2 | T3 | T4 | T5] | None = None,
|
|
487
489
|
timeout: Delta | None = None,
|
|
488
|
-
error:
|
|
490
|
+
error: MaybeType[BaseException] = TimeoutError,
|
|
489
491
|
ttl: Delta | None = None,
|
|
490
492
|
) -> RedisKey[T]: ...
|
|
491
493
|
def redis_key[T](
|
|
@@ -496,7 +498,7 @@ def redis_key[T](
|
|
|
496
498
|
serializer: Callable[[Any], bytes] | None = None,
|
|
497
499
|
deserializer: Callable[[bytes], Any] | None = None,
|
|
498
500
|
timeout: Delta | None = None,
|
|
499
|
-
error:
|
|
501
|
+
error: MaybeType[BaseException] = TimeoutError,
|
|
500
502
|
ttl: Delta | None = None,
|
|
501
503
|
) -> RedisKey[T]:
|
|
502
504
|
"""Create a redis key."""
|
|
@@ -522,7 +524,7 @@ async def publish[T](
|
|
|
522
524
|
/,
|
|
523
525
|
*,
|
|
524
526
|
serializer: Callable[[T], EncodableT],
|
|
525
|
-
timeout: Delta = _PUBLISH_TIMEOUT,
|
|
527
|
+
timeout: Delta | None = _PUBLISH_TIMEOUT,
|
|
526
528
|
) -> int: ...
|
|
527
529
|
@overload
|
|
528
530
|
async def publish(
|
|
@@ -532,7 +534,7 @@ async def publish(
|
|
|
532
534
|
/,
|
|
533
535
|
*,
|
|
534
536
|
serializer: None = None,
|
|
535
|
-
timeout: Delta = _PUBLISH_TIMEOUT,
|
|
537
|
+
timeout: Delta | None = _PUBLISH_TIMEOUT,
|
|
536
538
|
) -> int: ...
|
|
537
539
|
@overload
|
|
538
540
|
async def publish[T](
|
|
@@ -542,7 +544,7 @@ async def publish[T](
|
|
|
542
544
|
/,
|
|
543
545
|
*,
|
|
544
546
|
serializer: Callable[[T], EncodableT] | None = None,
|
|
545
|
-
timeout: Delta = _PUBLISH_TIMEOUT,
|
|
547
|
+
timeout: Delta | None = _PUBLISH_TIMEOUT,
|
|
546
548
|
) -> int: ...
|
|
547
549
|
async def publish[T](
|
|
548
550
|
redis: Redis,
|
|
@@ -551,7 +553,7 @@ async def publish[T](
|
|
|
551
553
|
/,
|
|
552
554
|
*,
|
|
553
555
|
serializer: Callable[[T], EncodableT] | None = None,
|
|
554
|
-
timeout: Delta = _PUBLISH_TIMEOUT,
|
|
556
|
+
timeout: Delta | None = _PUBLISH_TIMEOUT,
|
|
555
557
|
) -> int:
|
|
556
558
|
"""Publish an object to a channel."""
|
|
557
559
|
match data, serializer: # skipif-ci-and-not-linux
|
|
@@ -561,7 +563,7 @@ async def publish[T](
|
|
|
561
563
|
raise PublishError(data=data)
|
|
562
564
|
case _, Callable():
|
|
563
565
|
data_use = serializer(data)
|
|
564
|
-
case
|
|
566
|
+
case never:
|
|
565
567
|
assert_never(never)
|
|
566
568
|
async with timeout_td(timeout): # skipif-ci-and-not-linux
|
|
567
569
|
response = await redis.publish(channel, data_use) # skipif-ci-and-not-linux
|
|
@@ -583,11 +585,11 @@ class PublishError(Exception):
|
|
|
583
585
|
async def publish_many[T](
|
|
584
586
|
redis: Redis,
|
|
585
587
|
channel: str,
|
|
586
|
-
data: MaybeSequence[bytes |
|
|
588
|
+
data: MaybeSequence[bytes | str | T],
|
|
587
589
|
/,
|
|
588
590
|
*,
|
|
589
591
|
serializer: Callable[[T], EncodableT] | None = None,
|
|
590
|
-
timeout: Delta = _PUBLISH_TIMEOUT,
|
|
592
|
+
timeout: Delta | None = _PUBLISH_TIMEOUT,
|
|
591
593
|
) -> Sequence[bool]:
|
|
592
594
|
"""Publish an object/multiple objects to a channel."""
|
|
593
595
|
async with TaskGroup() as tg:
|
|
@@ -613,7 +615,7 @@ async def _try_publish[T](
|
|
|
613
615
|
/,
|
|
614
616
|
*,
|
|
615
617
|
serializer: Callable[[T], EncodableT] | None = None,
|
|
616
|
-
timeout: Delta = _PUBLISH_TIMEOUT,
|
|
618
|
+
timeout: Delta | None = _PUBLISH_TIMEOUT,
|
|
617
619
|
) -> bool:
|
|
618
620
|
try:
|
|
619
621
|
_ = await publish(redis, channel, data, serializer=serializer, timeout=timeout)
|
|
@@ -638,9 +640,11 @@ def subscribe(
|
|
|
638
640
|
/,
|
|
639
641
|
*,
|
|
640
642
|
timeout: Delta | None = _SUBSCRIBE_TIMEOUT,
|
|
641
|
-
sleep: Delta = _SUBSCRIBE_SLEEP,
|
|
642
643
|
output: Literal["raw"],
|
|
643
|
-
|
|
644
|
+
error_transform: Callable[[_RedisMessage, Exception], None] | None = None,
|
|
645
|
+
filter_: Callable[[bytes], bool] | None = None,
|
|
646
|
+
error_filter: Callable[[bytes, Exception], None] | None = None,
|
|
647
|
+
sleep: Delta = _SUBSCRIBE_SLEEP,
|
|
644
648
|
) -> AsyncIterator[Task[None]]: ...
|
|
645
649
|
@overload
|
|
646
650
|
@enhanced_async_context_manager
|
|
@@ -651,9 +655,11 @@ def subscribe(
|
|
|
651
655
|
/,
|
|
652
656
|
*,
|
|
653
657
|
timeout: Delta | None = _SUBSCRIBE_TIMEOUT,
|
|
654
|
-
sleep: Delta = _SUBSCRIBE_SLEEP,
|
|
655
658
|
output: Literal["bytes"],
|
|
659
|
+
error_transform: Callable[[_RedisMessage, Exception], None] | None = None,
|
|
656
660
|
filter_: Callable[[bytes], bool] | None = None,
|
|
661
|
+
error_filter: Callable[[bytes, Exception], None] | None = None,
|
|
662
|
+
sleep: Delta = _SUBSCRIBE_SLEEP,
|
|
657
663
|
) -> AsyncIterator[Task[None]]: ...
|
|
658
664
|
@overload
|
|
659
665
|
@enhanced_async_context_manager
|
|
@@ -664,9 +670,11 @@ def subscribe(
|
|
|
664
670
|
/,
|
|
665
671
|
*,
|
|
666
672
|
timeout: Delta | None = _SUBSCRIBE_TIMEOUT,
|
|
667
|
-
sleep: Delta = _SUBSCRIBE_SLEEP,
|
|
668
673
|
output: Literal["text"] = "text",
|
|
674
|
+
error_transform: Callable[[_RedisMessage, Exception], None] | None = None,
|
|
669
675
|
filter_: Callable[[str], bool] | None = None,
|
|
676
|
+
error_filter: Callable[[str, Exception], None] | None = None,
|
|
677
|
+
sleep: Delta = _SUBSCRIBE_SLEEP,
|
|
670
678
|
) -> AsyncIterator[Task[None]]: ...
|
|
671
679
|
@overload
|
|
672
680
|
@enhanced_async_context_manager
|
|
@@ -677,9 +685,11 @@ def subscribe[T](
|
|
|
677
685
|
/,
|
|
678
686
|
*,
|
|
679
687
|
timeout: Delta | None = _SUBSCRIBE_TIMEOUT,
|
|
680
|
-
sleep: Delta = _SUBSCRIBE_SLEEP,
|
|
681
688
|
output: Callable[[bytes], T],
|
|
689
|
+
error_transform: Callable[[_RedisMessage, Exception], None] | None = None,
|
|
682
690
|
filter_: Callable[[T], bool] | None = None,
|
|
691
|
+
error_filter: Callable[[T, Exception], None] | None = None,
|
|
692
|
+
sleep: Delta = _SUBSCRIBE_SLEEP,
|
|
683
693
|
) -> AsyncIterator[Task[None]]: ...
|
|
684
694
|
@enhanced_async_context_manager
|
|
685
695
|
async def subscribe[T](
|
|
@@ -689,28 +699,27 @@ async def subscribe[T](
|
|
|
689
699
|
/,
|
|
690
700
|
*,
|
|
691
701
|
timeout: Delta | None = _SUBSCRIBE_TIMEOUT,
|
|
692
|
-
sleep: Delta = _SUBSCRIBE_SLEEP,
|
|
693
702
|
output: Literal["raw", "bytes", "text"] | Callable[[bytes], T] = "text",
|
|
694
|
-
|
|
703
|
+
error_transform: Callable[[_RedisMessage, Exception], None] | None = None,
|
|
704
|
+
filter_: Callable[[T], bool] | None = None,
|
|
705
|
+
error_filter: Callable[[T, Exception], None] | None = None,
|
|
706
|
+
sleep: Delta = _SUBSCRIBE_SLEEP,
|
|
695
707
|
) -> AsyncIterator[Task[None]]:
|
|
696
708
|
"""Subscribe to the data of a given channel(s)."""
|
|
697
709
|
channels = list(always_iterable(channels)) # skipif-ci-and-not-linux
|
|
698
710
|
match output: # skipif-ci-and-not-linux
|
|
699
711
|
case "raw":
|
|
700
|
-
transform = cast("
|
|
712
|
+
transform = cast("Callable[[_RedisMessage], T]", identity)
|
|
701
713
|
case "bytes":
|
|
702
|
-
transform = cast("
|
|
714
|
+
transform = cast("Callable[[_RedisMessage], T]", itemgetter("data"))
|
|
703
715
|
case "text":
|
|
704
|
-
|
|
705
|
-
def transform(message: _RedisMessage, /) -> str: # pyright: ignore[reportRedeclaration]
|
|
706
|
-
return message["data"].decode()
|
|
707
|
-
|
|
716
|
+
transform = cast("Callable[[_RedisMessage], T]", _decoded_data)
|
|
708
717
|
case Callable() as deserialize:
|
|
709
718
|
|
|
710
719
|
def transform(message: _RedisMessage, /) -> T:
|
|
711
720
|
return deserialize(message["data"])
|
|
712
721
|
|
|
713
|
-
case
|
|
722
|
+
case never:
|
|
714
723
|
assert_never(never)
|
|
715
724
|
|
|
716
725
|
task = create_task( # skipif-ci-and-not-linux
|
|
@@ -720,8 +729,10 @@ async def subscribe[T](
|
|
|
720
729
|
transform,
|
|
721
730
|
queue,
|
|
722
731
|
timeout=timeout,
|
|
723
|
-
|
|
732
|
+
error_transform=error_transform,
|
|
724
733
|
filter_=filter_,
|
|
734
|
+
error_filter=error_filter,
|
|
735
|
+
sleep=sleep,
|
|
725
736
|
)
|
|
726
737
|
)
|
|
727
738
|
try: # skipif-ci-and-not-linux
|
|
@@ -730,27 +741,31 @@ async def subscribe[T](
|
|
|
730
741
|
try:
|
|
731
742
|
_ = task.cancel()
|
|
732
743
|
except RuntimeError as error: # pragma: no cover
|
|
733
|
-
from utilities.pytest import is_pytest
|
|
734
|
-
|
|
735
744
|
if (not is_pytest()) or (error.args[0] != "Event loop is closed"):
|
|
736
745
|
raise
|
|
737
746
|
with suppress(CancelledError):
|
|
738
747
|
await task
|
|
739
748
|
|
|
740
749
|
|
|
741
|
-
|
|
750
|
+
def _decoded_data(message: _RedisMessage, /) -> str:
|
|
751
|
+
return message["data"].decode()
|
|
752
|
+
|
|
753
|
+
|
|
754
|
+
async def _subscribe_core[T](
|
|
742
755
|
redis: Redis,
|
|
743
756
|
channels: MaybeIterable[str],
|
|
744
|
-
transform: Callable[[_RedisMessage],
|
|
757
|
+
transform: Callable[[_RedisMessage], T],
|
|
745
758
|
queue: Queue[Any],
|
|
746
759
|
/,
|
|
747
760
|
*,
|
|
748
761
|
timeout: Delta | None = _SUBSCRIBE_TIMEOUT,
|
|
762
|
+
error_transform: Callable[[_RedisMessage, Exception], None] | None = None,
|
|
763
|
+
filter_: Callable[[T], bool] | None = None,
|
|
764
|
+
error_filter: Callable[[T, Exception], None] | None = None,
|
|
749
765
|
sleep: Delta = _SUBSCRIBE_SLEEP,
|
|
750
|
-
filter_: Callable[[Any], bool] | None = None,
|
|
751
766
|
) -> None:
|
|
752
767
|
timeout_use = ( # skipif-ci-and-not-linux
|
|
753
|
-
None if timeout is None else
|
|
768
|
+
None if timeout is None else (to_nanoseconds(timeout) / 1e9)
|
|
754
769
|
)
|
|
755
770
|
is_subscribe_message = partial( # skipif-ci-and-not-linux
|
|
756
771
|
_is_message, channels={c.encode() for c in channels}
|
|
@@ -759,9 +774,14 @@ async def _subscribe_core(
|
|
|
759
774
|
while True:
|
|
760
775
|
message = await pubsub.get_message(timeout=timeout_use)
|
|
761
776
|
if is_subscribe_message(message):
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
777
|
+
_handle_message(
|
|
778
|
+
message,
|
|
779
|
+
transform,
|
|
780
|
+
queue,
|
|
781
|
+
error_transform=error_transform,
|
|
782
|
+
filter_=filter_,
|
|
783
|
+
error_filter=error_filter,
|
|
784
|
+
)
|
|
765
785
|
else:
|
|
766
786
|
await sleep_td(sleep)
|
|
767
787
|
|
|
@@ -769,17 +789,34 @@ async def _subscribe_core(
|
|
|
769
789
|
def _is_message(
|
|
770
790
|
message: Any, /, *, channels: Collection[bytes]
|
|
771
791
|
) -> TypeGuard[_RedisMessage]:
|
|
772
|
-
return (
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
792
|
+
return is_instance_gen(message, _RedisMessage) and (message["channel"] in channels)
|
|
793
|
+
|
|
794
|
+
|
|
795
|
+
def _handle_message[T](
|
|
796
|
+
message: _RedisMessage,
|
|
797
|
+
transform: Callable[[_RedisMessage], T],
|
|
798
|
+
queue: Queue[Any],
|
|
799
|
+
/,
|
|
800
|
+
*,
|
|
801
|
+
error_transform: Callable[[_RedisMessage, Exception], None] | None = None,
|
|
802
|
+
filter_: Callable[[T], bool] | None = None,
|
|
803
|
+
error_filter: Callable[[T, Exception], None] | None = None,
|
|
804
|
+
) -> None:
|
|
805
|
+
try:
|
|
806
|
+
transformed = transform(message)
|
|
807
|
+
except Exception as error: # noqa: BLE001
|
|
808
|
+
if error_transform is not None:
|
|
809
|
+
error_transform(message, error)
|
|
810
|
+
return
|
|
811
|
+
if filter_ is None:
|
|
812
|
+
queue.put_nowait(transformed)
|
|
813
|
+
return
|
|
814
|
+
try:
|
|
815
|
+
if filter_(transformed):
|
|
816
|
+
queue.put_nowait(transformed)
|
|
817
|
+
except Exception as error: # noqa: BLE001
|
|
818
|
+
if error_filter is not None:
|
|
819
|
+
error_filter(transformed, error)
|
|
783
820
|
|
|
784
821
|
|
|
785
822
|
class _RedisMessage(TypedDict):
|
utilities/sentinel.py
CHANGED
|
@@ -4,6 +4,8 @@ from dataclasses import dataclass
|
|
|
4
4
|
from re import IGNORECASE, search
|
|
5
5
|
from typing import Any, override
|
|
6
6
|
|
|
7
|
+
from typing_extensions import TypeIs
|
|
8
|
+
|
|
7
9
|
|
|
8
10
|
class _Meta(type):
|
|
9
11
|
"""Metaclass for the sentinel."""
|
|
@@ -34,6 +36,13 @@ class Sentinel(metaclass=_Meta):
|
|
|
34
36
|
|
|
35
37
|
sentinel = Sentinel()
|
|
36
38
|
|
|
39
|
+
##
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def is_sentinel(obj: Any, /) -> TypeIs[Sentinel]:
|
|
43
|
+
"""Check if an object is the sentinel."""
|
|
44
|
+
return obj is sentinel
|
|
45
|
+
|
|
37
46
|
|
|
38
47
|
##
|
|
39
48
|
|
|
@@ -58,6 +67,7 @@ __all__ = [
|
|
|
58
67
|
"SENTINEL_REPR",
|
|
59
68
|
"ParseSentinelError",
|
|
60
69
|
"Sentinel",
|
|
70
|
+
"is_sentinel",
|
|
61
71
|
"parse_sentinel",
|
|
62
72
|
"sentinel",
|
|
63
73
|
]
|
utilities/shelve.py
CHANGED
|
@@ -12,12 +12,15 @@ if TYPE_CHECKING:
|
|
|
12
12
|
from utilities.types import PathLike
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
type _Flag = Literal["r", "w", "c", "n"]
|
|
16
|
+
|
|
17
|
+
|
|
15
18
|
@contextmanager
|
|
16
19
|
def yield_shelf(
|
|
17
20
|
path: PathLike,
|
|
18
21
|
/,
|
|
19
22
|
*,
|
|
20
|
-
flag:
|
|
23
|
+
flag: _Flag = "c",
|
|
21
24
|
protocol: int | None = None,
|
|
22
25
|
writeback: bool = False,
|
|
23
26
|
) -> Iterator[Shelf[Any]]:
|
utilities/shutil.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import override
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def which(cmd: str, /) -> Path:
|
|
10
|
+
path = shutil.which(cmd)
|
|
11
|
+
if path is None:
|
|
12
|
+
raise WhichError(cmd=cmd)
|
|
13
|
+
return Path(path)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(kw_only=True, slots=True)
|
|
17
|
+
class WhichError(Exception):
|
|
18
|
+
cmd: str
|
|
19
|
+
|
|
20
|
+
@override
|
|
21
|
+
def __str__(self) -> str:
|
|
22
|
+
return f"{self.cmd!r} not found"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
__all__ = ["WhichError", "which"]
|
utilities/slack_sdk.py
CHANGED
|
@@ -15,7 +15,7 @@ if TYPE_CHECKING:
|
|
|
15
15
|
from slack_sdk.webhook import WebhookResponse
|
|
16
16
|
from whenever import TimeDelta
|
|
17
17
|
|
|
18
|
-
from utilities.types import Delta
|
|
18
|
+
from utilities.types import Delta, MaybeType
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
_TIMEOUT: Delta = MINUTE
|
|
@@ -39,11 +39,16 @@ def _get_client(url: str, /, *, timeout: Delta = _TIMEOUT) -> WebhookClient:
|
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
async def send_to_slack_async(
|
|
42
|
-
url: str,
|
|
42
|
+
url: str,
|
|
43
|
+
text: str,
|
|
44
|
+
/,
|
|
45
|
+
*,
|
|
46
|
+
timeout: TimeDelta = _TIMEOUT,
|
|
47
|
+
error: MaybeType[BaseException] = TimeoutError,
|
|
43
48
|
) -> None:
|
|
44
49
|
"""Send a message via Slack."""
|
|
45
50
|
client = _get_async_client(url, timeout=timeout)
|
|
46
|
-
async with timeout_td(timeout):
|
|
51
|
+
async with timeout_td(timeout, error=error):
|
|
47
52
|
response = await client.send(text=text)
|
|
48
53
|
if response.status_code != HTTPStatus.OK: # pragma: no cover
|
|
49
54
|
raise SendToSlackError(text=text, response=response)
|