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.

Files changed (97) hide show
  1. dycw_utilities-0.178.1.dist-info/METADATA +34 -0
  2. dycw_utilities-0.178.1.dist-info/RECORD +105 -0
  3. dycw_utilities-0.178.1.dist-info/WHEEL +4 -0
  4. dycw_utilities-0.178.1.dist-info/entry_points.txt +4 -0
  5. utilities/__init__.py +1 -1
  6. utilities/altair.py +13 -10
  7. utilities/asyncio.py +312 -787
  8. utilities/atomicwrites.py +18 -6
  9. utilities/atools.py +64 -4
  10. utilities/cachetools.py +9 -6
  11. utilities/click.py +195 -77
  12. utilities/concurrent.py +1 -1
  13. utilities/contextlib.py +216 -17
  14. utilities/contextvars.py +20 -1
  15. utilities/cryptography.py +3 -3
  16. utilities/dataclasses.py +15 -28
  17. utilities/docker.py +387 -0
  18. utilities/enum.py +2 -2
  19. utilities/errors.py +17 -3
  20. utilities/fastapi.py +28 -59
  21. utilities/fpdf2.py +2 -2
  22. utilities/functions.py +24 -269
  23. utilities/git.py +9 -30
  24. utilities/grp.py +28 -0
  25. utilities/gzip.py +31 -0
  26. utilities/http.py +3 -2
  27. utilities/hypothesis.py +513 -159
  28. utilities/importlib.py +17 -1
  29. utilities/inflect.py +12 -4
  30. utilities/iterables.py +33 -58
  31. utilities/jinja2.py +148 -0
  32. utilities/json.py +70 -0
  33. utilities/libcst.py +38 -17
  34. utilities/lightweight_charts.py +4 -7
  35. utilities/logging.py +136 -93
  36. utilities/math.py +8 -4
  37. utilities/more_itertools.py +43 -45
  38. utilities/operator.py +27 -27
  39. utilities/orjson.py +189 -36
  40. utilities/os.py +61 -4
  41. utilities/packaging.py +115 -0
  42. utilities/parse.py +8 -5
  43. utilities/pathlib.py +269 -40
  44. utilities/permissions.py +298 -0
  45. utilities/platform.py +7 -6
  46. utilities/polars.py +1205 -413
  47. utilities/polars_ols.py +1 -1
  48. utilities/postgres.py +408 -0
  49. utilities/pottery.py +43 -19
  50. utilities/pqdm.py +3 -3
  51. utilities/psutil.py +5 -57
  52. utilities/pwd.py +28 -0
  53. utilities/pydantic.py +4 -52
  54. utilities/pydantic_settings.py +240 -0
  55. utilities/pydantic_settings_sops.py +76 -0
  56. utilities/pyinstrument.py +7 -7
  57. utilities/pytest.py +104 -143
  58. utilities/pytest_plugins/__init__.py +1 -0
  59. utilities/pytest_plugins/pytest_randomly.py +23 -0
  60. utilities/pytest_plugins/pytest_regressions.py +56 -0
  61. utilities/pytest_regressions.py +26 -46
  62. utilities/random.py +11 -6
  63. utilities/re.py +1 -1
  64. utilities/redis.py +220 -343
  65. utilities/sentinel.py +10 -0
  66. utilities/shelve.py +4 -1
  67. utilities/shutil.py +25 -0
  68. utilities/slack_sdk.py +35 -104
  69. utilities/sqlalchemy.py +496 -471
  70. utilities/sqlalchemy_polars.py +29 -54
  71. utilities/string.py +2 -3
  72. utilities/subprocess.py +1977 -0
  73. utilities/tempfile.py +112 -4
  74. utilities/testbook.py +50 -0
  75. utilities/text.py +174 -42
  76. utilities/throttle.py +158 -0
  77. utilities/timer.py +2 -2
  78. utilities/traceback.py +70 -35
  79. utilities/types.py +102 -30
  80. utilities/typing.py +479 -19
  81. utilities/uuid.py +42 -5
  82. utilities/version.py +27 -26
  83. utilities/whenever.py +1559 -361
  84. utilities/zoneinfo.py +80 -22
  85. dycw_utilities-0.135.0.dist-info/METADATA +0 -39
  86. dycw_utilities-0.135.0.dist-info/RECORD +0 -96
  87. dycw_utilities-0.135.0.dist-info/WHEEL +0 -4
  88. dycw_utilities-0.135.0.dist-info/licenses/LICENSE +0 -21
  89. utilities/aiolimiter.py +0 -25
  90. utilities/arq.py +0 -216
  91. utilities/eventkit.py +0 -388
  92. utilities/luigi.py +0 -183
  93. utilities/period.py +0 -152
  94. utilities/pudb.py +0 -62
  95. utilities/python_dotenv.py +0 -101
  96. utilities/streamlit.py +0 -105
  97. 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 _ as never:
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(path: PathLike, /, *, overwrite: bool = False) -> Iterator[Path]:
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
- temp_path = Path(temp_dir, name)
124
+ temp_path1 = Path(temp_dir, name)
122
125
  try:
123
- yield temp_path
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(temp_path, path, overwrite=overwrite)
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 typing import TYPE_CHECKING, Any
4
+ from functools import partial
5
+ from pathlib import Path
6
+ from typing import TYPE_CHECKING, Any, cast, overload
5
7
 
6
- from atools import memoize
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 whenever import TimeDelta
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 ttl_cache(
104
- maxsize=inf if max_size is None else max_size,
105
- ttl=inf if max_duration is None else max_duration.in_seconds(),
106
- timer=timer,
107
- typed=typed_,
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, Sequence
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
- FilePath = click.Path(file_okay=True, dir_okay=False, path_type=pathlib.Path)
41
- DirPath = click.Path(file_okay=False, dir_okay=True, path_type=pathlib.Path)
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 _ContextSettings(TypedDict):
55
- context_settings: _HelpOptionNames
45
+ class _ContextSettingsInner(TypedDict):
46
+ max_content_width: int
47
+ help_option_names: list[str]
48
+ show_default: bool
56
49
 
57
50
 
58
- CONTEXT_SETTINGS_HELP_OPTION_NAMES = _ContextSettings(
59
- context_settings=_HelpOptionNames(help_option_names=["-h", "--help"])
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.parse_common_iso(value)
82
+ return whenever.Date.parse_iso(value)
86
83
  except ValueError as error:
87
84
  self.fail(str(error), param, ctx)
88
- case _ as never:
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.parse_common_iso(value)
108
+ return whenever.DateDelta.parse_iso(value)
112
109
  except ValueError as error:
113
110
  self.fail(str(error), param, ctx)
114
- case _ as never:
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.parse_common_iso(value)
134
+ return whenever.DateTimeDelta.parse_iso(value)
138
135
  except ValueError as error:
139
136
  self.fail(str(error), param, ctx)
140
- case _ as never:
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__(self, enum: type[E], /, *, case_sensitive: bool = False) -> None:
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 Freq(ParamType):
178
- """An frequency-valued parameter."""
177
+ class EnumPartial[E: enum.Enum](ParamType):
178
+ """An enum-valued parameter."""
179
179
 
180
- name = "freq"
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
- return self.name.upper()
199
+ cls = get_class_name(self._enum)
200
+ return f"ENUMPARTIAL[{cls}]"
185
201
 
186
202
  @override
187
203
  def convert(
188
- self, value: FreqLike, param: Parameter | None, ctx: Context | None
189
- ) -> utilities.whenever.Freq:
190
- """Convert a value into the `Freq` type."""
191
- match value:
192
- case utilities.whenever.Freq():
193
- return value
194
- case str():
195
- try:
196
- return utilities.whenever.Freq.parse(value)
197
- except _FreqParseError as error:
198
- self.fail(str(error), param, ctx)
199
- case _ as never:
200
- assert_never(never)
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 _ as never:
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 _ as never:
270
+ case never:
252
271
  assert_never(never)
253
272
 
254
273
 
255
- class Month(ParamType):
256
- """A month-valued parameter."""
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: MonthLike, param: Parameter | None, ctx: Context | None
267
- ) -> utilities.whenever.Month:
268
- """Convert a value into the `Month` type."""
269
- try:
270
- return utilities.whenever.Month.ensure(value)
271
- except _MonthParseCommonISOError as error:
272
- self.fail(str(error), param, ctx)
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.parse_common_iso(value)
342
+ return whenever.PlainDateTime.parse_iso(value)
295
343
  except ValueError as error:
296
344
  self.fail(str(error), param, ctx)
297
- case _ as never:
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.parse_common_iso(value)
368
+ return whenever.Time.parse_iso(value)
321
369
  except ValueError as error:
322
370
  self.fail(str(error), param, ctx)
323
- case _ as never:
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.parse_common_iso(value)
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 _ as never:
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.parse_common_iso(value)
472
+ return whenever.ZonedDateTime.parse_iso(value)
373
473
  except ValueError as error:
374
474
  self.fail(str(error), param, ctx)
375
- case _ as never:
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: Sequence[str],
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: Sequence[str],
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
- "CONTEXT_SETTINGS_HELP_OPTION_NAMES",
658
+ "CONTEXT_SETTINGS",
659
+ "UUID",
543
660
  "Date",
544
661
  "DateDelta",
545
662
  "DateTimeDelta",
546
- "DirPath",
547
663
  "Enum",
548
- "ExistingDirPath",
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
  ]
utilities/concurrent.py CHANGED
@@ -84,7 +84,7 @@ def concurrent_starmap[T](
84
84
  initargs=initargs,
85
85
  ) as pool:
86
86
  result = pool.map(apply, iterable, timeout=timeout, chunksize=chunksize)
87
- case _ as never:
87
+ case never:
88
88
  assert_never(never)
89
89
  return list(result)
90
90