dycw-utilities 0.131.17__py3-none-any.whl → 0.131.19__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.17.dist-info → dycw_utilities-0.131.19.dist-info}/METADATA +1 -1
- {dycw_utilities-0.131.17.dist-info → dycw_utilities-0.131.19.dist-info}/RECORD +19 -20
- utilities/__init__.py +1 -1
- utilities/datetime.py +3 -1183
- utilities/fastapi.py +5 -7
- utilities/functions.py +78 -45
- utilities/hypothesis.py +20 -269
- utilities/iterables.py +1 -30
- utilities/operator.py +21 -15
- utilities/orjson.py +67 -84
- utilities/parse.py +24 -53
- utilities/polars.py +6 -3
- utilities/pytest.py +83 -63
- utilities/types.py +3 -24
- utilities/typing.py +2 -15
- utilities/whenever2.py +147 -3
- utilities/zoneinfo.py +4 -0
- utilities/whenever.py +0 -444
- {dycw_utilities-0.131.17.dist-info → dycw_utilities-0.131.19.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.131.17.dist-info → dycw_utilities-0.131.19.dist-info}/licenses/LICENSE +0 -0
utilities/whenever.py
DELETED
@@ -1,444 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
import datetime as dt
|
4
|
-
import re
|
5
|
-
from contextlib import suppress
|
6
|
-
from dataclasses import dataclass
|
7
|
-
from typing import TYPE_CHECKING, override
|
8
|
-
|
9
|
-
from whenever import (
|
10
|
-
Date,
|
11
|
-
DateTimeDelta,
|
12
|
-
PlainDateTime,
|
13
|
-
Time,
|
14
|
-
TimeZoneNotFoundError,
|
15
|
-
ZonedDateTime,
|
16
|
-
)
|
17
|
-
|
18
|
-
from utilities.datetime import (
|
19
|
-
_MICROSECONDS_PER_DAY,
|
20
|
-
_MICROSECONDS_PER_SECOND,
|
21
|
-
ZERO_TIME,
|
22
|
-
check_date_not_datetime,
|
23
|
-
datetime_duration_to_microseconds,
|
24
|
-
parse_two_digit_year,
|
25
|
-
)
|
26
|
-
from utilities.math import ParseNumberError, parse_number
|
27
|
-
from utilities.re import (
|
28
|
-
ExtractGroupError,
|
29
|
-
ExtractGroupsError,
|
30
|
-
extract_group,
|
31
|
-
extract_groups,
|
32
|
-
)
|
33
|
-
from utilities.zoneinfo import UTC, ensure_time_zone, get_time_zone_name
|
34
|
-
|
35
|
-
if TYPE_CHECKING:
|
36
|
-
from utilities.types import Duration
|
37
|
-
|
38
|
-
|
39
|
-
MAX_SERIALIZABLE_TIMEDELTA = dt.timedelta(days=3652060, microseconds=-1)
|
40
|
-
MIN_SERIALIZABLE_TIMEDELTA = -MAX_SERIALIZABLE_TIMEDELTA
|
41
|
-
|
42
|
-
|
43
|
-
##
|
44
|
-
|
45
|
-
|
46
|
-
def check_valid_zoned_datetime(datetime: dt.datetime, /) -> None:
|
47
|
-
"""Check if a zoned datetime is valid."""
|
48
|
-
time_zone = ensure_time_zone(datetime) # skipif-ci-and-windows
|
49
|
-
datetime2 = datetime.replace(tzinfo=time_zone) # skipif-ci-and-windows
|
50
|
-
try: # skipif-ci-and-windows
|
51
|
-
result = (
|
52
|
-
ZonedDateTime.from_py_datetime(datetime2)
|
53
|
-
.to_tz(get_time_zone_name(UTC))
|
54
|
-
.to_tz(get_time_zone_name(time_zone))
|
55
|
-
.py_datetime()
|
56
|
-
)
|
57
|
-
except TimeZoneNotFoundError: # pragma: no cover
|
58
|
-
raise _CheckValidZonedDateTimeInvalidTimeZoneError(datetime=datetime) from None
|
59
|
-
if result != datetime2: # skipif-ci-and-windows
|
60
|
-
raise _CheckValidZonedDateTimeUnequalError(datetime=datetime, result=result)
|
61
|
-
|
62
|
-
|
63
|
-
@dataclass(kw_only=True, slots=True)
|
64
|
-
class CheckValidZonedDateTimeError(Exception):
|
65
|
-
datetime: dt.datetime
|
66
|
-
|
67
|
-
|
68
|
-
@dataclass(kw_only=True, slots=True)
|
69
|
-
class _CheckValidZonedDateTimeInvalidTimeZoneError(CheckValidZonedDateTimeError):
|
70
|
-
@override
|
71
|
-
def __str__(self) -> str:
|
72
|
-
return f"Invalid timezone; got {self.datetime.tzinfo}" # pragma: no cover
|
73
|
-
|
74
|
-
|
75
|
-
@dataclass(kw_only=True, slots=True)
|
76
|
-
class _CheckValidZonedDateTimeUnequalError(CheckValidZonedDateTimeError):
|
77
|
-
result: dt.datetime
|
78
|
-
|
79
|
-
@override
|
80
|
-
def __str__(self) -> str:
|
81
|
-
return f"Zoned datetime must be valid; got {self.datetime} != {self.result}" # skipif-ci-and-windows
|
82
|
-
|
83
|
-
|
84
|
-
##
|
85
|
-
|
86
|
-
|
87
|
-
_PARSE_DATE_YYMMDD_REGEX = re.compile(r"^(\d{2})(\d{2})(\d{2})$")
|
88
|
-
|
89
|
-
|
90
|
-
def parse_date(date: str, /) -> dt.date:
|
91
|
-
"""Parse a string into a date."""
|
92
|
-
try:
|
93
|
-
w_date = Date.parse_common_iso(date)
|
94
|
-
except ValueError:
|
95
|
-
try:
|
96
|
-
((year2, month, day),) = _PARSE_DATE_YYMMDD_REGEX.findall(date)
|
97
|
-
except ValueError:
|
98
|
-
raise ParseDateError(date=date) from None
|
99
|
-
year = parse_two_digit_year(year2)
|
100
|
-
return dt.date(year=int(year), month=int(month), day=int(day))
|
101
|
-
return w_date.py_date()
|
102
|
-
|
103
|
-
|
104
|
-
@dataclass(kw_only=True, slots=True)
|
105
|
-
class ParseDateError(Exception):
|
106
|
-
date: str
|
107
|
-
|
108
|
-
@override
|
109
|
-
def __str__(self) -> str:
|
110
|
-
return f"Unable to parse date; got {self.date!r}"
|
111
|
-
|
112
|
-
|
113
|
-
##
|
114
|
-
|
115
|
-
|
116
|
-
def parse_datetime(datetime: str, /) -> dt.datetime:
|
117
|
-
"""Parse a string into a datetime."""
|
118
|
-
with suppress(ParsePlainDateTimeError):
|
119
|
-
return parse_plain_datetime(datetime)
|
120
|
-
with suppress(ParseZonedDateTimeError):
|
121
|
-
return parse_zoned_datetime(datetime)
|
122
|
-
raise ParseDateTimeError(datetime=datetime) from None
|
123
|
-
|
124
|
-
|
125
|
-
@dataclass(kw_only=True, slots=True)
|
126
|
-
class ParseDateTimeError(Exception):
|
127
|
-
datetime: str
|
128
|
-
|
129
|
-
@override
|
130
|
-
def __str__(self) -> str:
|
131
|
-
return f"Unable to parse datetime; got {self.datetime!r}"
|
132
|
-
|
133
|
-
|
134
|
-
##
|
135
|
-
|
136
|
-
|
137
|
-
def parse_duration(duration: str, /) -> Duration:
|
138
|
-
"""Parse a string into a Duration."""
|
139
|
-
with suppress(ParseNumberError):
|
140
|
-
return parse_number(duration)
|
141
|
-
try:
|
142
|
-
return parse_timedelta(duration)
|
143
|
-
except ParseTimedeltaError:
|
144
|
-
raise ParseDurationError(duration=duration) from None
|
145
|
-
|
146
|
-
|
147
|
-
@dataclass(kw_only=True, slots=True)
|
148
|
-
class ParseDurationError(Exception):
|
149
|
-
duration: str
|
150
|
-
|
151
|
-
@override
|
152
|
-
def __str__(self) -> str:
|
153
|
-
return f"Unable to parse duration; got {self.duration!r}"
|
154
|
-
|
155
|
-
|
156
|
-
##
|
157
|
-
|
158
|
-
|
159
|
-
def parse_plain_datetime(datetime: str, /) -> dt.datetime:
|
160
|
-
"""Parse a string into a plain datetime."""
|
161
|
-
try:
|
162
|
-
ldt = PlainDateTime.parse_common_iso(datetime)
|
163
|
-
except ValueError:
|
164
|
-
raise ParsePlainDateTimeError(datetime=datetime) from None
|
165
|
-
return ldt.py_datetime()
|
166
|
-
|
167
|
-
|
168
|
-
@dataclass(kw_only=True, slots=True)
|
169
|
-
class ParsePlainDateTimeError(Exception):
|
170
|
-
datetime: str
|
171
|
-
|
172
|
-
@override
|
173
|
-
def __str__(self) -> str:
|
174
|
-
return f"Unable to parse plain datetime; got {self.datetime!r}"
|
175
|
-
|
176
|
-
|
177
|
-
##
|
178
|
-
|
179
|
-
|
180
|
-
def parse_time(time: str, /) -> dt.time:
|
181
|
-
"""Parse a string into a time."""
|
182
|
-
try:
|
183
|
-
w_time = Time.parse_common_iso(time)
|
184
|
-
except ValueError:
|
185
|
-
raise ParseTimeError(time=time) from None
|
186
|
-
return w_time.py_time()
|
187
|
-
|
188
|
-
|
189
|
-
@dataclass(kw_only=True, slots=True)
|
190
|
-
class ParseTimeError(Exception):
|
191
|
-
time: str
|
192
|
-
|
193
|
-
@override
|
194
|
-
def __str__(self) -> str:
|
195
|
-
return f"Unable to parse time; got {self.time!r}"
|
196
|
-
|
197
|
-
|
198
|
-
##
|
199
|
-
|
200
|
-
|
201
|
-
def parse_timedelta(timedelta: str, /) -> dt.timedelta:
|
202
|
-
"""Parse a string into a timedelta."""
|
203
|
-
with suppress(ExtractGroupError):
|
204
|
-
rest = extract_group(r"^-([\w\.]+)$", timedelta)
|
205
|
-
return -parse_timedelta(rest)
|
206
|
-
try:
|
207
|
-
days_str, time_str = extract_groups(r"^P(?:(\d+)D)?(?:T([\w\.]*))?$", timedelta)
|
208
|
-
except ExtractGroupsError:
|
209
|
-
raise _ParseTimedeltaParseError(timedelta=timedelta) from None
|
210
|
-
days = ZERO_TIME if days_str == "" else dt.timedelta(days=int(days_str))
|
211
|
-
if time_str == "":
|
212
|
-
time = ZERO_TIME
|
213
|
-
else:
|
214
|
-
time_part = DateTimeDelta.parse_common_iso(f"PT{time_str}").time_part()
|
215
|
-
_, nanoseconds = divmod(time_part.in_nanoseconds(), 1000)
|
216
|
-
if nanoseconds != 0:
|
217
|
-
raise _ParseTimedeltaNanosecondError(
|
218
|
-
timedelta=timedelta, nanoseconds=nanoseconds
|
219
|
-
)
|
220
|
-
time = dt.timedelta(microseconds=int(time_part.in_microseconds()))
|
221
|
-
return days + time
|
222
|
-
|
223
|
-
|
224
|
-
@dataclass(kw_only=True, slots=True)
|
225
|
-
class ParseTimedeltaError(Exception):
|
226
|
-
timedelta: str
|
227
|
-
|
228
|
-
|
229
|
-
@dataclass(kw_only=True, slots=True)
|
230
|
-
class _ParseTimedeltaParseError(ParseTimedeltaError):
|
231
|
-
@override
|
232
|
-
def __str__(self) -> str:
|
233
|
-
return f"Unable to parse timedelta; got {self.timedelta!r}"
|
234
|
-
|
235
|
-
|
236
|
-
@dataclass(kw_only=True, slots=True)
|
237
|
-
class _ParseTimedeltaNanosecondError(ParseTimedeltaError):
|
238
|
-
nanoseconds: int
|
239
|
-
|
240
|
-
@override
|
241
|
-
def __str__(self) -> str:
|
242
|
-
return f"Unable to parse timedelta; got {self.nanoseconds} nanoseconds"
|
243
|
-
|
244
|
-
|
245
|
-
##
|
246
|
-
|
247
|
-
|
248
|
-
def parse_zoned_datetime(datetime: str, /) -> dt.datetime:
|
249
|
-
"""Parse a string into a zoned datetime."""
|
250
|
-
try:
|
251
|
-
zdt = ZonedDateTime.parse_common_iso(datetime)
|
252
|
-
except ValueError:
|
253
|
-
raise ParseZonedDateTimeError(datetime=datetime) from None
|
254
|
-
return zdt.py_datetime()
|
255
|
-
|
256
|
-
|
257
|
-
@dataclass(kw_only=True, slots=True)
|
258
|
-
class ParseZonedDateTimeError(Exception):
|
259
|
-
datetime: str
|
260
|
-
|
261
|
-
@override
|
262
|
-
def __str__(self) -> str:
|
263
|
-
return f"Unable to parse zoned datetime; got {self.datetime!r}"
|
264
|
-
|
265
|
-
|
266
|
-
##
|
267
|
-
|
268
|
-
|
269
|
-
def serialize_date(date: dt.date, /) -> str:
|
270
|
-
"""Serialize a date."""
|
271
|
-
check_date_not_datetime(date)
|
272
|
-
return Date.from_py_date(date).format_common_iso()
|
273
|
-
|
274
|
-
|
275
|
-
##
|
276
|
-
|
277
|
-
|
278
|
-
def serialize_datetime(datetime: dt.datetime, /) -> str:
|
279
|
-
"""Serialize a datetime."""
|
280
|
-
try:
|
281
|
-
return serialize_plain_datetime(datetime)
|
282
|
-
except SerializePlainDateTimeError:
|
283
|
-
return serialize_zoned_datetime(datetime)
|
284
|
-
|
285
|
-
|
286
|
-
##
|
287
|
-
|
288
|
-
|
289
|
-
def serialize_duration(duration: Duration, /) -> str:
|
290
|
-
"""Serialize a duration."""
|
291
|
-
if isinstance(duration, int | float):
|
292
|
-
return str(duration)
|
293
|
-
try:
|
294
|
-
return serialize_timedelta(duration)
|
295
|
-
except SerializeTimeDeltaError as error:
|
296
|
-
raise SerializeDurationError(duration=error.timedelta) from None
|
297
|
-
|
298
|
-
|
299
|
-
@dataclass(kw_only=True, slots=True)
|
300
|
-
class SerializeDurationError(Exception):
|
301
|
-
duration: Duration
|
302
|
-
|
303
|
-
@override
|
304
|
-
def __str__(self) -> str:
|
305
|
-
return f"Unable to serialize duration; got {self.duration}"
|
306
|
-
|
307
|
-
|
308
|
-
##
|
309
|
-
|
310
|
-
|
311
|
-
def serialize_plain_datetime(datetime: dt.datetime, /) -> str:
|
312
|
-
"""Serialize a plain datetime."""
|
313
|
-
try:
|
314
|
-
pdt = PlainDateTime.from_py_datetime(datetime)
|
315
|
-
except ValueError:
|
316
|
-
raise SerializePlainDateTimeError(datetime=datetime) from None
|
317
|
-
return pdt.format_common_iso()
|
318
|
-
|
319
|
-
|
320
|
-
@dataclass(kw_only=True, slots=True)
|
321
|
-
class SerializePlainDateTimeError(Exception):
|
322
|
-
datetime: dt.datetime
|
323
|
-
|
324
|
-
@override
|
325
|
-
def __str__(self) -> str:
|
326
|
-
return f"Unable to serialize plain datetime; got {self.datetime}"
|
327
|
-
|
328
|
-
|
329
|
-
##
|
330
|
-
|
331
|
-
|
332
|
-
def serialize_time(time: dt.time, /) -> str:
|
333
|
-
"""Serialize a time."""
|
334
|
-
return Time.from_py_time(time).format_common_iso()
|
335
|
-
|
336
|
-
|
337
|
-
##
|
338
|
-
|
339
|
-
|
340
|
-
def serialize_timedelta(timedelta: dt.timedelta, /) -> str:
|
341
|
-
"""Serialize a timedelta."""
|
342
|
-
try:
|
343
|
-
dtd = _to_datetime_delta(timedelta)
|
344
|
-
except _ToDateTimeDeltaError as error:
|
345
|
-
raise SerializeTimeDeltaError(timedelta=error.timedelta) from None
|
346
|
-
return dtd.format_common_iso()
|
347
|
-
|
348
|
-
|
349
|
-
@dataclass(kw_only=True, slots=True)
|
350
|
-
class SerializeTimeDeltaError(Exception):
|
351
|
-
timedelta: dt.timedelta
|
352
|
-
|
353
|
-
@override
|
354
|
-
def __str__(self) -> str:
|
355
|
-
return f"Unable to serialize timedelta; got {self.timedelta}"
|
356
|
-
|
357
|
-
|
358
|
-
##
|
359
|
-
|
360
|
-
|
361
|
-
def serialize_zoned_datetime(datetime: dt.datetime, /) -> str:
|
362
|
-
"""Serialize a zoned datetime."""
|
363
|
-
if datetime.tzinfo is dt.UTC:
|
364
|
-
return serialize_zoned_datetime( # skipif-ci-and-windows
|
365
|
-
datetime.replace(tzinfo=UTC)
|
366
|
-
)
|
367
|
-
try:
|
368
|
-
zdt = ZonedDateTime.from_py_datetime(datetime)
|
369
|
-
except ValueError:
|
370
|
-
raise SerializeZonedDateTimeError(datetime=datetime) from None
|
371
|
-
return zdt.format_common_iso()
|
372
|
-
|
373
|
-
|
374
|
-
@dataclass(kw_only=True, slots=True)
|
375
|
-
class SerializeZonedDateTimeError(Exception):
|
376
|
-
datetime: dt.datetime
|
377
|
-
|
378
|
-
@override
|
379
|
-
def __str__(self) -> str:
|
380
|
-
return f"Unable to serialize zoned datetime; got {self.datetime}"
|
381
|
-
|
382
|
-
|
383
|
-
##
|
384
|
-
|
385
|
-
|
386
|
-
def _to_datetime_delta(timedelta: dt.timedelta, /) -> DateTimeDelta:
|
387
|
-
"""Serialize a timedelta."""
|
388
|
-
total_microseconds = datetime_duration_to_microseconds(timedelta)
|
389
|
-
if total_microseconds == 0:
|
390
|
-
return DateTimeDelta()
|
391
|
-
if total_microseconds >= 1:
|
392
|
-
days, remainder = divmod(total_microseconds, _MICROSECONDS_PER_DAY)
|
393
|
-
seconds, microseconds = divmod(remainder, _MICROSECONDS_PER_SECOND)
|
394
|
-
try:
|
395
|
-
dtd = DateTimeDelta(days=days, seconds=seconds, microseconds=microseconds)
|
396
|
-
except (OverflowError, ValueError):
|
397
|
-
raise _ToDateTimeDeltaError(timedelta=timedelta) from None
|
398
|
-
months, days, seconds, nanoseconds = dtd.in_months_days_secs_nanos()
|
399
|
-
return DateTimeDelta(
|
400
|
-
months=months, days=days, seconds=seconds, nanoseconds=nanoseconds
|
401
|
-
)
|
402
|
-
return -_to_datetime_delta(-timedelta)
|
403
|
-
|
404
|
-
|
405
|
-
@dataclass(kw_only=True, slots=True)
|
406
|
-
class _ToDateTimeDeltaError(Exception):
|
407
|
-
timedelta: dt.timedelta
|
408
|
-
|
409
|
-
@override
|
410
|
-
def __str__(self) -> str:
|
411
|
-
return f"Unable to create DateTimeDelta; got {self.timedelta}"
|
412
|
-
|
413
|
-
|
414
|
-
__all__ = [
|
415
|
-
"MAX_SERIALIZABLE_TIMEDELTA",
|
416
|
-
"MIN_SERIALIZABLE_TIMEDELTA",
|
417
|
-
"CheckValidZonedDateTimeError",
|
418
|
-
"ParseDateError",
|
419
|
-
"ParseDateTimeError",
|
420
|
-
"ParseDurationError",
|
421
|
-
"ParsePlainDateTimeError",
|
422
|
-
"ParseTimeError",
|
423
|
-
"ParseTimedeltaError",
|
424
|
-
"ParseZonedDateTimeError",
|
425
|
-
"SerializeDurationError",
|
426
|
-
"SerializePlainDateTimeError",
|
427
|
-
"SerializeTimeDeltaError",
|
428
|
-
"SerializeZonedDateTimeError",
|
429
|
-
"check_valid_zoned_datetime",
|
430
|
-
"parse_date",
|
431
|
-
"parse_datetime",
|
432
|
-
"parse_duration",
|
433
|
-
"parse_plain_datetime",
|
434
|
-
"parse_time",
|
435
|
-
"parse_timedelta",
|
436
|
-
"parse_zoned_datetime",
|
437
|
-
"serialize_date",
|
438
|
-
"serialize_datetime",
|
439
|
-
"serialize_duration",
|
440
|
-
"serialize_plain_datetime",
|
441
|
-
"serialize_time",
|
442
|
-
"serialize_timedelta",
|
443
|
-
"serialize_zoned_datetime",
|
444
|
-
]
|
File without changes
|
File without changes
|