dycw-utilities 0.135.0__py3-none-any.whl → 0.178.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.
Potentially problematic release.
This version of dycw-utilities might be problematic. Click here for more details.
- dycw_utilities-0.178.1.dist-info/METADATA +34 -0
- dycw_utilities-0.178.1.dist-info/RECORD +105 -0
- dycw_utilities-0.178.1.dist-info/WHEEL +4 -0
- dycw_utilities-0.178.1.dist-info/entry_points.txt +4 -0
- utilities/__init__.py +1 -1
- utilities/altair.py +13 -10
- utilities/asyncio.py +312 -787
- utilities/atomicwrites.py +18 -6
- utilities/atools.py +64 -4
- utilities/cachetools.py +9 -6
- utilities/click.py +195 -77
- utilities/concurrent.py +1 -1
- utilities/contextlib.py +216 -17
- utilities/contextvars.py +20 -1
- utilities/cryptography.py +3 -3
- utilities/dataclasses.py +15 -28
- utilities/docker.py +387 -0
- utilities/enum.py +2 -2
- utilities/errors.py +17 -3
- utilities/fastapi.py +28 -59
- utilities/fpdf2.py +2 -2
- utilities/functions.py +24 -269
- utilities/git.py +9 -30
- utilities/grp.py +28 -0
- utilities/gzip.py +31 -0
- utilities/http.py +3 -2
- utilities/hypothesis.py +513 -159
- utilities/importlib.py +17 -1
- utilities/inflect.py +12 -4
- utilities/iterables.py +33 -58
- utilities/jinja2.py +148 -0
- utilities/json.py +70 -0
- utilities/libcst.py +38 -17
- utilities/lightweight_charts.py +4 -7
- utilities/logging.py +136 -93
- utilities/math.py +8 -4
- utilities/more_itertools.py +43 -45
- utilities/operator.py +27 -27
- utilities/orjson.py +189 -36
- utilities/os.py +61 -4
- utilities/packaging.py +115 -0
- utilities/parse.py +8 -5
- utilities/pathlib.py +269 -40
- utilities/permissions.py +298 -0
- utilities/platform.py +7 -6
- utilities/polars.py +1205 -413
- utilities/polars_ols.py +1 -1
- utilities/postgres.py +408 -0
- utilities/pottery.py +43 -19
- utilities/pqdm.py +3 -3
- utilities/psutil.py +5 -57
- utilities/pwd.py +28 -0
- utilities/pydantic.py +4 -52
- utilities/pydantic_settings.py +240 -0
- utilities/pydantic_settings_sops.py +76 -0
- utilities/pyinstrument.py +7 -7
- utilities/pytest.py +104 -143
- utilities/pytest_plugins/__init__.py +1 -0
- utilities/pytest_plugins/pytest_randomly.py +23 -0
- utilities/pytest_plugins/pytest_regressions.py +56 -0
- utilities/pytest_regressions.py +26 -46
- utilities/random.py +11 -6
- utilities/re.py +1 -1
- utilities/redis.py +220 -343
- utilities/sentinel.py +10 -0
- utilities/shelve.py +4 -1
- utilities/shutil.py +25 -0
- utilities/slack_sdk.py +35 -104
- utilities/sqlalchemy.py +496 -471
- utilities/sqlalchemy_polars.py +29 -54
- utilities/string.py +2 -3
- utilities/subprocess.py +1977 -0
- utilities/tempfile.py +112 -4
- utilities/testbook.py +50 -0
- utilities/text.py +174 -42
- utilities/throttle.py +158 -0
- utilities/timer.py +2 -2
- utilities/traceback.py +70 -35
- utilities/types.py +102 -30
- utilities/typing.py +479 -19
- utilities/uuid.py +42 -5
- utilities/version.py +27 -26
- utilities/whenever.py +1559 -361
- utilities/zoneinfo.py +80 -22
- dycw_utilities-0.135.0.dist-info/METADATA +0 -39
- dycw_utilities-0.135.0.dist-info/RECORD +0 -96
- dycw_utilities-0.135.0.dist-info/WHEEL +0 -4
- dycw_utilities-0.135.0.dist-info/licenses/LICENSE +0 -21
- utilities/aiolimiter.py +0 -25
- utilities/arq.py +0 -216
- utilities/eventkit.py +0 -388
- utilities/luigi.py +0 -183
- utilities/period.py +0 -152
- utilities/pudb.py +0 -62
- utilities/python_dotenv.py +0 -101
- utilities/streamlit.py +0 -105
- utilities/typed_settings.py +0 -123
utilities/atomicwrites.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import gzip
|
|
3
4
|
import shutil
|
|
4
5
|
from contextlib import ExitStack, contextmanager
|
|
5
6
|
from dataclasses import dataclass
|
|
6
7
|
from pathlib import Path
|
|
7
|
-
from shutil import rmtree
|
|
8
|
+
from shutil import copyfileobj, rmtree
|
|
8
9
|
from typing import TYPE_CHECKING, assert_never, override
|
|
9
10
|
|
|
10
11
|
from atomicwrites import replace_atomic
|
|
@@ -56,7 +57,7 @@ def move(
|
|
|
56
57
|
raise ImpossibleCaseError(
|
|
57
58
|
case=[f"{source.is_file()=}", f"{source.is_dir()=}"]
|
|
58
59
|
)
|
|
59
|
-
case
|
|
60
|
+
case never:
|
|
60
61
|
assert_never(never)
|
|
61
62
|
|
|
62
63
|
|
|
@@ -111,21 +112,32 @@ def move_many(*paths: tuple[PathLike, PathLike], overwrite: bool = False) -> Non
|
|
|
111
112
|
|
|
112
113
|
|
|
113
114
|
@contextmanager
|
|
114
|
-
def writer(
|
|
115
|
+
def writer(
|
|
116
|
+
path: PathLike, /, *, compress: bool = False, overwrite: bool = False
|
|
117
|
+
) -> Iterator[Path]:
|
|
115
118
|
"""Yield a path for atomically writing files to disk."""
|
|
116
119
|
path = Path(path)
|
|
117
120
|
parent = path.parent
|
|
118
121
|
parent.mkdir(parents=True, exist_ok=True)
|
|
119
122
|
name = path.name
|
|
120
123
|
with TemporaryDirectory(suffix=".tmp", prefix=name, dir=parent) as temp_dir:
|
|
121
|
-
|
|
124
|
+
temp_path1 = Path(temp_dir, name)
|
|
122
125
|
try:
|
|
123
|
-
yield
|
|
126
|
+
yield temp_path1
|
|
124
127
|
except KeyboardInterrupt:
|
|
125
128
|
rmtree(temp_dir)
|
|
126
129
|
else:
|
|
130
|
+
if compress:
|
|
131
|
+
temp_path2 = Path(temp_dir, f"{name}.gz")
|
|
132
|
+
with (
|
|
133
|
+
temp_path1.open("rb") as source,
|
|
134
|
+
gzip.open(temp_path2, mode="wb") as dest,
|
|
135
|
+
):
|
|
136
|
+
copyfileobj(source, dest)
|
|
137
|
+
else:
|
|
138
|
+
temp_path2 = temp_path1
|
|
127
139
|
try:
|
|
128
|
-
move(
|
|
140
|
+
move(temp_path2, path, overwrite=overwrite)
|
|
129
141
|
except _MoveSourceNotFoundError as error:
|
|
130
142
|
raise _WriterTemporaryPathEmptyError(temp_path=error.source) from None
|
|
131
143
|
except _MoveFileExistsError as error:
|
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,23 +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
|
-
from click.types import StringParamType
|
|
12
|
+
from click.types import IntParamType, StringParamType
|
|
12
13
|
|
|
13
|
-
import utilities.whenever
|
|
14
14
|
from utilities.enum import EnsureEnumError, ensure_enum
|
|
15
|
-
from utilities.functions import EnsureStrError, ensure_str, get_class_name
|
|
16
|
-
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
|
|
17
17
|
from utilities.parse import ParseObjectError, parse_object
|
|
18
18
|
from utilities.text import split_str
|
|
19
|
-
from utilities.whenever import FreqLike, _FreqParseError, _MonthParseCommonISOError
|
|
20
19
|
|
|
21
20
|
if TYPE_CHECKING:
|
|
22
|
-
from collections.abc import Iterable
|
|
21
|
+
from collections.abc import Iterable
|
|
23
22
|
|
|
24
23
|
from utilities.types import (
|
|
25
24
|
DateDeltaLike,
|
|
@@ -29,35 +28,33 @@ if TYPE_CHECKING:
|
|
|
29
28
|
IPv4AddressLike,
|
|
30
29
|
IPv6AddressLike,
|
|
31
30
|
MaybeStr,
|
|
31
|
+
MonthDayLike,
|
|
32
|
+
PathLike,
|
|
32
33
|
PlainDateTimeLike,
|
|
33
34
|
TimeDeltaLike,
|
|
34
35
|
TimeLike,
|
|
36
|
+
YearMonthLike,
|
|
35
37
|
ZonedDateTimeLike,
|
|
36
38
|
)
|
|
37
|
-
from utilities.whenever import MonthLike
|
|
38
39
|
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
ExistingFilePath = click.Path(
|
|
43
|
-
exists=True, file_okay=True, dir_okay=False, path_type=pathlib.Path
|
|
44
|
-
)
|
|
45
|
-
ExistingDirPath = click.Path(
|
|
46
|
-
exists=True, file_okay=False, dir_okay=True, path_type=pathlib.Path
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
class _HelpOptionNames(TypedDict):
|
|
51
|
-
help_option_names: Sequence[str]
|
|
41
|
+
class _ContextSettings(TypedDict):
|
|
42
|
+
context_settings: _ContextSettingsInner
|
|
52
43
|
|
|
53
44
|
|
|
54
|
-
class
|
|
55
|
-
|
|
45
|
+
class _ContextSettingsInner(TypedDict):
|
|
46
|
+
max_content_width: int
|
|
47
|
+
help_option_names: list[str]
|
|
48
|
+
show_default: bool
|
|
56
49
|
|
|
57
50
|
|
|
58
|
-
|
|
59
|
-
|
|
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,
|
|
60
56
|
)
|
|
57
|
+
CONTEXT_SETTINGS = _ContextSettings(context_settings=_CONTEXT_SETTINGS_INNER)
|
|
61
58
|
|
|
62
59
|
|
|
63
60
|
# parameters
|
|
@@ -82,10 +79,10 @@ class Date(ParamType):
|
|
|
82
79
|
return value
|
|
83
80
|
case str():
|
|
84
81
|
try:
|
|
85
|
-
return whenever.Date.
|
|
82
|
+
return whenever.Date.parse_iso(value)
|
|
86
83
|
except ValueError as error:
|
|
87
84
|
self.fail(str(error), param, ctx)
|
|
88
|
-
case
|
|
85
|
+
case never:
|
|
89
86
|
assert_never(never)
|
|
90
87
|
|
|
91
88
|
|
|
@@ -108,10 +105,10 @@ class DateDelta(ParamType):
|
|
|
108
105
|
return value
|
|
109
106
|
case str():
|
|
110
107
|
try:
|
|
111
|
-
return whenever.DateDelta.
|
|
108
|
+
return whenever.DateDelta.parse_iso(value)
|
|
112
109
|
except ValueError as error:
|
|
113
110
|
self.fail(str(error), param, ctx)
|
|
114
|
-
case
|
|
111
|
+
case never:
|
|
115
112
|
assert_never(never)
|
|
116
113
|
|
|
117
114
|
|
|
@@ -134,10 +131,10 @@ class DateTimeDelta(ParamType):
|
|
|
134
131
|
return value
|
|
135
132
|
case str():
|
|
136
133
|
try:
|
|
137
|
-
return whenever.DateTimeDelta.
|
|
134
|
+
return whenever.DateTimeDelta.parse_iso(value)
|
|
138
135
|
except ValueError as error:
|
|
139
136
|
self.fail(str(error), param, ctx)
|
|
140
|
-
case
|
|
137
|
+
case never:
|
|
141
138
|
assert_never(never)
|
|
142
139
|
|
|
143
140
|
|
|
@@ -145,10 +142,13 @@ class Enum[E: enum.Enum](ParamType):
|
|
|
145
142
|
"""An enum-valued parameter."""
|
|
146
143
|
|
|
147
144
|
@override
|
|
148
|
-
def __init__(
|
|
145
|
+
def __init__(
|
|
146
|
+
self, enum: type[E], /, *, value: bool = False, case_sensitive: bool = False
|
|
147
|
+
) -> None:
|
|
149
148
|
cls = get_class_name(enum)
|
|
150
149
|
self.name = f"enum[{cls}]"
|
|
151
150
|
self._enum = enum
|
|
151
|
+
self._value = issubclass(self._enum, StrEnum) or value
|
|
152
152
|
self._case_sensitive = case_sensitive
|
|
153
153
|
super().__init__()
|
|
154
154
|
|
|
@@ -170,34 +170,53 @@ class Enum[E: enum.Enum](ParamType):
|
|
|
170
170
|
@override
|
|
171
171
|
def get_metavar(self, param: Parameter, ctx: Context) -> str | None:
|
|
172
172
|
_ = ctx
|
|
173
|
-
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
174
|
return _make_metavar(param, desc)
|
|
175
175
|
|
|
176
176
|
|
|
177
|
-
class
|
|
178
|
-
"""An
|
|
177
|
+
class EnumPartial[E: enum.Enum](ParamType):
|
|
178
|
+
"""An enum-valued parameter."""
|
|
179
179
|
|
|
180
|
-
|
|
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__()
|
|
181
196
|
|
|
182
197
|
@override
|
|
183
198
|
def __repr__(self) -> str:
|
|
184
|
-
|
|
199
|
+
cls = get_class_name(self._enum)
|
|
200
|
+
return f"ENUMPARTIAL[{cls}]"
|
|
185
201
|
|
|
186
202
|
@override
|
|
187
203
|
def convert(
|
|
188
|
-
self, value:
|
|
189
|
-
) ->
|
|
190
|
-
"""Convert a value into the `
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
+
return self.fail(f"{enum.value!r} is not a selected member")
|
|
214
|
+
|
|
215
|
+
@override
|
|
216
|
+
def get_metavar(self, param: Parameter, ctx: Context) -> str | None:
|
|
217
|
+
_ = ctx
|
|
218
|
+
desc = ",".join(str(e.value) if self._value else e.name for e in self._members)
|
|
219
|
+
return _make_metavar(param, desc)
|
|
201
220
|
|
|
202
221
|
|
|
203
222
|
class IPv4Address(ParamType):
|
|
@@ -222,7 +241,7 @@ class IPv4Address(ParamType):
|
|
|
222
241
|
return parse_object(ipaddress.IPv4Address, value)
|
|
223
242
|
except ParseObjectError as error:
|
|
224
243
|
self.fail(str(error), param, ctx)
|
|
225
|
-
case
|
|
244
|
+
case never:
|
|
226
245
|
assert_never(never)
|
|
227
246
|
|
|
228
247
|
|
|
@@ -248,14 +267,14 @@ class IPv6Address(ParamType):
|
|
|
248
267
|
return parse_object(ipaddress.IPv6Address, value)
|
|
249
268
|
except ParseObjectError as error:
|
|
250
269
|
self.fail(str(error), param, ctx)
|
|
251
|
-
case
|
|
270
|
+
case never:
|
|
252
271
|
assert_never(never)
|
|
253
272
|
|
|
254
273
|
|
|
255
|
-
class
|
|
256
|
-
"""A month-
|
|
274
|
+
class MonthDay(ParamType):
|
|
275
|
+
"""A month-day parameter."""
|
|
257
276
|
|
|
258
|
-
name = "month"
|
|
277
|
+
name = "month-day"
|
|
259
278
|
|
|
260
279
|
@override
|
|
261
280
|
def __repr__(self) -> str:
|
|
@@ -263,13 +282,42 @@ class Month(ParamType):
|
|
|
263
282
|
|
|
264
283
|
@override
|
|
265
284
|
def convert(
|
|
266
|
-
self, value:
|
|
267
|
-
) ->
|
|
268
|
-
"""Convert a value into the `
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
285
|
+
self, value: MonthDayLike, param: Parameter | None, ctx: Context | None
|
|
286
|
+
) -> whenever.MonthDay:
|
|
287
|
+
"""Convert a value into the `MonthDay` type."""
|
|
288
|
+
match value:
|
|
289
|
+
case whenever.MonthDay():
|
|
290
|
+
return value
|
|
291
|
+
case str():
|
|
292
|
+
try:
|
|
293
|
+
return whenever.MonthDay.parse_iso(value)
|
|
294
|
+
except ValueError as error:
|
|
295
|
+
self.fail(str(error), param, ctx)
|
|
296
|
+
case never:
|
|
297
|
+
assert_never(never)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
class Path(ParamType):
|
|
301
|
+
"""A path-valued parameter."""
|
|
302
|
+
|
|
303
|
+
name = "path"
|
|
304
|
+
|
|
305
|
+
@override
|
|
306
|
+
def __repr__(self) -> str:
|
|
307
|
+
return self.name.upper()
|
|
308
|
+
|
|
309
|
+
@override
|
|
310
|
+
def convert(
|
|
311
|
+
self, value: PathLike, param: Parameter | None, ctx: Context | None
|
|
312
|
+
) -> pathlib.Path:
|
|
313
|
+
"""Convert a value into the `Path` type."""
|
|
314
|
+
match value:
|
|
315
|
+
case pathlib.Path():
|
|
316
|
+
return value.expanduser()
|
|
317
|
+
case str():
|
|
318
|
+
return pathlib.Path(value).expanduser()
|
|
319
|
+
case never:
|
|
320
|
+
assert_never(never)
|
|
273
321
|
|
|
274
322
|
|
|
275
323
|
class PlainDateTime(ParamType):
|
|
@@ -291,10 +339,10 @@ class PlainDateTime(ParamType):
|
|
|
291
339
|
return value
|
|
292
340
|
case str():
|
|
293
341
|
try:
|
|
294
|
-
return whenever.PlainDateTime.
|
|
342
|
+
return whenever.PlainDateTime.parse_iso(value)
|
|
295
343
|
except ValueError as error:
|
|
296
344
|
self.fail(str(error), param, ctx)
|
|
297
|
-
case
|
|
345
|
+
case never:
|
|
298
346
|
assert_never(never)
|
|
299
347
|
|
|
300
348
|
|
|
@@ -317,10 +365,10 @@ class Time(ParamType):
|
|
|
317
365
|
return value
|
|
318
366
|
case str():
|
|
319
367
|
try:
|
|
320
|
-
return whenever.Time.
|
|
368
|
+
return whenever.Time.parse_iso(value)
|
|
321
369
|
except ValueError as error:
|
|
322
370
|
self.fail(str(error), param, ctx)
|
|
323
|
-
case
|
|
371
|
+
case never:
|
|
324
372
|
assert_never(never)
|
|
325
373
|
|
|
326
374
|
|
|
@@ -343,10 +391,62 @@ class TimeDelta(ParamType):
|
|
|
343
391
|
return value
|
|
344
392
|
case str():
|
|
345
393
|
try:
|
|
346
|
-
return whenever.TimeDelta.
|
|
394
|
+
return whenever.TimeDelta.parse_iso(value)
|
|
395
|
+
except ValueError as error:
|
|
396
|
+
self.fail(str(error), param, ctx)
|
|
397
|
+
case never:
|
|
398
|
+
assert_never(never)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
class UUID(ParamType):
|
|
402
|
+
"""A UUID-valued parameter."""
|
|
403
|
+
|
|
404
|
+
name = "uuid"
|
|
405
|
+
|
|
406
|
+
@override
|
|
407
|
+
def __repr__(self) -> str:
|
|
408
|
+
return self.name.upper()
|
|
409
|
+
|
|
410
|
+
@override
|
|
411
|
+
def convert(
|
|
412
|
+
self, value: uuid.UUID | str, param: Parameter | None, ctx: Context | None
|
|
413
|
+
) -> uuid.UUID:
|
|
414
|
+
"""Convert a value into the `UUID` type."""
|
|
415
|
+
match value:
|
|
416
|
+
case uuid.UUID():
|
|
417
|
+
return value
|
|
418
|
+
case str():
|
|
419
|
+
try:
|
|
420
|
+
return uuid.UUID(value)
|
|
421
|
+
except ValueError as error:
|
|
422
|
+
self.fail(str(error), param, ctx)
|
|
423
|
+
case never:
|
|
424
|
+
assert_never(never)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
class YearMonth(ParamType):
|
|
428
|
+
"""A year-month parameter."""
|
|
429
|
+
|
|
430
|
+
name = "year-month"
|
|
431
|
+
|
|
432
|
+
@override
|
|
433
|
+
def __repr__(self) -> str:
|
|
434
|
+
return self.name.upper()
|
|
435
|
+
|
|
436
|
+
@override
|
|
437
|
+
def convert(
|
|
438
|
+
self, value: YearMonthLike, param: Parameter | None, ctx: Context | None
|
|
439
|
+
) -> whenever.YearMonth:
|
|
440
|
+
"""Convert a value into the `YearMonth` type."""
|
|
441
|
+
match value:
|
|
442
|
+
case whenever.YearMonth():
|
|
443
|
+
return value
|
|
444
|
+
case str():
|
|
445
|
+
try:
|
|
446
|
+
return whenever.YearMonth.parse_iso(value)
|
|
347
447
|
except ValueError as error:
|
|
348
448
|
self.fail(str(error), param, ctx)
|
|
349
|
-
case
|
|
449
|
+
case never:
|
|
350
450
|
assert_never(never)
|
|
351
451
|
|
|
352
452
|
|
|
@@ -369,10 +469,10 @@ class ZonedDateTime(ParamType):
|
|
|
369
469
|
return value
|
|
370
470
|
case str():
|
|
371
471
|
try:
|
|
372
|
-
return whenever.ZonedDateTime.
|
|
472
|
+
return whenever.ZonedDateTime.parse_iso(value)
|
|
373
473
|
except ValueError as error:
|
|
374
474
|
self.fail(str(error), param, ctx)
|
|
375
|
-
case
|
|
475
|
+
case never:
|
|
376
476
|
assert_never(never)
|
|
377
477
|
|
|
378
478
|
|
|
@@ -424,7 +524,7 @@ class FrozenSetChoices(FrozenSetParameter[Choice, str]):
|
|
|
424
524
|
@override
|
|
425
525
|
def __init__(
|
|
426
526
|
self,
|
|
427
|
-
choices:
|
|
527
|
+
choices: list[str],
|
|
428
528
|
/,
|
|
429
529
|
*,
|
|
430
530
|
case_sensitive: bool = False,
|
|
@@ -445,6 +545,14 @@ class FrozenSetEnums[E: enum.Enum](FrozenSetParameter[Enum[E], E]):
|
|
|
445
545
|
super().__init__(Enum(enum, case_sensitive=case_sensitive), separator=separator)
|
|
446
546
|
|
|
447
547
|
|
|
548
|
+
class FrozenSetInts(FrozenSetParameter[IntParamType, int]):
|
|
549
|
+
"""A frozenset-of-ints-valued parameter."""
|
|
550
|
+
|
|
551
|
+
@override
|
|
552
|
+
def __init__(self, *, separator: str = ",") -> None:
|
|
553
|
+
super().__init__(IntParamType(), separator=separator)
|
|
554
|
+
|
|
555
|
+
|
|
448
556
|
class FrozenSetStrs(FrozenSetParameter[StringParamType, str]):
|
|
449
557
|
"""A frozenset-of-strs-valued parameter."""
|
|
450
558
|
|
|
@@ -501,7 +609,7 @@ class ListChoices(ListParameter[Choice, str]):
|
|
|
501
609
|
@override
|
|
502
610
|
def __init__(
|
|
503
611
|
self,
|
|
504
|
-
choices:
|
|
612
|
+
choices: list[str],
|
|
505
613
|
/,
|
|
506
614
|
*,
|
|
507
615
|
case_sensitive: bool = False,
|
|
@@ -522,6 +630,14 @@ class ListEnums[E: enum.Enum](ListParameter[Enum[E], E]):
|
|
|
522
630
|
super().__init__(Enum(enum, case_sensitive=case_sensitive), separator=separator)
|
|
523
631
|
|
|
524
632
|
|
|
633
|
+
class ListInts(ListParameter[IntParamType, int]):
|
|
634
|
+
"""A list-of-ints-valued parameter."""
|
|
635
|
+
|
|
636
|
+
@override
|
|
637
|
+
def __init__(self, *, separator: str = ",") -> None:
|
|
638
|
+
super().__init__(IntParamType(), separator=separator)
|
|
639
|
+
|
|
640
|
+
|
|
525
641
|
class ListStrs(ListParameter[StringParamType, str]):
|
|
526
642
|
"""A list-of-strs-valued parameter."""
|
|
527
643
|
|
|
@@ -539,16 +655,13 @@ def _make_metavar(param: Parameter, desc: str, /) -> str:
|
|
|
539
655
|
|
|
540
656
|
|
|
541
657
|
__all__ = [
|
|
542
|
-
"
|
|
658
|
+
"CONTEXT_SETTINGS",
|
|
659
|
+
"UUID",
|
|
543
660
|
"Date",
|
|
544
661
|
"DateDelta",
|
|
545
662
|
"DateTimeDelta",
|
|
546
|
-
"DirPath",
|
|
547
663
|
"Enum",
|
|
548
|
-
"
|
|
549
|
-
"ExistingFilePath",
|
|
550
|
-
"FilePath",
|
|
551
|
-
"Freq",
|
|
664
|
+
"EnumPartial",
|
|
552
665
|
"FrozenSetChoices",
|
|
553
666
|
"FrozenSetEnums",
|
|
554
667
|
"FrozenSetParameter",
|
|
@@ -557,10 +670,15 @@ __all__ = [
|
|
|
557
670
|
"IPv6Address",
|
|
558
671
|
"ListChoices",
|
|
559
672
|
"ListEnums",
|
|
673
|
+
"ListInts",
|
|
560
674
|
"ListParameter",
|
|
561
675
|
"ListStrs",
|
|
676
|
+
"MonthDay",
|
|
677
|
+
"Path",
|
|
678
|
+
"Path",
|
|
562
679
|
"PlainDateTime",
|
|
563
680
|
"Time",
|
|
564
681
|
"TimeDelta",
|
|
682
|
+
"YearMonth",
|
|
565
683
|
"ZonedDateTime",
|
|
566
684
|
]
|