dycw-utilities 0.131.16__py3-none-any.whl → 0.131.18__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.16.dist-info → dycw_utilities-0.131.18.dist-info}/METADATA +1 -1
- {dycw_utilities-0.131.16.dist-info → dycw_utilities-0.131.18.dist-info}/RECORD +18 -19
- utilities/__init__.py +1 -1
- utilities/asyncio.py +47 -60
- utilities/datetime.py +2 -279
- utilities/iterables.py +1 -30
- utilities/operator.py +21 -15
- utilities/orjson.py +67 -84
- utilities/parse.py +24 -53
- utilities/pottery.py +16 -19
- utilities/psutil.py +8 -7
- utilities/redis.py +108 -116
- utilities/slack_sdk.py +17 -18
- utilities/sqlalchemy.py +36 -30
- utilities/sqlalchemy_polars.py +12 -27
- utilities/whenever.py +2 -216
- utilities/tenacity.py +0 -145
- {dycw_utilities-0.131.16.dist-info → dycw_utilities-0.131.18.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.131.16.dist-info → dycw_utilities-0.131.18.dist-info}/licenses/LICENSE +0 -0
utilities/slack_sdk.py
CHANGED
@@ -7,21 +7,21 @@ from typing import TYPE_CHECKING, Any, Self, override
|
|
7
7
|
|
8
8
|
from slack_sdk.webhook.async_client import AsyncWebhookClient
|
9
9
|
|
10
|
-
from utilities.asyncio import Looper,
|
11
|
-
from utilities.datetime import MINUTE, SECOND, datetime_duration_to_float
|
10
|
+
from utilities.asyncio import Looper, timeout_td
|
12
11
|
from utilities.functools import cache
|
13
|
-
from utilities.math import safe_round
|
14
12
|
from utilities.sentinel import Sentinel, sentinel
|
13
|
+
from utilities.whenever2 import MINUTE, SECOND
|
15
14
|
|
16
15
|
if TYPE_CHECKING:
|
17
16
|
from collections.abc import Callable
|
18
17
|
|
19
18
|
from slack_sdk.webhook import WebhookResponse
|
19
|
+
from whenever import TimeDelta
|
20
20
|
|
21
|
-
from utilities.types import Coroutine1
|
21
|
+
from utilities.types import Coroutine1
|
22
22
|
|
23
23
|
|
24
|
-
_TIMEOUT:
|
24
|
+
_TIMEOUT: TimeDelta = MINUTE
|
25
25
|
|
26
26
|
|
27
27
|
##
|
@@ -42,14 +42,14 @@ class SlackHandlerService(Handler, Looper[str]):
|
|
42
42
|
url: str,
|
43
43
|
auto_start: bool = False,
|
44
44
|
empty_upon_exit: bool = True,
|
45
|
-
freq:
|
46
|
-
backoff:
|
45
|
+
freq: TimeDelta = SECOND,
|
46
|
+
backoff: TimeDelta = SECOND,
|
47
47
|
logger: str | None = None,
|
48
|
-
timeout:
|
48
|
+
timeout: TimeDelta | None = None,
|
49
49
|
_debug: bool = False,
|
50
50
|
level: int = NOTSET,
|
51
51
|
sender: Callable[[str, str], Coroutine1[None]] = _send_adapter,
|
52
|
-
send_timeout:
|
52
|
+
send_timeout: TimeDelta = SECOND,
|
53
53
|
) -> None:
|
54
54
|
Looper.__init__( # Looper first
|
55
55
|
self,
|
@@ -81,7 +81,7 @@ class SlackHandlerService(Handler, Looper[str]):
|
|
81
81
|
if self.empty():
|
82
82
|
return
|
83
83
|
text = "\n".join(self.get_all_nowait())
|
84
|
-
async with
|
84
|
+
async with timeout_td(self.send_timeout):
|
85
85
|
await self.sender(self.url, text)
|
86
86
|
|
87
87
|
@override
|
@@ -90,10 +90,10 @@ class SlackHandlerService(Handler, Looper[str]):
|
|
90
90
|
*,
|
91
91
|
auto_start: bool | Sentinel = sentinel,
|
92
92
|
empty_upon_exit: bool | Sentinel = sentinel,
|
93
|
-
freq:
|
94
|
-
backoff:
|
93
|
+
freq: TimeDelta | Sentinel = sentinel,
|
94
|
+
backoff: TimeDelta | Sentinel = sentinel,
|
95
95
|
logger: str | None | Sentinel = sentinel,
|
96
|
-
timeout:
|
96
|
+
timeout: TimeDelta | None | Sentinel = sentinel,
|
97
97
|
_debug: bool | Sentinel = sentinel,
|
98
98
|
**kwargs: Any,
|
99
99
|
) -> Self:
|
@@ -115,11 +115,11 @@ class SlackHandlerService(Handler, Looper[str]):
|
|
115
115
|
|
116
116
|
|
117
117
|
async def send_to_slack(
|
118
|
-
url: str, text: str, /, *, timeout:
|
118
|
+
url: str, text: str, /, *, timeout: TimeDelta = _TIMEOUT
|
119
119
|
) -> None:
|
120
120
|
"""Send a message via Slack."""
|
121
121
|
client = _get_client(url, timeout=timeout)
|
122
|
-
async with
|
122
|
+
async with timeout_td(timeout):
|
123
123
|
response = await client.send(text=text)
|
124
124
|
if response.status_code != HTTPStatus.OK: # pragma: no cover
|
125
125
|
raise SendToSlackError(text=text, response=response)
|
@@ -138,10 +138,9 @@ class SendToSlackError(Exception):
|
|
138
138
|
|
139
139
|
|
140
140
|
@cache
|
141
|
-
def _get_client(url: str, /, *, timeout:
|
141
|
+
def _get_client(url: str, /, *, timeout: TimeDelta = _TIMEOUT) -> AsyncWebhookClient:
|
142
142
|
"""Get the Slack client."""
|
143
|
-
|
144
|
-
return AsyncWebhookClient(url, timeout=timeout_use)
|
143
|
+
return AsyncWebhookClient(url, timeout=round(timeout.in_seconds()))
|
145
144
|
|
146
145
|
|
147
146
|
__all__ = ["SendToSlackError", "SlackHandlerService", "send_to_slack"]
|
utilities/sqlalchemy.py
CHANGED
@@ -18,7 +18,16 @@ from itertools import chain
|
|
18
18
|
from math import floor
|
19
19
|
from operator import ge, le
|
20
20
|
from re import search
|
21
|
-
from typing import
|
21
|
+
from typing import (
|
22
|
+
TYPE_CHECKING,
|
23
|
+
Any,
|
24
|
+
Literal,
|
25
|
+
TypeGuard,
|
26
|
+
TypeVar,
|
27
|
+
assert_never,
|
28
|
+
cast,
|
29
|
+
override,
|
30
|
+
)
|
22
31
|
|
23
32
|
from sqlalchemy import (
|
24
33
|
URL,
|
@@ -57,9 +66,8 @@ from sqlalchemy.orm import (
|
|
57
66
|
from sqlalchemy.orm.exc import UnmappedClassError
|
58
67
|
from sqlalchemy.pool import NullPool, Pool
|
59
68
|
|
60
|
-
from utilities.asyncio import Looper,
|
69
|
+
from utilities.asyncio import Looper, timeout_td
|
61
70
|
from utilities.contextlib import suppress_super_object_attribute_error
|
62
|
-
from utilities.datetime import SECOND
|
63
71
|
from utilities.functions import (
|
64
72
|
ensure_str,
|
65
73
|
get_class_name,
|
@@ -82,13 +90,11 @@ from utilities.iterables import (
|
|
82
90
|
)
|
83
91
|
from utilities.reprlib import get_repr
|
84
92
|
from utilities.text import snake_case
|
85
|
-
from utilities.types import
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
TupleOrStrMapping,
|
91
|
-
)
|
93
|
+
from utilities.types import MaybeIterable, MaybeType, StrMapping, TupleOrStrMapping
|
94
|
+
from utilities.whenever2 import SECOND
|
95
|
+
|
96
|
+
if TYPE_CHECKING:
|
97
|
+
from whenever import TimeDelta
|
92
98
|
|
93
99
|
_T = TypeVar("_T")
|
94
100
|
type _EngineOrConnectionOrAsync = Engine | Connection | AsyncEngine | AsyncConnection
|
@@ -105,7 +111,7 @@ async def check_engine(
|
|
105
111
|
engine: AsyncEngine,
|
106
112
|
/,
|
107
113
|
*,
|
108
|
-
timeout:
|
114
|
+
timeout: TimeDelta | None = None,
|
109
115
|
error: type[Exception] = TimeoutError,
|
110
116
|
num_tables: int | tuple[int, float] | None = None,
|
111
117
|
) -> None:
|
@@ -229,7 +235,7 @@ async def ensure_tables_created(
|
|
229
235
|
engine: AsyncEngine,
|
230
236
|
/,
|
231
237
|
*tables_or_orms: TableOrORMInstOrClass,
|
232
|
-
timeout:
|
238
|
+
timeout: TimeDelta | None = None,
|
233
239
|
error: type[Exception] = TimeoutError,
|
234
240
|
) -> None:
|
235
241
|
"""Ensure a table/set of tables is/are created."""
|
@@ -258,7 +264,7 @@ async def ensure_tables_created(
|
|
258
264
|
async def ensure_tables_dropped(
|
259
265
|
engine: AsyncEngine,
|
260
266
|
*tables_or_orms: TableOrORMInstOrClass,
|
261
|
-
timeout:
|
267
|
+
timeout: TimeDelta | None = None,
|
262
268
|
error: type[Exception] = TimeoutError,
|
263
269
|
) -> None:
|
264
270
|
"""Ensure a table/set of tables is/are dropped."""
|
@@ -384,9 +390,9 @@ async def insert_items(
|
|
384
390
|
snake: bool = False,
|
385
391
|
chunk_size_frac: float = CHUNK_SIZE_FRAC,
|
386
392
|
assume_tables_exist: bool = False,
|
387
|
-
timeout_create:
|
393
|
+
timeout_create: TimeDelta | None = None,
|
388
394
|
error_create: type[Exception] = TimeoutError,
|
389
|
-
timeout_insert:
|
395
|
+
timeout_insert: TimeDelta | None = None,
|
390
396
|
error_insert: type[Exception] = TimeoutError,
|
391
397
|
) -> None:
|
392
398
|
"""Insert a set of items into a database.
|
@@ -485,9 +491,9 @@ async def migrate_data(
|
|
485
491
|
table_or_orm_to: TableOrORMInstOrClass | None = None,
|
486
492
|
chunk_size_frac: float = CHUNK_SIZE_FRAC,
|
487
493
|
assume_tables_exist: bool = False,
|
488
|
-
timeout_create:
|
494
|
+
timeout_create: TimeDelta | None = None,
|
489
495
|
error_create: type[Exception] = TimeoutError,
|
490
|
-
timeout_insert:
|
496
|
+
timeout_insert: TimeDelta | None = None,
|
491
497
|
error_insert: type[Exception] = TimeoutError,
|
492
498
|
) -> None:
|
493
499
|
"""Migrate the contents of a table from one database to another."""
|
@@ -615,8 +621,8 @@ class UpsertService(Looper[_InsertItem]):
|
|
615
621
|
"""Service to upsert items to a database."""
|
616
622
|
|
617
623
|
# base
|
618
|
-
freq:
|
619
|
-
backoff:
|
624
|
+
freq: TimeDelta = field(default=SECOND, repr=False)
|
625
|
+
backoff: TimeDelta = field(default=SECOND, repr=False)
|
620
626
|
empty_upon_exit: bool = field(default=True, repr=False)
|
621
627
|
# self
|
622
628
|
engine: AsyncEngine
|
@@ -624,9 +630,9 @@ class UpsertService(Looper[_InsertItem]):
|
|
624
630
|
selected_or_all: _SelectedOrAll = "selected"
|
625
631
|
chunk_size_frac: float = CHUNK_SIZE_FRAC
|
626
632
|
assume_tables_exist: bool = False
|
627
|
-
timeout_create:
|
633
|
+
timeout_create: TimeDelta | None = None
|
628
634
|
error_create: type[Exception] = TimeoutError
|
629
|
-
timeout_insert:
|
635
|
+
timeout_insert: TimeDelta | None = None
|
630
636
|
error_insert: type[Exception] = TimeoutError
|
631
637
|
|
632
638
|
@override
|
@@ -651,11 +657,11 @@ class UpsertServiceMixin:
|
|
651
657
|
"""Mix-in for the upsert service."""
|
652
658
|
|
653
659
|
# base - looper
|
654
|
-
upsert_service_freq:
|
655
|
-
upsert_service_backoff:
|
660
|
+
upsert_service_freq: TimeDelta = field(default=SECOND, repr=False)
|
661
|
+
upsert_service_backoff: TimeDelta = field(default=SECOND, repr=False)
|
656
662
|
upsert_service_empty_upon_exit: bool = field(default=False, repr=False)
|
657
663
|
upsert_service_logger: str | None = field(default=None, repr=False)
|
658
|
-
upsert_service_timeout:
|
664
|
+
upsert_service_timeout: TimeDelta | None = field(default=None, repr=False)
|
659
665
|
upsert_service_debug: bool = field(default=False, repr=False)
|
660
666
|
# base - upsert service
|
661
667
|
upsert_service_database: AsyncEngine
|
@@ -663,9 +669,9 @@ class UpsertServiceMixin:
|
|
663
669
|
upsert_service_selected_or_all: _SelectedOrAll = "selected"
|
664
670
|
upsert_service_chunk_size_frac: float = CHUNK_SIZE_FRAC
|
665
671
|
upsert_service_assume_tables_exist: bool = False
|
666
|
-
upsert_service_timeout_create:
|
672
|
+
upsert_service_timeout_create: TimeDelta | None = None
|
667
673
|
upsert_service_error_create: type[Exception] = TimeoutError
|
668
|
-
upsert_service_timeout_insert:
|
674
|
+
upsert_service_timeout_insert: TimeDelta | None = None
|
669
675
|
upsert_service_error_insert: type[Exception] = TimeoutError
|
670
676
|
# self
|
671
677
|
_upsert_service: UpsertService = field(init=False, repr=False)
|
@@ -713,9 +719,9 @@ async def upsert_items(
|
|
713
719
|
selected_or_all: _SelectedOrAll = "selected",
|
714
720
|
chunk_size_frac: float = CHUNK_SIZE_FRAC,
|
715
721
|
assume_tables_exist: bool = False,
|
716
|
-
timeout_create:
|
722
|
+
timeout_create: TimeDelta | None = None,
|
717
723
|
error_create: type[Exception] = TimeoutError,
|
718
|
-
timeout_insert:
|
724
|
+
timeout_insert: TimeDelta | None = None,
|
719
725
|
error_insert: type[Exception] = TimeoutError,
|
720
726
|
) -> None:
|
721
727
|
"""Upsert a set of items into a database.
|
@@ -835,11 +841,11 @@ async def yield_connection(
|
|
835
841
|
engine: AsyncEngine,
|
836
842
|
/,
|
837
843
|
*,
|
838
|
-
timeout:
|
844
|
+
timeout: TimeDelta | None = None,
|
839
845
|
error: MaybeType[BaseException] = TimeoutError,
|
840
846
|
) -> AsyncIterator[AsyncConnection]:
|
841
847
|
"""Yield an async connection."""
|
842
|
-
async with
|
848
|
+
async with timeout_td(timeout, error=error), engine.begin() as conn:
|
843
849
|
yield conn
|
844
850
|
|
845
851
|
|
utilities/sqlalchemy_polars.py
CHANGED
@@ -26,7 +26,7 @@ from polars import (
|
|
26
26
|
from sqlalchemy import Column, Select, select
|
27
27
|
from sqlalchemy.exc import DuplicateColumnError
|
28
28
|
|
29
|
-
from utilities.asyncio import
|
29
|
+
from utilities.asyncio import timeout_td
|
30
30
|
from utilities.functions import identity
|
31
31
|
from utilities.iterables import (
|
32
32
|
CheckDuplicatesError,
|
@@ -63,11 +63,8 @@ if TYPE_CHECKING:
|
|
63
63
|
from sqlalchemy.ext.asyncio import AsyncEngine
|
64
64
|
from sqlalchemy.sql import ColumnCollection
|
65
65
|
from sqlalchemy.sql.base import ReadOnlyColumnCollection
|
66
|
-
from
|
67
|
-
from tenacity.stop import StopBaseT
|
68
|
-
from tenacity.wait import WaitBaseT
|
66
|
+
from whenever import TimeDelta
|
69
67
|
|
70
|
-
import utilities.types
|
71
68
|
from utilities.types import MaybeType, TimeZoneLike
|
72
69
|
|
73
70
|
|
@@ -81,9 +78,9 @@ async def insert_dataframe(
|
|
81
78
|
chunk_size_frac: float = CHUNK_SIZE_FRAC,
|
82
79
|
assume_tables_exist: bool = False,
|
83
80
|
upsert: Literal["selected", "all"] | None = None,
|
84
|
-
timeout_create:
|
81
|
+
timeout_create: TimeDelta | None = None,
|
85
82
|
error_create: type[Exception] = TimeoutError,
|
86
|
-
timeout_insert:
|
83
|
+
timeout_insert: TimeDelta | None = None,
|
87
84
|
error_insert: type[Exception] = TimeoutError,
|
88
85
|
) -> None:
|
89
86
|
"""Insert/upsert a DataFrame into a database."""
|
@@ -231,10 +228,7 @@ async def select_to_dataframe(
|
|
231
228
|
in_clauses: tuple[Column[Any], Iterable[Any]] | None = None,
|
232
229
|
in_clauses_chunk_size: int | None = None,
|
233
230
|
chunk_size_frac: float = CHUNK_SIZE_FRAC,
|
234
|
-
|
235
|
-
wait: WaitBaseT | None = None,
|
236
|
-
retry: RetryBaseT | None = None,
|
237
|
-
timeout: utilities.types.Duration | None = None,
|
231
|
+
timeout: TimeDelta | None = None,
|
238
232
|
**kwargs: Any,
|
239
233
|
) -> DataFrame: ...
|
240
234
|
@overload
|
@@ -249,10 +243,7 @@ async def select_to_dataframe(
|
|
249
243
|
in_clauses: None = None,
|
250
244
|
in_clauses_chunk_size: int | None = None,
|
251
245
|
chunk_size_frac: float = CHUNK_SIZE_FRAC,
|
252
|
-
|
253
|
-
wait: WaitBaseT | None = None,
|
254
|
-
retry: RetryBaseT | None = None,
|
255
|
-
timeout: utilities.types.Duration | None = None,
|
246
|
+
timeout: TimeDelta | None = None,
|
256
247
|
**kwargs: Any,
|
257
248
|
) -> Iterable[DataFrame]: ...
|
258
249
|
@overload
|
@@ -267,10 +258,7 @@ async def select_to_dataframe(
|
|
267
258
|
in_clauses: tuple[Column[Any], Iterable[Any]],
|
268
259
|
in_clauses_chunk_size: int | None = None,
|
269
260
|
chunk_size_frac: float = CHUNK_SIZE_FRAC,
|
270
|
-
|
271
|
-
wait: WaitBaseT | None = None,
|
272
|
-
retry: RetryBaseT | None = None,
|
273
|
-
timeout: utilities.types.Duration | None = None,
|
261
|
+
timeout: TimeDelta | None = None,
|
274
262
|
**kwargs: Any,
|
275
263
|
) -> AsyncIterable[DataFrame]: ...
|
276
264
|
@overload
|
@@ -285,10 +273,7 @@ async def select_to_dataframe(
|
|
285
273
|
in_clauses: tuple[Column[Any], Iterable[Any]] | None = None,
|
286
274
|
in_clauses_chunk_size: int | None = None,
|
287
275
|
chunk_size_frac: float = CHUNK_SIZE_FRAC,
|
288
|
-
|
289
|
-
wait: WaitBaseT | None = None,
|
290
|
-
retry: RetryBaseT | None = None,
|
291
|
-
timeout: utilities.types.Duration | None = None,
|
276
|
+
timeout: TimeDelta | None = None,
|
292
277
|
**kwargs: Any,
|
293
278
|
) -> DataFrame | Iterable[DataFrame] | AsyncIterable[DataFrame]: ...
|
294
279
|
async def select_to_dataframe(
|
@@ -302,7 +287,7 @@ async def select_to_dataframe(
|
|
302
287
|
in_clauses: tuple[Column[Any], Iterable[Any]] | None = None,
|
303
288
|
in_clauses_chunk_size: int | None = None,
|
304
289
|
chunk_size_frac: float = CHUNK_SIZE_FRAC,
|
305
|
-
timeout:
|
290
|
+
timeout: TimeDelta | None = None,
|
306
291
|
error: MaybeType[BaseException] = TimeoutError,
|
307
292
|
**kwargs: Any,
|
308
293
|
) -> DataFrame | Iterable[DataFrame] | AsyncIterable[DataFrame]:
|
@@ -311,7 +296,7 @@ async def select_to_dataframe(
|
|
311
296
|
sel = _select_to_dataframe_apply_snake(sel)
|
312
297
|
schema = _select_to_dataframe_map_select_to_df_schema(sel, time_zone=time_zone)
|
313
298
|
if in_clauses is None:
|
314
|
-
async with
|
299
|
+
async with timeout_td(timeout, error=error):
|
315
300
|
return read_database(
|
316
301
|
sel,
|
317
302
|
cast("Any", engine),
|
@@ -328,7 +313,7 @@ async def select_to_dataframe(
|
|
328
313
|
chunk_size_frac=chunk_size_frac,
|
329
314
|
)
|
330
315
|
if batch_size is None:
|
331
|
-
async with
|
316
|
+
async with timeout_td(timeout, error=error):
|
332
317
|
dfs = [
|
333
318
|
await select_to_dataframe(
|
334
319
|
sel,
|
@@ -349,7 +334,7 @@ async def select_to_dataframe(
|
|
349
334
|
return DataFrame(schema=schema)
|
350
335
|
|
351
336
|
async def yield_dfs() -> AsyncIterator[DataFrame]:
|
352
|
-
async with
|
337
|
+
async with timeout_td(timeout, error=error):
|
353
338
|
for sel_i in sels:
|
354
339
|
for df in await select_to_dataframe(
|
355
340
|
sel_i,
|
utilities/whenever.py
CHANGED
@@ -1,27 +1,17 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import datetime as dt
|
4
|
-
import re
|
5
4
|
from contextlib import suppress
|
6
5
|
from dataclasses import dataclass
|
7
6
|
from typing import TYPE_CHECKING, override
|
8
7
|
|
9
|
-
from whenever import
|
10
|
-
Date,
|
11
|
-
DateTimeDelta,
|
12
|
-
PlainDateTime,
|
13
|
-
Time,
|
14
|
-
TimeZoneNotFoundError,
|
15
|
-
ZonedDateTime,
|
16
|
-
)
|
8
|
+
from whenever import DateTimeDelta, TimeZoneNotFoundError, ZonedDateTime
|
17
9
|
|
18
10
|
from utilities.datetime import (
|
19
11
|
_MICROSECONDS_PER_DAY,
|
20
12
|
_MICROSECONDS_PER_SECOND,
|
21
13
|
ZERO_TIME,
|
22
|
-
check_date_not_datetime,
|
23
14
|
datetime_duration_to_microseconds,
|
24
|
-
parse_two_digit_year,
|
25
15
|
)
|
26
16
|
from utilities.math import ParseNumberError, parse_number
|
27
17
|
from utilities.re import (
|
@@ -35,7 +25,6 @@ from utilities.zoneinfo import UTC, ensure_time_zone, get_time_zone_name
|
|
35
25
|
if TYPE_CHECKING:
|
36
26
|
from utilities.types import Duration
|
37
27
|
|
38
|
-
|
39
28
|
MAX_SERIALIZABLE_TIMEDELTA = dt.timedelta(days=3652060, microseconds=-1)
|
40
29
|
MIN_SERIALIZABLE_TIMEDELTA = -MAX_SERIALIZABLE_TIMEDELTA
|
41
30
|
|
@@ -84,56 +73,6 @@ class _CheckValidZonedDateTimeUnequalError(CheckValidZonedDateTimeError):
|
|
84
73
|
##
|
85
74
|
|
86
75
|
|
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
76
|
def parse_duration(duration: str, /) -> Duration:
|
138
77
|
"""Parse a string into a Duration."""
|
139
78
|
with suppress(ParseNumberError):
|
@@ -156,48 +95,6 @@ class ParseDurationError(Exception):
|
|
156
95
|
##
|
157
96
|
|
158
97
|
|
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
98
|
def parse_timedelta(timedelta: str, /) -> dt.timedelta:
|
202
99
|
"""Parse a string into a timedelta."""
|
203
100
|
with suppress(ExtractGroupError):
|
@@ -245,47 +142,6 @@ class _ParseTimedeltaNanosecondError(ParseTimedeltaError):
|
|
245
142
|
##
|
246
143
|
|
247
144
|
|
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
145
|
def serialize_duration(duration: Duration, /) -> str:
|
290
146
|
"""Serialize a duration."""
|
291
147
|
if isinstance(duration, int | float):
|
@@ -308,35 +164,6 @@ class SerializeDurationError(Exception):
|
|
308
164
|
##
|
309
165
|
|
310
166
|
|
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
167
|
def serialize_timedelta(timedelta: dt.timedelta, /) -> str:
|
341
168
|
"""Serialize a timedelta."""
|
342
169
|
try:
|
@@ -358,31 +185,6 @@ class SerializeTimeDeltaError(Exception):
|
|
358
185
|
##
|
359
186
|
|
360
187
|
|
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
188
|
def _to_datetime_delta(timedelta: dt.timedelta, /) -> DateTimeDelta:
|
387
189
|
"""Serialize a timedelta."""
|
388
190
|
total_microseconds = datetime_duration_to_microseconds(timedelta)
|
@@ -415,30 +217,14 @@ __all__ = [
|
|
415
217
|
"MAX_SERIALIZABLE_TIMEDELTA",
|
416
218
|
"MIN_SERIALIZABLE_TIMEDELTA",
|
417
219
|
"CheckValidZonedDateTimeError",
|
418
|
-
"ParseDateError",
|
419
|
-
"ParseDateTimeError",
|
420
220
|
"ParseDurationError",
|
421
|
-
"ParsePlainDateTimeError",
|
422
|
-
"ParseTimeError",
|
423
221
|
"ParseTimedeltaError",
|
424
|
-
"ParseZonedDateTimeError",
|
425
222
|
"SerializeDurationError",
|
426
|
-
"SerializePlainDateTimeError",
|
427
223
|
"SerializeTimeDeltaError",
|
428
|
-
"SerializeZonedDateTimeError",
|
429
224
|
"check_valid_zoned_datetime",
|
430
|
-
"
|
431
|
-
"parse_datetime",
|
225
|
+
"check_valid_zoned_datetime",
|
432
226
|
"parse_duration",
|
433
|
-
"parse_plain_datetime",
|
434
|
-
"parse_time",
|
435
227
|
"parse_timedelta",
|
436
|
-
"parse_zoned_datetime",
|
437
|
-
"serialize_date",
|
438
|
-
"serialize_datetime",
|
439
228
|
"serialize_duration",
|
440
|
-
"serialize_plain_datetime",
|
441
|
-
"serialize_time",
|
442
229
|
"serialize_timedelta",
|
443
|
-
"serialize_zoned_datetime",
|
444
230
|
]
|