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.
- {dycw_utilities-0.133.6.dist-info → dycw_utilities-0.134.0.dist-info}/METADATA +1 -1
- {dycw_utilities-0.133.6.dist-info → dycw_utilities-0.134.0.dist-info}/RECORD +39 -39
- utilities/__init__.py +1 -1
- utilities/arq.py +13 -20
- utilities/asyncio.py +59 -74
- utilities/atools.py +10 -13
- utilities/cachetools.py +11 -17
- utilities/click.py +61 -55
- utilities/concurrent.py +7 -10
- utilities/dataclasses.py +69 -91
- utilities/enum.py +24 -21
- utilities/eventkit.py +34 -48
- utilities/functions.py +133 -168
- utilities/functools.py +14 -18
- utilities/hypothesis.py +34 -44
- utilities/iterables.py +165 -179
- utilities/luigi.py +3 -15
- utilities/memory_profiler.py +11 -15
- utilities/more_itertools.py +85 -94
- utilities/operator.py +5 -7
- utilities/optuna.py +6 -6
- utilities/pathlib.py +1 -0
- utilities/period.py +7 -9
- utilities/polars.py +5 -16
- utilities/pqdm.py +7 -8
- utilities/pydantic.py +2 -4
- utilities/pytest.py +14 -23
- utilities/python_dotenv.py +5 -9
- utilities/random.py +2 -3
- utilities/redis.py +163 -181
- utilities/slack_sdk.py +2 -2
- utilities/sqlalchemy.py +4 -14
- utilities/timer.py +6 -0
- utilities/typed_settings.py +7 -10
- utilities/types.py +10 -94
- utilities/typing.py +32 -43
- utilities/uuid.py +1 -0
- {dycw_utilities-0.133.6.dist-info → dycw_utilities-0.134.0.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.133.6.dist-info → dycw_utilities-0.134.0.dist-info}/licenses/LICENSE +0 -0
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,
|
4
|
+
from typing import TYPE_CHECKING, Any
|
5
5
|
|
6
6
|
from atools import memoize
|
7
7
|
|
8
|
-
from utilities.types import
|
8
|
+
from utilities.types import Coro
|
9
9
|
|
10
10
|
if TYPE_CHECKING:
|
11
11
|
from whenever import TimeDelta
|
12
12
|
|
13
13
|
|
14
|
-
|
15
|
-
|
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:
|
18
|
+
async def call_memoized[**P, T](
|
19
|
+
func: Callable[P, Coro[T]],
|
23
20
|
refresh: TimeDelta | None = None,
|
24
21
|
/,
|
25
|
-
*args:
|
26
|
-
**kwargs:
|
27
|
-
) ->
|
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:
|
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,
|
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
|
-
|
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[
|
37
|
+
class TTLSet[T: Hashable](MutableSet[T]):
|
44
38
|
"""A TTL-set."""
|
45
39
|
|
46
|
-
_cache: TTLCache[
|
40
|
+
_cache: TTLCache[T, None]
|
47
41
|
|
48
42
|
@override
|
49
43
|
def __init__(
|
50
44
|
self,
|
51
|
-
iterable: Iterable[
|
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[
|
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:
|
84
|
+
def add(self, value: T) -> None:
|
91
85
|
self._cache[value] = None
|
92
86
|
|
93
87
|
@override
|
94
|
-
def discard(self, value:
|
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[[
|
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,
|
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
|
144
|
+
class Enum[E: enum.Enum](ParamType):
|
149
145
|
"""An enum-valued parameter."""
|
150
146
|
|
151
|
-
|
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"
|
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[
|
166
|
-
) ->
|
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
|
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
|
382
|
+
class FrozenSetParameter[P: ParamType, T](ParamType):
|
384
383
|
"""A frozenset-valued parameter."""
|
385
384
|
|
386
|
-
|
387
|
-
|
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
|
-
|
395
|
-
return f"FROZENSET[{desc}]"
|
394
|
+
return f"FROZENSET[{self._param!r}]"
|
396
395
|
|
397
396
|
@override
|
398
397
|
def convert(
|
399
|
-
self,
|
400
|
-
|
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[
|
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
|
459
|
+
class ListParameter[P: ParamType, T](ParamType):
|
466
460
|
"""A list-valued parameter."""
|
467
461
|
|
468
|
-
|
469
|
-
|
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
|
-
|
477
|
-
return f"LIST[{desc}]"
|
471
|
+
return f"LIST[{self._param!r}]"
|
478
472
|
|
479
473
|
@override
|
480
474
|
def convert(
|
481
|
-
self,
|
482
|
-
|
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
|
508
|
-
"""A
|
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
|
-
|
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,
|
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
|
-
|
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[
|
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[...,
|
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[
|
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)
|