dycw-utilities 0.126.10__py3-none-any.whl → 0.126.11__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.126.10
3
+ Version: 0.126.11
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -1,4 +1,4 @@
1
- utilities/__init__.py,sha256=_LTfJC5B-XkbCDy-opJNk2Pu2qwhA3B84Baq2rX5bbA,61
1
+ utilities/__init__.py,sha256=zZdDjXYw0iXgNb3iikpaOv2GNjXKtYX9dLFyhvGm1Bs,61
2
2
  utilities/altair.py,sha256=Gpja-flOo-Db0PIPJLJsgzAlXWoKUjPU1qY-DQ829ek,9156
3
3
  utilities/asyncio.py,sha256=phbGti22VSe9cu-SwM1vP8kyUg8AUDHvvciMvE6JnCg,51842
4
4
  utilities/atomicwrites.py,sha256=geFjn9Pwn-tTrtoGjDDxWli9NqbYfy3gGL6ZBctiqSo,5393
@@ -61,7 +61,7 @@ utilities/pytest_regressions.py,sha256=-SVT9647Dg6-JcdsiaDKXe3NdOmmrvGevLKWwGjxq
61
61
  utilities/python_dotenv.py,sha256=iWcnpXbH7S6RoXHiLlGgyuH6udCupAcPd_gQ0eAenQ0,3190
62
62
  utilities/random.py,sha256=lYdjgxB7GCfU_fwFVl5U-BIM_HV3q6_urL9byjrwDM8,4157
63
63
  utilities/re.py,sha256=5J4d8VwIPFVrX2Eb8zfoxImDv7IwiN_U7mJ07wR2Wvs,3958
64
- utilities/redis.py,sha256=XywncvQak9AYgdeN2M3vDJM9MEJsIFSfOiWVCKZVGjY,32697
64
+ utilities/redis.py,sha256=pMKJjNI5e0lG-FZh2_idMBBmfgNr53KIGjBquZQOLZc,37556
65
65
  utilities/reprlib.py,sha256=Re9bk3n-kC__9DxQmRlevqFA86pE6TtVfWjUgpbVOv0,1849
66
66
  utilities/rich.py,sha256=t50MwwVBsoOLxzmeVFSVpjno4OW6Ufum32skXbV8-Bs,1911
67
67
  utilities/scipy.py,sha256=X6ROnHwiUhAmPhM0jkfEh0-Fd9iRvwiqtCQMOLmOQF8,945
@@ -69,7 +69,7 @@ utilities/sentinel.py,sha256=3jIwgpMekWgDAxPDA_hXMP2St43cPhciKN3LWiZ7kv0,1248
69
69
  utilities/shelve.py,sha256=HZsMwK4tcIfg3sh0gApx4-yjQnrY4o3V3ZRimvRhoW0,738
70
70
  utilities/slack_sdk.py,sha256=h2DiVkcFyYcT5zzZOAo6CSith5BBlHUbXeOJSL1neK8,5948
71
71
  utilities/socket.py,sha256=K77vfREvzoVTrpYKo6MZakol0EYu2q1sWJnnZqL0So0,118
72
- utilities/sqlalchemy.py,sha256=KraI3PGrIs8ZpTQi0rZzBMjlcPbgWNipXlLySeu4aiY,36765
72
+ utilities/sqlalchemy.py,sha256=rHixzaYl_QHTR1dAhLj15ntiE5kwZWY4MKIr97crDts,39602
73
73
  utilities/sqlalchemy_polars.py,sha256=s7hQNep2O5DTgIRXyN_JRQma7a4DAtNd25tshaZW8iw,15490
74
74
  utilities/statsmodels.py,sha256=koyiBHvpMcSiBfh99wFUfSggLNx7cuAw3rwyfAhoKpQ,3410
75
75
  utilities/streamlit.py,sha256=U9PJBaKP1IdSykKhPZhIzSPTZsmLsnwbEPZWzNhJPKk,2955
@@ -91,7 +91,7 @@ utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
91
91
  utilities/whenever.py,sha256=jS31ZAY5OMxFxLja_Yo5Fidi87Pd-GoVZ7Vi_teqVDA,16743
92
92
  utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
93
93
  utilities/zoneinfo.py,sha256=-5j7IQ9nb7gR43rdgA7ms05im-XuqhAk9EJnQBXxCoQ,1874
94
- dycw_utilities-0.126.10.dist-info/METADATA,sha256=3hajeWxg9om0FI0yM_fkaoFNZFG_j4slCdaV-NRNswU,12804
95
- dycw_utilities-0.126.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
96
- dycw_utilities-0.126.10.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
97
- dycw_utilities-0.126.10.dist-info/RECORD,,
94
+ dycw_utilities-0.126.11.dist-info/METADATA,sha256=BpSldlx63eNjZ98CKmHpEi6Ee2Zm5HqWnYZrKbBOICI,12804
95
+ dycw_utilities-0.126.11.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
96
+ dycw_utilities-0.126.11.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
97
+ dycw_utilities-0.126.11.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.126.10"
3
+ __version__ = "0.126.11"
utilities/redis.py CHANGED
@@ -25,7 +25,14 @@ from typing import (
25
25
  from redis.asyncio import Redis
26
26
  from redis.typing import EncodableT
27
27
 
28
- from utilities.asyncio import EnhancedQueue, InfiniteQueueLooper, Looper, timeout_dur
28
+ from utilities.asyncio import (
29
+ EnhancedQueue,
30
+ InfiniteQueueLooper,
31
+ Looper,
32
+ LooperTimeoutError,
33
+ timeout_dur,
34
+ )
35
+ from utilities.contextlib import suppress_super_object_attribute_error
29
36
  from utilities.datetime import (
30
37
  MILLISECOND,
31
38
  SECOND,
@@ -72,6 +79,9 @@ _V2 = TypeVar("_V2")
72
79
  _V3 = TypeVar("_V3")
73
80
 
74
81
 
82
+ _PUBLISH_TIMEOUT: Duration = SECOND
83
+
84
+
75
85
  ##
76
86
 
77
87
 
@@ -548,9 +558,6 @@ def redis_key(
548
558
  ##
549
559
 
550
560
 
551
- _PUBLISH_TIMEOUT: Duration = SECOND
552
-
553
-
554
561
  @overload
555
562
  async def publish(
556
563
  redis: Redis,
@@ -666,7 +673,7 @@ class PublishService(Looper[tuple[str, _T]]):
666
673
  # self
667
674
  redis: Redis
668
675
  serializer: Callable[[_T], EncodableT] = serialize
669
- publish_timeout: Duration = SECOND
676
+ publish_timeout: Duration = _PUBLISH_TIMEOUT
670
677
 
671
678
  @override
672
679
  async def core(self) -> None:
@@ -685,6 +692,55 @@ class PublishService(Looper[tuple[str, _T]]):
685
692
  ##
686
693
 
687
694
 
695
+ @dataclass(kw_only=True)
696
+ class PublishServiceMixin(Generic[_T]):
697
+ """Mix-in for the publish service."""
698
+
699
+ # base - looper
700
+ publish_service_freq: Duration = field(default=MILLISECOND, repr=False)
701
+ publish_service_backoff: Duration = field(default=SECOND, repr=False)
702
+ publish_service_empty_upon_exit: bool = field(default=False, repr=False)
703
+ publish_service_logger: str | None = field(default=None, repr=False)
704
+ publish_service_timeout: Duration | None = field(default=None, repr=False)
705
+ publish_service_timeout_error: type[Exception] = field(
706
+ default=LooperTimeoutError, repr=False
707
+ )
708
+ publish_service_debug: bool = field(default=False, repr=False)
709
+ _is_pending_restart: Event = field(default_factory=Event, init=False, repr=False)
710
+ # base - publish service
711
+ publish_service_redis: Redis
712
+ publish_service_serializer: Callable[[_T], EncodableT] = serialize
713
+ publish_service_publish_timeout: Duration = _PUBLISH_TIMEOUT
714
+ # self
715
+ _publish_service: PublishService[_T] = field(init=False, repr=False)
716
+
717
+ def __post_init__(self) -> None:
718
+ with suppress_super_object_attribute_error(): # skipif-ci-and-not-linux
719
+ super().__post_init__() # pyright: ignore[reportAttributeAccessIssue]
720
+ self._publish_service = PublishService( # skipif-ci-and-not-linux
721
+ # looper
722
+ freq=self.publish_service_freq,
723
+ backoff=self.publish_service_backoff,
724
+ empty_upon_exit=self.publish_service_empty_upon_exit,
725
+ logger=self.publish_service_logger,
726
+ timeout=self.publish_service_timeout,
727
+ timeout_error=self.publish_service_timeout_error,
728
+ _debug=self.publish_service_debug,
729
+ # publish service
730
+ redis=self.publish_service_redis,
731
+ serializer=self.publish_service_serializer,
732
+ publish_timeout=self.publish_service_publish_timeout,
733
+ )
734
+
735
+ def _yield_sub_loopers(self) -> Iterator[Looper[Any]]:
736
+ with suppress_super_object_attribute_error(): # skipif-ci-and-not-linux
737
+ yield from super()._yield_sub_loopers() # pyright: ignore[reportAttributeAccessIssue]
738
+ yield self._publish_service # skipif-ci-and-not-linux
739
+
740
+
741
+ ##
742
+
743
+
688
744
  _SUBSCRIBE_TIMEOUT: Duration = SECOND
689
745
  _SUBSCRIBE_SLEEP: Duration = MILLISECOND
690
746
 
@@ -920,6 +976,58 @@ class SubscribeService(Looper[_T]):
920
976
  ##
921
977
 
922
978
 
979
+ @dataclass(kw_only=True)
980
+ class SubscribeServiceMixin(Generic[_T]):
981
+ """Mix-in for the subscribe service."""
982
+
983
+ # base - looper
984
+ subscribe_service_freq: Duration = field(default=MILLISECOND, repr=False)
985
+ subscribe_service_backoff: Duration = field(default=SECOND, repr=False)
986
+ subscribe_service_empty_upon_exit: bool = field(default=False, repr=False)
987
+ subscribe_service_logger: str | None = field(default=None, repr=False)
988
+ subscribe_service_timeout: Duration | None = field(default=None, repr=False)
989
+ subscribe_service_timeout_error: type[Exception] = field(
990
+ default=LooperTimeoutError, repr=False
991
+ )
992
+ subscribe_service_debug: bool = field(default=False, repr=False)
993
+ # base - looper
994
+ subscribe_service_redis: Redis
995
+ subscribe_service_channel: str
996
+ subscribe_service_deserializer: Callable[[bytes], _T] = deserialize
997
+ subscribe_service_subscribe_sleep: Duration = _SUBSCRIBE_SLEEP
998
+ subscribe_service_subscribe_timeout: Duration | None = _SUBSCRIBE_TIMEOUT
999
+ # self
1000
+ _subscribe_service: SubscribeService[_T] = field(init=False, repr=False)
1001
+
1002
+ def __post_init__(self) -> None:
1003
+ with suppress_super_object_attribute_error(): # skipif-ci-and-not-linux
1004
+ super().__post_init__() # pyright: ignore[reportAttributeAccessIssue]
1005
+ self._subscribe_service = SubscribeService( # skipif-ci-and-not-linux
1006
+ # looper
1007
+ freq=self.subscribe_service_freq,
1008
+ backoff=self.subscribe_service_backoff,
1009
+ empty_upon_exit=self.subscribe_service_empty_upon_exit,
1010
+ logger=self.subscribe_service_logger,
1011
+ timeout=self.subscribe_service_timeout,
1012
+ timeout_error=self.subscribe_service_timeout_error,
1013
+ _debug=self.subscribe_service_debug,
1014
+ # subscribe service
1015
+ redis=self.subscribe_service_redis,
1016
+ channel=self.subscribe_service_channel,
1017
+ deserializer=self.subscribe_service_deserializer,
1018
+ subscribe_sleep=self.subscribe_service_subscribe_sleep,
1019
+ subscribe_timeout=self.subscribe_service_subscribe_timeout,
1020
+ )
1021
+
1022
+ def _yield_sub_loopers(self) -> Iterator[Looper[Any]]:
1023
+ with suppress_super_object_attribute_error(): # skipif-ci-and-not-linux
1024
+ yield from super()._yield_sub_loopers() # pyright: ignore[reportAttributeAccessIssue]
1025
+ yield self._subscribe_service # skipif-ci-and-not-linux
1026
+
1027
+
1028
+ ##
1029
+
1030
+
923
1031
  @asynccontextmanager
924
1032
  async def yield_pubsub(
925
1033
  redis: Redis, channels: MaybeIterable[str], /
@@ -1000,11 +1108,13 @@ def _deserialize(
1000
1108
 
1001
1109
  __all__ = [
1002
1110
  "PublishService",
1111
+ "PublishServiceMixin",
1003
1112
  "Publisher",
1004
1113
  "PublisherError",
1005
1114
  "RedisHashMapKey",
1006
1115
  "RedisKey",
1007
1116
  "SubscribeService",
1117
+ "SubscribeServiceMixin",
1008
1118
  "publish",
1009
1119
  "redis_hash_map_key",
1010
1120
  "redis_key",
utilities/sqlalchemy.py CHANGED
@@ -57,7 +57,13 @@ from sqlalchemy.orm import (
57
57
  from sqlalchemy.orm.exc import UnmappedClassError
58
58
  from sqlalchemy.pool import NullPool, Pool
59
59
 
60
- from utilities.asyncio import InfiniteQueueLooper, Looper, timeout_dur
60
+ from utilities.asyncio import (
61
+ InfiniteQueueLooper,
62
+ Looper,
63
+ LooperTimeoutError,
64
+ timeout_dur,
65
+ )
66
+ from utilities.contextlib import suppress_super_object_attribute_error
61
67
  from utilities.datetime import SECOND
62
68
  from utilities.functions import (
63
69
  ensure_str,
@@ -691,6 +697,63 @@ class UpsertService(Looper[_InsertItem]):
691
697
  )
692
698
 
693
699
 
700
+ @dataclass(kw_only=True)
701
+ class UpsertServiceMixin:
702
+ """Mix-in for the upsert service."""
703
+
704
+ # base - looper
705
+ upsert_service_freq: Duration = field(default=SECOND, repr=False)
706
+ upsert_service_backoff: Duration = field(default=SECOND, repr=False)
707
+ upsert_service_empty_upon_exit: bool = field(default=False, repr=False)
708
+ upsert_service_logger: str | None = field(default=None, repr=False)
709
+ upsert_service_timeout: Duration | None = field(default=None, repr=False)
710
+ upsert_service_timeout_error: type[Exception] = field(
711
+ default=LooperTimeoutError, repr=False
712
+ )
713
+ upsert_service_debug: bool = field(default=False, repr=False)
714
+ # base - upsert service
715
+ upsert_service_database: AsyncEngine
716
+ upsert_service_snake: bool = False
717
+ upsert_service_selected_or_all: _SelectedOrAll = "selected"
718
+ upsert_service_chunk_size_frac: float = CHUNK_SIZE_FRAC
719
+ upsert_service_assume_tables_exist: bool = False
720
+ upsert_service_timeout_create: Duration | None = None
721
+ upsert_service_error_create: type[Exception] = TimeoutError
722
+ upsert_service_timeout_insert: Duration | None = None
723
+ upsert_service_error_insert: type[Exception] = TimeoutError
724
+ # self
725
+ _upsert_service: UpsertService = field(init=False, repr=False)
726
+
727
+ def __post_init__(self) -> None:
728
+ with suppress_super_object_attribute_error():
729
+ super().__post_init__() # pyright: ignore[reportAttributeAccessIssue]
730
+ self._upsert_service = UpsertService(
731
+ # looper
732
+ freq=self.upsert_service_freq,
733
+ backoff=self.upsert_service_backoff,
734
+ empty_upon_exit=self.upsert_service_empty_upon_exit,
735
+ logger=self.upsert_service_logger,
736
+ timeout=self.upsert_service_timeout,
737
+ timeout_error=self.upsert_service_timeout_error,
738
+ _debug=self.upsert_service_debug,
739
+ # upsert service
740
+ engine=self.upsert_service_database,
741
+ snake=self.upsert_service_snake,
742
+ selected_or_all=self.upsert_service_selected_or_all,
743
+ chunk_size_frac=self.upsert_service_chunk_size_frac,
744
+ assume_tables_exist=self.upsert_service_assume_tables_exist,
745
+ timeout_create=self.upsert_service_timeout_create,
746
+ error_create=self.upsert_service_error_create,
747
+ timeout_insert=self.upsert_service_timeout_insert,
748
+ error_insert=self.upsert_service_error_insert,
749
+ )
750
+
751
+ def _yield_sub_loopers(self) -> Iterator[Looper[Any]]:
752
+ with suppress_super_object_attribute_error():
753
+ yield from super()._yield_sub_loopers() # pyright: ignore[reportAttributeAccessIssue]
754
+ yield self._upsert_service
755
+
756
+
694
757
  ##
695
758
 
696
759
 
@@ -1147,6 +1210,7 @@ __all__ = [
1147
1210
  "TablenameMixin",
1148
1211
  "UpsertItemsError",
1149
1212
  "UpsertService",
1213
+ "UpsertServiceMixin",
1150
1214
  "Upserter",
1151
1215
  "UpserterError",
1152
1216
  "check_engine",