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.
- {dycw_utilities-0.131.10.dist-info → dycw_utilities-0.131.12.dist-info}/METADATA +1 -1
- {dycw_utilities-0.131.10.dist-info → dycw_utilities-0.131.12.dist-info}/RECORD +21 -21
- utilities/__init__.py +1 -1
- utilities/click.py +6 -6
- utilities/datetime.py +11 -18
- utilities/fastapi.py +2 -4
- utilities/fpdf2.py +2 -2
- utilities/hypothesis.py +8 -11
- utilities/logging.py +54 -69
- utilities/math.py +10 -9
- utilities/orjson.py +46 -45
- utilities/pyinstrument.py +2 -3
- utilities/re.py +12 -13
- utilities/traceback.py +18 -21
- utilities/types.py +49 -10
- utilities/typing.py +25 -1
- utilities/tzlocal.py +2 -26
- utilities/whenever.py +6 -6
- utilities/whenever2.py +68 -2
- {dycw_utilities-0.131.10.dist-info → dycw_utilities-0.131.12.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.131.10.dist-info → dycw_utilities-0.131.12.dist-info}/licenses/LICENSE +0 -0
@@ -1,30 +1,30 @@
|
|
1
|
-
utilities/__init__.py,sha256=
|
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=
|
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=
|
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=
|
20
|
-
utilities/fpdf2.py,sha256=
|
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
|
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=
|
35
|
+
utilities/logging.py,sha256=zm5k0Cduxtx2H2o7odxUTJtPNkJS85mqHYN1cS5Kc1w,17863
|
36
36
|
utilities/luigi.py,sha256=fpH9MbxJDuo6-k9iCXRayFRtiVbUtibCJKugf7ygpv0,5988
|
37
|
-
utilities/math.py,sha256
|
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=
|
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=
|
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=
|
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=
|
83
|
-
utilities/types.py,sha256=
|
84
|
-
utilities/typing.py,sha256=
|
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=
|
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=
|
91
|
-
utilities/whenever2.py,sha256=
|
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.
|
95
|
-
dycw_utilities-0.131.
|
96
|
-
dycw_utilities-0.131.
|
97
|
-
dycw_utilities-0.131.
|
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
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:
|
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:
|
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:
|
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:
|
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:
|
478
|
+
def get_date(*, date: MaybeCallablePyDate | Sentinel) -> dt.date | Sentinel: ...
|
486
479
|
@overload
|
487
480
|
def get_date(
|
488
|
-
*, date:
|
481
|
+
*, date: MaybeCallablePyDate | None | Sentinel = sentinel
|
489
482
|
) -> dt.date | None | Sentinel: ...
|
490
483
|
def get_date(
|
491
|
-
*, date:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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.
|
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
|
-
|
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.
|
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(),
|
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,
|
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_:
|
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_:
|
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_:
|
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_:
|
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_:
|
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_:
|
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
|
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 =
|
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
|
-
|
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
|
-
|
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:
|
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(
|
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+)__(\
|
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,
|
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:
|
485
|
-
end:
|
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
|
-
|
509
|
-
except
|
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,
|
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
|
-
|
517
|
-
except
|
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=
|
520
|
+
end=PlainDateTime.parse_common_iso(end).assume_tz(LOCAL_TIME_ZONE_NAME),
|
526
521
|
)
|
527
522
|
try:
|
528
|
-
|
529
|
-
except
|
530
|
-
|
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,
|
550
|
-
tail = f"{index}__{
|
551
|
-
case int() as index,
|
552
|
-
tail = f"{index}__{
|
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:
|
565
|
-
end:
|
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:
|
583
|
-
end:
|
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:
|