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.
- {dycw_utilities-0.119.0.dist-info → dycw_utilities-0.121.0.dist-info}/METADATA +3 -3
- {dycw_utilities-0.119.0.dist-info → dycw_utilities-0.121.0.dist-info}/RECORD +10 -10
- utilities/__init__.py +1 -1
- utilities/asyncio.py +21 -13
- utilities/redis.py +6 -6
- utilities/slack_sdk.py +2 -2
- utilities/sqlalchemy.py +6 -6
- utilities/types.py +2 -0
- {dycw_utilities-0.119.0.dist-info → dycw_utilities-0.121.0.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.119.0.dist-info → dycw_utilities-0.121.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,12 +1,12 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: dycw-utilities
|
3
|
-
Version: 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.
|
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.
|
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=
|
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=
|
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=
|
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=
|
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=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=
|
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.
|
92
|
-
dycw_utilities-0.
|
93
|
-
dycw_utilities-0.
|
94
|
-
dycw_utilities-0.
|
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
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:
|
132
|
-
sleep_restart:
|
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:
|
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
|
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
|
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
|
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
|
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,
|
615
|
+
yield (None, PublisherError) # skipif-ci-and-not-linux
|
616
616
|
|
617
617
|
|
618
618
|
@dataclass(kw_only=True)
|
619
|
-
class
|
620
|
-
publisher:
|
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
|
-
"
|
816
|
-
"
|
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
|
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", "
|
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
|
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,
|
644
|
+
yield (None, UpserterError)
|
645
645
|
|
646
646
|
|
647
647
|
@dataclass(kw_only=True)
|
648
|
-
class
|
649
|
-
upserter:
|
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
|
-
"
|
1112
|
-
"
|
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",
|
File without changes
|
File without changes
|