dycw-utilities 0.131.13__py3-none-any.whl → 0.131.15__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.131.13
3
+ Version: 0.131.15
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=3uDzFx7UMHsyNY8dbs9U0P66UUimaERvIAvdIFvrju8,61
1
+ utilities/__init__.py,sha256=TF72KRTntycdsGaFpyLS49_hW9qrELSmZAvg2kQ0BrA,61
2
2
  utilities/aiolimiter.py,sha256=mD0wEiqMgwpty4XTbawFpnkkmJS6R4JRsVXFUaoitSU,628
3
3
  utilities/altair.py,sha256=HeZBVUocjkrTNwwKrClppsIqgNFF-ykv05HfZSoHYno,9104
4
4
  utilities/asyncio.py,sha256=yfKvAIDCRrWdyQMVZMo4DJQx4nVrXoAcqwhNuF95Ryo,38186
@@ -24,7 +24,7 @@ utilities/getpass.py,sha256=DfN5UgMAtFCqS3dSfFHUfqIMZX2shXvwphOz_6J6f6A,103
24
24
  utilities/git.py,sha256=oi7-_l5e9haSANSCvQw25ufYGoNahuUPHAZ6114s3JQ,1191
25
25
  utilities/hashlib.py,sha256=SVTgtguur0P4elppvzOBbLEjVM3Pea0eWB61yg2ilxo,309
26
26
  utilities/http.py,sha256=WcahTcKYRtZ04WXQoWt5EGCgFPcyHD3EJdlMfxvDt-0,946
27
- utilities/hypothesis.py,sha256=3mZbEhs5yTcigV2N8PSBqe8pdg2JOYf6e5XOwgGAwjQ,45206
27
+ utilities/hypothesis.py,sha256=hEyuJoLH0c6So0P5OESvyANpfM49Ld1iYXC4bZvacm4,42603
28
28
  utilities/importlib.py,sha256=mV1xT_O_zt_GnZZ36tl3xOmMaN_3jErDWY54fX39F6Y,429
29
29
  utilities/inflect.py,sha256=DbqB5Q9FbRGJ1NbvEiZBirRMxCxgrz91zy5jCO9ZIs0,347
30
30
  utilities/ipython.py,sha256=V2oMYHvEKvlNBzxDXdLvKi48oUq2SclRg5xasjaXStw,763
@@ -33,7 +33,7 @@ utilities/jupyter.py,sha256=ft5JA7fBxXKzP-L9W8f2-wbF0QeYc_2uLQNFDVk4Z-M,2917
33
33
  utilities/libcst.py,sha256=Jto5ppzRzsxn4AD32IS8n0lbgLYXwsVJB6EY8giNZyY,4974
34
34
  utilities/lightweight_charts.py,sha256=JrkrAZMo6JID2Eoc9QCc05Y_pK4l2zsApIhmii1z2Ig,2764
35
35
  utilities/logging.py,sha256=zm5k0Cduxtx2H2o7odxUTJtPNkJS85mqHYN1cS5Kc1w,17863
36
- utilities/luigi.py,sha256=fpH9MbxJDuo6-k9iCXRayFRtiVbUtibCJKugf7ygpv0,5988
36
+ utilities/luigi.py,sha256=UAt4TDMtinLAN7sipX0jSvH-aZzHUTQbHB3Rwtbq994,4840
37
37
  utilities/math.py,sha256=_6vrDyjtaqE_OFE-F2DNWrDG_J_kMl3nFAJsok9v_bY,26862
38
38
  utilities/memory_profiler.py,sha256=tf2C51P2lCujPGvRt2Rfc7VEw5LDXmVPCG3z_AvBmbU,962
39
39
  utilities/modules.py,sha256=iuvLluJya-hvl1Q25-Jk3dLgx2Es3ck4SjJiEkAlVTs,3195
@@ -80,6 +80,7 @@ utilities/text.py,sha256=ymBFlP_cA8OgNnZRVNs7FAh7OG8HxE6YkiLEMZv5g_A,11297
80
80
  utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
81
81
  utilities/timer.py,sha256=VeSl3ot8-f4D1d3HjjSsgKvjxHJGXd_sW4KcTExOR64,2475
82
82
  utilities/traceback.py,sha256=cMXrCD59CROnezAU8VW67CxZ8Igc5QmaxlV8qrBvNMs,8504
83
+ utilities/typed_settings.py,sha256=zUA0_CmVJT5rwrm3e-dZO83OdPXEel4NfVK24NAD5Vk,1779
83
84
  utilities/types.py,sha256=CHQke10ETEpypxppYVhWp1G68S6mvifalrRLolYBcCg,19506
84
85
  utilities/typing.py,sha256=VuGuztLSkTicxgVwI5wrVOTcY70OlzwsTU7LcFVjGlY,14169
85
86
  utilities/tzdata.py,sha256=fgNVj66yUbCSI_-vrRVzSD3gtf-L_8IEJEPjP_Jel5Y,266
@@ -91,7 +92,7 @@ utilities/whenever.py,sha256=2NQ-0SnLNW2kFpefP9dVE8H0RbaeusXYLPmv282Jpto,16755
91
92
  utilities/whenever2.py,sha256=iFVL4CjuIOpzsDU6li5smHnDEqam30-FtTgXWeHuWiE,7510
92
93
  utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
93
94
  utilities/zoneinfo.py,sha256=gJPr9l7V8s3Y7TXpCGYEM1S81Rplb9e4MoV9Nvy2VU8,1852
94
- dycw_utilities-0.131.13.dist-info/METADATA,sha256=BSLseH0v8xkeiTFmvQ2ITtMLy6gHtk6aFRAmuNQEy_k,1585
95
- dycw_utilities-0.131.13.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
96
- dycw_utilities-0.131.13.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
97
- dycw_utilities-0.131.13.dist-info/RECORD,,
95
+ dycw_utilities-0.131.15.dist-info/METADATA,sha256=Aztn9G7ACPHL_tGTOBayxzN6aPT_8gmKwsTUcieAoUA,1585
96
+ dycw_utilities-0.131.15.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
97
+ dycw_utilities-0.131.15.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
98
+ dycw_utilities-0.131.15.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.131.13"
3
+ __version__ = "0.131.15"
utilities/hypothesis.py CHANGED
@@ -716,85 +716,6 @@ def lists_fixed_length(
716
716
  ##
717
717
 
718
718
 
719
- @composite
720
- def min_and_max_datetimes(
721
- draw: DrawFn,
722
- /,
723
- *,
724
- min_value: MaybeSearchStrategy[dt.datetime | None] = None,
725
- max_value: MaybeSearchStrategy[dt.datetime | None] = None,
726
- time_zone: MaybeSearchStrategy[ZoneInfo | timezone] = UTC,
727
- round_: MathRoundMode | None = None,
728
- timedelta: dt.timedelta | None = None,
729
- rel_tol: float | None = None,
730
- abs_tol: float | None = None,
731
- valid: bool = False,
732
- ) -> tuple[dt.datetime, dt.datetime]:
733
- """Strategy for generating min/max datetimes."""
734
- match min_value, max_value:
735
- case None, None:
736
- return draw(
737
- pairs(
738
- zoned_datetimes(
739
- time_zone=time_zone,
740
- round_=round_,
741
- timedelta=timedelta,
742
- rel_tol=rel_tol,
743
- abs_tol=abs_tol,
744
- valid=valid,
745
- ),
746
- sorted=True,
747
- )
748
- )
749
- case None, dt.datetime():
750
- min_value_ = draw(
751
- zoned_datetimes(
752
- max_value=max_value,
753
- time_zone=time_zone,
754
- round_=round_,
755
- timedelta=timedelta,
756
- rel_tol=rel_tol,
757
- abs_tol=abs_tol,
758
- valid=valid,
759
- )
760
- )
761
- return min_value_, max_value
762
- case dt.datetime(), None:
763
- max_value_ = draw(
764
- zoned_datetimes(
765
- min_value=min_value,
766
- time_zone=time_zone,
767
- round_=round_,
768
- timedelta=timedelta,
769
- rel_tol=rel_tol,
770
- abs_tol=abs_tol,
771
- valid=valid,
772
- )
773
- )
774
- return min_value, max_value_
775
- case dt.datetime(), dt.datetime():
776
- _ = assume(min_value <= max_value)
777
- return min_value, max_value
778
- case _, _:
779
- strategy = zoned_datetimes(
780
- time_zone=time_zone,
781
- round_=round_,
782
- timedelta=timedelta,
783
- rel_tol=rel_tol,
784
- abs_tol=abs_tol,
785
- valid=valid,
786
- )
787
- min_value_ = draw2(draw, min_value, strategy)
788
- max_value_ = draw2(draw, max_value, strategy)
789
- _ = assume(min_value_ <= max_value_)
790
- return min_value_, max_value_
791
- case _ as never:
792
- assert_never(never)
793
-
794
-
795
- ##
796
-
797
-
798
719
  @composite
799
720
  def months(
800
721
  draw: DrawFn,
@@ -1561,14 +1482,12 @@ __all__ = [
1561
1482
  "int64s",
1562
1483
  "int_arrays",
1563
1484
  "lists_fixed_length",
1564
- "min_and_max_datetimes",
1565
1485
  "months",
1566
1486
  "namespace_mixins",
1567
1487
  "numbers",
1568
1488
  "pairs",
1569
1489
  "paths",
1570
1490
  "plain_datetimes",
1571
- "plain_datetimes",
1572
1491
  "plain_datetimes_whenever",
1573
1492
  "random_states",
1574
1493
  "sentinels",
utilities/luigi.py CHANGED
@@ -2,121 +2,91 @@ from __future__ import annotations
2
2
 
3
3
  from abc import ABC, abstractmethod
4
4
  from pathlib import Path
5
- from typing import TYPE_CHECKING, Any, Literal, cast, overload, override
5
+ from typing import (
6
+ TYPE_CHECKING,
7
+ Any,
8
+ Literal,
9
+ TypeVar,
10
+ assert_never,
11
+ cast,
12
+ overload,
13
+ override,
14
+ )
6
15
 
7
16
  import luigi
8
17
  from luigi import Parameter, PathParameter, Target, Task
9
18
  from luigi import build as _build
10
-
11
- from utilities.datetime import EPOCH_UTC
19
+ from luigi.parameter import ParameterVisibility, _no_value
20
+ from whenever import ZonedDateTime
12
21
 
13
22
  if TYPE_CHECKING:
14
- import datetime as dt
15
- from collections.abc import Iterable
23
+ from collections.abc import Callable, Iterable
16
24
 
17
25
  from luigi.execution_summary import LuigiRunResult
18
26
 
19
- from utilities.types import LogLevel, MaybeStr, PathLike
20
-
21
-
22
- # parameters
23
-
24
-
25
- class DateHourParameter(luigi.DateHourParameter):
26
- """A parameter which takes the value of an hourly `dt.datetime`."""
27
-
28
- def __init__(self, interval: int = 1, **kwargs: Any) -> None:
29
- super().__init__(interval, EPOCH_UTC, **kwargs)
30
-
31
- @override
32
- def normalize(self, dt: MaybeStr[dt.datetime]) -> dt.datetime:
33
- from utilities.whenever import ensure_zoned_datetime
34
-
35
- return ensure_zoned_datetime(dt)
36
-
37
- @override
38
- def parse(self, s: str) -> dt.datetime:
39
- from utilities.whenever import parse_zoned_datetime
40
-
41
- return parse_zoned_datetime(s)
42
-
43
- @override
44
- def serialize(self, dt: dt.datetime) -> str:
45
- from utilities.whenever import serialize_zoned_datetime
46
-
47
- return serialize_zoned_datetime(dt)
48
-
49
-
50
- class DateMinuteParameter(luigi.DateMinuteParameter):
51
- """A parameter which takes the value of a minutely `dt.datetime`."""
52
-
53
- def __init__(self, interval: int = 1, **kwargs: Any) -> None:
54
- super().__init__(interval=interval, start=EPOCH_UTC, **kwargs)
55
-
56
- @override
57
- def normalize(self, dt: MaybeStr[dt.datetime]) -> dt.datetime:
58
- from utilities.whenever import ensure_zoned_datetime
59
-
60
- return ensure_zoned_datetime(dt)
61
-
62
- @override
63
- def parse(self, s: str) -> dt.datetime:
64
- from utilities.whenever import parse_zoned_datetime
65
-
66
- return parse_zoned_datetime(s)
67
-
68
- @override
69
- def serialize(self, dt: dt.datetime) -> str:
70
- from utilities.whenever import serialize_zoned_datetime
71
-
72
- return serialize_zoned_datetime(dt)
73
-
27
+ from utilities.types import DateTimeRoundUnit, LogLevel, PathLike, ZonedDateTimeLike
74
28
 
75
- class DateSecondParameter(luigi.DateSecondParameter):
76
- """A parameter which takes the value of a secondly `dt.datetime`."""
77
29
 
78
- def __init__(self, interval: int = 1, **kwargs: Any) -> None:
79
- super().__init__(interval, EPOCH_UTC, **kwargs)
30
+ _T = TypeVar("_T")
80
31
 
81
- @override
82
- def normalize(self, dt: MaybeStr[dt.datetime]) -> dt.datetime:
83
- from utilities.whenever import ensure_zoned_datetime
84
-
85
- return ensure_zoned_datetime(dt)
86
-
87
- @override
88
- def parse(self, s: str) -> dt.datetime:
89
- from utilities.whenever import parse_zoned_datetime
90
-
91
- return parse_zoned_datetime(s)
92
32
 
93
- @override
94
- def serialize(self, dt: dt.datetime) -> str:
95
- from utilities.whenever import serialize_zoned_datetime
96
-
97
- return serialize_zoned_datetime(dt)
98
-
99
-
100
- class TimeParameter(Parameter):
101
- """A parameter which takes the value of a `dt.time`."""
102
-
103
- @override
104
- def normalize(self, x: MaybeStr[dt.time]) -> dt.time:
105
- from utilities.whenever import ensure_time
106
-
107
- return ensure_time(x)
108
-
109
- @override
110
- def parse(self, x: str) -> dt.time:
111
- from utilities.whenever import parse_time
112
-
113
- return parse_time(x)
33
+ # parameters
114
34
 
115
- @override
116
- def serialize(self, x: dt.time) -> str:
117
- from utilities.whenever import serialize_time
118
35
 
119
- return serialize_time(x)
36
+ class ZonedDateTimeParameter(Parameter):
37
+ """A parameter which takes the value of a zoned datetime."""
38
+
39
+ _unit: DateTimeRoundUnit
40
+ _increment: int
41
+
42
+ @override
43
+ def __init__(
44
+ self,
45
+ default: Any = _no_value,
46
+ is_global: bool = False,
47
+ significant: bool = True,
48
+ description: str | None = None,
49
+ config_path: None = None,
50
+ positional: bool = True,
51
+ always_in_help: bool = False,
52
+ batch_method: Callable[[Iterable[_T]], _T] | None = None,
53
+ visibility: ParameterVisibility = ParameterVisibility.PUBLIC,
54
+ *,
55
+ unit: DateTimeRoundUnit = "second",
56
+ increment: int = 1,
57
+ ) -> None:
58
+ super().__init__(
59
+ default,
60
+ is_global,
61
+ significant,
62
+ description,
63
+ config_path,
64
+ positional,
65
+ always_in_help,
66
+ batch_method,
67
+ visibility,
68
+ )
69
+ self._unit = unit
70
+ self._increment = increment
71
+
72
+ @override
73
+ def normalize(self, x: ZonedDateTimeLike) -> ZonedDateTime:
74
+ match x:
75
+ case ZonedDateTime() as date_time:
76
+ ...
77
+ case str() as text:
78
+ date_time = ZonedDateTime.parse_common_iso(text)
79
+ case _ as never:
80
+ assert_never(never)
81
+ return date_time.round(self._unit, increment=self._increment, mode="floor")
82
+
83
+ @override
84
+ def parse(self, x: str) -> ZonedDateTime:
85
+ return ZonedDateTime.parse_common_iso(x)
86
+
87
+ @override
88
+ def serialize(self, x: ZonedDateTime) -> str:
89
+ return x.format_common_iso()
120
90
 
121
91
 
122
92
  # targets
@@ -217,12 +187,9 @@ def build(
217
187
 
218
188
 
219
189
  __all__ = [
220
- "DateHourParameter",
221
- "DateMinuteParameter",
222
- "DateSecondParameter",
223
190
  "ExternalFile",
224
191
  "ExternalTask",
225
192
  "PathTarget",
226
- "TimeParameter",
193
+ "ZonedDateTimeParameter",
227
194
  "build",
228
195
  ]
@@ -0,0 +1,61 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any, TypeVar, override
4
+
5
+ from typed_settings.converters import TSConverter
6
+ from whenever import (
7
+ Date,
8
+ DateDelta,
9
+ DateTimeDelta,
10
+ PlainDateTime,
11
+ Time,
12
+ TimeDelta,
13
+ ZonedDateTime,
14
+ )
15
+
16
+ if TYPE_CHECKING:
17
+ from collections.abc import Callable
18
+
19
+
20
+ _T = TypeVar("_T")
21
+
22
+
23
+ class ExtendedTSConverter(TSConverter):
24
+ """An extension of the TSConverter for custom types."""
25
+
26
+ @override
27
+ def __init__(
28
+ self,
29
+ *,
30
+ resolve_paths: bool = True,
31
+ strlist_sep: str | Callable[[str], list] | None = ":",
32
+ ) -> None:
33
+ super().__init__(resolve_paths=resolve_paths, strlist_sep=strlist_sep)
34
+ cases: list[tuple[type[Any], Callable[..., Any]]] = [
35
+ (Date, Date.parse_common_iso),
36
+ (DateDelta, DateDelta.parse_common_iso),
37
+ (DateTimeDelta, DateTimeDelta.parse_common_iso),
38
+ (PlainDateTime, PlainDateTime.parse_common_iso),
39
+ (Time, Time.parse_common_iso),
40
+ (TimeDelta, TimeDelta.parse_common_iso),
41
+ (ZonedDateTime, ZonedDateTime.parse_common_iso),
42
+ ]
43
+ extras = {cls: _make_converter(cls, func) for cls, func in cases}
44
+ self.scalar_converters |= extras
45
+
46
+
47
+ def _make_converter(
48
+ cls: type[_T], parser: Callable[[str], _T], /
49
+ ) -> Callable[[Any, type[Any]], Any]:
50
+ def hook(value: _T | str, _: type[_T] = cls, /) -> Any:
51
+ if not isinstance(value, (cls, str)): # pragma: no cover
52
+ msg = f"Invalid type {type(value).__name__!r}; expected '{cls.__name__}' or 'str'"
53
+ raise TypeError(msg)
54
+ if isinstance(value, str):
55
+ return parser(value)
56
+ return value
57
+
58
+ return hook
59
+
60
+
61
+ __all__ = ["ExtendedTSConverter"]