dycw-utilities 0.131.10__py3-none-any.whl → 0.131.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.131.10
3
+ Version: 0.131.11
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -1,18 +1,18 @@
1
- utilities/__init__.py,sha256=cONWlHZLOohX6BsePAlAXt2CKhMSrwzSki10hHq4rjw,61
1
+ utilities/__init__.py,sha256=wP5J8Mq8g6P6zyTVUVaa3X4tWb427OMAAyoOjt4J0qQ,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
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
8
- utilities/click.py,sha256=CianelgUj_M2SBpuvhmRPKOGxuvLM04FHllUWtL7hok,14647
8
+ utilities/click.py,sha256=8gRYeyu9KQ3uim0UpC8VnFnOOKD3DyGwMJ7k0Qns1lM,14659
9
9
  utilities/concurrent.py,sha256=s2scTEd2AhXVTW4hpASU2qxV_DiVLALfms55cCQzCvM,2886
10
10
  utilities/contextlib.py,sha256=lpaLJBy3X0UGLWjM98jkQZZq8so4fRmoK-Bheq0uOW4,1027
11
11
  utilities/contextvars.py,sha256=RsSGGrbQqqZ67rOydnM7WWIsM2lIE31UHJLejnHJPWY,505
12
12
  utilities/cryptography.py,sha256=_CiK_K6c_-uQuUhsUNjNjTL-nqxAh4_1zTfS11Xe120,972
13
13
  utilities/cvxpy.py,sha256=Rv1-fD-XYerosCavRF8Pohop2DBkU3AlFaGTfD8AEAA,13776
14
14
  utilities/dataclasses.py,sha256=iiC1wpGXWhaocIikzwBt8bbLWyImoUlOlcDZJGejaIg,33011
15
- utilities/datetime.py,sha256=aiPh2OZK2g9gn4yEeSO0lODOmvx8U_rGn6XeSzyk4VY,38738
15
+ utilities/datetime.py,sha256=cDb0-fE7rRt3Gwl5vJ1HW0ZWf-wW7PHsQruD6or9hUo,38770
16
16
  utilities/enum.py,sha256=HoRwVCWzsnH0vpO9ZEcAAIZLMv0Sn2vJxxA4sYMQgDs,5793
17
17
  utilities/errors.py,sha256=nC7ZYtxxDBMfrTHtT_MByBfup_wfGQFRo3eDt-0ZPe8,1045
18
18
  utilities/eventkit.py,sha256=6M5Xu1SzN-juk9PqBHwy5dS-ta7T0qA6SMpDsakOJ0E,13039
@@ -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=-m9YWVgDkKdBfPkZHJ4-vCOGORVHqgtDhbXllfPrr7o,49873
27
+ utilities/hypothesis.py,sha256=spJCB3bKcSMFKuSwi184xfTbom_HEBLB0_-AiPnSR-A,49822
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
@@ -32,9 +32,9 @@ utilities/iterables.py,sha256=mDqw2_0MUVp-P8FklgcaVTi2TXduH0MxbhTDzzhSBho,44915
32
32
  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
- utilities/logging.py,sha256=9XpYHrSVnW2_wYTWQb7LllVxcgq-U2vjUIlM4M60LCE,18407
35
+ utilities/logging.py,sha256=GP2BqpUlb9T7v90sdavsweJnOxfSdK7cJKFdgKURJZo,17892
36
36
  utilities/luigi.py,sha256=fpH9MbxJDuo6-k9iCXRayFRtiVbUtibCJKugf7ygpv0,5988
37
- utilities/math.py,sha256=-mQgbah-dPJwOEWf3SonrFoVZ2AVxMgpeQ3dfVa-oJA,26764
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
40
40
  utilities/more_itertools.py,sha256=tBbjjKx8_Uv_TCjxhPwrGfAx_jRHtvLIZqXVWAsjzqA,8842
@@ -61,7 +61,7 @@ utilities/pytest.py,sha256=zP4CWKXpRVk4aRDRxolUAvqQwX7wgDO8lzmkQfuZaZo,7832
61
61
  utilities/pytest_regressions.py,sha256=YI55B7EtLjhz7zPJZ6NK9bWrxrKCKabWZJe1cwcbA5o,5082
62
62
  utilities/python_dotenv.py,sha256=edXsvHZhZnYeqfMfrsRRpj7_9eJI6uizh3xLx8Q9B3w,3228
63
63
  utilities/random.py,sha256=lYdjgxB7GCfU_fwFVl5U-BIM_HV3q6_urL9byjrwDM8,4157
64
- utilities/re.py,sha256=J7pJOn-Zn5nJ6rvWwMeJWP86EtMk_klhBexnoSaQiZA,4610
64
+ utilities/re.py,sha256=6qxeV0rQZaBDKWcB7apSBmxtg_XzoGY-EdegTkMn-ZY,4578
65
65
  utilities/redis.py,sha256=IceT5EjgrebVkGL8X3M35xlqjI2c7zFbyV1P4dExN4M,36037
66
66
  utilities/reprlib.py,sha256=ssYTcBW-TeRh3fhCJv57sopTZHF5FrPyyUg9yp5XBlo,3953
67
67
  utilities/scipy.py,sha256=wZJM7fEgBAkLSYYvSmsg5ac-QuwAI0BGqHVetw1_Hb0,947
@@ -79,19 +79,19 @@ utilities/tenacity.py,sha256=1PUvODiBVgeqIh7G5TRt5WWMSqjLYkEqP53itT97WQc,4914
79
79
  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
- utilities/traceback.py,sha256=l9onlqDdW5GCSIFbU_-htBE7KlsvsJdNtGrK6-k0RCQ,8759
83
- utilities/types.py,sha256=Ubd3VBiqZ71eS71WSemHEM4oRF-5fnwd3PnMAm8IZaI,18461
82
+ utilities/traceback.py,sha256=U8Du13z0qFo7sntup2x8xLw49KNb-QtEv7-UgziZ_Cw,8769
83
+ utilities/types.py,sha256=CHQke10ETEpypxppYVhWp1G68S6mvifalrRLolYBcCg,19506
84
84
  utilities/typing.py,sha256=kQWywPcRbFBKmvQBELmgbiqSHsnlo_D0ru53vl6KDeY,13846
85
85
  utilities/tzdata.py,sha256=yCf70NICwAeazN3_JcXhWvRqCy06XJNQ42j7r6gw3HY,1217
86
86
  utilities/tzlocal.py,sha256=P5BjqTiYskeCwjE7i9zycCFXO4MWdZgYCh4jut-LpzA,1042
87
87
  utilities/uuid.py,sha256=jJTFxz-CWgltqNuzmythB7iEQ-Q1mCwPevUfKthZT3c,611
88
88
  utilities/version.py,sha256=ufhJMmI6KPs1-3wBI71aj5wCukd3sP_m11usLe88DNA,5117
89
89
  utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
90
- utilities/whenever.py,sha256=jS31ZAY5OMxFxLja_Yo5Fidi87Pd-GoVZ7Vi_teqVDA,16743
91
- utilities/whenever2.py,sha256=JHixGl6KibK8GUF13GeBjvWMYsFHRDSXixSo0xMSJFM,5437
90
+ utilities/whenever.py,sha256=2NQ-0SnLNW2kFpefP9dVE8H0RbaeusXYLPmv282Jpto,16755
91
+ utilities/whenever2.py,sha256=76tFaAVX5CfXOjjMJ1Sg3LW85RoYARNZIALseCLG81k,7047
92
92
  utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
93
93
  utilities/zoneinfo.py,sha256=tvcgu3QzDxe2suTexi2QzRGpin7VK1TjHa0JYYxT69I,1862
94
- dycw_utilities-0.131.10.dist-info/METADATA,sha256=-HOUsSw5_v1-mDFC9l1PXOLDdbSUCRRbZYCgf-nmnbY,1585
95
- dycw_utilities-0.131.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
96
- dycw_utilities-0.131.10.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
97
- dycw_utilities-0.131.10.dist-info/RECORD,,
94
+ dycw_utilities-0.131.11.dist-info/METADATA,sha256=m2QhrLfCIpPGxtKXK8nVbiyX5ldnlLwNHjQnHy9UWkk,1585
95
+ dycw_utilities-0.131.11.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
96
+ dycw_utilities-0.131.11.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
97
+ dycw_utilities-0.131.11.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.131.10"
3
+ __version__ = "0.131.11"
utilities/click.py CHANGED
@@ -23,13 +23,13 @@ from utilities.functions import EnsureStrError, ensure_str, get_class_name
23
23
  from utilities.iterables import is_iterable_not_str
24
24
  from utilities.text import split_str
25
25
  from utilities.types import (
26
- DateLike,
27
26
  DateTimeLike,
28
27
  EnumLike,
29
28
  MaybeStr,
29
+ PyDateLike,
30
+ PyTimeDeltaLike,
31
+ PyTimeLike,
30
32
  TEnum,
31
- TimeDeltaLike,
32
- TimeLike,
33
33
  )
34
34
 
35
35
  if TYPE_CHECKING:
@@ -76,7 +76,7 @@ class Date(ParamType):
76
76
 
77
77
  @override
78
78
  def convert(
79
- self, value: DateLike, param: Parameter | None, ctx: Context | None
79
+ self, value: PyDateLike, param: Parameter | None, ctx: Context | None
80
80
  ) -> dt.date:
81
81
  """Convert a value into the `Date` type."""
82
82
  from utilities.whenever import EnsureDateError, ensure_date
@@ -197,7 +197,7 @@ class Time(ParamType):
197
197
 
198
198
  @override
199
199
  def convert(
200
- self, value: TimeLike, param: Parameter | None, ctx: Context | None
200
+ self, value: PyTimeLike, param: Parameter | None, ctx: Context | None
201
201
  ) -> dt.time:
202
202
  """Convert a value into the `Time` type."""
203
203
  from utilities.whenever import EnsureTimeError, ensure_time
@@ -219,7 +219,7 @@ class Timedelta(ParamType):
219
219
 
220
220
  @override
221
221
  def convert(
222
- self, value: TimeDeltaLike, param: Parameter | None, ctx: Context | None
222
+ self, value: PyTimeDeltaLike, param: Parameter | None, ctx: Context | None
223
223
  ) -> dt.timedelta:
224
224
  """Convert a value into the `Timedelta` type."""
225
225
  from utilities.whenever import EnsureTimedeltaError, ensure_timedelta
utilities/datetime.py CHANGED
@@ -31,9 +31,9 @@ if TYPE_CHECKING:
31
31
  from utilities.types import (
32
32
  DateOrDateTime,
33
33
  Duration,
34
- MaybeCallableDate,
35
- MaybeCallableDateTime,
36
- RoundMode,
34
+ MathRoundMode,
35
+ MaybeCallablePyDate,
36
+ MaybeCallablePyDateTime,
37
37
  TimeZoneLike,
38
38
  )
39
39
 
@@ -476,19 +476,19 @@ def format_datetime_local_and_utc(datetime: dt.datetime, /) -> str:
476
476
 
477
477
 
478
478
  @overload
479
- def get_date(*, date: MaybeCallableDate) -> dt.date: ...
479
+ def get_date(*, date: MaybeCallablePyDate) -> dt.date: ...
480
480
  @overload
481
481
  def get_date(*, date: None) -> None: ...
482
482
  @overload
483
483
  def get_date(*, date: Sentinel) -> Sentinel: ...
484
484
  @overload
485
- def get_date(*, date: MaybeCallableDate | Sentinel) -> dt.date | Sentinel: ...
485
+ def get_date(*, date: MaybeCallablePyDate | Sentinel) -> dt.date | Sentinel: ...
486
486
  @overload
487
487
  def get_date(
488
- *, date: MaybeCallableDate | None | Sentinel = sentinel
488
+ *, date: MaybeCallablePyDate | None | Sentinel = sentinel
489
489
  ) -> dt.date | None | Sentinel: ...
490
490
  def get_date(
491
- *, date: MaybeCallableDate | None | Sentinel = sentinel
491
+ *, date: MaybeCallablePyDate | None | Sentinel = sentinel
492
492
  ) -> dt.date | None | Sentinel:
493
493
  """Get the date."""
494
494
  match date:
@@ -504,13 +504,13 @@ def get_date(
504
504
 
505
505
 
506
506
  @overload
507
- def get_datetime(*, datetime: MaybeCallableDateTime) -> dt.datetime: ...
507
+ def get_datetime(*, datetime: MaybeCallablePyDateTime) -> dt.datetime: ...
508
508
  @overload
509
509
  def get_datetime(*, datetime: None) -> None: ...
510
510
  @overload
511
511
  def get_datetime(*, datetime: Sentinel) -> Sentinel: ...
512
512
  def get_datetime(
513
- *, datetime: MaybeCallableDateTime | None | Sentinel = sentinel
513
+ *, datetime: MaybeCallablePyDateTime | None | Sentinel = sentinel
514
514
  ) -> dt.datetime | None | Sentinel:
515
515
  """Get the datetime."""
516
516
  match datetime:
@@ -755,7 +755,7 @@ def mean_datetime(
755
755
  /,
756
756
  *,
757
757
  weights: Iterable[SupportsFloat] | None = None,
758
- mode: RoundMode = "standard",
758
+ mode: MathRoundMode = "standard",
759
759
  rel_tol: float | None = None,
760
760
  abs_tol: float | None = None,
761
761
  ) -> dt.datetime:
@@ -790,7 +790,7 @@ def mean_timedelta(
790
790
  /,
791
791
  *,
792
792
  weights: Iterable[SupportsFloat] | None = None,
793
- mode: RoundMode = "standard",
793
+ mode: MathRoundMode = "standard",
794
794
  rel_tol: float | None = None,
795
795
  abs_tol: float | None = None,
796
796
  ) -> dt.timedelta:
@@ -1022,7 +1022,7 @@ def round_datetime(
1022
1022
  duration: Duration,
1023
1023
  /,
1024
1024
  *,
1025
- mode: RoundMode = "standard",
1025
+ mode: MathRoundMode = "standard",
1026
1026
  rel_tol: float | None = None,
1027
1027
  abs_tol: float | None = None,
1028
1028
  ) -> dt.datetime:
utilities/hypothesis.py CHANGED
@@ -48,7 +48,7 @@ from hypothesis.strategies import (
48
48
  uuids,
49
49
  )
50
50
  from hypothesis.utils.conventions import not_set
51
- from whenever import Date, DateDelta, PlainDateTime, Time, TimeDelta
51
+ from whenever import Date, DateDelta, PlainDateTime, Time, TimeDelta, ZonedDateTime
52
52
 
53
53
  from utilities.datetime import (
54
54
  DATETIME_MAX_NAIVE,
@@ -112,10 +112,9 @@ if TYPE_CHECKING:
112
112
 
113
113
  from hypothesis.database import ExampleDatabase
114
114
  from numpy.random import RandomState
115
- from whenever import ZonedDateTime
116
115
 
117
116
  from utilities.numpy import NDArrayB, NDArrayF, NDArrayI, NDArrayO
118
- from utilities.types import Duration, Number, RoundMode, TimeZoneLike
117
+ from utilities.types import Duration, MathRoundMode, Number, TimeZoneLike
119
118
 
120
119
 
121
120
  _T = TypeVar("_T")
@@ -725,7 +724,7 @@ def min_and_max_datetimes(
725
724
  min_value: MaybeSearchStrategy[dt.datetime | None] = None,
726
725
  max_value: MaybeSearchStrategy[dt.datetime | None] = None,
727
726
  time_zone: MaybeSearchStrategy[ZoneInfo | timezone] = UTC,
728
- round_: RoundMode | None = None,
727
+ round_: MathRoundMode | None = None,
729
728
  timedelta: dt.timedelta | None = None,
730
729
  rel_tol: float | None = None,
731
730
  abs_tol: float | None = None,
@@ -804,7 +803,7 @@ def min_and_maybe_max_datetimes(
804
803
  min_value: MaybeSearchStrategy[dt.datetime | None] = None,
805
804
  max_value: MaybeSearchStrategy[dt.datetime | None | Sentinel] = sentinel,
806
805
  time_zone: MaybeSearchStrategy[ZoneInfo | timezone] = UTC,
807
- round_: RoundMode | None = None,
806
+ round_: MathRoundMode | None = None,
808
807
  timedelta: dt.timedelta | None = None,
809
808
  rel_tol: float | None = None,
810
809
  abs_tol: float | None = None,
@@ -1036,7 +1035,7 @@ def plain_datetimes(
1036
1035
  *,
1037
1036
  min_value: MaybeSearchStrategy[dt.datetime] = DATETIME_MIN_NAIVE,
1038
1037
  max_value: MaybeSearchStrategy[dt.datetime] = DATETIME_MAX_NAIVE,
1039
- round_: RoundMode | None = None,
1038
+ round_: MathRoundMode | None = None,
1040
1039
  timedelta: dt.timedelta | None = None,
1041
1040
  rel_tol: float | None = None,
1042
1041
  abs_tol: float | None = None,
@@ -1056,7 +1055,7 @@ def plain_datetimes(
1056
1055
 
1057
1056
  @dataclass(kw_only=True, slots=True)
1058
1057
  class PlainDateTimesError(Exception):
1059
- round_: RoundMode
1058
+ round_: MathRoundMode
1060
1059
 
1061
1060
  @override
1062
1061
  def __str__(self) -> str:
@@ -1577,7 +1576,7 @@ def zoned_datetimes(
1577
1576
  min_value: MaybeSearchStrategy[dt.datetime] = DATETIME_MIN_UTC + DAY,
1578
1577
  max_value: MaybeSearchStrategy[dt.datetime] = DATETIME_MAX_UTC - DAY,
1579
1578
  time_zone: MaybeSearchStrategy[ZoneInfo | timezone] = UTC,
1580
- round_: RoundMode | None = None,
1579
+ round_: MathRoundMode | None = None,
1581
1580
  timedelta: dt.timedelta | None = None,
1582
1581
  rel_tol: float | None = None,
1583
1582
  abs_tol: float | None = None,
@@ -1626,7 +1625,7 @@ def zoned_datetimes(
1626
1625
 
1627
1626
  @dataclass(kw_only=True, slots=True)
1628
1627
  class ZonedDateTimesError(Exception):
1629
- round_: RoundMode
1628
+ round_: MathRoundMode
1630
1629
 
1631
1630
  @override
1632
1631
  def __str__(self) -> str:
@@ -1646,8 +1645,6 @@ def zoned_datetimes_whenever(
1646
1645
  time_zone: MaybeSearchStrategy[TimeZoneLike] = UTC,
1647
1646
  ) -> ZonedDateTime:
1648
1647
  """Strategy for generating zoned datetimes."""
1649
- from whenever import PlainDateTime, ZonedDateTime
1650
-
1651
1648
  min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
1652
1649
  time_zone_ = ensure_time_zone(draw2(draw, time_zone))
1653
1650
  match min_value_:
utilities/logging.py CHANGED
@@ -34,12 +34,7 @@ from typing import (
34
34
 
35
35
  from utilities.atomicwrites import move_many
36
36
  from utilities.dataclasses import replace_non_sentinel
37
- from utilities.datetime import (
38
- SECOND,
39
- parse_datetime_compact,
40
- round_datetime,
41
- serialize_compact,
42
- )
37
+ from utilities.datetime import parse_datetime_compact, serialize_compact
43
38
  from utilities.errors import ImpossibleCaseError
44
39
  from utilities.iterables import OneEmptyError, always_iterable, one
45
40
  from utilities.pathlib import ensure_suffix, get_path
@@ -484,12 +479,6 @@ class _RotatingLogFile:
484
479
  start: dt.datetime | None = None
485
480
  end: dt.datetime | None = None
486
481
 
487
- def __post_init__(self) -> None:
488
- if self.start is not None:
489
- self.start = round_datetime(self.start, SECOND)
490
- if self.end is not None:
491
- self.end = round_datetime(self.end, SECOND)
492
-
493
482
  @classmethod
494
483
  def from_path(
495
484
  cls,
@@ -582,12 +571,6 @@ class _Rotation:
582
571
  start: dt.datetime | None | Sentinel = sentinel
583
572
  end: dt.datetime | Sentinel = sentinel
584
573
 
585
- def __post_init__(self) -> None:
586
- if isinstance(self.start, dt.datetime):
587
- self.start = round_datetime(self.start, SECOND)
588
- if isinstance(self.end, dt.datetime):
589
- self.end = round_datetime(self.end, SECOND)
590
-
591
574
  @cached_property
592
575
  def destination(self) -> Path:
593
576
  return self.file.replace(index=self.index, start=self.start, end=self.end).path
utilities/math.py CHANGED
@@ -8,9 +8,10 @@ from re import Match, search
8
8
  from typing import TYPE_CHECKING, Literal, assert_never, overload, override
9
9
 
10
10
  from utilities.errors import ImpossibleCaseError
11
+ from utilities.re import ExtractGroupsError, extract_groups
11
12
 
12
13
  if TYPE_CHECKING:
13
- from utilities.types import Number, RoundMode, Sign
14
+ from utilities.types import MathRoundMode, Number, Sign
14
15
 
15
16
 
16
17
  MIN_FLOAT32, MAX_FLOAT32 = -3.4028234663852886e38, 3.4028234663852886e38
@@ -708,7 +709,7 @@ def round_(
708
709
  x: float,
709
710
  /,
710
711
  *,
711
- mode: RoundMode = "standard",
712
+ mode: MathRoundMode = "standard",
712
713
  rel_tol: float | None = None,
713
714
  abs_tol: float | None = None,
714
715
  ) -> int:
@@ -748,7 +749,7 @@ def round_(
748
749
 
749
750
  def _round_tie_standard(
750
751
  x: float,
751
- mode: RoundMode,
752
+ mode: MathRoundMode,
752
753
  /,
753
754
  *,
754
755
  rel_tol: float | None = None,
@@ -757,9 +758,9 @@ def _round_tie_standard(
757
758
  """Round a float to an integer using the standard method."""
758
759
  frac, _ = modf(x)
759
760
  if _is_close(abs(frac), 0.5, rel_tol=rel_tol, abs_tol=abs_tol):
760
- mode_use: RoundMode = mode
761
+ mode_use: MathRoundMode = mode
761
762
  else:
762
- mode_use: RoundMode = "standard"
763
+ mode_use: MathRoundMode = "standard"
763
764
  return round_(x, mode=mode_use)
764
765
 
765
766
 
@@ -775,9 +776,9 @@ def round_float_imprecisions(
775
776
  ) -> float:
776
777
  """Round a float, removing binary representation imprecisions."""
777
778
  try:
778
- ((head, tail),) = _ROUND_FLOAT_IMPRECISIONS_PATTERN.findall(str(x))
779
- except ValueError:
780
- ((head, tail),) = _ROUND_FLOAT_IMPRECISIONS_PATTERN.findall(f"{x:.20f}")
779
+ head, tail = extract_groups(_ROUND_FLOAT_IMPRECISIONS_PATTERN, str(x))
780
+ except ExtractGroupsError:
781
+ head, tail = extract_groups(_ROUND_FLOAT_IMPRECISIONS_PATTERN, f"{x:.20f}")
781
782
  half = ceil(decimals / 2)
782
783
  pattern0 = search(rf"^([0-9]+?)(0{{{half},}})([0-9]+?)$", tail)
783
784
  pattern9 = search(rf"^(0*)([0-9]+?)(9{{{half},}})([0-9]+?)$", tail)
@@ -823,7 +824,7 @@ def round_to_float(
823
824
  y: float,
824
825
  /,
825
826
  *,
826
- mode: RoundMode = "standard",
827
+ mode: MathRoundMode = "standard",
827
828
  rel_tol: float | None = None,
828
829
  abs_tol: float | None = None,
829
830
  ) -> float:
utilities/re.py CHANGED
@@ -5,8 +5,6 @@ from dataclasses import dataclass
5
5
  from re import Pattern
6
6
  from typing import TYPE_CHECKING, assert_never, override
7
7
 
8
- from utilities.iterables import OneEmptyError, OneNonUniqueError, one
9
-
10
8
  if TYPE_CHECKING:
11
9
  from utilities.types import PatternLike
12
10
 
@@ -36,16 +34,17 @@ def extract_group(pattern: PatternLike, text: str, /, *, flags: int = 0) -> str:
36
34
  raise _ExtractGroupNoCaptureGroupsError(pattern=pattern_use, text=text)
37
35
  case 1:
38
36
  matches: list[str] = pattern_use.findall(text)
39
- try:
40
- return one(matches)
41
- except OneEmptyError:
42
- raise _ExtractGroupNoMatchesError(
43
- pattern=pattern_use, text=text
44
- ) from None
45
- except OneNonUniqueError:
46
- raise _ExtractGroupMultipleMatchesError(
47
- pattern=pattern_use, text=text, matches=matches
48
- ) from None
37
+ match len(matches):
38
+ case 0:
39
+ raise _ExtractGroupNoMatchesError(
40
+ pattern=pattern_use, text=text
41
+ ) from None
42
+ case 1:
43
+ return matches[0]
44
+ case _:
45
+ raise _ExtractGroupMultipleMatchesError(
46
+ pattern=pattern_use, text=text, matches=matches
47
+ ) from None
49
48
  case _:
50
49
  raise _ExtractGroupMultipleCaptureGroupsError(
51
50
  pattern=pattern_use, text=text
@@ -109,7 +108,7 @@ def extract_groups(pattern: PatternLike, text: str, /, *, flags: int = 0) -> lis
109
108
  case 1, 1:
110
109
  return matches
111
110
  case 1, _:
112
- return list(one(matches))
111
+ return list(matches[0])
113
112
  case _:
114
113
  raise _ExtractGroupsMultipleMatchesError(
115
114
  pattern=pattern_use, text=text, matches=matches
utilities/traceback.py CHANGED
@@ -36,7 +36,7 @@ if TYPE_CHECKING:
36
36
  from traceback import FrameSummary
37
37
  from types import TracebackType
38
38
 
39
- from utilities.types import MaybeCallableDateTime, MaybeCallablePathLike, PathLike
39
+ from utilities.types import MaybeCallablePathLike, MaybeCallablePyDateTime, PathLike
40
40
  from utilities.version import MaybeCallableVersionLike
41
41
 
42
42
 
@@ -51,7 +51,7 @@ def format_exception_stack(
51
51
  /,
52
52
  *,
53
53
  header: bool = False,
54
- start: MaybeCallableDateTime | None = _START,
54
+ start: MaybeCallablePyDateTime | None = _START,
55
55
  version: MaybeCallableVersionLike | None = None,
56
56
  capture_locals: bool = False,
57
57
  max_width: int = RICH_MAX_WIDTH,
@@ -82,7 +82,7 @@ def format_exception_stack(
82
82
 
83
83
  def _yield_header_lines(
84
84
  *,
85
- start: MaybeCallableDateTime | None = _START,
85
+ start: MaybeCallablePyDateTime | None = _START,
86
86
  version: MaybeCallableVersionLike | None = None,
87
87
  ) -> Iterator[str]:
88
88
  """Yield the header lines."""
@@ -193,7 +193,7 @@ def _trim_path(path: PathLike, pattern: str, /) -> Path | None:
193
193
 
194
194
  def make_except_hook(
195
195
  *,
196
- start: MaybeCallableDateTime | None = _START,
196
+ start: MaybeCallablePyDateTime | None = _START,
197
197
  version: MaybeCallableVersionLike | None = None,
198
198
  path: MaybeCallablePathLike | None = None,
199
199
  max_width: int = RICH_MAX_WIDTH,
@@ -228,7 +228,7 @@ def _make_except_hook_inner(
228
228
  traceback: TracebackType | None,
229
229
  /,
230
230
  *,
231
- start: MaybeCallableDateTime | None = _START,
231
+ start: MaybeCallablePyDateTime | None = _START,
232
232
  version: MaybeCallableVersionLike | None = None,
233
233
  path: MaybeCallablePathLike | None = None,
234
234
  max_width: int = RICH_MAX_WIDTH,
utilities/types.py CHANGED
@@ -20,6 +20,16 @@ from typing import (
20
20
  )
21
21
  from zoneinfo import ZoneInfo
22
22
 
23
+ from whenever import (
24
+ Date,
25
+ DateDelta,
26
+ DateTimeDelta,
27
+ PlainDateTime,
28
+ Time,
29
+ TimeDelta,
30
+ ZonedDateTime,
31
+ )
32
+
23
33
  _T_co = TypeVar("_T_co", covariant=True)
24
34
  _T_contra = TypeVar("_T_contra", contravariant=True)
25
35
 
@@ -88,17 +98,16 @@ TDataclass = TypeVar("TDataclass", bound=Dataclass)
88
98
 
89
99
 
90
100
  # datetime
91
- type DateLike = MaybeStr[dt.date]
92
- type DateTimeLike = MaybeStr[dt.datetime]
93
101
  type DateOrDateTime = dt.date | dt.datetime
102
+ type DateTimeLike = MaybeStr[dt.datetime]
94
103
  type Duration = Number | dt.timedelta
95
104
  type DurationLike = MaybeStr[Duration]
96
105
  type DurationOrEveryDuration = Duration | tuple[Literal["every"], Duration]
97
- type MaybeCallableDate = MaybeCallable[dt.date]
98
- type MaybeCallableDateTime = MaybeCallable[dt.datetime]
99
- type TimeLike = MaybeStr[dt.time]
100
- type TimeDeltaLike = MaybeStr[dt.timedelta]
101
- type WeekDay = Literal["mon", "tue", "wed", "thu", "fri", "sat", "sun"]
106
+ type MaybeCallablePyDate = MaybeCallable[dt.date]
107
+ type MaybeCallablePyDateTime = MaybeCallable[dt.datetime]
108
+ type PyDateLike = MaybeStr[dt.date]
109
+ type PyTimeDeltaLike = MaybeStr[dt.timedelta]
110
+ type PyTimeLike = MaybeStr[dt.time]
102
111
 
103
112
 
104
113
  # enum
@@ -133,7 +142,7 @@ type LoggerOrName = MaybeStr[Logger]
133
142
 
134
143
  # math
135
144
  type Number = int | float
136
- type RoundMode = Literal[
145
+ type MathRoundMode = Literal[
137
146
  "standard",
138
147
  "floor",
139
148
  "ceil",
@@ -259,6 +268,25 @@ type ExcInfo = tuple[type[BaseException], BaseException, TracebackType]
259
268
  type OptExcInfo = ExcInfo | tuple[None, None, None]
260
269
 
261
270
 
271
+ # whenever
272
+ type DateDeltaLike = MaybeStr[DateDelta]
273
+ type DateLike = MaybeStr[Date]
274
+ type DateTimeDeltaLike = MaybeStr[DateTimeDelta]
275
+ type MaybeCallableDate = MaybeCallable[Date]
276
+ type MaybeCallableZonedDateTime = MaybeCallable[ZonedDateTime]
277
+ type PlainDateTimeLike = MaybeStr[PlainDateTime]
278
+ type TimeDeltaLike = MaybeStr[TimeDelta]
279
+ type TimeLike = MaybeStr[Time]
280
+ type ZonedDateTimeLike = MaybeStr[ZonedDateTime]
281
+ type DateTimeRoundUnit = Literal[
282
+ "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"
283
+ ]
284
+ type DateTimeRoundMode = Literal[
285
+ "ceil", "floor", "half_ceil", "half_floor", "half_even"
286
+ ]
287
+ type WeekDay = Literal["mon", "tue", "wed", "thu", "fri", "sat", "sun"]
288
+
289
+
262
290
  # zoneinfo
263
291
  # fmt: off
264
292
  type TimeZone = Literal[
@@ -271,9 +299,13 @@ type TimeZoneLike = ZoneInfo | Literal["local"] | TimeZone | dt.tzinfo | dt.date
271
299
  __all__ = [
272
300
  "Coroutine1",
273
301
  "Dataclass",
302
+ "DateDeltaLike",
274
303
  "DateLike",
275
304
  "DateOrDateTime",
305
+ "DateTimeDeltaLike",
276
306
  "DateTimeLike",
307
+ "DateTimeRoundMode",
308
+ "DateTimeRoundUnit",
277
309
  "Duration",
278
310
  "DurationLike",
279
311
  "DurationOrEveryDuration",
@@ -282,12 +314,15 @@ __all__ = [
282
314
  "IterableHashable",
283
315
  "LogLevel",
284
316
  "LoggerOrName",
317
+ "MathRoundMode",
285
318
  "MaybeAwaitable",
286
319
  "MaybeCallable",
287
320
  "MaybeCallableDate",
288
- "MaybeCallableDateTime",
289
321
  "MaybeCallableEvent",
290
322
  "MaybeCallablePathLike",
323
+ "MaybeCallablePyDate",
324
+ "MaybeCallablePyDateTime",
325
+ "MaybeCallableZonedDateTime",
291
326
  "MaybeCoroutine1",
292
327
  "MaybeIterable",
293
328
  "MaybeIterableHashable",
@@ -300,7 +335,10 @@ __all__ = [
300
335
  "ParseObjectExtra",
301
336
  "PathLike",
302
337
  "PatternLike",
303
- "RoundMode",
338
+ "PlainDateTimeLike",
339
+ "PyDateLike",
340
+ "PyTimeDeltaLike",
341
+ "PyTimeLike",
304
342
  "Seed",
305
343
  "SerializeObjectExtra",
306
344
  "Sign",
@@ -343,4 +381,5 @@ __all__ = [
343
381
  "TupleOrStrMapping",
344
382
  "TypeLike",
345
383
  "WeekDay",
384
+ "ZonedDateTimeLike",
346
385
  ]
utilities/whenever.py CHANGED
@@ -34,12 +34,12 @@ from utilities.zoneinfo import UTC, ensure_time_zone, get_time_zone_name
34
34
 
35
35
  if TYPE_CHECKING:
36
36
  from utilities.types import (
37
- DateLike,
38
37
  DateTimeLike,
39
38
  Duration,
40
39
  DurationLike,
41
- TimeDeltaLike,
42
- TimeLike,
40
+ PyDateLike,
41
+ PyTimeDeltaLike,
42
+ PyTimeLike,
43
43
  )
44
44
 
45
45
 
@@ -91,7 +91,7 @@ class _CheckValidZonedDateTimeUnequalError(CheckValidZonedDateTimeError):
91
91
  ##
92
92
 
93
93
 
94
- def ensure_date(date: DateLike, /) -> dt.date:
94
+ def ensure_date(date: PyDateLike, /) -> dt.date:
95
95
  """Ensure the object is a date."""
96
96
  if isinstance(date, dt.date):
97
97
  check_date_not_datetime(date)
@@ -180,7 +180,7 @@ class EnsurePlainDateTimeError(Exception):
180
180
  ##
181
181
 
182
182
 
183
- def ensure_time(time: TimeLike, /) -> dt.time:
183
+ def ensure_time(time: PyTimeLike, /) -> dt.time:
184
184
  """Ensure the object is a time."""
185
185
  if isinstance(time, dt.time):
186
186
  return time
@@ -202,7 +202,7 @@ class EnsureTimeError(Exception):
202
202
  ##
203
203
 
204
204
 
205
- def ensure_timedelta(timedelta: TimeDeltaLike, /) -> dt.timedelta:
205
+ def ensure_timedelta(timedelta: PyTimeDeltaLike, /) -> dt.timedelta:
206
206
  """Ensure the object is a timedelta."""
207
207
  if isinstance(timedelta, dt.timedelta):
208
208
  return timedelta
utilities/whenever2.py CHANGED
@@ -1,9 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import datetime as dt
4
+ from collections.abc import Callable
4
5
  from functools import cache
5
6
  from logging import LogRecord
6
- from typing import TYPE_CHECKING, Any, override
7
+ from typing import TYPE_CHECKING, Any, assert_never, overload, override
7
8
 
8
9
  from whenever import (
9
10
  Date,
@@ -15,12 +16,17 @@ from whenever import (
15
16
  ZonedDateTime,
16
17
  )
17
18
 
19
+ from utilities.sentinel import Sentinel, sentinel
18
20
  from utilities.zoneinfo import UTC, get_time_zone_name
19
21
 
20
22
  if TYPE_CHECKING:
21
23
  from zoneinfo import ZoneInfo
22
24
 
23
- from utilities.types import TimeZoneLike
25
+ from utilities.types import (
26
+ MaybeCallableDate,
27
+ MaybeCallableZonedDateTime,
28
+ TimeZoneLike,
29
+ )
24
30
 
25
31
 
26
32
  ## bounds
@@ -118,6 +124,52 @@ def get_today_local() -> Date:
118
124
 
119
125
  TODAY_LOCAL = get_today_local()
120
126
 
127
+ ##
128
+
129
+
130
+ @overload
131
+ def to_date(*, date: MaybeCallableDate) -> Date: ...
132
+ @overload
133
+ def to_date(*, date: None) -> None: ...
134
+ @overload
135
+ def to_date(*, date: Sentinel) -> Sentinel: ...
136
+ @overload
137
+ def to_date(*, date: MaybeCallableDate | Sentinel) -> Date | Sentinel: ...
138
+ @overload
139
+ def to_date(
140
+ *, date: MaybeCallableDate | None | Sentinel = sentinel
141
+ ) -> Date | None | Sentinel: ...
142
+ def to_date(
143
+ *, date: MaybeCallableDate | None | Sentinel = sentinel
144
+ ) -> Date | None | Sentinel:
145
+ """Get the date."""
146
+ match date:
147
+ case Date() | None | Sentinel():
148
+ return date
149
+ case Callable() as func:
150
+ return to_date(date=func())
151
+ case _ as never:
152
+ assert_never(never)
153
+
154
+
155
+ @overload
156
+ def to_zoned_date_time(*, date_time: MaybeCallableZonedDateTime) -> ZonedDateTime: ...
157
+ @overload
158
+ def to_zoned_date_time(*, date_time: None) -> None: ...
159
+ @overload
160
+ def to_zoned_date_time(*, date_time: Sentinel) -> Sentinel: ...
161
+ def to_zoned_date_time(
162
+ *, date_time: MaybeCallableZonedDateTime | None | Sentinel = sentinel
163
+ ) -> ZonedDateTime | None | Sentinel:
164
+ """Resolve into a zoned date_time."""
165
+ match date_time:
166
+ case ZonedDateTime() | None | Sentinel():
167
+ return date_time
168
+ case Callable() as func:
169
+ return to_zoned_date_time(date_time=func())
170
+ case _ as never:
171
+ assert_never(never)
172
+
121
173
 
122
174
  ##
123
175
 
@@ -210,4 +262,6 @@ __all__ = [
210
262
  "get_now_local",
211
263
  "get_today",
212
264
  "get_today_local",
265
+ "to_date",
266
+ "to_zoned_date_time",
213
267
  ]