dycw-utilities 0.136.3__py3-none-any.whl → 0.136.5__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.136.3
3
+ Version: 0.136.5
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -1,7 +1,6 @@
1
- utilities/__init__.py,sha256=BmPyeyqP4do_46ks7_IMxZ-pOQGgO4-MnFQ9bw0zx_E,60
1
+ utilities/__init__.py,sha256=YnEnDHzwZotFIueP2TiR3whPRliR8W86Na6TxjOkcfQ,60
2
2
  utilities/aiolimiter.py,sha256=mD0wEiqMgwpty4XTbawFpnkkmJS6R4JRsVXFUaoitSU,628
3
3
  utilities/altair.py,sha256=HeZBVUocjkrTNwwKrClppsIqgNFF-ykv05HfZSoHYno,9104
4
- utilities/arq.py,sha256=S-sfBfY-E1ErRKf4sSXt2YyCjKvu-pBlOECDfjBebRA,6399
5
4
  utilities/asyncio.py,sha256=dcGeKQzjLBXxKzZkVIk5oZsFXEcynVbRB9iNB5XEDZk,38526
6
5
  utilities/atomicwrites.py,sha256=geFjn9Pwn-tTrtoGjDDxWli9NqbYfy3gGL6ZBctiqSo,5393
7
6
  utilities/atools.py,sha256=9im2g8OCf-Iynqa8bAv8N0Ycj9QvrJmGO7yLCZEdgII,986
@@ -24,13 +23,13 @@ utilities/getpass.py,sha256=DfN5UgMAtFCqS3dSfFHUfqIMZX2shXvwphOz_6J6f6A,103
24
23
  utilities/git.py,sha256=oi7-_l5e9haSANSCvQw25ufYGoNahuUPHAZ6114s3JQ,1191
25
24
  utilities/hashlib.py,sha256=SVTgtguur0P4elppvzOBbLEjVM3Pea0eWB61yg2ilxo,309
26
25
  utilities/http.py,sha256=WcahTcKYRtZ04WXQoWt5EGCgFPcyHD3EJdlMfxvDt-0,946
27
- utilities/hypothesis.py,sha256=_OFFvb8rVvMgXo04zaIFYw4p32QGrVHzJsR8pL3G8l4,36195
26
+ utilities/hypothesis.py,sha256=Ru3ZBhtGiuj-1vJjGuclymlYDAV_NC0WoUg9LY873d8,38044
28
27
  utilities/importlib.py,sha256=mV1xT_O_zt_GnZZ36tl3xOmMaN_3jErDWY54fX39F6Y,429
29
28
  utilities/inflect.py,sha256=DbqB5Q9FbRGJ1NbvEiZBirRMxCxgrz91zy5jCO9ZIs0,347
30
29
  utilities/ipython.py,sha256=V2oMYHvEKvlNBzxDXdLvKi48oUq2SclRg5xasjaXStw,763
31
30
  utilities/iterables.py,sha256=wlcm0PS2fKG-H0r0k367NOLrryMbfXLlwzUeAmBSvv8,43392
32
31
  utilities/jupyter.py,sha256=ft5JA7fBxXKzP-L9W8f2-wbF0QeYc_2uLQNFDVk4Z-M,2917
33
- utilities/libcst.py,sha256=Jto5ppzRzsxn4AD32IS8n0lbgLYXwsVJB6EY8giNZyY,4974
32
+ utilities/libcst.py,sha256=_yG3TWvgx8Z34S2oxI-b4iSJxZQcnWwqL-23sIGu8X4,4974
34
33
  utilities/lightweight_charts.py,sha256=JrkrAZMo6JID2Eoc9QCc05Y_pK4l2zsApIhmii1z2Ig,2764
35
34
  utilities/logging.py,sha256=j0xS7bNdZcMAobWSRahpg_d7GWewd_99oXvexrjWm6k,17841
36
35
  utilities/luigi.py,sha256=wK7cB3y8NXeSa8d6r_yTKRmjMguNmIPmy52yg10vPaI,4774
@@ -89,7 +88,7 @@ utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
89
88
  utilities/whenever.py,sha256=A-yoOqBqrcVD1yDINDsTFDw7dq9-zgUGn_f8CxVUQJs,23332
90
89
  utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
91
90
  utilities/zoneinfo.py,sha256=oEH-nL3t4h9uawyZqWDtNtDAl6M-CLpLYGI_nI6DulM,1971
92
- dycw_utilities-0.136.3.dist-info/METADATA,sha256=Hi7aRq1msI4KXQLVrnun8CojINzZHpxfP26XxstkH7w,1637
93
- dycw_utilities-0.136.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
94
- dycw_utilities-0.136.3.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
95
- dycw_utilities-0.136.3.dist-info/RECORD,,
91
+ dycw_utilities-0.136.5.dist-info/METADATA,sha256=IzGxc0DUvPm2PhmY1Ful44RBfat_JFpa8acA-KKoHXc,1637
92
+ dycw_utilities-0.136.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
93
+ dycw_utilities-0.136.5.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
94
+ dycw_utilities-0.136.5.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.136.3"
3
+ __version__ = "0.136.5"
utilities/hypothesis.py CHANGED
@@ -106,6 +106,7 @@ if TYPE_CHECKING:
106
106
  from collections.abc import Collection, Hashable, Iterable, Iterator
107
107
 
108
108
  from hypothesis.database import ExampleDatabase
109
+ from libcst import Import, ImportFrom
109
110
  from numpy.random import RandomState
110
111
 
111
112
  from utilities.numpy import NDArrayB, NDArrayF, NDArrayI, NDArrayO
@@ -555,6 +556,59 @@ def hashables() -> SearchStrategy[Hashable]:
555
556
  ##
556
557
 
557
558
 
559
+ @composite
560
+ def import_froms(
561
+ draw: DrawFn,
562
+ /,
563
+ *,
564
+ min_depth: MaybeSearchStrategy[int | None] = None,
565
+ max_depth: MaybeSearchStrategy[int | None] = None,
566
+ ) -> ImportFrom:
567
+ """Strategy for generating import-froms."""
568
+ from utilities.libcst import generate_import_from
569
+
570
+ min_depth_, max_depth_ = [draw2(draw, d) for d in [min_depth, max_depth]]
571
+ path = draw(
572
+ paths(
573
+ min_depth=1 if min_depth_ is None else max(min_depth_, 1),
574
+ max_depth=max_depth_,
575
+ )
576
+ )
577
+ module = ".".join(path.parts)
578
+ name = draw(text_ascii(min_size=1))
579
+ asname = draw(text_ascii(min_size=1) | none())
580
+ return generate_import_from(module, name, asname=asname)
581
+
582
+
583
+ ##
584
+
585
+
586
+ @composite
587
+ def imports(
588
+ draw: DrawFn,
589
+ /,
590
+ *,
591
+ min_depth: MaybeSearchStrategy[int | None] = None,
592
+ max_depth: MaybeSearchStrategy[int | None] = None,
593
+ ) -> Import:
594
+ """Strategy for generating imports."""
595
+ from utilities.libcst import generate_import
596
+
597
+ min_depth_, max_depth_ = [draw2(draw, d) for d in [min_depth, max_depth]]
598
+ path = draw(
599
+ paths(
600
+ min_depth=1 if min_depth_ is None else max(min_depth_, 1),
601
+ max_depth=max_depth_,
602
+ )
603
+ )
604
+ module = ".".join(path.parts)
605
+ asname = draw(text_ascii(min_size=1) | none())
606
+ return generate_import(module, asname=asname)
607
+
608
+
609
+ ##
610
+
611
+
558
612
  @composite
559
613
  def int_arrays(
560
614
  draw: DrawFn,
@@ -746,11 +800,32 @@ def _pairs_map[T](elements: list[T], /) -> tuple[T, T]:
746
800
  ##
747
801
 
748
802
 
749
- def paths() -> SearchStrategy[Path]:
803
+ @composite
804
+ def paths(
805
+ draw: DrawFn,
806
+ /,
807
+ *,
808
+ min_depth: MaybeSearchStrategy[int | None] = None,
809
+ max_depth: MaybeSearchStrategy[int | None] = None,
810
+ ) -> Path:
750
811
  """Strategy for generating `Path`s."""
812
+ min_depth_, max_depth_ = [draw2(draw, d) for d in [min_depth, max_depth]]
813
+ parts = draw(
814
+ lists(
815
+ _path_parts(),
816
+ min_size=0 if min_depth_ is None else min_depth_,
817
+ max_size=max_depth_,
818
+ )
819
+ )
820
+ return Path(*parts)
821
+
822
+
823
+ @composite
824
+ def _path_parts(draw: DrawFn, /) -> str:
825
+ part = draw(text_ascii(min_size=1, max_size=10))
751
826
  reserved = {"AUX", "NUL"}
752
- strategy = text_ascii(min_size=1, max_size=10).filter(lambda x: x not in reserved)
753
- return lists(strategy, max_size=10).map(lambda parts: Path(*parts))
827
+ _ = assume(part not in reserved)
828
+ return part
754
829
 
755
830
 
756
831
  ##
@@ -1292,6 +1367,8 @@ __all__ = [
1292
1367
  "freqs",
1293
1368
  "git_repos",
1294
1369
  "hashables",
1370
+ "import_froms",
1371
+ "imports",
1295
1372
  "int32s",
1296
1373
  "int64s",
1297
1374
  "int_arrays",
utilities/libcst.py CHANGED
@@ -23,16 +23,6 @@ from libcst import (
23
23
  from utilities.errors import ImpossibleCaseError
24
24
 
25
25
 
26
- def generate_from_import(
27
- module: str, name: str, /, *, asname: str | None = None
28
- ) -> ImportFrom:
29
- """Generate an `ImportFrom` object."""
30
- alias = ImportAlias(
31
- name=Name(name), asname=AsName(Name(asname)) if asname else None
32
- )
33
- return ImportFrom(module=split_dotted_str(module), names=[alias])
34
-
35
-
36
26
  def generate_f_string(var: str, suffix: str, /) -> FormattedString:
37
27
  """Generate an f-string."""
38
28
  return FormattedString([
@@ -49,6 +39,16 @@ def generate_import(module: str, /, *, asname: str | None = None) -> Import:
49
39
  return Import(names=[alias])
50
40
 
51
41
 
42
+ def generate_import_from(
43
+ module: str, name: str, /, *, asname: str | None = None
44
+ ) -> ImportFrom:
45
+ """Generate an `ImportFrom` object."""
46
+ alias = ImportAlias(
47
+ name=Name(name), asname=AsName(Name(asname)) if asname else None
48
+ )
49
+ return ImportFrom(module=split_dotted_str(module), names=[alias])
50
+
51
+
52
52
  ##
53
53
 
54
54
 
@@ -170,8 +170,8 @@ def render_module(source: str | Module, /) -> str:
170
170
  __all__ = [
171
171
  "ParseImportError",
172
172
  "generate_f_string",
173
- "generate_from_import",
174
173
  "generate_import",
174
+ "generate_import_from",
175
175
  "join_dotted_str",
176
176
  "parse_import",
177
177
  "render_module",
utilities/arq.py DELETED
@@ -1,216 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass
4
- from functools import wraps
5
- from itertools import chain
6
- from typing import TYPE_CHECKING, Any, Self, cast, override
7
-
8
- from arq.constants import default_queue_name, expires_extra_ms
9
- from arq.cron import cron
10
-
11
- from utilities.dataclasses import replace_non_sentinel
12
- from utilities.sentinel import Sentinel, sentinel
13
-
14
- if TYPE_CHECKING:
15
- from collections.abc import Callable, Iterable, Sequence
16
- from datetime import datetime, timedelta, timezone
17
-
18
- from arq.connections import ArqRedis, RedisSettings
19
- from arq.cron import CronJob
20
- from arq.jobs import Deserializer, Job, Serializer
21
- from arq.typing import (
22
- OptionType,
23
- SecondsTimedelta,
24
- StartupShutdown,
25
- WeekdayOptionType,
26
- WorkerCoroutine,
27
- )
28
- from arq.worker import Function
29
-
30
- from utilities.types import Coro, StrMapping
31
-
32
-
33
- def cron_raw(
34
- coroutine: Callable[..., Coro[Any]],
35
- /,
36
- *,
37
- name: str | None = None,
38
- month: OptionType = None,
39
- day: OptionType = None,
40
- weekday: WeekdayOptionType = None,
41
- hour: OptionType = None,
42
- minute: OptionType = None,
43
- second: OptionType = 0,
44
- microsecond: int = 123_456,
45
- run_at_startup: bool = False,
46
- unique: bool = True,
47
- job_id: str | None = None,
48
- timeout: SecondsTimedelta | None = None,
49
- keep_result: float | None = 0,
50
- keep_result_forever: bool | None = False,
51
- max_tries: int | None = 1,
52
- args: Iterable[Any] | None = None,
53
- kwargs: StrMapping | None = None,
54
- ) -> CronJob:
55
- """Create a cron job with a raw coroutine function."""
56
- lifted = _lift_cron(
57
- coroutine, *(() if args is None else args), **({} if kwargs is None else kwargs)
58
- )
59
- return cron(
60
- lifted,
61
- name=name,
62
- month=month,
63
- day=day,
64
- weekday=weekday,
65
- hour=hour,
66
- minute=minute,
67
- second=second,
68
- microsecond=microsecond,
69
- run_at_startup=run_at_startup,
70
- unique=unique,
71
- job_id=job_id,
72
- timeout=timeout,
73
- keep_result=keep_result,
74
- keep_result_forever=keep_result_forever,
75
- max_tries=max_tries,
76
- )
77
-
78
-
79
- def _lift_cron[**P, T](
80
- func: Callable[P, Coro[T]], *args: P.args, **kwargs: P.kwargs
81
- ) -> WorkerCoroutine:
82
- """Lift a coroutine function & call arg/kwargs for `cron`."""
83
-
84
- @wraps(func)
85
- async def wrapped(ctx: StrMapping, /) -> T:
86
- _ = ctx
87
- return await func(*args, **kwargs)
88
-
89
- return cast("Any", wrapped)
90
-
91
-
92
- ##
93
-
94
-
95
- @dataclass(kw_only=True, slots=True)
96
- class _JobEnqueuer:
97
- """Enqueuer of jobs."""
98
-
99
- job_id: str | None = None
100
- queue_name: str | None = None
101
- defer_until: datetime | None = None
102
- defer_by: int | float | timedelta | None = None
103
- expires: int | float | timedelta | None = None
104
- job_try: int | None = None
105
-
106
- async def __call__[**P, T](
107
- self,
108
- redis: ArqRedis,
109
- function: Callable[P, Coro[T]],
110
- *args: P.args,
111
- **kwargs: P.kwargs,
112
- ) -> Job | None:
113
- return await redis.enqueue_job( # skipif-ci-and-not-linux
114
- function.__name__,
115
- *args,
116
- _job_id=self.job_id,
117
- _queue_name=self.queue_name,
118
- _defer_until=self.defer_until,
119
- _defer_by=self.defer_by,
120
- _expires=self.expires,
121
- _job_try=self.job_try,
122
- **kwargs,
123
- )
124
-
125
- def settings(
126
- self,
127
- *,
128
- job_id: str | None | Sentinel = sentinel,
129
- queue_name: str | None | Sentinel = sentinel,
130
- defer_until: datetime | None | Sentinel = sentinel,
131
- defer_by: float | timedelta | None | Sentinel = sentinel,
132
- expires: float | timedelta | None | Sentinel = sentinel,
133
- job_try: int | None | Sentinel = sentinel,
134
- ) -> Self:
135
- """Replace elements of the enqueuer."""
136
- return replace_non_sentinel( # skipif-ci-and-not-linux
137
- self,
138
- job_id=job_id,
139
- queue_name=queue_name,
140
- defer_until=defer_until,
141
- defer_by=defer_by,
142
- expires=expires,
143
- job_try=job_try,
144
- )
145
-
146
-
147
- job_enqueuer = _JobEnqueuer()
148
-
149
-
150
- ##
151
-
152
-
153
- class _WorkerMeta(type):
154
- @override
155
- def __new__(
156
- mcs: type[_WorkerMeta],
157
- name: str,
158
- bases: tuple[type, ...],
159
- namespace: dict[str, Any],
160
- /,
161
- ) -> type[Worker]:
162
- cls = cast("type[Worker]", super().__new__(mcs, name, bases, namespace))
163
- cls.functions = tuple(chain(cls.functions, map(cls._lift, cls.functions_raw)))
164
- return cls
165
-
166
- @classmethod
167
- def _lift[**P, T](cls, func: Callable[P, Coro[T]]) -> WorkerCoroutine:
168
- """Lift a coroutine function to accept the required `ctx` argument."""
169
-
170
- @wraps(func)
171
- async def wrapped(ctx: StrMapping, *args: P.args, **kwargs: P.kwargs) -> T:
172
- _ = ctx
173
- return await func(*args, **kwargs)
174
-
175
- return cast("Any", wrapped)
176
-
177
-
178
- @dataclass(kw_only=True)
179
- class Worker(metaclass=_WorkerMeta):
180
- """Base class for all workers."""
181
-
182
- functions: Sequence[Function | WorkerCoroutine] = ()
183
- functions_raw: Sequence[Callable[..., Coro[Any]]] = ()
184
- queue_name: str | None = default_queue_name
185
- cron_jobs: Sequence[CronJob] | None = None
186
- redis_settings: RedisSettings | None = None
187
- redis_pool: ArqRedis | None = None
188
- burst: bool = False
189
- on_startup: StartupShutdown | None = None
190
- on_shutdown: StartupShutdown | None = None
191
- on_job_start: StartupShutdown | None = None
192
- on_job_end: StartupShutdown | None = None
193
- after_job_end: StartupShutdown | None = None
194
- handle_signals: bool = True
195
- job_completion_wait: int = 0
196
- max_jobs: int = 10
197
- job_timeout: SecondsTimedelta = 300
198
- keep_result: SecondsTimedelta = 3600
199
- keep_result_forever: bool = False
200
- poll_delay: SecondsTimedelta = 0.5
201
- queue_read_limit: int | None = None
202
- max_tries: int = 5
203
- health_check_interval: SecondsTimedelta = 3600
204
- health_check_key: str | None = None
205
- ctx: dict[Any, Any] | None = None
206
- retry_jobs: bool = True
207
- allow_abort_jobs: bool = False
208
- max_burst_jobs: int = -1
209
- job_serializer: Serializer | None = None
210
- job_deserializer: Deserializer | None = None
211
- expires_extra_ms: int = expires_extra_ms
212
- timezone: timezone | None = None
213
- log_results: bool = True
214
-
215
-
216
- __all__ = ["Worker", "cron", "job_enqueuer"]