dycw-utilities 0.148.5__py3-none-any.whl → 0.174.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.
Potentially problematic release.
This version of dycw-utilities might be problematic. Click here for more details.
- dycw_utilities-0.174.12.dist-info/METADATA +41 -0
- dycw_utilities-0.174.12.dist-info/RECORD +104 -0
- dycw_utilities-0.174.12.dist-info/WHEEL +4 -0
- {dycw_utilities-0.148.5.dist-info → dycw_utilities-0.174.12.dist-info}/entry_points.txt +3 -0
- utilities/__init__.py +1 -1
- utilities/{eventkit.py → aeventkit.py} +12 -11
- utilities/altair.py +7 -6
- utilities/asyncio.py +113 -64
- utilities/atomicwrites.py +1 -1
- utilities/atools.py +64 -4
- utilities/cachetools.py +9 -6
- utilities/click.py +145 -49
- utilities/concurrent.py +1 -1
- utilities/contextlib.py +4 -2
- utilities/contextvars.py +20 -1
- utilities/cryptography.py +3 -3
- utilities/dataclasses.py +15 -28
- utilities/docker.py +292 -0
- utilities/enum.py +2 -2
- utilities/errors.py +1 -1
- utilities/fastapi.py +8 -3
- utilities/fpdf2.py +2 -2
- utilities/functions.py +20 -297
- utilities/git.py +19 -0
- utilities/grp.py +28 -0
- utilities/hypothesis.py +360 -78
- utilities/inflect.py +1 -1
- utilities/iterables.py +12 -58
- utilities/jinja2.py +148 -0
- utilities/json.py +1 -1
- utilities/libcst.py +7 -7
- utilities/logging.py +74 -85
- utilities/math.py +8 -4
- utilities/more_itertools.py +4 -6
- utilities/operator.py +1 -1
- utilities/orjson.py +86 -34
- utilities/os.py +49 -2
- utilities/parse.py +2 -2
- utilities/pathlib.py +66 -34
- utilities/permissions.py +297 -0
- utilities/platform.py +5 -5
- utilities/polars.py +932 -420
- utilities/polars_ols.py +1 -1
- utilities/postgres.py +296 -174
- utilities/pottery.py +8 -73
- utilities/pqdm.py +3 -3
- utilities/pwd.py +28 -0
- utilities/pydantic.py +11 -0
- utilities/pydantic_settings.py +240 -0
- utilities/pydantic_settings_sops.py +76 -0
- utilities/pyinstrument.py +5 -5
- utilities/pytest.py +155 -46
- utilities/pytest_plugins/pytest_randomly.py +1 -1
- utilities/pytest_plugins/pytest_regressions.py +7 -3
- utilities/pytest_regressions.py +2 -3
- utilities/random.py +11 -6
- utilities/re.py +1 -1
- utilities/redis.py +101 -64
- utilities/sentinel.py +10 -0
- utilities/shelve.py +4 -1
- utilities/shutil.py +25 -0
- utilities/slack_sdk.py +8 -3
- utilities/sqlalchemy.py +422 -352
- utilities/sqlalchemy_polars.py +28 -52
- utilities/string.py +1 -1
- utilities/subprocess.py +864 -0
- utilities/tempfile.py +62 -4
- utilities/testbook.py +50 -0
- utilities/text.py +165 -42
- utilities/timer.py +2 -2
- utilities/traceback.py +46 -36
- utilities/types.py +62 -23
- utilities/typing.py +479 -19
- utilities/uuid.py +42 -5
- utilities/version.py +27 -26
- utilities/whenever.py +661 -151
- utilities/zoneinfo.py +80 -22
- dycw_utilities-0.148.5.dist-info/METADATA +0 -41
- dycw_utilities-0.148.5.dist-info/RECORD +0 -95
- dycw_utilities-0.148.5.dist-info/WHEEL +0 -4
- dycw_utilities-0.148.5.dist-info/licenses/LICENSE +0 -21
- utilities/period.py +0 -237
- utilities/typed_settings.py +0 -144
utilities/atools.py
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from collections.abc import Callable
|
|
4
|
-
from
|
|
4
|
+
from functools import partial
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import TYPE_CHECKING, Any, cast, overload
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
import atools
|
|
9
|
+
from whenever import TimeDelta
|
|
7
10
|
|
|
8
|
-
from utilities.types import Coro
|
|
11
|
+
from utilities.types import Coro, PathLike
|
|
9
12
|
|
|
10
13
|
if TYPE_CHECKING:
|
|
11
|
-
from
|
|
14
|
+
from atools._memoize_decorator import Keygen, Pickler
|
|
12
15
|
|
|
13
16
|
|
|
14
17
|
type _Key[**P, T] = tuple[Callable[P, Coro[T]], TimeDelta]
|
|
@@ -36,4 +39,61 @@ async def call_memoized[**P, T](
|
|
|
36
39
|
return await memoized_func(*args, **kwargs)
|
|
37
40
|
|
|
38
41
|
|
|
42
|
+
##
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@overload
|
|
46
|
+
def memoize[F: Callable[..., Coro[Any]]](
|
|
47
|
+
func: F,
|
|
48
|
+
/,
|
|
49
|
+
*,
|
|
50
|
+
db_path: PathLike | None = None,
|
|
51
|
+
duration: float | TimeDelta | None = None,
|
|
52
|
+
keygen: Keygen | None = None,
|
|
53
|
+
pickler: Pickler | None = None,
|
|
54
|
+
size: int | None = None,
|
|
55
|
+
) -> F: ...
|
|
56
|
+
@overload
|
|
57
|
+
def memoize[F: Callable[..., Coro[Any]]](
|
|
58
|
+
func: None = None,
|
|
59
|
+
/,
|
|
60
|
+
*,
|
|
61
|
+
db_path: PathLike | None = None,
|
|
62
|
+
duration: float | TimeDelta | None = None,
|
|
63
|
+
keygen: Keygen | None = None,
|
|
64
|
+
pickler: Pickler | None = None,
|
|
65
|
+
size: int | None = None,
|
|
66
|
+
) -> Callable[[F], F]: ...
|
|
67
|
+
def memoize[F: Callable[..., Coro[Any]]](
|
|
68
|
+
func: F | None = None,
|
|
69
|
+
/,
|
|
70
|
+
*,
|
|
71
|
+
db_path: PathLike | None = None,
|
|
72
|
+
duration: float | TimeDelta | None = None,
|
|
73
|
+
keygen: Keygen | None = None,
|
|
74
|
+
pickler: Pickler | None = None,
|
|
75
|
+
size: int | None = None,
|
|
76
|
+
) -> F | Callable[[F], F]:
|
|
77
|
+
"""Memoize a function."""
|
|
78
|
+
if func is None:
|
|
79
|
+
result = partial(
|
|
80
|
+
memoize,
|
|
81
|
+
db_path=db_path,
|
|
82
|
+
duration=duration,
|
|
83
|
+
keygen=keygen,
|
|
84
|
+
pickler=pickler,
|
|
85
|
+
size=size,
|
|
86
|
+
)
|
|
87
|
+
return cast("Callable[[F], F]", result)
|
|
88
|
+
return atools.memoize(
|
|
89
|
+
db_path=None if db_path is None else Path(db_path),
|
|
90
|
+
duration=duration.py_timedelta()
|
|
91
|
+
if isinstance(duration, TimeDelta)
|
|
92
|
+
else duration,
|
|
93
|
+
keygen=keygen,
|
|
94
|
+
pickler=pickler,
|
|
95
|
+
size=size,
|
|
96
|
+
)(func)
|
|
97
|
+
|
|
98
|
+
|
|
39
99
|
__all__ = ["call_memoized"]
|
utilities/cachetools.py
CHANGED
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from collections.abc import Callable, Hashable, Iterable, Iterator, MutableSet
|
|
4
4
|
from math import inf
|
|
5
5
|
from time import monotonic
|
|
6
|
-
from typing import TYPE_CHECKING, Any, override
|
|
6
|
+
from typing import TYPE_CHECKING, Any, cast, override
|
|
7
7
|
|
|
8
8
|
import cachetools
|
|
9
9
|
from cachetools.func import ttl_cache
|
|
@@ -100,11 +100,14 @@ def cache[F: Callable](
|
|
|
100
100
|
typed_: bool = False,
|
|
101
101
|
) -> Callable[[F], F]:
|
|
102
102
|
"""Decorate a function with `max_size` and/or `ttl` settings."""
|
|
103
|
-
return
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
103
|
+
return cast(
|
|
104
|
+
"F",
|
|
105
|
+
ttl_cache(
|
|
106
|
+
maxsize=max_size,
|
|
107
|
+
ttl=inf if max_duration is None else max_duration.in_seconds(),
|
|
108
|
+
timer=timer,
|
|
109
|
+
typed=typed_,
|
|
110
|
+
),
|
|
108
111
|
)
|
|
109
112
|
|
|
110
113
|
|
utilities/click.py
CHANGED
|
@@ -3,21 +3,22 @@ from __future__ import annotations
|
|
|
3
3
|
import enum
|
|
4
4
|
import ipaddress
|
|
5
5
|
import pathlib
|
|
6
|
+
import uuid
|
|
7
|
+
from enum import StrEnum
|
|
6
8
|
from typing import TYPE_CHECKING, TypedDict, assert_never, override
|
|
7
9
|
|
|
8
|
-
import click
|
|
9
10
|
import whenever
|
|
10
11
|
from click import Choice, Context, Parameter, ParamType
|
|
11
12
|
from click.types import IntParamType, StringParamType
|
|
12
13
|
|
|
13
14
|
from utilities.enum import EnsureEnumError, ensure_enum
|
|
14
|
-
from utilities.functions import EnsureStrError, ensure_str, get_class_name
|
|
15
|
-
from utilities.iterables import is_iterable_not_str
|
|
15
|
+
from utilities.functions import EnsureStrError, ensure_str, get_class, get_class_name
|
|
16
|
+
from utilities.iterables import is_iterable_not_str, one_unique
|
|
16
17
|
from utilities.parse import ParseObjectError, parse_object
|
|
17
18
|
from utilities.text import split_str
|
|
18
19
|
|
|
19
20
|
if TYPE_CHECKING:
|
|
20
|
-
from collections.abc import Iterable
|
|
21
|
+
from collections.abc import Iterable
|
|
21
22
|
|
|
22
23
|
from utilities.types import (
|
|
23
24
|
DateDeltaLike,
|
|
@@ -28,6 +29,7 @@ if TYPE_CHECKING:
|
|
|
28
29
|
IPv6AddressLike,
|
|
29
30
|
MaybeStr,
|
|
30
31
|
MonthDayLike,
|
|
32
|
+
PathLike,
|
|
31
33
|
PlainDateTimeLike,
|
|
32
34
|
TimeDeltaLike,
|
|
33
35
|
TimeLike,
|
|
@@ -36,27 +38,23 @@ if TYPE_CHECKING:
|
|
|
36
38
|
)
|
|
37
39
|
|
|
38
40
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
ExistingFilePath = click.Path(
|
|
42
|
-
exists=True, file_okay=True, dir_okay=False, path_type=pathlib.Path
|
|
43
|
-
)
|
|
44
|
-
ExistingDirPath = click.Path(
|
|
45
|
-
exists=True, file_okay=False, dir_okay=True, path_type=pathlib.Path
|
|
46
|
-
)
|
|
47
|
-
|
|
41
|
+
class _ContextSettings(TypedDict):
|
|
42
|
+
context_settings: _ContextSettingsInner
|
|
48
43
|
|
|
49
|
-
class _HelpOptionNames(TypedDict):
|
|
50
|
-
help_option_names: Sequence[str]
|
|
51
44
|
|
|
45
|
+
class _ContextSettingsInner(TypedDict):
|
|
46
|
+
max_content_width: int
|
|
47
|
+
help_option_names: list[str]
|
|
48
|
+
show_default: bool
|
|
52
49
|
|
|
53
|
-
class _ContextSettings(TypedDict):
|
|
54
|
-
context_settings: _HelpOptionNames
|
|
55
50
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
51
|
+
_MAX_CONTENT_WIDTH = 120
|
|
52
|
+
_CONTEXT_SETTINGS_INNER = _ContextSettingsInner(
|
|
53
|
+
max_content_width=_MAX_CONTENT_WIDTH,
|
|
54
|
+
help_option_names=["-h", "--help"],
|
|
55
|
+
show_default=True,
|
|
59
56
|
)
|
|
57
|
+
CONTEXT_SETTINGS = _ContextSettings(context_settings=_CONTEXT_SETTINGS_INNER)
|
|
60
58
|
|
|
61
59
|
|
|
62
60
|
# parameters
|
|
@@ -81,10 +79,10 @@ class Date(ParamType):
|
|
|
81
79
|
return value
|
|
82
80
|
case str():
|
|
83
81
|
try:
|
|
84
|
-
return whenever.Date.
|
|
82
|
+
return whenever.Date.parse_iso(value)
|
|
85
83
|
except ValueError as error:
|
|
86
84
|
self.fail(str(error), param, ctx)
|
|
87
|
-
case
|
|
85
|
+
case never:
|
|
88
86
|
assert_never(never)
|
|
89
87
|
|
|
90
88
|
|
|
@@ -107,10 +105,10 @@ class DateDelta(ParamType):
|
|
|
107
105
|
return value
|
|
108
106
|
case str():
|
|
109
107
|
try:
|
|
110
|
-
return whenever.DateDelta.
|
|
108
|
+
return whenever.DateDelta.parse_iso(value)
|
|
111
109
|
except ValueError as error:
|
|
112
110
|
self.fail(str(error), param, ctx)
|
|
113
|
-
case
|
|
111
|
+
case never:
|
|
114
112
|
assert_never(never)
|
|
115
113
|
|
|
116
114
|
|
|
@@ -133,10 +131,10 @@ class DateTimeDelta(ParamType):
|
|
|
133
131
|
return value
|
|
134
132
|
case str():
|
|
135
133
|
try:
|
|
136
|
-
return whenever.DateTimeDelta.
|
|
134
|
+
return whenever.DateTimeDelta.parse_iso(value)
|
|
137
135
|
except ValueError as error:
|
|
138
136
|
self.fail(str(error), param, ctx)
|
|
139
|
-
case
|
|
137
|
+
case never:
|
|
140
138
|
assert_never(never)
|
|
141
139
|
|
|
142
140
|
|
|
@@ -144,10 +142,13 @@ class Enum[E: enum.Enum](ParamType):
|
|
|
144
142
|
"""An enum-valued parameter."""
|
|
145
143
|
|
|
146
144
|
@override
|
|
147
|
-
def __init__(
|
|
145
|
+
def __init__(
|
|
146
|
+
self, enum: type[E], /, *, value: bool = False, case_sensitive: bool = False
|
|
147
|
+
) -> None:
|
|
148
148
|
cls = get_class_name(enum)
|
|
149
149
|
self.name = f"enum[{cls}]"
|
|
150
150
|
self._enum = enum
|
|
151
|
+
self._value = issubclass(self._enum, StrEnum) or value
|
|
151
152
|
self._case_sensitive = case_sensitive
|
|
152
153
|
super().__init__()
|
|
153
154
|
|
|
@@ -169,7 +170,53 @@ class Enum[E: enum.Enum](ParamType):
|
|
|
169
170
|
@override
|
|
170
171
|
def get_metavar(self, param: Parameter, ctx: Context) -> str | None:
|
|
171
172
|
_ = ctx
|
|
172
|
-
desc = ",".join(e.name for e in self._enum)
|
|
173
|
+
desc = ",".join(str(e.value) if self._value else e.name for e in self._enum)
|
|
174
|
+
return _make_metavar(param, desc)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class EnumPartial[E: enum.Enum](ParamType):
|
|
178
|
+
"""An enum-valued parameter."""
|
|
179
|
+
|
|
180
|
+
@override
|
|
181
|
+
def __init__(
|
|
182
|
+
self,
|
|
183
|
+
members: Iterable[E],
|
|
184
|
+
/,
|
|
185
|
+
*,
|
|
186
|
+
value: bool = False,
|
|
187
|
+
case_sensitive: bool = False,
|
|
188
|
+
) -> None:
|
|
189
|
+
self._members = list(members)
|
|
190
|
+
self._enum = one_unique(get_class(e) for e in self._members)
|
|
191
|
+
cls = get_class_name(self._enum)
|
|
192
|
+
self.name = f"enum-partial[{cls}]"
|
|
193
|
+
self._value = issubclass(self._enum, StrEnum) or value
|
|
194
|
+
self._case_sensitive = case_sensitive
|
|
195
|
+
super().__init__()
|
|
196
|
+
|
|
197
|
+
@override
|
|
198
|
+
def __repr__(self) -> str:
|
|
199
|
+
cls = get_class_name(self._enum)
|
|
200
|
+
return f"ENUMPARTIAL[{cls}]"
|
|
201
|
+
|
|
202
|
+
@override
|
|
203
|
+
def convert(
|
|
204
|
+
self, value: EnumLike[E], param: Parameter | None, ctx: Context | None
|
|
205
|
+
) -> E:
|
|
206
|
+
"""Convert a value into the `Enum` type."""
|
|
207
|
+
try:
|
|
208
|
+
enum = ensure_enum(value, self._enum, case_sensitive=self._case_sensitive)
|
|
209
|
+
except EnsureEnumError as error:
|
|
210
|
+
self.fail(str(error), param, ctx)
|
|
211
|
+
if enum in self._members:
|
|
212
|
+
return enum
|
|
213
|
+
self.fail(f"{enum.value!r} is not a selected member")
|
|
214
|
+
return None
|
|
215
|
+
|
|
216
|
+
@override
|
|
217
|
+
def get_metavar(self, param: Parameter, ctx: Context) -> str | None:
|
|
218
|
+
_ = ctx
|
|
219
|
+
desc = ",".join(str(e.value) if self._value else e.name for e in self._members)
|
|
173
220
|
return _make_metavar(param, desc)
|
|
174
221
|
|
|
175
222
|
|
|
@@ -195,7 +242,7 @@ class IPv4Address(ParamType):
|
|
|
195
242
|
return parse_object(ipaddress.IPv4Address, value)
|
|
196
243
|
except ParseObjectError as error:
|
|
197
244
|
self.fail(str(error), param, ctx)
|
|
198
|
-
case
|
|
245
|
+
case never:
|
|
199
246
|
assert_never(never)
|
|
200
247
|
|
|
201
248
|
|
|
@@ -221,7 +268,7 @@ class IPv6Address(ParamType):
|
|
|
221
268
|
return parse_object(ipaddress.IPv6Address, value)
|
|
222
269
|
except ParseObjectError as error:
|
|
223
270
|
self.fail(str(error), param, ctx)
|
|
224
|
-
case
|
|
271
|
+
case never:
|
|
225
272
|
assert_never(never)
|
|
226
273
|
|
|
227
274
|
|
|
@@ -244,10 +291,33 @@ class MonthDay(ParamType):
|
|
|
244
291
|
return value
|
|
245
292
|
case str():
|
|
246
293
|
try:
|
|
247
|
-
return whenever.MonthDay.
|
|
294
|
+
return whenever.MonthDay.parse_iso(value)
|
|
248
295
|
except ValueError as error:
|
|
249
296
|
self.fail(str(error), param, ctx)
|
|
250
|
-
case
|
|
297
|
+
case never:
|
|
298
|
+
assert_never(never)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
class Path(ParamType):
|
|
302
|
+
"""A path-valued parameter."""
|
|
303
|
+
|
|
304
|
+
name = "path"
|
|
305
|
+
|
|
306
|
+
@override
|
|
307
|
+
def __repr__(self) -> str:
|
|
308
|
+
return self.name.upper()
|
|
309
|
+
|
|
310
|
+
@override
|
|
311
|
+
def convert(
|
|
312
|
+
self, value: PathLike, param: Parameter | None, ctx: Context | None
|
|
313
|
+
) -> pathlib.Path:
|
|
314
|
+
"""Convert a value into the `Path` type."""
|
|
315
|
+
match value:
|
|
316
|
+
case pathlib.Path():
|
|
317
|
+
return value.expanduser()
|
|
318
|
+
case str():
|
|
319
|
+
return pathlib.Path(value).expanduser()
|
|
320
|
+
case never:
|
|
251
321
|
assert_never(never)
|
|
252
322
|
|
|
253
323
|
|
|
@@ -270,10 +340,10 @@ class PlainDateTime(ParamType):
|
|
|
270
340
|
return value
|
|
271
341
|
case str():
|
|
272
342
|
try:
|
|
273
|
-
return whenever.PlainDateTime.
|
|
343
|
+
return whenever.PlainDateTime.parse_iso(value)
|
|
274
344
|
except ValueError as error:
|
|
275
345
|
self.fail(str(error), param, ctx)
|
|
276
|
-
case
|
|
346
|
+
case never:
|
|
277
347
|
assert_never(never)
|
|
278
348
|
|
|
279
349
|
|
|
@@ -296,10 +366,10 @@ class Time(ParamType):
|
|
|
296
366
|
return value
|
|
297
367
|
case str():
|
|
298
368
|
try:
|
|
299
|
-
return whenever.Time.
|
|
369
|
+
return whenever.Time.parse_iso(value)
|
|
300
370
|
except ValueError as error:
|
|
301
371
|
self.fail(str(error), param, ctx)
|
|
302
|
-
case
|
|
372
|
+
case never:
|
|
303
373
|
assert_never(never)
|
|
304
374
|
|
|
305
375
|
|
|
@@ -322,10 +392,36 @@ class TimeDelta(ParamType):
|
|
|
322
392
|
return value
|
|
323
393
|
case str():
|
|
324
394
|
try:
|
|
325
|
-
return whenever.TimeDelta.
|
|
395
|
+
return whenever.TimeDelta.parse_iso(value)
|
|
396
|
+
except ValueError as error:
|
|
397
|
+
self.fail(str(error), param, ctx)
|
|
398
|
+
case never:
|
|
399
|
+
assert_never(never)
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
class UUID(ParamType):
|
|
403
|
+
"""A UUID-valued parameter."""
|
|
404
|
+
|
|
405
|
+
name = "uuid"
|
|
406
|
+
|
|
407
|
+
@override
|
|
408
|
+
def __repr__(self) -> str:
|
|
409
|
+
return self.name.upper()
|
|
410
|
+
|
|
411
|
+
@override
|
|
412
|
+
def convert(
|
|
413
|
+
self, value: uuid.UUID | str, param: Parameter | None, ctx: Context | None
|
|
414
|
+
) -> uuid.UUID:
|
|
415
|
+
"""Convert a value into the `UUID` type."""
|
|
416
|
+
match value:
|
|
417
|
+
case uuid.UUID():
|
|
418
|
+
return value
|
|
419
|
+
case str():
|
|
420
|
+
try:
|
|
421
|
+
return uuid.UUID(value)
|
|
326
422
|
except ValueError as error:
|
|
327
423
|
self.fail(str(error), param, ctx)
|
|
328
|
-
case
|
|
424
|
+
case never:
|
|
329
425
|
assert_never(never)
|
|
330
426
|
|
|
331
427
|
|
|
@@ -348,10 +444,10 @@ class YearMonth(ParamType):
|
|
|
348
444
|
return value
|
|
349
445
|
case str():
|
|
350
446
|
try:
|
|
351
|
-
return whenever.YearMonth.
|
|
447
|
+
return whenever.YearMonth.parse_iso(value)
|
|
352
448
|
except ValueError as error:
|
|
353
449
|
self.fail(str(error), param, ctx)
|
|
354
|
-
case
|
|
450
|
+
case never:
|
|
355
451
|
assert_never(never)
|
|
356
452
|
|
|
357
453
|
|
|
@@ -374,10 +470,10 @@ class ZonedDateTime(ParamType):
|
|
|
374
470
|
return value
|
|
375
471
|
case str():
|
|
376
472
|
try:
|
|
377
|
-
return whenever.ZonedDateTime.
|
|
473
|
+
return whenever.ZonedDateTime.parse_iso(value)
|
|
378
474
|
except ValueError as error:
|
|
379
475
|
self.fail(str(error), param, ctx)
|
|
380
|
-
case
|
|
476
|
+
case never:
|
|
381
477
|
assert_never(never)
|
|
382
478
|
|
|
383
479
|
|
|
@@ -429,7 +525,7 @@ class FrozenSetChoices(FrozenSetParameter[Choice, str]):
|
|
|
429
525
|
@override
|
|
430
526
|
def __init__(
|
|
431
527
|
self,
|
|
432
|
-
choices:
|
|
528
|
+
choices: list[str],
|
|
433
529
|
/,
|
|
434
530
|
*,
|
|
435
531
|
case_sensitive: bool = False,
|
|
@@ -514,7 +610,7 @@ class ListChoices(ListParameter[Choice, str]):
|
|
|
514
610
|
@override
|
|
515
611
|
def __init__(
|
|
516
612
|
self,
|
|
517
|
-
choices:
|
|
613
|
+
choices: list[str],
|
|
518
614
|
/,
|
|
519
615
|
*,
|
|
520
616
|
case_sensitive: bool = False,
|
|
@@ -560,15 +656,13 @@ def _make_metavar(param: Parameter, desc: str, /) -> str:
|
|
|
560
656
|
|
|
561
657
|
|
|
562
658
|
__all__ = [
|
|
563
|
-
"
|
|
659
|
+
"CONTEXT_SETTINGS",
|
|
660
|
+
"UUID",
|
|
564
661
|
"Date",
|
|
565
662
|
"DateDelta",
|
|
566
663
|
"DateTimeDelta",
|
|
567
|
-
"DirPath",
|
|
568
664
|
"Enum",
|
|
569
|
-
"
|
|
570
|
-
"ExistingFilePath",
|
|
571
|
-
"FilePath",
|
|
665
|
+
"EnumPartial",
|
|
572
666
|
"FrozenSetChoices",
|
|
573
667
|
"FrozenSetEnums",
|
|
574
668
|
"FrozenSetParameter",
|
|
@@ -581,6 +675,8 @@ __all__ = [
|
|
|
581
675
|
"ListParameter",
|
|
582
676
|
"ListStrs",
|
|
583
677
|
"MonthDay",
|
|
678
|
+
"Path",
|
|
679
|
+
"Path",
|
|
584
680
|
"PlainDateTime",
|
|
585
681
|
"Time",
|
|
586
682
|
"TimeDelta",
|
utilities/concurrent.py
CHANGED
utilities/contextlib.py
CHANGED
|
@@ -8,7 +8,7 @@ from contextlib import (
|
|
|
8
8
|
asynccontextmanager,
|
|
9
9
|
contextmanager,
|
|
10
10
|
)
|
|
11
|
-
from functools import partial
|
|
11
|
+
from functools import partial, wraps
|
|
12
12
|
from signal import SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM, getsignal, signal
|
|
13
13
|
from typing import TYPE_CHECKING, Any, assert_never, cast, overload
|
|
14
14
|
|
|
@@ -77,6 +77,7 @@ def enhanced_context_manager[**P, T_co](
|
|
|
77
77
|
make_gcm = contextmanager(func)
|
|
78
78
|
|
|
79
79
|
@contextmanager
|
|
80
|
+
@wraps(func)
|
|
80
81
|
def wrapped(*args: P.args, **kwargs: P.kwargs) -> Iterator[T_co]:
|
|
81
82
|
gcm = make_gcm(*args, **kwargs)
|
|
82
83
|
sigabrt0 = _swap_handler(SIGABRT, gcm) if sigabrt else None
|
|
@@ -159,6 +160,7 @@ def enhanced_async_context_manager[**P, T_co](
|
|
|
159
160
|
make_agcm = asynccontextmanager(func)
|
|
160
161
|
|
|
161
162
|
@asynccontextmanager
|
|
163
|
+
@wraps(func)
|
|
162
164
|
async def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncIterator[T_co]:
|
|
163
165
|
agcm = make_agcm(*args, **kwargs)
|
|
164
166
|
sigabrt0 = _swap_handler(SIGABRT, agcm) if sigabrt else None
|
|
@@ -210,7 +212,7 @@ def _make_handler(
|
|
|
210
212
|
_ = loop.call_soon_threadsafe(
|
|
211
213
|
create_task, agcm.__aexit__(None, None, None)
|
|
212
214
|
)
|
|
213
|
-
case
|
|
215
|
+
case never:
|
|
214
216
|
assert_never(never)
|
|
215
217
|
if callable(orig_handler): # pragma: no cover
|
|
216
218
|
orig_handler(signum, frame)
|
utilities/contextvars.py
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from contextlib import contextmanager
|
|
3
4
|
from contextvars import ContextVar
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from collections.abc import Iterator
|
|
9
|
+
|
|
4
10
|
|
|
5
11
|
##
|
|
6
12
|
|
|
@@ -19,4 +25,17 @@ def set_global_breakpoint() -> None:
|
|
|
19
25
|
_ = _GLOBAL_BREAKPOINT.set(True)
|
|
20
26
|
|
|
21
27
|
|
|
22
|
-
|
|
28
|
+
##
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@contextmanager
|
|
32
|
+
def yield_set_context(var: ContextVar[bool], /) -> Iterator[None]:
|
|
33
|
+
"""Yield a context var as being set."""
|
|
34
|
+
token = var.set(True)
|
|
35
|
+
try:
|
|
36
|
+
yield
|
|
37
|
+
finally:
|
|
38
|
+
_ = var.reset(token)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
__all__ = ["global_breakpoint", "set_global_breakpoint", "yield_set_context"]
|
utilities/cryptography.py
CHANGED
|
@@ -11,18 +11,18 @@ _ENV_VAR = "FERNET_KEY"
|
|
|
11
11
|
|
|
12
12
|
def encrypt(text: str, /, *, env_var: str = _ENV_VAR) -> bytes:
|
|
13
13
|
"""Encrypt a string."""
|
|
14
|
-
return get_fernet(env_var
|
|
14
|
+
return get_fernet(env_var).encrypt(text.encode())
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def decrypt(text: bytes, /, *, env_var: str = _ENV_VAR) -> str:
|
|
18
18
|
"""Encrypt a string."""
|
|
19
|
-
return get_fernet(env_var
|
|
19
|
+
return get_fernet(env_var).decrypt(text).decode()
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
##
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
def get_fernet(
|
|
25
|
+
def get_fernet(env_var: str = _ENV_VAR, /) -> Fernet:
|
|
26
26
|
"""Get the Fernet key."""
|
|
27
27
|
if (key := getenv(env_var)) is None:
|
|
28
28
|
raise GetFernetError(env_var=env_var)
|