dycw-utilities 0.131.10__py3-none-any.whl → 0.131.12__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.12
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -1,30 +1,30 @@
1
- utilities/__init__.py,sha256=cONWlHZLOohX6BsePAlAXt2CKhMSrwzSki10hHq4rjw,61
1
+ utilities/__init__.py,sha256=Mewpkpu3DcrrhZzBvzBQpI4GkBsPPKRq0bdDX_sR7t4,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=uPQdUgJJ9KuF-pogjYRbI9lOK-i5UBzPHexWe4pOVEo,38713
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
19
- utilities/fastapi.py,sha256=8ABDSOH99j7H8cpKLZhUT2JzU18wiBqcTQuBUlTe-QE,2937
20
- utilities/fpdf2.py,sha256=lqizPXpzdwfJk3ChcbbWVa7WNXO2uvy7KDzWQtVTnXA,1831
19
+ utilities/fastapi.py,sha256=zDNPgfYNTZWQfS87y8ekgoJvTvmq07gv5m5kOyMYoX8,2796
20
+ utilities/fpdf2.py,sha256=PQysO4BcSMRpg-h-t2Pm7yatyk2yFl1inkvtKs1NG4M,1853
21
21
  utilities/functions.py,sha256=jgt592voaHNtX56qX0SRvFveVCRmSIxCZmqvpLZCnY8,27305
22
22
  utilities/functools.py,sha256=WrpHt7NLNWSUn9A1Q_ZIWlNaYZOEI4IFKyBG9HO3BC4,1643
23
23
  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,16 +32,16 @@ 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=zm5k0Cduxtx2H2o7odxUTJtPNkJS85mqHYN1cS5Kc1w,17863
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
41
41
  utilities/numpy.py,sha256=Xn23sA2ZbVNqwUYEgNJD3XBYH6IbCri_WkHSNhg3NkY,26122
42
42
  utilities/operator.py,sha256=0M2yZJ0PODH47ogFEnkGMBe_cfxwZR02T_92LZVZvHo,3715
43
43
  utilities/optuna.py,sha256=loyJGWTzljgdJaoLhP09PT8Jz6o_pwBOwehY33lHkhw,1923
44
- utilities/orjson.py,sha256=vmPsuOOxrQBAg6aEVVKHfOX9A04QlSa162El5HrIT9E,36889
44
+ utilities/orjson.py,sha256=oD9sQYd3bQKZi2dwpQsaRVwCSlSQ2sREl83UZP4LD7w,36962
45
45
  utilities/os.py,sha256=D_FyyT-6TtqiN9KSS7c9g1fnUtgxmyMtzAjmYLkk46A,3587
46
46
  utilities/parse.py,sha256=vsZ2jf_ceSI_Kta9titixufysJaVXh0Whjz1T4awJZw,18938
47
47
  utilities/pathlib.py,sha256=PK41rf1c9Wqv7h8f5R7H3_Lhq_gQZTUJD5tu3gMHVaU,3247
@@ -55,13 +55,13 @@ utilities/pqdm.py,sha256=foRytQybmOQ05pjt5LF7ANyzrIa--4ScDE3T2wd31a4,3118
55
55
  utilities/psutil.py,sha256=RtbLKOoIJhqrJmEoHDBVeSD-KPzshtS0FtRXBP9_w2s,3751
56
56
  utilities/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
57
  utilities/pydantic.py,sha256=aP6OKowg2Md4rgQuQ5qTSF4bTbBuq7WtRb7zS3JSRGY,1841
58
- utilities/pyinstrument.py,sha256=KU7_wPX63TY_8kSps0WZ0ijpN7vXQUkp175vdg-CIdE,899
58
+ utilities/pyinstrument.py,sha256=3MPNDbZW_1Aj1aA1_f-yqPStgmjIFxPwIafYq2dsaTs,853
59
59
  utilities/pyrsistent.py,sha256=wVOVIe_68AAaa-lUE9y-TEzDawVp1uEIc_zfoDgr5ww,2287
60
60
  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
84
- utilities/typing.py,sha256=kQWywPcRbFBKmvQBELmgbiqSHsnlo_D0ru53vl6KDeY,13846
82
+ utilities/traceback.py,sha256=cMXrCD59CROnezAU8VW67CxZ8Igc5QmaxlV8qrBvNMs,8504
83
+ utilities/types.py,sha256=CHQke10ETEpypxppYVhWp1G68S6mvifalrRLolYBcCg,19506
84
+ utilities/typing.py,sha256=VuGuztLSkTicxgVwI5wrVOTcY70OlzwsTU7LcFVjGlY,14169
85
85
  utilities/tzdata.py,sha256=yCf70NICwAeazN3_JcXhWvRqCy06XJNQ42j7r6gw3HY,1217
86
- utilities/tzlocal.py,sha256=P5BjqTiYskeCwjE7i9zycCFXO4MWdZgYCh4jut-LpzA,1042
86
+ utilities/tzlocal.py,sha256=xbBBzVIUKMk8AkhuIp1qxGRNBioIa5I09dpeoBnIOOU,662
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=WiDVsgHA-4E-KiIJ25-R4qAWvtyBQk1EkhjMszFzQMM,7455
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.12.dist-info/METADATA,sha256=8G8r4jcsCl-iXqwXZ2vq8Kx7M4S2A6xCNxQA3Blrv64,1585
95
+ dycw_utilities-0.131.12.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
96
+ dycw_utilities-0.131.12.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
97
+ dycw_utilities-0.131.12.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.12"
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
@@ -21,21 +21,14 @@ from utilities.iterables import OneEmptyError, one
21
21
  from utilities.math import SafeRoundError, round_, safe_round
22
22
  from utilities.platform import SYSTEM
23
23
  from utilities.sentinel import Sentinel, sentinel
24
- from utilities.types import MaybeStr
24
+ from utilities.types import MaybeCallablePyDate, MaybeCallablePyDateTime, MaybeStr
25
25
  from utilities.typing import is_instance_gen
26
26
  from utilities.zoneinfo import UTC, ensure_time_zone, get_time_zone_name
27
27
 
28
28
  if TYPE_CHECKING:
29
29
  from collections.abc import Iterator
30
30
 
31
- from utilities.types import (
32
- DateOrDateTime,
33
- Duration,
34
- MaybeCallableDate,
35
- MaybeCallableDateTime,
36
- RoundMode,
37
- TimeZoneLike,
38
- )
31
+ from utilities.types import DateOrDateTime, Duration, MathRoundMode, TimeZoneLike
39
32
 
40
33
 
41
34
  _DAYS_PER_YEAR = 365.25
@@ -476,19 +469,19 @@ def format_datetime_local_and_utc(datetime: dt.datetime, /) -> str:
476
469
 
477
470
 
478
471
  @overload
479
- def get_date(*, date: MaybeCallableDate) -> dt.date: ...
472
+ def get_date(*, date: MaybeCallablePyDate) -> dt.date: ...
480
473
  @overload
481
474
  def get_date(*, date: None) -> None: ...
482
475
  @overload
483
476
  def get_date(*, date: Sentinel) -> Sentinel: ...
484
477
  @overload
485
- def get_date(*, date: MaybeCallableDate | Sentinel) -> dt.date | Sentinel: ...
478
+ def get_date(*, date: MaybeCallablePyDate | Sentinel) -> dt.date | Sentinel: ...
486
479
  @overload
487
480
  def get_date(
488
- *, date: MaybeCallableDate | None | Sentinel = sentinel
481
+ *, date: MaybeCallablePyDate | None | Sentinel = sentinel
489
482
  ) -> dt.date | None | Sentinel: ...
490
483
  def get_date(
491
- *, date: MaybeCallableDate | None | Sentinel = sentinel
484
+ *, date: MaybeCallablePyDate | None | Sentinel = sentinel
492
485
  ) -> dt.date | None | Sentinel:
493
486
  """Get the date."""
494
487
  match date:
@@ -504,13 +497,13 @@ def get_date(
504
497
 
505
498
 
506
499
  @overload
507
- def get_datetime(*, datetime: MaybeCallableDateTime) -> dt.datetime: ...
500
+ def get_datetime(*, datetime: MaybeCallablePyDateTime) -> dt.datetime: ...
508
501
  @overload
509
502
  def get_datetime(*, datetime: None) -> None: ...
510
503
  @overload
511
504
  def get_datetime(*, datetime: Sentinel) -> Sentinel: ...
512
505
  def get_datetime(
513
- *, datetime: MaybeCallableDateTime | None | Sentinel = sentinel
506
+ *, datetime: MaybeCallablePyDateTime | None | Sentinel = sentinel
514
507
  ) -> dt.datetime | None | Sentinel:
515
508
  """Get the datetime."""
516
509
  match datetime:
@@ -755,7 +748,7 @@ def mean_datetime(
755
748
  /,
756
749
  *,
757
750
  weights: Iterable[SupportsFloat] | None = None,
758
- mode: RoundMode = "standard",
751
+ mode: MathRoundMode = "standard",
759
752
  rel_tol: float | None = None,
760
753
  abs_tol: float | None = None,
761
754
  ) -> dt.datetime:
@@ -790,7 +783,7 @@ def mean_timedelta(
790
783
  /,
791
784
  *,
792
785
  weights: Iterable[SupportsFloat] | None = None,
793
- mode: RoundMode = "standard",
786
+ mode: MathRoundMode = "standard",
794
787
  rel_tol: float | None = None,
795
788
  abs_tol: float | None = None,
796
789
  ) -> dt.timedelta:
@@ -1022,7 +1015,7 @@ def round_datetime(
1022
1015
  duration: Duration,
1023
1016
  /,
1024
1017
  *,
1025
- mode: RoundMode = "standard",
1018
+ mode: MathRoundMode = "standard",
1026
1019
  rel_tol: float | None = None,
1027
1020
  abs_tol: float | None = None,
1028
1021
  ) -> dt.datetime:
utilities/fastapi.py CHANGED
@@ -9,8 +9,7 @@ from uvicorn import Config, Server
9
9
 
10
10
  from utilities.asyncio import Looper
11
11
  from utilities.datetime import SECOND, datetime_duration_to_float
12
- from utilities.tzlocal import get_now_local # skipif-ci
13
- from utilities.whenever import serialize_zoned_datetime # skipif-ci
12
+ from utilities.whenever2 import get_now_local
14
13
 
15
14
  if TYPE_CHECKING:
16
15
  from types import TracebackType
@@ -31,8 +30,7 @@ class _PingerReceiverApp(FastAPI):
31
30
 
32
31
  @self.get("/ping") # skipif-ci
33
32
  def ping() -> str:
34
- now = serialize_zoned_datetime(get_now_local()) # skipif-ci
35
- return f"pong @ {now}" # skipif-ci
33
+ return f"pong @ {get_now_local()}" # skipif-ci
36
34
 
37
35
  _ = ping # skipif-ci
38
36
 
utilities/fpdf2.py CHANGED
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, override
6
6
  from fpdf import FPDF
7
7
  from fpdf.enums import XPos, YPos
8
8
 
9
- from utilities.tzlocal import get_now_local
9
+ from utilities.whenever2 import format_compact, get_now
10
10
 
11
11
  if TYPE_CHECKING:
12
12
  from collections.abc import Iterator
@@ -47,7 +47,7 @@ def yield_pdf(*, header: str | None = None) -> Iterator[_BasePDF]:
47
47
  def footer(self) -> None:
48
48
  self.set_y(-15)
49
49
  self.set_font(family="Helvetica", style="I", size=8)
50
- page_no, now = self.page_no(), get_now_local()
50
+ page_no, now = self.page_no(), format_compact(get_now())
51
51
  text = f"page {page_no}/{{}}; {now}"
52
52
  _ = self.cell(
53
53
  w=0,
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
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import datetime as dt
4
3
  import re
5
4
  from dataclasses import dataclass, field
6
5
  from functools import cached_property
@@ -19,7 +18,6 @@ from logging import (
19
18
  from logging.handlers import BaseRotatingHandler, TimedRotatingFileHandler
20
19
  from pathlib import Path
21
20
  from re import Pattern
22
- from time import time
23
21
  from typing import (
24
22
  TYPE_CHECKING,
25
23
  Any,
@@ -32,22 +30,31 @@ from typing import (
32
30
  override,
33
31
  )
34
32
 
33
+ from whenever import PlainDateTime, ZonedDateTime
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
- )
43
37
  from utilities.errors import ImpossibleCaseError
44
38
  from utilities.iterables import OneEmptyError, always_iterable, one
45
39
  from utilities.pathlib import ensure_suffix, get_path
40
+ from utilities.re import (
41
+ ExtractGroupError,
42
+ ExtractGroupsError,
43
+ extract_group,
44
+ extract_groups,
45
+ )
46
46
  from utilities.sentinel import Sentinel, sentinel
47
- from utilities.tzlocal import get_now_local
47
+ from utilities.tzlocal import LOCAL_TIME_ZONE_NAME
48
+ from utilities.whenever2 import (
49
+ WheneverLogRecord,
50
+ format_compact,
51
+ get_now,
52
+ get_now_local,
53
+ )
48
54
 
49
55
  if TYPE_CHECKING:
50
56
  from collections.abc import Callable, Iterable, Mapping
57
+ from datetime import time
51
58
  from logging import _FilterType
52
59
 
53
60
  from utilities.types import (
@@ -59,7 +66,9 @@ if TYPE_CHECKING:
59
66
  )
60
67
 
61
68
 
62
- _DEFAULT_FORMAT = "{asctime} | {name}:{funcName}:{lineno} | {levelname:8} | {message}"
69
+ _DEFAULT_FORMAT = (
70
+ "{zoned_datetime} | {name}:{funcName}:{lineno} | {levelname:8} | {message}"
71
+ )
63
72
  _DEFAULT_DATEFMT = "%Y-%m-%d %H:%M:%S"
64
73
  _DEFAULT_BACKUP_COUNT: int = 100
65
74
  _DEFAULT_MAX_BYTES: int = 10 * 1024**2
@@ -81,7 +90,6 @@ def add_filters(handler: Handler, /, *filters: _FilterType) -> None:
81
90
  def basic_config(
82
91
  *,
83
92
  obj: LoggerOrName | Handler | None = None,
84
- whenever: bool = False,
85
93
  format_: str = _DEFAULT_FORMAT,
86
94
  datefmt: str = _DEFAULT_DATEFMT,
87
95
  level: LogLevel = "INFO",
@@ -98,7 +106,6 @@ def basic_config(
98
106
  logger.addHandler(handler := StreamHandler())
99
107
  basic_config(
100
108
  obj=handler,
101
- whenever=whenever,
102
109
  format_=format_,
103
110
  datefmt=datefmt,
104
111
  level=level,
@@ -109,7 +116,6 @@ def basic_config(
109
116
  case str() as name:
110
117
  basic_config(
111
118
  obj=get_logger(logger=name),
112
- whenever=whenever,
113
119
  format_=format_,
114
120
  datefmt=datefmt,
115
121
  level=level,
@@ -122,7 +128,6 @@ def basic_config(
122
128
  if filters is not None:
123
129
  add_filters(handler, *always_iterable(filters))
124
130
  formatter = get_formatter(
125
- whenever=whenever,
126
131
  format_=format_,
127
132
  datefmt=datefmt,
128
133
  plain=plain,
@@ -184,18 +189,13 @@ class _FieldStyleDict(TypedDict):
184
189
 
185
190
  def get_formatter(
186
191
  *,
187
- whenever: bool = False,
188
192
  format_: str = _DEFAULT_FORMAT,
189
193
  datefmt: str = _DEFAULT_DATEFMT,
190
194
  plain: bool = False,
191
195
  color_field_styles: Mapping[str, _FieldStyleKeys] | None = None,
192
196
  ) -> Formatter:
193
197
  """Get the formatter; colored if available."""
194
- if whenever:
195
- from utilities.whenever2 import WheneverLogRecord
196
-
197
- setLogRecordFactory(WheneverLogRecord)
198
- format_ = format_.replace("{asctime}", "{zoned_datetime}")
198
+ setLogRecordFactory(WheneverLogRecord)
199
199
  if plain:
200
200
  return _get_plain_formatter(format_=format_, datefmt=datefmt)
201
201
  try:
@@ -204,8 +204,7 @@ def get_formatter(
204
204
  return _get_plain_formatter(format_=format_, datefmt=datefmt)
205
205
  default = cast("dict[_FieldStyleKeys, _FieldStyleDict]", DEFAULT_FIELD_STYLES)
206
206
  field_styles = {cast("str", k): v for k, v in default.items()}
207
- if whenever:
208
- field_styles["zoned_datetime"] = default["asctime"]
207
+ field_styles["zoned_datetime"] = default["asctime"]
209
208
  if color_field_styles is not None:
210
209
  field_styles.update({k: default[v] for k, v in color_field_styles.items()})
211
210
  return ColoredFormatter(
@@ -261,7 +260,6 @@ class GetLoggingLevelNumberError(Exception):
261
260
  def setup_logging(
262
261
  *,
263
262
  logger: LoggerOrName | None = None,
264
- whenever: bool = False,
265
263
  format_: str = _DEFAULT_FORMAT,
266
264
  datefmt: str = _DEFAULT_DATEFMT,
267
265
  console_level: LogLevel = "INFO",
@@ -277,7 +275,6 @@ def setup_logging(
277
275
  """Set up logger."""
278
276
  basic_config(
279
277
  obj=logger,
280
- whenever=whenever,
281
278
  format_=f"{console_prefix} {format_}",
282
279
  datefmt=datefmt,
283
280
  level=console_level,
@@ -300,7 +297,6 @@ def setup_logging(
300
297
  logger_use.addHandler(handler)
301
298
  basic_config(
302
299
  obj=handler,
303
- whenever=whenever,
304
300
  format_=format_,
305
301
  datefmt=datefmt,
306
302
  level=level,
@@ -335,7 +331,7 @@ class SizeAndTimeRotatingFileHandler(BaseRotatingHandler):
335
331
  interval: int = 1,
336
332
  backupCount: int = _DEFAULT_BACKUP_COUNT,
337
333
  utc: bool = False,
338
- atTime: dt.time | None = None,
334
+ atTime: time | None = None,
339
335
  ) -> None:
340
336
  path = Path(filename)
341
337
  path.parent.mkdir(parents=True, exist_ok=True)
@@ -387,7 +383,7 @@ class SizeAndTimeRotatingFileHandler(BaseRotatingHandler):
387
383
  if not self.delay: # pragma: no cover
388
384
  self.stream = self._open()
389
385
  self._time_handler.rolloverAt = ( # skipif-ci-and-windows
390
- self._time_handler.computeRollover(int(time()))
386
+ self._time_handler.computeRollover(get_now().timestamp())
391
387
  )
392
388
 
393
389
  def _should_rollover(self, record: LogRecord, /) -> bool:
@@ -405,10 +401,8 @@ class SizeAndTimeRotatingFileHandler(BaseRotatingHandler):
405
401
  def _compute_rollover_patterns(stem: str, suffix: str, /) -> _RolloverPatterns:
406
402
  return _RolloverPatterns(
407
403
  pattern1=re.compile(rf"^{stem}\.(\d+){suffix}$"),
408
- pattern2=re.compile(rf"^{stem}\.(\d+)__(\d{{8}}T\d{{6}}){suffix}$"),
409
- pattern3=re.compile(
410
- rf"^{stem}\.(\d+)__(\d{{8}}T\d{{6}})__(\d{{8}}T\d{{6}}){suffix}$"
411
- ),
404
+ pattern2=re.compile(rf"^{stem}\.(\d+)__([\dT]+?){suffix}$"),
405
+ pattern3=re.compile(rf"^{stem}\.(\d+)__([\dT]+?)__([\dT]+?){suffix}$"),
412
406
  )
413
407
 
414
408
 
@@ -457,7 +451,7 @@ def _compute_rollover_actions(
457
451
  rotations.add(
458
452
  _Rotation(file=file, index=curr + 1, start=start, end=end)
459
453
  )
460
- case int() as index, dt.datetime(), dt.datetime():
454
+ case int() as index, ZonedDateTime(), ZonedDateTime():
461
455
  rotations.add(_Rotation(file=file, index=index + 1))
462
456
  case _: # pragma: no cover
463
457
  raise NotImplementedError
@@ -481,14 +475,8 @@ class _RotatingLogFile:
481
475
  stem: str
482
476
  suffix: str
483
477
  index: int | None = None
484
- start: dt.datetime | None = None
485
- end: dt.datetime | None = None
486
-
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)
478
+ start: ZonedDateTime | None = None
479
+ end: ZonedDateTime | None = None
492
480
 
493
481
  @classmethod
494
482
  def from_path(
@@ -505,16 +493,23 @@ class _RotatingLogFile:
505
493
  if patterns is None:
506
494
  patterns = _compute_rollover_patterns(stem, suffix)
507
495
  try:
508
- (index,) = patterns.pattern1.findall(path.name)
509
- except ValueError:
496
+ index, start, end = extract_groups(patterns.pattern3, path.name)
497
+ except ExtractGroupsError:
510
498
  pass
511
499
  else:
512
500
  return cls(
513
- directory=path.parent, stem=stem, suffix=suffix, index=int(index)
501
+ directory=path.parent,
502
+ stem=stem,
503
+ suffix=suffix,
504
+ index=int(index),
505
+ start=PlainDateTime.parse_common_iso(start).assume_tz(
506
+ LOCAL_TIME_ZONE_NAME
507
+ ),
508
+ end=PlainDateTime.parse_common_iso(end).assume_tz(LOCAL_TIME_ZONE_NAME),
514
509
  )
515
510
  try:
516
- ((index, end),) = patterns.pattern2.findall(path.name)
517
- except ValueError:
511
+ index, end = extract_groups(patterns.pattern2, path.name)
512
+ except ExtractGroupsError:
518
513
  pass
519
514
  else:
520
515
  return cls(
@@ -522,21 +517,17 @@ class _RotatingLogFile:
522
517
  stem=stem,
523
518
  suffix=suffix,
524
519
  index=int(index),
525
- end=parse_datetime_compact(end),
520
+ end=PlainDateTime.parse_common_iso(end).assume_tz(LOCAL_TIME_ZONE_NAME),
526
521
  )
527
522
  try:
528
- ((index, start, end),) = patterns.pattern3.findall(path.name)
529
- except ValueError:
530
- return cls(directory=path.parent, stem=stem, suffix=suffix)
523
+ index = extract_group(patterns.pattern1, path.name)
524
+ except ExtractGroupError:
525
+ pass
531
526
  else:
532
527
  return cls(
533
- directory=path.parent,
534
- stem=stem,
535
- suffix=suffix,
536
- index=int(index),
537
- start=parse_datetime_compact(start),
538
- end=parse_datetime_compact(end),
528
+ directory=path.parent, stem=stem, suffix=suffix, index=int(index)
539
529
  )
530
+ return cls(directory=path.parent, stem=stem, suffix=suffix)
540
531
 
541
532
  @cached_property
542
533
  def path(self) -> Path:
@@ -546,10 +537,10 @@ class _RotatingLogFile:
546
537
  tail = None
547
538
  case int() as index, None, None:
548
539
  tail = str(index)
549
- case int() as index, None, dt.datetime() as end:
550
- tail = f"{index}__{serialize_compact(end)}"
551
- case int() as index, dt.datetime() as start, dt.datetime() as end:
552
- tail = f"{index}__{serialize_compact(start)}__{serialize_compact(end)}"
540
+ case int() as index, None, ZonedDateTime() as end:
541
+ tail = f"{index}__{format_compact(end)}"
542
+ case int() as index, ZonedDateTime() as start, ZonedDateTime() as end:
543
+ tail = f"{index}__{format_compact(start)}__{format_compact(end)}"
553
544
  case _: # pragma: no cover
554
545
  raise ImpossibleCaseError(
555
546
  case=[f"{self.index=}", f"{self.start=}", f"{self.end=}"]
@@ -561,8 +552,8 @@ class _RotatingLogFile:
561
552
  self,
562
553
  *,
563
554
  index: int | None | Sentinel = sentinel,
564
- start: dt.datetime | None | Sentinel = sentinel,
565
- end: dt.datetime | None | Sentinel = sentinel,
555
+ start: ZonedDateTime | None | Sentinel = sentinel,
556
+ end: ZonedDateTime | None | Sentinel = sentinel,
566
557
  ) -> Self:
567
558
  return replace_non_sentinel(self, index=index, start=start, end=end)
568
559
 
@@ -579,14 +570,8 @@ class _Deletion:
579
570
  class _Rotation:
580
571
  file: _RotatingLogFile
581
572
  index: int = 0
582
- start: dt.datetime | None | Sentinel = sentinel
583
- end: dt.datetime | Sentinel = sentinel
584
-
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)
573
+ start: ZonedDateTime | None | Sentinel = sentinel
574
+ end: ZonedDateTime | Sentinel = sentinel
590
575
 
591
576
  @cached_property
592
577
  def destination(self) -> Path: