dycw-utilities 0.119.0__py3-none-any.whl → 0.121.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.
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.119.0
3
+ Version: 0.121.0
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
7
7
  Requires-Dist: typing-extensions<4.14,>=4.13.1
8
8
  Provides-Extra: test
9
- Requires-Dist: hypothesis<6.132,>=6.131.19; extra == 'test'
9
+ Requires-Dist: hypothesis<6.132,>=6.131.20; extra == 'test'
10
10
  Requires-Dist: pytest-asyncio<0.27,>=0.26.0; extra == 'test'
11
11
  Requires-Dist: pytest-cov<6.2,>=6.1.1; extra == 'test'
12
12
  Requires-Dist: pytest-instafail<0.6,>=0.5.0; extra == 'test'
@@ -80,7 +80,7 @@ Provides-Extra: zzz-test-hypothesis
80
80
  Requires-Dist: aiosqlite<0.22,>=0.21.0; extra == 'zzz-test-hypothesis'
81
81
  Requires-Dist: asyncpg<0.31,>=0.30.0; extra == 'zzz-test-hypothesis'
82
82
  Requires-Dist: greenlet<3.3,>=3.2.0; extra == 'zzz-test-hypothesis'
83
- Requires-Dist: hypothesis<6.132,>=6.131.19; extra == 'zzz-test-hypothesis'
83
+ Requires-Dist: hypothesis<6.132,>=6.131.20; extra == 'zzz-test-hypothesis'
84
84
  Requires-Dist: luigi<3.7,>=3.6.0; extra == 'zzz-test-hypothesis'
85
85
  Requires-Dist: numpy<2.3,>=2.2.6; extra == 'zzz-test-hypothesis'
86
86
  Requires-Dist: pathvalidate<3.3,>=3.2.3; extra == 'zzz-test-hypothesis'
@@ -1,7 +1,7 @@
1
- utilities/__init__.py,sha256=ayFWZ1qOKB0Lqd63WRIXSBg4vZDal1xvPPVJr5LEPEA,60
1
+ utilities/__init__.py,sha256=FKqdZTwX6QRMMK9ehPxm3uM8OyIJA4Mgay0d8pgY6Nw,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=HGX79AKzpQbbDBW3paxAXrhWYeudcJjiO1ETU40d_-8,18463
4
+ utilities/asyncio.py,sha256=gn8dxBsNzFfFzrNpImF731LAmgLwEQM3uiwKuUPlg08,18822
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
@@ -59,15 +59,15 @@ 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=OHw3J2dBA5QssDluKXAG1zIAK2mJJTd6uBuf_1YQuAE,26646
62
+ utilities/redis.py,sha256=i5G3k-EIi2JSf9arLIu0YW6viGsiYtxAoynbb4KvJek,26628
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
66
66
  utilities/sentinel.py,sha256=3jIwgpMekWgDAxPDA_hXMP2St43cPhciKN3LWiZ7kv0,1248
67
67
  utilities/shelve.py,sha256=HZsMwK4tcIfg3sh0gApx4-yjQnrY4o3V3ZRimvRhoW0,738
68
- utilities/slack_sdk.py,sha256=NLHmWYK6wc5bz4CGImugXceaToasNBLSqA5sd5ld2r4,3307
68
+ utilities/slack_sdk.py,sha256=A2f7-DYOngRoUP6ZdLIaUQ6Lfzgru5Xp3U3k5JfEkQE,3301
69
69
  utilities/socket.py,sha256=K77vfREvzoVTrpYKo6MZakol0EYu2q1sWJnnZqL0So0,118
70
- utilities/sqlalchemy.py,sha256=09stMwvmI68zlk-DSy9GDk5_YxcMddLh87RPC8Bs4yY,35469
70
+ utilities/sqlalchemy.py,sha256=iwNJyR0Dx47l81aHEq5P6eMzgduVxxGH531OVUp2k_A,35451
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
@@ -78,7 +78,7 @@ utilities/text.py,sha256=Fo12N4aA7k2rnb4W4vH9iiDh88Q5_nvRssTkfNsvVM8,10965
78
78
  utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
79
79
  utilities/timer.py,sha256=Rkc49KSpHuC8s7vUxGO9DU55U9I6yDKnchsQqrUCVBs,4075
80
80
  utilities/traceback.py,sha256=p9WATV-e4_5AW6SvyRBiU-MY8XnEFcKgrFNUoFzalXI,27521
81
- utilities/types.py,sha256=FhE1b8v_s_IlmXucwY7jAMWAq9cfpzyKssQwgwb3jnM,18267
81
+ utilities/types.py,sha256=2f1DqTZTMRlpCPWPd9-rh_uwmRPv9UdBoi_Bfv7Ccmo,18374
82
82
  utilities/typing.py,sha256=H6ysJkI830aRwLsMKz0SZIw4cpcsm7d6KhQOwr-SDh0,13817
83
83
  utilities/tzdata.py,sha256=yCf70NICwAeazN3_JcXhWvRqCy06XJNQ42j7r6gw3HY,1217
84
84
  utilities/tzlocal.py,sha256=3upDNFBvGh1l9njmLR2z2S6K6VxQSb7QizYGUbAH3JU,960
@@ -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.119.0.dist-info/METADATA,sha256=XG8mqNyIqDEemm7CPwNSAdYJS_z_rsP8SqlYoT-DEAY,12943
92
- dycw_utilities-0.119.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
93
- dycw_utilities-0.119.0.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
94
- dycw_utilities-0.119.0.dist-info/RECORD,,
91
+ dycw_utilities-0.121.0.dist-info/METADATA,sha256=YvumB9cX9CE9qNSb60yruXQ-NlHPyugeA91GzOJLPUM,12943
92
+ dycw_utilities-0.121.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
93
+ dycw_utilities-0.121.0.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
94
+ dycw_utilities-0.121.0.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.119.0"
3
+ __version__ = "0.121.0"
utilities/asyncio.py CHANGED
@@ -26,7 +26,6 @@ from typing import (
26
26
  TYPE_CHECKING,
27
27
  Any,
28
28
  Generic,
29
- Literal,
30
29
  NoReturn,
31
30
  TextIO,
32
31
  TypeVar,
@@ -49,6 +48,7 @@ from utilities.reprlib import get_repr
49
48
  from utilities.sentinel import Sentinel, sentinel
50
49
  from utilities.types import (
51
50
  Coroutine1,
51
+ DurationOrEveryDuration,
52
52
  MaybeCallableEvent,
53
53
  MaybeType,
54
54
  THashable,
@@ -121,17 +121,14 @@ class EnhancedTaskGroup(TaskGroup):
121
121
  ##
122
122
 
123
123
 
124
- type _DurationOrEvery = Duration | tuple[Literal["every"], Duration]
125
-
126
-
127
124
  @dataclass(kw_only=True, unsafe_hash=True)
128
125
  class InfiniteLooper(ABC, Generic[THashable]):
129
126
  """An infinite loop which can throw exceptions by setting events."""
130
127
 
131
- sleep_core: _DurationOrEvery = SECOND
132
- sleep_restart: _DurationOrEvery = MINUTE
128
+ sleep_core: DurationOrEveryDuration = SECOND
129
+ sleep_restart: DurationOrEveryDuration = MINUTE
133
130
  logger: str | None = None
134
- _events: Mapping[THashable, Event] = field(
131
+ _events: Mapping[THashable | None, Event] = field(
135
132
  default_factory=dict, init=False, repr=False, hash=False
136
133
  )
137
134
 
@@ -229,7 +226,7 @@ class InfiniteLooper(ABC, Generic[THashable]):
229
226
  msgs.append(f"Sleeping {self._sleep_restart_desc}...")
230
227
  getLogger(name=self.logger).error("\n".join(msgs))
231
228
 
232
- def _raise_error(self, event: THashable, /) -> NoReturn:
229
+ def _raise_error(self, event: THashable | None, /) -> NoReturn:
233
230
  """Raise the error corresponding to given event."""
234
231
  mapping = dict(self._yield_events_and_exceptions())
235
232
  error = mapping.get(event, InfiniteLooperError)
@@ -241,7 +238,7 @@ class InfiniteLooper(ABC, Generic[THashable]):
241
238
  event: Event() for event, _ in self._yield_events_and_exceptions()
242
239
  }
243
240
 
244
- async def _run_sleep(self, sleep: _DurationOrEvery, /) -> None:
241
+ async def _run_sleep(self, sleep: DurationOrEveryDuration, /) -> None:
245
242
  """Sleep until the next part of the loop."""
246
243
  match sleep:
247
244
  case int() | float() | dt.timedelta() as duration:
@@ -264,12 +261,12 @@ class InfiniteLooper(ABC, Generic[THashable]):
264
261
  case _ as never:
265
262
  assert_never(never)
266
263
 
267
- def _set_event(self, event: THashable, /) -> None:
264
+ def _set_event(self, *, event: THashable | None = None) -> None:
268
265
  """Set the given event."""
269
266
  try:
270
267
  event_obj = self._events[event]
271
268
  except KeyError:
272
- raise InfiniteLooperError(looper=self, event=event) from None
269
+ raise _InfiniteLooperNoSuchEventError(looper=self, event=event) from None
273
270
  event_obj.set()
274
271
 
275
272
  def _yield_coroutines(self) -> Iterator[Callable[[], Coroutine1[None]]]:
@@ -278,14 +275,18 @@ class InfiniteLooper(ABC, Generic[THashable]):
278
275
 
279
276
  def _yield_events_and_exceptions(
280
277
  self,
281
- ) -> Iterator[tuple[THashable, MaybeType[BaseException]]]:
278
+ ) -> Iterator[tuple[THashable | None, MaybeType[BaseException]]]:
282
279
  """Yield the events & exceptions."""
283
- yield from []
280
+ yield (None, _InfiniteLooperDefaultEventError)
284
281
 
285
282
 
286
283
  @dataclass(kw_only=True, slots=True)
287
284
  class InfiniteLooperError(Exception):
288
285
  looper: InfiniteLooper[Any]
286
+
287
+
288
+ @dataclass(kw_only=True, slots=True)
289
+ class _InfiniteLooperNoSuchEventError(InfiniteLooperError):
289
290
  event: Hashable
290
291
 
291
292
  @override
@@ -293,6 +294,13 @@ class InfiniteLooperError(Exception):
293
294
  return f"{get_class_name(self.looper)!r} does not have an event {self.event!r}"
294
295
 
295
296
 
297
+ @dataclass(kw_only=True, slots=True)
298
+ class _InfiniteLooperDefaultEventError(InfiniteLooperError):
299
+ @override
300
+ def __str__(self) -> str:
301
+ return f"{get_class_name(self.looper)!r} default event error"
302
+
303
+
296
304
  ##
297
305
 
298
306
 
utilities/redis.py CHANGED
@@ -589,7 +589,7 @@ async def publish(
589
589
 
590
590
 
591
591
  @dataclass(kw_only=True)
592
- class PublisherIQL(InfiniteQueueLooper[None, tuple[str, EncodableT]]):
592
+ class Publisher(InfiniteQueueLooper[None, tuple[str, EncodableT]]):
593
593
  """Publish a set of messages to Redis."""
594
594
 
595
595
  redis: Redis
@@ -612,12 +612,12 @@ class PublisherIQL(InfiniteQueueLooper[None, tuple[str, EncodableT]]):
612
612
  def _yield_events_and_exceptions(
613
613
  self,
614
614
  ) -> Iterator[tuple[None, MaybeType[BaseException]]]:
615
- yield (None, PublisherIQLError) # skipif-ci-and-not-linux
615
+ yield (None, PublisherError) # skipif-ci-and-not-linux
616
616
 
617
617
 
618
618
  @dataclass(kw_only=True)
619
- class PublisherIQLError(Exception):
620
- publisher: PublisherIQL
619
+ class PublisherError(Exception):
620
+ publisher: Publisher
621
621
 
622
622
  @override
623
623
  def __str__(self) -> str:
@@ -812,8 +812,8 @@ _ = _TestRedis
812
812
 
813
813
 
814
814
  __all__ = [
815
- "PublisherIQL",
816
- "PublisherIQLError",
815
+ "Publisher",
816
+ "PublisherError",
817
817
  "RedisHashMapKey",
818
818
  "RedisKey",
819
819
  "publish",
utilities/slack_sdk.py CHANGED
@@ -35,7 +35,7 @@ async def _send_adapter(url: str, text: str, /) -> None:
35
35
 
36
36
 
37
37
  @dataclass(init=False, unsafe_hash=True)
38
- class SlackHandlerIQL(Handler, InfiniteQueueLooper[None, str]):
38
+ class SlackHandler(Handler, InfiniteQueueLooper[None, str]):
39
39
  """Handler for sending messages to Slack."""
40
40
 
41
41
  @override
@@ -110,4 +110,4 @@ def _get_client(url: str, /, *, timeout: Duration = _TIMEOUT) -> AsyncWebhookCli
110
110
  return AsyncWebhookClient(url, timeout=timeout_use)
111
111
 
112
112
 
113
- __all__ = ["SendToSlackError", "SlackHandlerIQL", "send_to_slack"]
113
+ __all__ = ["SendToSlackError", "SlackHandler", "send_to_slack"]
utilities/sqlalchemy.py CHANGED
@@ -609,7 +609,7 @@ class TablenameMixin:
609
609
 
610
610
 
611
611
  @dataclass(kw_only=True)
612
- class UpserterIQL(InfiniteQueueLooper[None, _InsertItem]):
612
+ class Upserter(InfiniteQueueLooper[None, _InsertItem]):
613
613
  """Upsert a set of items to a database."""
614
614
 
615
615
  engine: AsyncEngine
@@ -641,12 +641,12 @@ class UpserterIQL(InfiniteQueueLooper[None, _InsertItem]):
641
641
  def _yield_events_and_exceptions(
642
642
  self,
643
643
  ) -> Iterator[tuple[None, MaybeType[BaseException]]]:
644
- yield (None, UpserterIQLError)
644
+ yield (None, UpserterError)
645
645
 
646
646
 
647
647
  @dataclass(kw_only=True)
648
- class UpserterIQLError(Exception):
649
- upserter: UpserterIQL
648
+ class UpserterError(Exception):
649
+ upserter: Upserter
650
650
 
651
651
  @override
652
652
  def __str__(self) -> str:
@@ -1108,8 +1108,8 @@ __all__ = [
1108
1108
  "InsertItemsError",
1109
1109
  "TablenameMixin",
1110
1110
  "UpsertItemsError",
1111
- "UpserterIQL",
1112
- "UpserterIQLError",
1111
+ "Upserter",
1112
+ "UpserterError",
1113
1113
  "check_engine",
1114
1114
  "columnwise_max",
1115
1115
  "columnwise_min",
utilities/types.py CHANGED
@@ -92,6 +92,7 @@ type DateTimeLike = MaybeStr[dt.datetime]
92
92
  type DateOrDateTime = dt.date | dt.datetime
93
93
  type Duration = Number | dt.timedelta
94
94
  type DurationLike = MaybeStr[Duration]
95
+ type DurationOrEveryDuration = Duration | tuple[Literal["every"], Duration]
95
96
  type MaybeCallableDate = MaybeCallable[dt.date]
96
97
  type MaybeCallableDateTime = MaybeCallable[dt.datetime]
97
98
  type TimeLike = MaybeStr[dt.time]
@@ -270,6 +271,7 @@ __all__ = [
270
271
  "DateTimeLike",
271
272
  "Duration",
272
273
  "DurationLike",
274
+ "DurationOrEveryDuration",
273
275
  "EnumLike",
274
276
  "ExcInfo",
275
277
  "IterableHashable",