dycw-utilities 0.129.10__py3-none-any.whl → 0.175.17__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.175.17.dist-info/METADATA +34 -0
- dycw_utilities-0.175.17.dist-info/RECORD +103 -0
- dycw_utilities-0.175.17.dist-info/WHEEL +4 -0
- dycw_utilities-0.175.17.dist-info/entry_points.txt +4 -0
- utilities/__init__.py +1 -1
- utilities/altair.py +14 -14
- utilities/asyncio.py +350 -819
- utilities/atomicwrites.py +18 -6
- utilities/atools.py +77 -22
- utilities/cachetools.py +24 -29
- utilities/click.py +393 -237
- utilities/concurrent.py +8 -11
- utilities/contextlib.py +216 -17
- utilities/contextvars.py +20 -1
- utilities/cryptography.py +3 -3
- utilities/dataclasses.py +83 -118
- utilities/docker.py +293 -0
- utilities/enum.py +26 -23
- utilities/errors.py +17 -3
- utilities/fastapi.py +29 -65
- utilities/fpdf2.py +3 -3
- utilities/functions.py +169 -416
- utilities/functools.py +18 -19
- utilities/git.py +9 -30
- utilities/grp.py +28 -0
- utilities/gzip.py +31 -0
- utilities/http.py +3 -2
- utilities/hypothesis.py +738 -589
- utilities/importlib.py +17 -1
- utilities/inflect.py +25 -0
- utilities/iterables.py +194 -262
- utilities/jinja2.py +148 -0
- utilities/json.py +70 -0
- utilities/libcst.py +38 -17
- utilities/lightweight_charts.py +5 -9
- utilities/logging.py +345 -543
- utilities/math.py +18 -13
- utilities/memory_profiler.py +11 -15
- utilities/more_itertools.py +200 -131
- utilities/operator.py +33 -29
- utilities/optuna.py +6 -6
- utilities/orjson.py +272 -137
- utilities/os.py +61 -4
- utilities/parse.py +59 -61
- utilities/pathlib.py +281 -40
- utilities/permissions.py +298 -0
- utilities/pickle.py +2 -2
- utilities/platform.py +24 -5
- utilities/polars.py +1214 -430
- utilities/polars_ols.py +1 -1
- utilities/postgres.py +408 -0
- utilities/pottery.py +113 -26
- utilities/pqdm.py +10 -11
- utilities/psutil.py +6 -57
- utilities/pwd.py +28 -0
- utilities/pydantic.py +4 -54
- utilities/pydantic_settings.py +240 -0
- utilities/pydantic_settings_sops.py +76 -0
- utilities/pyinstrument.py +8 -10
- utilities/pytest.py +227 -121
- 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 +13 -9
- utilities/re.py +58 -28
- utilities/redis.py +401 -550
- utilities/scipy.py +1 -1
- utilities/sentinel.py +10 -0
- utilities/shelve.py +4 -1
- utilities/shutil.py +25 -0
- utilities/slack_sdk.py +36 -106
- utilities/sqlalchemy.py +502 -473
- utilities/sqlalchemy_polars.py +38 -94
- utilities/string.py +2 -3
- utilities/subprocess.py +1572 -0
- utilities/tempfile.py +86 -4
- utilities/testbook.py +50 -0
- utilities/text.py +165 -42
- utilities/timer.py +37 -65
- utilities/traceback.py +158 -929
- utilities/types.py +146 -116
- utilities/typing.py +531 -71
- utilities/tzdata.py +1 -53
- utilities/tzlocal.py +6 -23
- utilities/uuid.py +43 -5
- utilities/version.py +27 -26
- utilities/whenever.py +1776 -386
- utilities/zoneinfo.py +84 -22
- dycw_utilities-0.129.10.dist-info/METADATA +0 -241
- dycw_utilities-0.129.10.dist-info/RECORD +0 -96
- dycw_utilities-0.129.10.dist-info/WHEEL +0 -4
- dycw_utilities-0.129.10.dist-info/licenses/LICENSE +0 -21
- utilities/datetime.py +0 -1409
- utilities/eventkit.py +0 -402
- utilities/loguru.py +0 -144
- utilities/luigi.py +0 -228
- utilities/period.py +0 -324
- utilities/pyrsistent.py +0 -89
- utilities/python_dotenv.py +0 -105
- utilities/streamlit.py +0 -105
- utilities/sys.py +0 -87
- utilities/tenacity.py +0 -145
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,44 +1,99 @@
|
|
|
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.
|
|
9
|
-
from utilities.types import Coroutine1
|
|
11
|
+
from utilities.types import Coro, PathLike
|
|
10
12
|
|
|
11
13
|
if TYPE_CHECKING:
|
|
12
|
-
import
|
|
14
|
+
from atools._memoize_decorator import Keygen, Pickler
|
|
13
15
|
|
|
14
|
-
from utilities.types import Duration
|
|
15
16
|
|
|
17
|
+
type _Key[**P, T] = tuple[Callable[P, Coro[T]], TimeDelta]
|
|
18
|
+
_MEMOIZED_FUNCS: dict[_Key, Callable[..., Coro[Any]]] = {}
|
|
16
19
|
|
|
17
|
-
_P = ParamSpec("_P")
|
|
18
|
-
_R = TypeVar("_R")
|
|
19
|
-
_AsyncFunc = Callable[_P, Coroutine1[_R]]
|
|
20
|
-
type _Key = tuple[_AsyncFunc, dt.timedelta]
|
|
21
|
-
_MEMOIZED_FUNCS: dict[_Key, _AsyncFunc] = {}
|
|
22
20
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
refresh: Duration | None = None,
|
|
21
|
+
async def call_memoized[**P, T](
|
|
22
|
+
func: Callable[P, Coro[T]],
|
|
23
|
+
refresh: TimeDelta | None = None,
|
|
27
24
|
/,
|
|
28
|
-
*args:
|
|
29
|
-
**kwargs:
|
|
30
|
-
) ->
|
|
25
|
+
*args: P.args,
|
|
26
|
+
**kwargs: P.kwargs,
|
|
27
|
+
) -> T:
|
|
31
28
|
"""Call an asynchronous function, with possible memoization."""
|
|
32
29
|
if refresh is None:
|
|
33
30
|
return await func(*args, **kwargs)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
memoized_func: _AsyncFunc[_P, _R]
|
|
31
|
+
key: _Key = (func, refresh)
|
|
32
|
+
memoized_func: Callable[P, Coro[T]]
|
|
37
33
|
try:
|
|
38
34
|
memoized_func = _MEMOIZED_FUNCS[key]
|
|
39
35
|
except KeyError:
|
|
40
|
-
memoized_func = _MEMOIZED_FUNCS[(key)] = memoize(duration=refresh)(
|
|
36
|
+
memoized_func = _MEMOIZED_FUNCS[(key)] = memoize(duration=refresh.in_seconds())(
|
|
37
|
+
func
|
|
38
|
+
)
|
|
41
39
|
return await memoized_func(*args, **kwargs)
|
|
42
40
|
|
|
43
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
|
+
|
|
44
99
|
__all__ = ["call_memoized"]
|
utilities/cachetools.py
CHANGED
|
@@ -1,39 +1,31 @@
|
|
|
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, cast, override
|
|
7
7
|
|
|
8
8
|
import cachetools
|
|
9
9
|
from cachetools.func import ttl_cache
|
|
10
10
|
|
|
11
|
-
from utilities.datetime import datetime_duration_to_float
|
|
12
|
-
|
|
13
11
|
if TYPE_CHECKING:
|
|
14
|
-
from
|
|
15
|
-
|
|
16
|
-
_K = TypeVar("_K")
|
|
17
|
-
_T = TypeVar("_T")
|
|
18
|
-
_V = TypeVar("_V")
|
|
12
|
+
from whenever import TimeDelta
|
|
19
13
|
|
|
20
14
|
|
|
21
|
-
class TTLCache(cachetools.TTLCache[
|
|
15
|
+
class TTLCache[K: Hashable, V](cachetools.TTLCache[K, V]):
|
|
22
16
|
"""A TTL-cache."""
|
|
23
17
|
|
|
24
18
|
def __init__(
|
|
25
19
|
self,
|
|
26
20
|
*,
|
|
27
21
|
max_size: int | None = None,
|
|
28
|
-
max_duration:
|
|
22
|
+
max_duration: TimeDelta | None = None,
|
|
29
23
|
timer: Callable[[], float] = monotonic,
|
|
30
24
|
get_size_of: Callable[[Any], int] | None = None,
|
|
31
25
|
) -> None:
|
|
32
26
|
super().__init__(
|
|
33
27
|
maxsize=inf if max_size is None else max_size,
|
|
34
|
-
ttl=inf
|
|
35
|
-
if max_duration is None
|
|
36
|
-
else datetime_duration_to_float(max_duration),
|
|
28
|
+
ttl=inf if max_duration is None else max_duration.in_seconds(),
|
|
37
29
|
timer=timer,
|
|
38
30
|
getsizeof=get_size_of,
|
|
39
31
|
)
|
|
@@ -42,19 +34,19 @@ class TTLCache(cachetools.TTLCache[_K, _V]):
|
|
|
42
34
|
##
|
|
43
35
|
|
|
44
36
|
|
|
45
|
-
class TTLSet(MutableSet[
|
|
37
|
+
class TTLSet[T: Hashable](MutableSet[T]):
|
|
46
38
|
"""A TTL-set."""
|
|
47
39
|
|
|
48
|
-
_cache: TTLCache[
|
|
40
|
+
_cache: TTLCache[T, None]
|
|
49
41
|
|
|
50
42
|
@override
|
|
51
43
|
def __init__(
|
|
52
44
|
self,
|
|
53
|
-
iterable: Iterable[
|
|
45
|
+
iterable: Iterable[T] | None = None,
|
|
54
46
|
/,
|
|
55
47
|
*,
|
|
56
48
|
max_size: int | None = None,
|
|
57
|
-
max_duration:
|
|
49
|
+
max_duration: TimeDelta | None = None,
|
|
58
50
|
timer: Callable[[], float] = monotonic,
|
|
59
51
|
get_size_of: Callable[[Any], int] | None = None,
|
|
60
52
|
) -> None:
|
|
@@ -73,7 +65,7 @@ class TTLSet(MutableSet[_T]):
|
|
|
73
65
|
return self._cache.__contains__(x)
|
|
74
66
|
|
|
75
67
|
@override
|
|
76
|
-
def __iter__(self) -> Iterator[
|
|
68
|
+
def __iter__(self) -> Iterator[T]:
|
|
77
69
|
return self._cache.__iter__()
|
|
78
70
|
|
|
79
71
|
@override
|
|
@@ -89,30 +81,33 @@ class TTLSet(MutableSet[_T]):
|
|
|
89
81
|
return set(self._cache).__str__()
|
|
90
82
|
|
|
91
83
|
@override
|
|
92
|
-
def add(self, value:
|
|
84
|
+
def add(self, value: T) -> None:
|
|
93
85
|
self._cache[value] = None
|
|
94
86
|
|
|
95
87
|
@override
|
|
96
|
-
def discard(self, value:
|
|
88
|
+
def discard(self, value: T) -> None:
|
|
97
89
|
del self._cache[value]
|
|
98
90
|
|
|
99
91
|
|
|
100
92
|
##
|
|
101
93
|
|
|
102
94
|
|
|
103
|
-
def cache(
|
|
95
|
+
def cache[F: Callable](
|
|
104
96
|
*,
|
|
105
97
|
max_size: int | None = None,
|
|
106
|
-
max_duration:
|
|
98
|
+
max_duration: TimeDelta | None = None,
|
|
107
99
|
timer: Callable[[], float] = monotonic,
|
|
108
100
|
typed_: bool = False,
|
|
109
|
-
) -> Callable[[
|
|
101
|
+
) -> Callable[[F], F]:
|
|
110
102
|
"""Decorate a function with `max_size` and/or `ttl` settings."""
|
|
111
|
-
return
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
+
),
|
|
116
111
|
)
|
|
117
112
|
|
|
118
113
|
|