dycw-utilities 0.133.7__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.7.dist-info → dycw_utilities-0.134.0.dist-info}/METADATA +1 -1
- {dycw_utilities-0.133.7.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 +31 -51
- 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.7.dist-info → dycw_utilities-0.134.0.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.133.7.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,11 +141,11 @@ 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
|
152
|
-
def __init__(self, enum: type[
|
148
|
+
def __init__(self, enum: type[E], /, *, case_sensitive: bool = False) -> None:
|
153
149
|
cls = get_class_name(enum)
|
154
150
|
self.name = f"enum[{cls}]"
|
155
151
|
self._enum = enum
|
@@ -163,8 +159,8 @@ class Enum(ParamType, Generic[TEnum]):
|
|
163
159
|
|
164
160
|
@override
|
165
161
|
def convert(
|
166
|
-
self, value: EnumLike[
|
167
|
-
) ->
|
162
|
+
self, value: EnumLike[E], param: Parameter | None, ctx: Context | None
|
163
|
+
) -> E:
|
168
164
|
"""Convert a value into the `Enum` type."""
|
169
165
|
try:
|
170
166
|
return ensure_enum(value, self._enum, case_sensitive=self._case_sensitive)
|
@@ -383,11 +379,11 @@ class ZonedDateTime(ParamType):
|
|
383
379
|
# parameters - frozenset
|
384
380
|
|
385
381
|
|
386
|
-
class FrozenSetParameter
|
382
|
+
class FrozenSetParameter[P: ParamType, T](ParamType):
|
387
383
|
"""A frozenset-valued parameter."""
|
388
384
|
|
389
385
|
@override
|
390
|
-
def __init__(self, param:
|
386
|
+
def __init__(self, param: P, /, *, separator: str = ",") -> None:
|
391
387
|
self.name = f"frozenset[{param.name}]"
|
392
388
|
self._param = param
|
393
389
|
self._separator = separator
|
@@ -399,11 +395,8 @@ class FrozenSetParameter(ParamType, Generic[_TParam, _T]):
|
|
399
395
|
|
400
396
|
@override
|
401
397
|
def convert(
|
402
|
-
self,
|
403
|
-
|
404
|
-
param: Parameter | None,
|
405
|
-
ctx: Context | None,
|
406
|
-
) -> frozenset[_T]:
|
398
|
+
self, value: MaybeStr[Iterable[T]], param: Parameter | None, ctx: Context | None
|
399
|
+
) -> frozenset[T]:
|
407
400
|
"""Convert a value into the `ListDates` type."""
|
408
401
|
if is_iterable_not_str(value):
|
409
402
|
return frozenset(value)
|
@@ -442,17 +435,12 @@ class FrozenSetChoices(FrozenSetParameter[Choice, str]):
|
|
442
435
|
)
|
443
436
|
|
444
437
|
|
445
|
-
class FrozenSetEnums(FrozenSetParameter[Enum[
|
438
|
+
class FrozenSetEnums[E: enum.Enum](FrozenSetParameter[Enum[E], E]):
|
446
439
|
"""A frozenset-of-enums-valued parameter."""
|
447
440
|
|
448
441
|
@override
|
449
442
|
def __init__(
|
450
|
-
self,
|
451
|
-
enum: type[TEnum],
|
452
|
-
/,
|
453
|
-
*,
|
454
|
-
case_sensitive: bool = False,
|
455
|
-
separator: str = ",",
|
443
|
+
self, enum: type[E], /, *, case_sensitive: bool = False, separator: str = ","
|
456
444
|
) -> None:
|
457
445
|
super().__init__(Enum(enum, case_sensitive=case_sensitive), separator=separator)
|
458
446
|
|
@@ -468,11 +456,11 @@ class FrozenSetStrs(FrozenSetParameter[StringParamType, str]):
|
|
468
456
|
# parameters - list
|
469
457
|
|
470
458
|
|
471
|
-
class ListParameter
|
459
|
+
class ListParameter[P: ParamType, T](ParamType):
|
472
460
|
"""A list-valued parameter."""
|
473
461
|
|
474
462
|
@override
|
475
|
-
def __init__(self, param:
|
463
|
+
def __init__(self, param: P, /, *, separator: str = ",") -> None:
|
476
464
|
self.name = f"list[{param.name}]"
|
477
465
|
self._param = param
|
478
466
|
self._separator = separator
|
@@ -484,11 +472,8 @@ class ListParameter(ParamType, Generic[_TParam, _T]):
|
|
484
472
|
|
485
473
|
@override
|
486
474
|
def convert(
|
487
|
-
self,
|
488
|
-
|
489
|
-
param: Parameter | None,
|
490
|
-
ctx: Context | None,
|
491
|
-
) -> list[_T]:
|
475
|
+
self, value: MaybeStr[Iterable[T]], param: Parameter | None, ctx: Context | None
|
476
|
+
) -> list[T]:
|
492
477
|
"""Convert a value into the `List` type."""
|
493
478
|
if is_iterable_not_str(value):
|
494
479
|
return list(value)
|
@@ -527,17 +512,12 @@ class ListChoices(ListParameter[Choice, str]):
|
|
527
512
|
)
|
528
513
|
|
529
514
|
|
530
|
-
class ListEnums(ListParameter[Enum[
|
515
|
+
class ListEnums[E: enum.Enum](ListParameter[Enum[E], E]):
|
531
516
|
"""A list-of-enums-valued parameter."""
|
532
517
|
|
533
518
|
@override
|
534
519
|
def __init__(
|
535
|
-
self,
|
536
|
-
enum: type[TEnum],
|
537
|
-
/,
|
538
|
-
*,
|
539
|
-
case_sensitive: bool = False,
|
540
|
-
separator: str = ",",
|
520
|
+
self, enum: type[E], /, *, case_sensitive: bool = False, separator: str = ","
|
541
521
|
) -> None:
|
542
522
|
super().__init__(Enum(enum, case_sensitive=case_sensitive), separator=separator)
|
543
523
|
|
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)
|