dycw-utilities 0.133.0__py3-none-any.whl → 0.133.1__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.133.0.dist-info → dycw_utilities-0.133.1.dist-info}/METADATA +1 -1
- {dycw_utilities-0.133.0.dist-info → dycw_utilities-0.133.1.dist-info}/RECORD +9 -9
- utilities/__init__.py +1 -1
- utilities/click.py +26 -1
- utilities/hypothesis.py +36 -0
- utilities/typed_settings.py +2 -0
- utilities/whenever.py +129 -2
- {dycw_utilities-0.133.0.dist-info → dycw_utilities-0.133.1.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.133.0.dist-info → dycw_utilities-0.133.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,4 +1,4 @@
|
|
1
|
-
utilities/__init__.py,sha256=
|
1
|
+
utilities/__init__.py,sha256=Z40RN6IDVR495cIDu4FY4lG1K8hcbWIcPkM_PM_rASQ,60
|
2
2
|
utilities/aiolimiter.py,sha256=mD0wEiqMgwpty4XTbawFpnkkmJS6R4JRsVXFUaoitSU,628
|
3
3
|
utilities/altair.py,sha256=HeZBVUocjkrTNwwKrClppsIqgNFF-ykv05HfZSoHYno,9104
|
4
4
|
utilities/arq.py,sha256=YkwvWoL930hgeU9VP8iuP3RhMf0t8sm7O8qsD9TiyWo,4688
|
@@ -6,7 +6,7 @@ utilities/asyncio.py,sha256=USWMMrHqPVRr20vlIn_n5JLimyqa-5xLhuqDYWJed8A,37586
|
|
6
6
|
utilities/atomicwrites.py,sha256=geFjn9Pwn-tTrtoGjDDxWli9NqbYfy3gGL6ZBctiqSo,5393
|
7
7
|
utilities/atools.py,sha256=-bFGIrwYMFR7xl39j02DZMsO_u5x5_Ph7bRlBUFVYyw,1048
|
8
8
|
utilities/cachetools.py,sha256=uBtEv4hD-TuCPX_cQy1lOpLF-QqfwnYGSf0o4Soqydc,2826
|
9
|
-
utilities/click.py,sha256=
|
9
|
+
utilities/click.py,sha256=2k7Ss2qKwYb1JCDB5IWpNf-B2WTyjKR1GxDW-Y6anPs,15736
|
10
10
|
utilities/concurrent.py,sha256=s2scTEd2AhXVTW4hpASU2qxV_DiVLALfms55cCQzCvM,2886
|
11
11
|
utilities/contextlib.py,sha256=lpaLJBy3X0UGLWjM98jkQZZq8so4fRmoK-Bheq0uOW4,1027
|
12
12
|
utilities/contextvars.py,sha256=RsSGGrbQqqZ67rOydnM7WWIsM2lIE31UHJLejnHJPWY,505
|
@@ -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=
|
27
|
+
utilities/hypothesis.py,sha256=LWEcjL0ip0cwawcIHhnWPVnB1OEH1GJnYEwYU6xtFpo,36259
|
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
|
@@ -78,7 +78,7 @@ utilities/text.py,sha256=ymBFlP_cA8OgNnZRVNs7FAh7OG8HxE6YkiLEMZv5g_A,11297
|
|
78
78
|
utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
|
79
79
|
utilities/timer.py,sha256=oYqRQ-G-DMOOHB6a4yP5-PJDVimLnbNkMnkOj_jUmFg,2474
|
80
80
|
utilities/traceback.py,sha256=i-790AQbTrDA8MiYyOcYPFpm48I558VR_kL_7x4ypfY,8503
|
81
|
-
utilities/typed_settings.py,sha256=
|
81
|
+
utilities/typed_settings.py,sha256=io3bhnglxO5FRNuTz1vpgbbGgvyj6VGJ5pytPRUeJo4,3769
|
82
82
|
utilities/types.py,sha256=ZvD16TobtB47IgMo2CK_CCdJsvhrTqAZgqgqbCME2T0,19223
|
83
83
|
utilities/typing.py,sha256=kVWK6ciV8T0MKxnFQcMSEr_XlRisspH5aBTTosMUh30,13872
|
84
84
|
utilities/tzdata.py,sha256=fgNVj66yUbCSI_-vrRVzSD3gtf-L_8IEJEPjP_Jel5Y,266
|
@@ -86,10 +86,10 @@ utilities/tzlocal.py,sha256=KyCXEgCTjqGFx-389JdTuhMRUaT06U1RCMdWoED-qro,728
|
|
86
86
|
utilities/uuid.py,sha256=jJTFxz-CWgltqNuzmythB7iEQ-Q1mCwPevUfKthZT3c,611
|
87
87
|
utilities/version.py,sha256=ufhJMmI6KPs1-3wBI71aj5wCukd3sP_m11usLe88DNA,5117
|
88
88
|
utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
|
89
|
-
utilities/whenever.py,sha256=
|
89
|
+
utilities/whenever.py,sha256=A-yoOqBqrcVD1yDINDsTFDw7dq9-zgUGn_f8CxVUQJs,23332
|
90
90
|
utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
|
91
91
|
utilities/zoneinfo.py,sha256=oEH-nL3t4h9uawyZqWDtNtDAl6M-CLpLYGI_nI6DulM,1971
|
92
|
-
dycw_utilities-0.133.
|
93
|
-
dycw_utilities-0.133.
|
94
|
-
dycw_utilities-0.133.
|
95
|
-
dycw_utilities-0.133.
|
92
|
+
dycw_utilities-0.133.1.dist-info/METADATA,sha256=fZj3XXeQfb7y2obavSG6zSthc-oG_Zt-I33T3RVsHOE,1522
|
93
|
+
dycw_utilities-0.133.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
94
|
+
dycw_utilities-0.133.1.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
|
95
|
+
dycw_utilities-0.133.1.dist-info/RECORD,,
|
utilities/__init__.py
CHANGED
utilities/click.py
CHANGED
@@ -29,7 +29,7 @@ from utilities.types import (
|
|
29
29
|
TimeLike,
|
30
30
|
ZonedDateTimeLike,
|
31
31
|
)
|
32
|
-
from utilities.whenever import _MonthParseCommonISOError
|
32
|
+
from utilities.whenever import FreqLike, _FreqParseError, _MonthParseCommonISOError
|
33
33
|
|
34
34
|
if TYPE_CHECKING:
|
35
35
|
from collections.abc import Iterable, Sequence
|
@@ -177,6 +177,30 @@ class Enum(ParamType, Generic[TEnum]):
|
|
177
177
|
return _make_metavar(param, desc)
|
178
178
|
|
179
179
|
|
180
|
+
class Freq(ParamType):
|
181
|
+
"""An frequency-valued parameter."""
|
182
|
+
|
183
|
+
@override
|
184
|
+
def __repr__(self) -> str:
|
185
|
+
return "FREQ"
|
186
|
+
|
187
|
+
@override
|
188
|
+
def convert(
|
189
|
+
self, value: FreqLike, param: Parameter | None, ctx: Context | None
|
190
|
+
) -> utilities.whenever.Freq:
|
191
|
+
"""Convert a value into the `Freq` type."""
|
192
|
+
match value:
|
193
|
+
case utilities.whenever.Freq():
|
194
|
+
return value
|
195
|
+
case str():
|
196
|
+
try:
|
197
|
+
return utilities.whenever.Freq.parse(value)
|
198
|
+
except _FreqParseError as error:
|
199
|
+
self.fail(str(error), param, ctx)
|
200
|
+
case _ as never:
|
201
|
+
assert_never(never)
|
202
|
+
|
203
|
+
|
180
204
|
class IPv4Address(ParamType):
|
181
205
|
"""An IPv4 address-valued parameter."""
|
182
206
|
|
@@ -519,6 +543,7 @@ __all__ = [
|
|
519
543
|
"ExistingDirPath",
|
520
544
|
"ExistingFilePath",
|
521
545
|
"FilePath",
|
546
|
+
"Freq",
|
522
547
|
"FrozenSetChoices",
|
523
548
|
"FrozenSetEnums",
|
524
549
|
"FrozenSetParameter",
|
utilities/hypothesis.py
CHANGED
@@ -77,6 +77,8 @@ from utilities.pathlib import temp_cwd
|
|
77
77
|
from utilities.platform import IS_WINDOWS
|
78
78
|
from utilities.sentinel import Sentinel, sentinel
|
79
79
|
from utilities.tempfile import TEMP_DIR, TemporaryDirectory
|
80
|
+
from utilities.types import DateTimeRoundUnit
|
81
|
+
from utilities.typing import get_literal_elements
|
80
82
|
from utilities.version import Version
|
81
83
|
from utilities.whenever import (
|
82
84
|
DATE_DELTA_MAX,
|
@@ -100,6 +102,7 @@ from utilities.whenever import (
|
|
100
102
|
TIME_DELTA_MIN,
|
101
103
|
TIME_MAX,
|
102
104
|
TIME_MIN,
|
105
|
+
Freq,
|
103
106
|
Month,
|
104
107
|
to_date_time_delta,
|
105
108
|
to_days,
|
@@ -502,6 +505,38 @@ def floats_extra(
|
|
502
505
|
##
|
503
506
|
|
504
507
|
|
508
|
+
@composite
|
509
|
+
def freqs(
|
510
|
+
draw: DrawFn, /, *, unit: MaybeSearchStrategy[DateTimeRoundUnit | None] = None
|
511
|
+
) -> Freq:
|
512
|
+
unit_ = draw2(draw, unit, _freq_units())
|
513
|
+
match unit_:
|
514
|
+
case "day":
|
515
|
+
return Freq(unit=unit_)
|
516
|
+
case "hour":
|
517
|
+
return Freq(unit=unit_, increment=draw(_freq_increments(24)))
|
518
|
+
case "minute" | "second":
|
519
|
+
return Freq(unit=unit_, increment=draw(_freq_increments(60)))
|
520
|
+
case "millisecond" | "microsecond" | "nanosecond":
|
521
|
+
return Freq(unit=unit_, increment=draw(_freq_increments(1000)))
|
522
|
+
case _ as never:
|
523
|
+
assert_never(never)
|
524
|
+
|
525
|
+
|
526
|
+
@composite
|
527
|
+
def _freq_units(draw: DrawFn, /) -> DateTimeRoundUnit:
|
528
|
+
return draw(sampled_from(get_literal_elements(DateTimeRoundUnit)))
|
529
|
+
|
530
|
+
|
531
|
+
@composite
|
532
|
+
def _freq_increments(draw: DrawFn, n: int, /) -> int:
|
533
|
+
divisors = [i for i in range(1, n) if n % i == 0]
|
534
|
+
return draw(sampled_from(divisors))
|
535
|
+
|
536
|
+
|
537
|
+
##
|
538
|
+
|
539
|
+
|
505
540
|
@composite
|
506
541
|
def git_repos(draw: DrawFn, /) -> Path:
|
507
542
|
path = draw(temp_paths())
|
@@ -1264,6 +1299,7 @@ __all__ = [
|
|
1264
1299
|
"float64s",
|
1265
1300
|
"float_arrays",
|
1266
1301
|
"floats_extra",
|
1302
|
+
"freqs",
|
1267
1303
|
"git_repos",
|
1268
1304
|
"hashables",
|
1269
1305
|
"int32s",
|
utilities/typed_settings.py
CHANGED
@@ -21,6 +21,7 @@ from whenever import (
|
|
21
21
|
)
|
22
22
|
|
23
23
|
from utilities.iterables import always_iterable
|
24
|
+
from utilities.whenever import Freq
|
24
25
|
|
25
26
|
if TYPE_CHECKING:
|
26
27
|
from collections.abc import Callable
|
@@ -52,6 +53,7 @@ class ExtendedTSConverter(TSConverter):
|
|
52
53
|
(Date, Date.parse_common_iso),
|
53
54
|
(DateDelta, DateDelta.parse_common_iso),
|
54
55
|
(DateTimeDelta, DateTimeDelta.parse_common_iso),
|
56
|
+
(Freq, Freq.parse),
|
55
57
|
(IPv4Address, IPv4Address),
|
56
58
|
(IPv6Address, IPv6Address),
|
57
59
|
(PlainDateTime, PlainDateTime.parse_common_iso),
|
utilities/whenever.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
from collections.abc import Callable, Iterable
|
3
|
+
from collections.abc import Callable, Iterable, Mapping
|
4
4
|
from dataclasses import dataclass, replace
|
5
5
|
from functools import cache
|
6
6
|
from logging import LogRecord
|
@@ -8,9 +8,12 @@ from statistics import fmean
|
|
8
8
|
from typing import (
|
9
9
|
TYPE_CHECKING,
|
10
10
|
Any,
|
11
|
+
ClassVar,
|
12
|
+
Literal,
|
11
13
|
Self,
|
12
14
|
SupportsFloat,
|
13
15
|
assert_never,
|
16
|
+
cast,
|
14
17
|
overload,
|
15
18
|
override,
|
16
19
|
)
|
@@ -28,7 +31,7 @@ from utilities.math import sign
|
|
28
31
|
from utilities.platform import get_strftime
|
29
32
|
from utilities.re import ExtractGroupsError, extract_groups
|
30
33
|
from utilities.sentinel import Sentinel, sentinel
|
31
|
-
from utilities.types import MaybeStr
|
34
|
+
from utilities.types import DateTimeRoundUnit, MaybeStr
|
32
35
|
from utilities.tzlocal import LOCAL_TIME_ZONE, LOCAL_TIME_ZONE_NAME
|
33
36
|
from utilities.zoneinfo import UTC, get_time_zone_name
|
34
37
|
|
@@ -166,6 +169,127 @@ def format_compact(datetime: ZonedDateTime, /) -> str:
|
|
166
169
|
##
|
167
170
|
|
168
171
|
|
172
|
+
class Freq:
|
173
|
+
"""A rounding frequency."""
|
174
|
+
|
175
|
+
unit: DateTimeRoundUnit
|
176
|
+
increment: int
|
177
|
+
_mapping: ClassVar[Mapping[DateTimeRoundUnit, _DateTimeRoundUnitAbbrev]] = {
|
178
|
+
"day": "D",
|
179
|
+
"hour": "H",
|
180
|
+
"minute": "M",
|
181
|
+
"second": "S",
|
182
|
+
"millisecond": "ms",
|
183
|
+
"microsecond": "us",
|
184
|
+
"nanosecond": "ns",
|
185
|
+
}
|
186
|
+
|
187
|
+
def __init__(
|
188
|
+
self, *, unit: DateTimeRoundUnit = "second", increment: int = 1
|
189
|
+
) -> None:
|
190
|
+
super().__init__()
|
191
|
+
if (unit == "day") and (increment != 1):
|
192
|
+
raise _FreqDayIncrementError(increment=increment)
|
193
|
+
if (unit == "hour") and not ((0 < increment < 24) and (24 % increment == 0)):
|
194
|
+
raise _FreqIncrementError(unit=unit, increment=increment, divisor=24)
|
195
|
+
if (unit in {"minute", "second"}) and not (
|
196
|
+
(0 < increment < 60) and (60 % increment == 0)
|
197
|
+
):
|
198
|
+
raise _FreqIncrementError(unit=unit, increment=increment, divisor=60)
|
199
|
+
if (unit in {"millisecond", "microsecond", "nanosecond"}) and not (
|
200
|
+
(0 < increment < 1000) and (1000 % increment == 0)
|
201
|
+
):
|
202
|
+
raise _FreqIncrementError(unit=unit, increment=increment, divisor=1000)
|
203
|
+
self.unit = unit
|
204
|
+
self.increment = increment
|
205
|
+
|
206
|
+
@override
|
207
|
+
def __eq__(self, other: object, /) -> bool:
|
208
|
+
if not isinstance(other, Freq):
|
209
|
+
return NotImplemented
|
210
|
+
return (self.unit == other.unit) and (self.increment == other.increment)
|
211
|
+
|
212
|
+
@override
|
213
|
+
def __hash__(self) -> int:
|
214
|
+
return hash((self.unit, self.increment))
|
215
|
+
|
216
|
+
@override
|
217
|
+
def __repr__(self) -> str:
|
218
|
+
return f"{type(self).__name__}(unit={self.unit!r}, increment={self.increment})"
|
219
|
+
|
220
|
+
@classmethod
|
221
|
+
def parse(cls, text: str, /) -> Self:
|
222
|
+
try:
|
223
|
+
increment, abbrev = extract_groups(r"^(\d*)(D|H|M|S|ms|us|ns)$", text)
|
224
|
+
except ExtractGroupsError:
|
225
|
+
raise _FreqParseError(text=text) from None
|
226
|
+
return cls(
|
227
|
+
unit=cls._expand(cast("_DateTimeRoundUnitAbbrev", abbrev)),
|
228
|
+
increment=int(increment) if len(increment) >= 1 else 1,
|
229
|
+
)
|
230
|
+
|
231
|
+
def serialize(self) -> str:
|
232
|
+
if self.increment == 1:
|
233
|
+
return self._abbreviation
|
234
|
+
return f"{self.increment}{self._abbreviation}"
|
235
|
+
|
236
|
+
@classmethod
|
237
|
+
def _abbreviate(cls, unit: DateTimeRoundUnit, /) -> _DateTimeRoundUnitAbbrev:
|
238
|
+
return cls._mapping[unit]
|
239
|
+
|
240
|
+
@property
|
241
|
+
def _abbreviation(self) -> _DateTimeRoundUnitAbbrev:
|
242
|
+
return self._mapping[self.unit]
|
243
|
+
|
244
|
+
@classmethod
|
245
|
+
def _expand(cls, unit: _DateTimeRoundUnitAbbrev, /) -> DateTimeRoundUnit:
|
246
|
+
values: set[DateTimeRoundUnit] = {
|
247
|
+
k for k, v in cls._mapping.items() if v == unit
|
248
|
+
}
|
249
|
+
(value,) = values
|
250
|
+
return value
|
251
|
+
|
252
|
+
|
253
|
+
type FreqLike = MaybeStr[Freq]
|
254
|
+
type _DateTimeRoundUnitAbbrev = Literal["D", "H", "M", "S", "ms", "us", "ns"]
|
255
|
+
|
256
|
+
|
257
|
+
@dataclass(kw_only=True, slots=True)
|
258
|
+
class FreqError(Exception): ...
|
259
|
+
|
260
|
+
|
261
|
+
@dataclass(kw_only=True, slots=True)
|
262
|
+
class _FreqDayIncrementError(FreqError):
|
263
|
+
increment: int
|
264
|
+
|
265
|
+
@override
|
266
|
+
def __str__(self) -> str:
|
267
|
+
return f"Increment must be 1 for the 'day' unit; got {self.increment}"
|
268
|
+
|
269
|
+
|
270
|
+
@dataclass(kw_only=True, slots=True)
|
271
|
+
class _FreqIncrementError(FreqError):
|
272
|
+
unit: DateTimeRoundUnit
|
273
|
+
increment: int
|
274
|
+
divisor: int
|
275
|
+
|
276
|
+
@override
|
277
|
+
def __str__(self) -> str:
|
278
|
+
return f"Increment must be a proper divisor of {self.divisor} for the {self.unit!r} unit; got {self.increment}"
|
279
|
+
|
280
|
+
|
281
|
+
@dataclass(kw_only=True, slots=True)
|
282
|
+
class _FreqParseError(FreqError):
|
283
|
+
text: str
|
284
|
+
|
285
|
+
@override
|
286
|
+
def __str__(self) -> str:
|
287
|
+
return f"Unable to parse frequency; got {self.text!r}"
|
288
|
+
|
289
|
+
|
290
|
+
##
|
291
|
+
|
292
|
+
|
169
293
|
def from_timestamp(i: float, /, *, time_zone: TimeZoneLike = UTC) -> ZonedDateTime:
|
170
294
|
"""Get a zoned datetime from a timestamp."""
|
171
295
|
return ZonedDateTime.from_timestamp(i, tz=get_time_zone_name(time_zone))
|
@@ -736,6 +860,9 @@ __all__ = [
|
|
736
860
|
"ZONED_DATE_TIME_MAX",
|
737
861
|
"ZONED_DATE_TIME_MIN",
|
738
862
|
"DateOrMonth",
|
863
|
+
"Freq",
|
864
|
+
"FreqError",
|
865
|
+
"FreqLike",
|
739
866
|
"MeanDateTimeError",
|
740
867
|
"MinMaxDateError",
|
741
868
|
"Month",
|
File without changes
|
File without changes
|