dycw-utilities 0.133.6__py3-none-any.whl → 0.134.0__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.
utilities/atools.py CHANGED
@@ -1,35 +1,32 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from collections.abc import Callable
4
- from typing import TYPE_CHECKING, ParamSpec, TypeVar
4
+ from typing import TYPE_CHECKING, Any
5
5
 
6
6
  from atools import memoize
7
7
 
8
- from utilities.types import Coroutine1
8
+ from utilities.types import Coro
9
9
 
10
10
  if TYPE_CHECKING:
11
11
  from whenever import TimeDelta
12
12
 
13
13
 
14
- _P = ParamSpec("_P")
15
- _R = TypeVar("_R")
16
- _AsyncFunc = Callable[_P, Coroutine1[_R]]
17
- type _Key = tuple[_AsyncFunc, TimeDelta]
18
- _MEMOIZED_FUNCS: dict[_Key, _AsyncFunc] = {}
14
+ type _Key[**P, T] = tuple[Callable[P, Coro[T]], TimeDelta]
15
+ _MEMOIZED_FUNCS: dict[_Key, Callable[..., Coro[Any]]] = {}
19
16
 
20
17
 
21
- async def call_memoized(
22
- func: _AsyncFunc[_P, _R],
18
+ async def call_memoized[**P, T](
19
+ func: Callable[P, Coro[T]],
23
20
  refresh: TimeDelta | None = None,
24
21
  /,
25
- *args: _P.args,
26
- **kwargs: _P.kwargs,
27
- ) -> _R:
22
+ *args: P.args,
23
+ **kwargs: P.kwargs,
24
+ ) -> T:
28
25
  """Call an asynchronous function, with possible memoization."""
29
26
  if refresh is None:
30
27
  return await func(*args, **kwargs)
31
28
  key: _Key = (func, refresh)
32
- memoized_func: _AsyncFunc[_P, _R]
29
+ memoized_func: Callable[P, Coro[T]]
33
30
  try:
34
31
  memoized_func = _MEMOIZED_FUNCS[key]
35
32
  except KeyError:
utilities/cachetools.py CHANGED
@@ -1,9 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
- from collections.abc import Callable, Iterable, Iterator, MutableSet
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, TypeVar, override
6
+ from typing import TYPE_CHECKING, Any, override
7
7
 
8
8
  import cachetools
9
9
  from cachetools.func import ttl_cache
@@ -11,14 +11,8 @@ from cachetools.func import ttl_cache
11
11
  if TYPE_CHECKING:
12
12
  from whenever import TimeDelta
13
13
 
14
- from utilities.types import TCallable
15
14
 
16
- _K = TypeVar("_K")
17
- _T = TypeVar("_T")
18
- _V = TypeVar("_V")
19
-
20
-
21
- class TTLCache(cachetools.TTLCache[_K, _V]):
15
+ class TTLCache[K: Hashable, V](cachetools.TTLCache[K, V]):
22
16
  """A TTL-cache."""
23
17
 
24
18
  def __init__(
@@ -40,15 +34,15 @@ class TTLCache(cachetools.TTLCache[_K, _V]):
40
34
  ##
41
35
 
42
36
 
43
- class TTLSet(MutableSet[_T]):
37
+ class TTLSet[T: Hashable](MutableSet[T]):
44
38
  """A TTL-set."""
45
39
 
46
- _cache: TTLCache[_T, None]
40
+ _cache: TTLCache[T, None]
47
41
 
48
42
  @override
49
43
  def __init__(
50
44
  self,
51
- iterable: Iterable[_T] | None = None,
45
+ iterable: Iterable[T] | None = None,
52
46
  /,
53
47
  *,
54
48
  max_size: int | None = None,
@@ -71,7 +65,7 @@ class TTLSet(MutableSet[_T]):
71
65
  return self._cache.__contains__(x)
72
66
 
73
67
  @override
74
- def __iter__(self) -> Iterator[_T]:
68
+ def __iter__(self) -> Iterator[T]:
75
69
  return self._cache.__iter__()
76
70
 
77
71
  @override
@@ -87,24 +81,24 @@ class TTLSet(MutableSet[_T]):
87
81
  return set(self._cache).__str__()
88
82
 
89
83
  @override
90
- def add(self, value: _T) -> None:
84
+ def add(self, value: T) -> None:
91
85
  self._cache[value] = None
92
86
 
93
87
  @override
94
- def discard(self, value: _T) -> None:
88
+ def discard(self, value: T) -> None:
95
89
  del self._cache[value]
96
90
 
97
91
 
98
92
  ##
99
93
 
100
94
 
101
- def cache(
95
+ def cache[F: Callable](
102
96
  *,
103
97
  max_size: int | None = None,
104
98
  max_duration: TimeDelta | None = None,
105
99
  timer: Callable[[], float] = monotonic,
106
100
  typed_: bool = False,
107
- ) -> Callable[[TCallable], TCallable]:
101
+ ) -> Callable[[F], F]:
108
102
  """Decorate a function with `max_size` and/or `ttl` settings."""
109
103
  return ttl_cache(
110
104
  maxsize=inf if max_size is None else max_size,
utilities/click.py CHANGED
@@ -1,8 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import enum
3
4
  import ipaddress
4
5
  import pathlib
5
- from typing import TYPE_CHECKING, Generic, TypedDict, TypeVar, assert_never, override
6
+ from typing import TYPE_CHECKING, TypedDict, assert_never, override
6
7
 
7
8
  import click
8
9
  import whenever
@@ -15,32 +16,27 @@ from utilities.functions import EnsureStrError, ensure_str, get_class_name
15
16
  from utilities.iterables import is_iterable_not_str
16
17
  from utilities.parse import ParseObjectError, parse_object
17
18
  from utilities.text import split_str
18
- from utilities.types import (
19
- DateDeltaLike,
20
- DateLike,
21
- DateTimeDeltaLike,
22
- EnumLike,
23
- IPv4AddressLike,
24
- IPv6AddressLike,
25
- MaybeStr,
26
- PlainDateTimeLike,
27
- TEnum,
28
- TimeDeltaLike,
29
- TimeLike,
30
- ZonedDateTimeLike,
31
- )
32
19
  from utilities.whenever import FreqLike, _FreqParseError, _MonthParseCommonISOError
33
20
 
34
21
  if TYPE_CHECKING:
35
22
  from collections.abc import Iterable, Sequence
36
23
 
24
+ from utilities.types import (
25
+ DateDeltaLike,
26
+ DateLike,
27
+ DateTimeDeltaLike,
28
+ EnumLike,
29
+ IPv4AddressLike,
30
+ IPv6AddressLike,
31
+ MaybeStr,
32
+ PlainDateTimeLike,
33
+ TimeDeltaLike,
34
+ TimeLike,
35
+ ZonedDateTimeLike,
36
+ )
37
37
  from utilities.whenever import MonthLike
38
38
 
39
39
 
40
- _T = TypeVar("_T")
41
- _TParam = TypeVar("_TParam", bound=ParamType)
42
-
43
-
44
40
  FilePath = click.Path(file_okay=True, dir_okay=False, path_type=pathlib.Path)
45
41
  DirPath = click.Path(file_okay=False, dir_okay=True, path_type=pathlib.Path)
46
42
  ExistingFilePath = click.Path(
@@ -145,12 +141,13 @@ class DateTimeDelta(ParamType):
145
141
  assert_never(never)
146
142
 
147
143
 
148
- class Enum(ParamType, Generic[TEnum]):
144
+ class Enum[E: enum.Enum](ParamType):
149
145
  """An enum-valued parameter."""
150
146
 
151
- def __init__(self, enum: type[TEnum], /, *, case_sensitive: bool = False) -> None:
147
+ @override
148
+ def __init__(self, enum: type[E], /, *, case_sensitive: bool = False) -> None:
152
149
  cls = get_class_name(enum)
153
- self.name = f"ENUM[{cls}]"
150
+ self.name = f"enum[{cls}]"
154
151
  self._enum = enum
155
152
  self._case_sensitive = case_sensitive
156
153
  super().__init__()
@@ -162,8 +159,8 @@ class Enum(ParamType, Generic[TEnum]):
162
159
 
163
160
  @override
164
161
  def convert(
165
- self, value: EnumLike[TEnum], param: Parameter | None, ctx: Context | None
166
- ) -> TEnum:
162
+ self, value: EnumLike[E], param: Parameter | None, ctx: Context | None
163
+ ) -> E:
167
164
  """Convert a value into the `Enum` type."""
168
165
  try:
169
166
  return ensure_enum(value, self._enum, case_sensitive=self._case_sensitive)
@@ -180,9 +177,11 @@ class Enum(ParamType, Generic[TEnum]):
180
177
  class Freq(ParamType):
181
178
  """An frequency-valued parameter."""
182
179
 
180
+ name = "freq"
181
+
183
182
  @override
184
183
  def __repr__(self) -> str:
185
- return "FREQ"
184
+ return self.name.upper()
186
185
 
187
186
  @override
188
187
  def convert(
@@ -380,27 +379,24 @@ class ZonedDateTime(ParamType):
380
379
  # parameters - frozenset
381
380
 
382
381
 
383
- class FrozenSetParameter(ParamType, Generic[_TParam, _T]):
382
+ class FrozenSetParameter[P: ParamType, T](ParamType):
384
383
  """A frozenset-valued parameter."""
385
384
 
386
- def __init__(self, param: _TParam, /, *, separator: str = ",") -> None:
387
- self.name = f"FROZENSET[{param.name}]"
385
+ @override
386
+ def __init__(self, param: P, /, *, separator: str = ",") -> None:
387
+ self.name = f"frozenset[{param.name}]"
388
388
  self._param = param
389
389
  self._separator = separator
390
390
  super().__init__()
391
391
 
392
392
  @override
393
393
  def __repr__(self) -> str:
394
- desc = repr(self._param)
395
- return f"FROZENSET[{desc}]"
394
+ return f"FROZENSET[{self._param!r}]"
396
395
 
397
396
  @override
398
397
  def convert(
399
- self,
400
- value: MaybeStr[Iterable[_T]],
401
- param: Parameter | None,
402
- ctx: Context | None,
403
- ) -> frozenset[_T]:
398
+ self, value: MaybeStr[Iterable[T]], param: Parameter | None, ctx: Context | None
399
+ ) -> frozenset[T]:
404
400
  """Convert a value into the `ListDates` type."""
405
401
  if is_iterable_not_str(value):
406
402
  return frozenset(value)
@@ -425,6 +421,7 @@ class FrozenSetParameter(ParamType, Generic[_TParam, _T]):
425
421
  class FrozenSetChoices(FrozenSetParameter[Choice, str]):
426
422
  """A frozenset-of-choices-valued parameter."""
427
423
 
424
+ @override
428
425
  def __init__(
429
426
  self,
430
427
  choices: Sequence[str],
@@ -438,16 +435,12 @@ class FrozenSetChoices(FrozenSetParameter[Choice, str]):
438
435
  )
439
436
 
440
437
 
441
- class FrozenSetEnums(FrozenSetParameter[Enum[TEnum], TEnum]):
438
+ class FrozenSetEnums[E: enum.Enum](FrozenSetParameter[Enum[E], E]):
442
439
  """A frozenset-of-enums-valued parameter."""
443
440
 
441
+ @override
444
442
  def __init__(
445
- self,
446
- enum: type[TEnum],
447
- /,
448
- *,
449
- case_sensitive: bool = False,
450
- separator: str = ",",
443
+ self, enum: type[E], /, *, case_sensitive: bool = False, separator: str = ","
451
444
  ) -> None:
452
445
  super().__init__(Enum(enum, case_sensitive=case_sensitive), separator=separator)
453
446
 
@@ -455,6 +448,7 @@ class FrozenSetEnums(FrozenSetParameter[Enum[TEnum], TEnum]):
455
448
  class FrozenSetStrs(FrozenSetParameter[StringParamType, str]):
456
449
  """A frozenset-of-strs-valued parameter."""
457
450
 
451
+ @override
458
452
  def __init__(self, *, separator: str = ",") -> None:
459
453
  super().__init__(StringParamType(), separator=separator)
460
454
 
@@ -462,27 +456,24 @@ class FrozenSetStrs(FrozenSetParameter[StringParamType, str]):
462
456
  # parameters - list
463
457
 
464
458
 
465
- class ListParameter(ParamType, Generic[_TParam, _T]):
459
+ class ListParameter[P: ParamType, T](ParamType):
466
460
  """A list-valued parameter."""
467
461
 
468
- def __init__(self, param: _TParam, /, *, separator: str = ",") -> None:
469
- self.name = f"LIST[{param.name}]"
462
+ @override
463
+ def __init__(self, param: P, /, *, separator: str = ",") -> None:
464
+ self.name = f"list[{param.name}]"
470
465
  self._param = param
471
466
  self._separator = separator
472
467
  super().__init__()
473
468
 
474
469
  @override
475
470
  def __repr__(self) -> str:
476
- desc = repr(self._param)
477
- return f"LIST[{desc}]"
471
+ return f"LIST[{self._param!r}]"
478
472
 
479
473
  @override
480
474
  def convert(
481
- self,
482
- value: MaybeStr[Iterable[_T]],
483
- param: Parameter | None,
484
- ctx: Context | None,
485
- ) -> list[_T]:
475
+ self, value: MaybeStr[Iterable[T]], param: Parameter | None, ctx: Context | None
476
+ ) -> list[T]:
486
477
  """Convert a value into the `List` type."""
487
478
  if is_iterable_not_str(value):
488
479
  return list(value)
@@ -504,16 +495,29 @@ class ListParameter(ParamType, Generic[_TParam, _T]):
504
495
  return _make_metavar(param, desc)
505
496
 
506
497
 
507
- class ListEnums(ListParameter[Enum[TEnum], TEnum]):
508
- """A list-of-enums-valued parameter."""
498
+ class ListChoices(ListParameter[Choice, str]):
499
+ """A frozenset-of-choices-valued parameter."""
509
500
 
501
+ @override
510
502
  def __init__(
511
503
  self,
512
- enum: type[TEnum],
504
+ choices: Sequence[str],
513
505
  /,
514
506
  *,
515
507
  case_sensitive: bool = False,
516
508
  separator: str = ",",
509
+ ) -> None:
510
+ super().__init__(
511
+ Choice(choices, case_sensitive=case_sensitive), separator=separator
512
+ )
513
+
514
+
515
+ class ListEnums[E: enum.Enum](ListParameter[Enum[E], E]):
516
+ """A list-of-enums-valued parameter."""
517
+
518
+ @override
519
+ def __init__(
520
+ self, enum: type[E], /, *, case_sensitive: bool = False, separator: str = ","
517
521
  ) -> None:
518
522
  super().__init__(Enum(enum, case_sensitive=case_sensitive), separator=separator)
519
523
 
@@ -521,6 +525,7 @@ class ListEnums(ListParameter[Enum[TEnum], TEnum]):
521
525
  class ListStrs(ListParameter[StringParamType, str]):
522
526
  """A list-of-strs-valued parameter."""
523
527
 
528
+ @override
524
529
  def __init__(self, *, separator: str = ",") -> None:
525
530
  super().__init__(StringParamType(), separator=separator)
526
531
 
@@ -550,6 +555,7 @@ __all__ = [
550
555
  "FrozenSetStrs",
551
556
  "IPv4Address",
552
557
  "IPv6Address",
558
+ "ListChoices",
553
559
  "ListEnums",
554
560
  "ListParameter",
555
561
  "ListStrs",
utilities/concurrent.py CHANGED
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
4
4
  from functools import partial
5
- from typing import TYPE_CHECKING, Any, TypeVar, assert_never
5
+ from typing import TYPE_CHECKING, Any, assert_never
6
6
 
7
7
  from utilities.iterables import apply_to_tuple
8
8
  from utilities.os import get_cpu_use
@@ -15,11 +15,8 @@ if TYPE_CHECKING:
15
15
  from utilities.os import IntOrAll
16
16
 
17
17
 
18
- _T = TypeVar("_T")
19
-
20
-
21
- def concurrent_map(
22
- func: Callable[..., _T],
18
+ def concurrent_map[T](
19
+ func: Callable[..., T],
23
20
  /,
24
21
  *iterables: Iterable[Any],
25
22
  parallelism: Parallelism = "processes",
@@ -31,7 +28,7 @@ def concurrent_map(
31
28
  thread_name_prefix: str = "",
32
29
  timeout: float | None = None,
33
30
  chunksize: int = 1,
34
- ) -> list[_T]:
31
+ ) -> list[T]:
35
32
  """Concurrent map."""
36
33
  return concurrent_starmap(
37
34
  func,
@@ -51,8 +48,8 @@ def concurrent_map(
51
48
  ##
52
49
 
53
50
 
54
- def concurrent_starmap(
55
- func: Callable[..., _T],
51
+ def concurrent_starmap[T](
52
+ func: Callable[..., T],
56
53
  iterable: Iterable[tuple[Any, ...]],
57
54
  /,
58
55
  *,
@@ -65,7 +62,7 @@ def concurrent_starmap(
65
62
  thread_name_prefix: str = "",
66
63
  timeout: float | None = None,
67
64
  chunksize: int = 1,
68
- ) -> list[_T]:
65
+ ) -> list[T]:
69
66
  """Concurrent map."""
70
67
  max_workers_use = get_cpu_use(n=max_workers)
71
68
  apply = partial(apply_to_tuple, func)