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/math.py
CHANGED
|
@@ -8,9 +8,10 @@ from re import Match, search
|
|
|
8
8
|
from typing import TYPE_CHECKING, Literal, assert_never, overload, override
|
|
9
9
|
|
|
10
10
|
from utilities.errors import ImpossibleCaseError
|
|
11
|
+
from utilities.re import ExtractGroupsError, extract_groups
|
|
11
12
|
|
|
12
13
|
if TYPE_CHECKING:
|
|
13
|
-
from utilities.types import
|
|
14
|
+
from utilities.types import MathRoundMode, Number, Sign
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
MIN_FLOAT32, MAX_FLOAT32 = -3.4028234663852886e38, 3.4028234663852886e38
|
|
@@ -640,7 +641,10 @@ def _is_close(
|
|
|
640
641
|
##
|
|
641
642
|
|
|
642
643
|
|
|
643
|
-
|
|
644
|
+
MAX_DECIMALS = 10
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
def number_of_decimals(x: float, /, *, max_decimals: int = MAX_DECIMALS) -> int:
|
|
644
648
|
"""Get the number of decimals."""
|
|
645
649
|
_, frac = divmod(x, 1)
|
|
646
650
|
results = (
|
|
@@ -708,7 +712,7 @@ def round_(
|
|
|
708
712
|
x: float,
|
|
709
713
|
/,
|
|
710
714
|
*,
|
|
711
|
-
mode:
|
|
715
|
+
mode: MathRoundMode = "standard",
|
|
712
716
|
rel_tol: float | None = None,
|
|
713
717
|
abs_tol: float | None = None,
|
|
714
718
|
) -> int:
|
|
@@ -730,7 +734,7 @@ def round_(
|
|
|
730
734
|
return 0
|
|
731
735
|
case -1:
|
|
732
736
|
return floor(x)
|
|
733
|
-
case
|
|
737
|
+
case never:
|
|
734
738
|
assert_never(never)
|
|
735
739
|
case "standard-tie-floor":
|
|
736
740
|
return _round_tie_standard(x, "floor", rel_tol=rel_tol, abs_tol=abs_tol)
|
|
@@ -742,13 +746,13 @@ def round_(
|
|
|
742
746
|
)
|
|
743
747
|
case "standard-tie-away-zero":
|
|
744
748
|
return _round_tie_standard(x, "away-zero", rel_tol=rel_tol, abs_tol=abs_tol)
|
|
745
|
-
case
|
|
749
|
+
case never:
|
|
746
750
|
assert_never(never)
|
|
747
751
|
|
|
748
752
|
|
|
749
753
|
def _round_tie_standard(
|
|
750
754
|
x: float,
|
|
751
|
-
mode:
|
|
755
|
+
mode: MathRoundMode,
|
|
752
756
|
/,
|
|
753
757
|
*,
|
|
754
758
|
rel_tol: float | None = None,
|
|
@@ -757,9 +761,9 @@ def _round_tie_standard(
|
|
|
757
761
|
"""Round a float to an integer using the standard method."""
|
|
758
762
|
frac, _ = modf(x)
|
|
759
763
|
if _is_close(abs(frac), 0.5, rel_tol=rel_tol, abs_tol=abs_tol):
|
|
760
|
-
mode_use:
|
|
764
|
+
mode_use: MathRoundMode = mode
|
|
761
765
|
else:
|
|
762
|
-
mode_use:
|
|
766
|
+
mode_use: MathRoundMode = "standard"
|
|
763
767
|
return round_(x, mode=mode_use)
|
|
764
768
|
|
|
765
769
|
|
|
@@ -775,9 +779,9 @@ def round_float_imprecisions(
|
|
|
775
779
|
) -> float:
|
|
776
780
|
"""Round a float, removing binary representation imprecisions."""
|
|
777
781
|
try:
|
|
778
|
-
|
|
779
|
-
except
|
|
780
|
-
|
|
782
|
+
head, tail = extract_groups(_ROUND_FLOAT_IMPRECISIONS_PATTERN, str(x))
|
|
783
|
+
except ExtractGroupsError:
|
|
784
|
+
head, tail = extract_groups(_ROUND_FLOAT_IMPRECISIONS_PATTERN, f"{x:.20f}")
|
|
781
785
|
half = ceil(decimals / 2)
|
|
782
786
|
pattern0 = search(rf"^([0-9]+?)(0{{{half},}})([0-9]+?)$", tail)
|
|
783
787
|
pattern9 = search(rf"^(0*)([0-9]+?)(9{{{half},}})([0-9]+?)$", tail)
|
|
@@ -823,7 +827,7 @@ def round_to_float(
|
|
|
823
827
|
y: float,
|
|
824
828
|
/,
|
|
825
829
|
*,
|
|
826
|
-
mode:
|
|
830
|
+
mode: MathRoundMode = "standard",
|
|
827
831
|
rel_tol: float | None = None,
|
|
828
832
|
abs_tol: float | None = None,
|
|
829
833
|
) -> float:
|
|
@@ -875,7 +879,7 @@ def sign(
|
|
|
875
879
|
if is_negative(x, rel_tol=rel_tol, abs_tol=abs_tol):
|
|
876
880
|
return -1
|
|
877
881
|
return 0
|
|
878
|
-
case
|
|
882
|
+
case never:
|
|
879
883
|
assert_never(never)
|
|
880
884
|
|
|
881
885
|
|
|
@@ -888,6 +892,7 @@ def significant_figures(x: float, /, *, n: int = 2) -> str:
|
|
|
888
892
|
|
|
889
893
|
|
|
890
894
|
__all__ = [
|
|
895
|
+
"MAX_DECIMALS",
|
|
891
896
|
"MAX_FLOAT32",
|
|
892
897
|
"MAX_FLOAT64",
|
|
893
898
|
"MAX_INT8",
|
utilities/memory_profiler.py
CHANGED
|
@@ -2,31 +2,19 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from functools import wraps
|
|
5
|
-
from typing import TYPE_CHECKING, Any,
|
|
5
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
6
6
|
|
|
7
7
|
from memory_profiler import memory_usage
|
|
8
|
-
from typing_extensions import ParamSpec
|
|
9
8
|
|
|
10
9
|
if TYPE_CHECKING:
|
|
11
10
|
from collections.abc import Callable
|
|
12
11
|
|
|
13
|
-
_P = ParamSpec("_P")
|
|
14
|
-
_T = TypeVar("_T")
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
@dataclass(kw_only=True, slots=True)
|
|
18
|
-
class Output(Generic[_T]):
|
|
19
|
-
"""A function output, and its memory usage."""
|
|
20
|
-
|
|
21
|
-
value: _T
|
|
22
|
-
memory: float
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def memory_profiled(func: Callable[_P, _T], /) -> Callable[_P, Output[_T]]:
|
|
13
|
+
def memory_profiled[**P, T](func: Callable[P, T], /) -> Callable[P, Output[T]]:
|
|
26
14
|
"""Call a function, but also profile its maximum memory usage."""
|
|
27
15
|
|
|
28
16
|
@wraps(func)
|
|
29
|
-
def wrapped(*args:
|
|
17
|
+
def wrapped(*args: P.args, **kwargs: P.kwargs) -> Output[T]:
|
|
30
18
|
memory, value = memory_usage(
|
|
31
19
|
cast("Any", (func, args, kwargs)), max_usage=True, retval=True
|
|
32
20
|
)
|
|
@@ -35,4 +23,12 @@ def memory_profiled(func: Callable[_P, _T], /) -> Callable[_P, Output[_T]]:
|
|
|
35
23
|
return wrapped
|
|
36
24
|
|
|
37
25
|
|
|
26
|
+
@dataclass(kw_only=True, slots=True)
|
|
27
|
+
class Output[T]:
|
|
28
|
+
"""A function output, and its memory usage."""
|
|
29
|
+
|
|
30
|
+
value: T
|
|
31
|
+
memory: float
|
|
32
|
+
|
|
33
|
+
|
|
38
34
|
__all__ = ["Output", "memory_profiled"]
|
utilities/more_itertools.py
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
from collections.abc import Callable, Hashable
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
from itertools import islice
|
|
6
6
|
from textwrap import indent
|
|
7
7
|
from typing import (
|
|
8
8
|
TYPE_CHECKING,
|
|
9
9
|
Any,
|
|
10
|
-
Generic,
|
|
11
10
|
Literal,
|
|
12
11
|
TypeGuard,
|
|
13
|
-
TypeVar,
|
|
14
12
|
assert_never,
|
|
15
13
|
cast,
|
|
16
14
|
overload,
|
|
@@ -23,140 +21,213 @@ from more_itertools import peekable as _peekable
|
|
|
23
21
|
from utilities.functions import get_class_name
|
|
24
22
|
from utilities.iterables import OneNonUniqueError, one
|
|
25
23
|
from utilities.reprlib import get_repr
|
|
26
|
-
from utilities.sentinel import Sentinel, sentinel
|
|
27
|
-
from utilities.types import THashable
|
|
24
|
+
from utilities.sentinel import Sentinel, is_sentinel, sentinel
|
|
28
25
|
|
|
29
26
|
if TYPE_CHECKING:
|
|
30
|
-
from collections.abc import
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
_T = TypeVar("_T")
|
|
34
|
-
_U = TypeVar("_U")
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
##
|
|
27
|
+
from collections.abc import Iterable, Iterator, Mapping, Sequence
|
|
38
28
|
|
|
39
29
|
|
|
40
30
|
@overload
|
|
41
|
-
def bucket_mapping(
|
|
42
|
-
iterable: Iterable[
|
|
43
|
-
func: Callable[[
|
|
31
|
+
def bucket_mapping[T, UH: Hashable](
|
|
32
|
+
iterable: Iterable[T],
|
|
33
|
+
func: Callable[[T], UH],
|
|
44
34
|
/,
|
|
45
35
|
*,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
) -> Mapping[THashable, _U]: ...
|
|
36
|
+
pre: None = None,
|
|
37
|
+
post: None = None,
|
|
38
|
+
) -> Mapping[UH, Iterator[T]]: ...
|
|
50
39
|
@overload
|
|
51
|
-
def bucket_mapping(
|
|
52
|
-
iterable: Iterable[
|
|
53
|
-
func: Callable[[
|
|
40
|
+
def bucket_mapping[T, UH: Hashable](
|
|
41
|
+
iterable: Iterable[T],
|
|
42
|
+
func: Callable[[T], UH],
|
|
54
43
|
/,
|
|
55
44
|
*,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
) -> Mapping[THashable, _T]: ...
|
|
45
|
+
pre: None = None,
|
|
46
|
+
post: Literal["list"],
|
|
47
|
+
) -> Mapping[UH, list[T]]: ...
|
|
60
48
|
@overload
|
|
61
|
-
def bucket_mapping(
|
|
62
|
-
iterable: Iterable[
|
|
63
|
-
func: Callable[[
|
|
49
|
+
def bucket_mapping[T, UH: Hashable](
|
|
50
|
+
iterable: Iterable[T],
|
|
51
|
+
func: Callable[[T], UH],
|
|
64
52
|
/,
|
|
65
53
|
*,
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
) -> Mapping[
|
|
54
|
+
pre: None = None,
|
|
55
|
+
post: Literal["tuple"],
|
|
56
|
+
) -> Mapping[UH, tuple[T, ...]]: ...
|
|
69
57
|
@overload
|
|
70
|
-
def bucket_mapping(
|
|
71
|
-
iterable: Iterable[
|
|
72
|
-
func: Callable[[
|
|
58
|
+
def bucket_mapping[T, UH: Hashable](
|
|
59
|
+
iterable: Iterable[T],
|
|
60
|
+
func: Callable[[T], UH],
|
|
73
61
|
/,
|
|
74
62
|
*,
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
) -> Mapping[
|
|
63
|
+
pre: None = None,
|
|
64
|
+
post: Literal["set"],
|
|
65
|
+
) -> Mapping[UH, set[T]]: ...
|
|
78
66
|
@overload
|
|
79
|
-
def bucket_mapping(
|
|
80
|
-
iterable: Iterable[
|
|
81
|
-
func: Callable[[
|
|
67
|
+
def bucket_mapping[T, UH: Hashable](
|
|
68
|
+
iterable: Iterable[T],
|
|
69
|
+
func: Callable[[T], UH],
|
|
82
70
|
/,
|
|
83
71
|
*,
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
) -> Mapping[
|
|
72
|
+
pre: None = None,
|
|
73
|
+
post: Literal["frozenset"],
|
|
74
|
+
) -> Mapping[UH, frozenset[T]]: ...
|
|
87
75
|
@overload
|
|
88
|
-
def bucket_mapping(
|
|
89
|
-
iterable: Iterable[
|
|
90
|
-
func: Callable[[
|
|
76
|
+
def bucket_mapping[T, UH: Hashable](
|
|
77
|
+
iterable: Iterable[T],
|
|
78
|
+
func: Callable[[T], UH],
|
|
91
79
|
/,
|
|
92
80
|
*,
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
) -> Mapping[
|
|
81
|
+
pre: None = None,
|
|
82
|
+
post: Literal["unique"],
|
|
83
|
+
) -> Mapping[UH, T]: ...
|
|
96
84
|
@overload
|
|
97
|
-
def bucket_mapping(
|
|
98
|
-
iterable: Iterable[
|
|
99
|
-
func: Callable[[
|
|
85
|
+
def bucket_mapping[T, U, UH: Hashable](
|
|
86
|
+
iterable: Iterable[T],
|
|
87
|
+
func: Callable[[T], UH],
|
|
100
88
|
/,
|
|
101
89
|
*,
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
90
|
+
pre: Callable[[T], U] | None = None,
|
|
91
|
+
post: None = None,
|
|
92
|
+
) -> Mapping[UH, Iterator[U]]: ...
|
|
93
|
+
@overload
|
|
94
|
+
def bucket_mapping[T, U, UH: Hashable](
|
|
95
|
+
iterable: Iterable[T],
|
|
96
|
+
func: Callable[[T], UH],
|
|
97
|
+
/,
|
|
98
|
+
*,
|
|
99
|
+
pre: Callable[[T], U],
|
|
100
|
+
post: Literal["list"],
|
|
101
|
+
) -> Mapping[UH, list[U]]: ...
|
|
102
|
+
@overload
|
|
103
|
+
def bucket_mapping[T, U, UH: Hashable](
|
|
104
|
+
iterable: Iterable[T],
|
|
105
|
+
func: Callable[[T], UH],
|
|
106
|
+
/,
|
|
107
|
+
*,
|
|
108
|
+
pre: Callable[[T], U],
|
|
109
|
+
post: Literal["tuple"],
|
|
110
|
+
) -> Mapping[UH, tuple[U, ...]]: ...
|
|
111
|
+
@overload
|
|
112
|
+
def bucket_mapping[T, U, UH: Hashable](
|
|
113
|
+
iterable: Iterable[T],
|
|
114
|
+
func: Callable[[T], UH],
|
|
115
|
+
/,
|
|
116
|
+
*,
|
|
117
|
+
pre: Callable[[T], U],
|
|
118
|
+
post: Literal["set"],
|
|
119
|
+
) -> Mapping[UH, set[U]]: ...
|
|
120
|
+
@overload
|
|
121
|
+
def bucket_mapping[T, U, UH: Hashable](
|
|
122
|
+
iterable: Iterable[T],
|
|
123
|
+
func: Callable[[T], UH],
|
|
124
|
+
/,
|
|
125
|
+
*,
|
|
126
|
+
pre: Callable[[T], U],
|
|
127
|
+
post: Literal["frozenset"],
|
|
128
|
+
) -> Mapping[UH, frozenset[U]]: ...
|
|
129
|
+
@overload
|
|
130
|
+
def bucket_mapping[T, U, UH: Hashable](
|
|
131
|
+
iterable: Iterable[T],
|
|
132
|
+
func: Callable[[T], UH],
|
|
133
|
+
/,
|
|
134
|
+
*,
|
|
135
|
+
pre: Callable[[T], U] | None = None,
|
|
136
|
+
post: Literal["unique"],
|
|
137
|
+
) -> Mapping[UH, U]: ...
|
|
138
|
+
@overload
|
|
139
|
+
def bucket_mapping[T, U, UH: Hashable](
|
|
140
|
+
iterable: Iterable[T],
|
|
141
|
+
func: Callable[[T], UH],
|
|
142
|
+
/,
|
|
143
|
+
*,
|
|
144
|
+
pre: Callable[[T], U] | None = None,
|
|
145
|
+
post: Literal["list", "tuple", "set", "frozenset", "unique"] | None = None,
|
|
105
146
|
) -> (
|
|
106
|
-
Mapping[
|
|
107
|
-
| Mapping[
|
|
108
|
-
| Mapping[
|
|
109
|
-
| Mapping[
|
|
110
|
-
| Mapping[
|
|
111
|
-
| Mapping[
|
|
147
|
+
Mapping[UH, Iterator[T]]
|
|
148
|
+
| Mapping[UH, list[T]]
|
|
149
|
+
| Mapping[UH, tuple[T, ...]]
|
|
150
|
+
| Mapping[UH, set[T]]
|
|
151
|
+
| Mapping[UH, frozenset[T]]
|
|
152
|
+
| Mapping[UH, T]
|
|
153
|
+
| Mapping[UH, Iterator[U]]
|
|
154
|
+
| Mapping[UH, list[U]]
|
|
155
|
+
| Mapping[UH, tuple[U, ...]]
|
|
156
|
+
| Mapping[UH, set[U]]
|
|
157
|
+
| Mapping[UH, frozenset[U]]
|
|
158
|
+
| Mapping[UH, U]
|
|
112
159
|
): ...
|
|
113
|
-
def bucket_mapping(
|
|
114
|
-
iterable: Iterable[
|
|
115
|
-
func: Callable[[
|
|
160
|
+
def bucket_mapping[T, U, UH: Hashable](
|
|
161
|
+
iterable: Iterable[T],
|
|
162
|
+
func: Callable[[T], UH],
|
|
116
163
|
/,
|
|
117
164
|
*,
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
unique: bool = False,
|
|
165
|
+
pre: Callable[[T], U] | None = None,
|
|
166
|
+
post: Literal["list", "tuple", "set", "frozenset", "unique"] | None = None,
|
|
121
167
|
) -> (
|
|
122
|
-
Mapping[
|
|
123
|
-
| Mapping[
|
|
124
|
-
| Mapping[
|
|
125
|
-
| Mapping[
|
|
126
|
-
| Mapping[
|
|
127
|
-
| Mapping[
|
|
168
|
+
Mapping[UH, Iterator[T]]
|
|
169
|
+
| Mapping[UH, list[T]]
|
|
170
|
+
| Mapping[UH, tuple[T, ...]]
|
|
171
|
+
| Mapping[UH, set[T]]
|
|
172
|
+
| Mapping[UH, frozenset[T]]
|
|
173
|
+
| Mapping[UH, T]
|
|
174
|
+
| Mapping[UH, Iterator[U]]
|
|
175
|
+
| Mapping[UH, list[U]]
|
|
176
|
+
| Mapping[UH, tuple[U, ...]]
|
|
177
|
+
| Mapping[UH, set[U]]
|
|
178
|
+
| Mapping[UH, frozenset[U]]
|
|
179
|
+
| Mapping[UH, U]
|
|
128
180
|
):
|
|
129
181
|
"""Bucket the values of iterable into a mapping."""
|
|
130
|
-
|
|
131
|
-
mapping = {key:
|
|
132
|
-
match
|
|
133
|
-
case None,
|
|
134
|
-
|
|
135
|
-
case None,
|
|
136
|
-
|
|
137
|
-
case
|
|
138
|
-
|
|
139
|
-
case
|
|
140
|
-
|
|
141
|
-
case
|
|
182
|
+
bckt = bucket(iterable, func)
|
|
183
|
+
mapping = {key: bckt[key] for key in bckt}
|
|
184
|
+
match pre, post:
|
|
185
|
+
case None, None:
|
|
186
|
+
return mapping
|
|
187
|
+
case None, "list":
|
|
188
|
+
return {k: list(v) for k, v in mapping.items()}
|
|
189
|
+
case None, "tuple":
|
|
190
|
+
return {k: tuple(v) for k, v in mapping.items()}
|
|
191
|
+
case None, "set":
|
|
192
|
+
return {k: set(v) for k, v in mapping.items()}
|
|
193
|
+
case None, "frozenset":
|
|
194
|
+
return {k: frozenset(v) for k, v in mapping.items()}
|
|
195
|
+
case None, "unique":
|
|
196
|
+
return _bucket_mapping_unique(mapping)
|
|
197
|
+
case Callable(), None:
|
|
198
|
+
return {k: map(pre, v) for k, v in mapping.items()}
|
|
199
|
+
case Callable(), "list":
|
|
200
|
+
return {k: list(map(pre, v)) for k, v in mapping.items()}
|
|
201
|
+
case Callable(), "tuple":
|
|
202
|
+
return {k: tuple(map(pre, v)) for k, v in mapping.items()}
|
|
203
|
+
case Callable(), "set":
|
|
204
|
+
return {k: set(map(pre, v)) for k, v in mapping.items()}
|
|
205
|
+
case Callable(), "frozenset":
|
|
206
|
+
return {k: frozenset(map(pre, v)) for k, v in mapping.items()}
|
|
207
|
+
case Callable(), "unique":
|
|
208
|
+
return _bucket_mapping_unique({k: map(pre, v) for k, v in mapping.items()})
|
|
209
|
+
case never:
|
|
142
210
|
assert_never(never)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _bucket_mapping_unique[K: Hashable, V](
|
|
214
|
+
mapping: Mapping[K, Iterable[V]], /
|
|
215
|
+
) -> Mapping[K, V]:
|
|
216
|
+
results: dict[K, V] = {}
|
|
217
|
+
errors: dict[K, tuple[V, V]] = {}
|
|
147
218
|
for key, value in mapping.items():
|
|
148
219
|
try:
|
|
149
220
|
results[key] = one(value)
|
|
150
221
|
except OneNonUniqueError as error:
|
|
151
|
-
|
|
152
|
-
if len(
|
|
153
|
-
raise BucketMappingError(errors=
|
|
222
|
+
errors[key] = (error.first, error.second)
|
|
223
|
+
if len(errors) >= 1:
|
|
224
|
+
raise BucketMappingError(errors=errors)
|
|
154
225
|
return results
|
|
155
226
|
|
|
156
227
|
|
|
157
228
|
@dataclass(kw_only=True, slots=True)
|
|
158
|
-
class BucketMappingError
|
|
159
|
-
errors: Mapping[
|
|
229
|
+
class BucketMappingError[K: Hashable, V](Exception):
|
|
230
|
+
errors: Mapping[K, tuple[V, V]]
|
|
160
231
|
|
|
161
232
|
@override
|
|
162
233
|
def __str__(self) -> str:
|
|
@@ -171,9 +242,9 @@ class BucketMappingError(Exception, Generic[THashable, _U]):
|
|
|
171
242
|
##
|
|
172
243
|
|
|
173
244
|
|
|
174
|
-
def partition_list(
|
|
175
|
-
pred: Callable[[
|
|
176
|
-
) -> tuple[list[
|
|
245
|
+
def partition_list[T](
|
|
246
|
+
pred: Callable[[T], bool], iterable: Iterable[T], /
|
|
247
|
+
) -> tuple[list[T], list[T]]:
|
|
177
248
|
"""Partition with lists."""
|
|
178
249
|
false, true = partition(pred, iterable)
|
|
179
250
|
return list(false), list(true)
|
|
@@ -182,48 +253,46 @@ def partition_list(
|
|
|
182
253
|
##
|
|
183
254
|
|
|
184
255
|
|
|
185
|
-
def partition_typeguard(
|
|
186
|
-
pred: Callable[[
|
|
187
|
-
) -> tuple[Iterator[
|
|
256
|
+
def partition_typeguard[T, U](
|
|
257
|
+
pred: Callable[[T], TypeGuard[U]], iterable: Iterable[T], /
|
|
258
|
+
) -> tuple[Iterator[T], Iterator[U]]:
|
|
188
259
|
"""Partition with a typeguarded function."""
|
|
189
260
|
false, true = partition(pred, iterable)
|
|
190
|
-
true = cast("Iterator[
|
|
261
|
+
true = cast("Iterator[U]", true)
|
|
191
262
|
return false, true
|
|
192
263
|
|
|
193
264
|
|
|
194
265
|
##
|
|
195
266
|
|
|
196
267
|
|
|
197
|
-
class peekable(_peekable
|
|
268
|
+
class peekable[T](_peekable): # noqa: N801
|
|
198
269
|
"""Peekable which supports dropwhile/takewhile methods."""
|
|
199
270
|
|
|
200
|
-
def __init__(self, iterable: Iterable[
|
|
271
|
+
def __init__(self, iterable: Iterable[T], /) -> None:
|
|
201
272
|
super().__init__(iterable)
|
|
202
273
|
|
|
203
274
|
@override
|
|
204
|
-
def __iter__(self) -> Iterator[
|
|
275
|
+
def __iter__(self) -> Iterator[T]: # pyright: ignore[reportIncompatibleMethodOverride]
|
|
205
276
|
while bool(self):
|
|
206
277
|
yield next(self)
|
|
207
278
|
|
|
208
279
|
@override
|
|
209
|
-
def __next__(self) ->
|
|
280
|
+
def __next__(self) -> T:
|
|
210
281
|
return super().__next__()
|
|
211
282
|
|
|
212
|
-
def dropwhile(self, predicate: Callable[[
|
|
283
|
+
def dropwhile(self, predicate: Callable[[T], bool], /) -> None:
|
|
213
284
|
while bool(self) and predicate(self.peek()):
|
|
214
285
|
_ = next(self)
|
|
215
286
|
|
|
216
287
|
@overload
|
|
217
|
-
def peek(self, *, default: Sentinel = sentinel) ->
|
|
288
|
+
def peek(self, *, default: Sentinel = sentinel) -> T: ...
|
|
218
289
|
@overload
|
|
219
|
-
def peek(self, *, default:
|
|
290
|
+
def peek[U](self, *, default: U) -> T | U: ...
|
|
220
291
|
@override
|
|
221
292
|
def peek(self, *, default: Any = sentinel) -> Any: # pyright: ignore[reportIncompatibleMethodOverride]
|
|
222
|
-
if
|
|
223
|
-
return super().peek()
|
|
224
|
-
return super().peek(default=default)
|
|
293
|
+
return super().peek() if is_sentinel(default) else super().peek(default=default)
|
|
225
294
|
|
|
226
|
-
def takewhile(self, predicate: Callable[[
|
|
295
|
+
def takewhile(self, predicate: Callable[[T], bool], /) -> Iterator[T]:
|
|
227
296
|
while bool(self) and predicate(self.peek()):
|
|
228
297
|
yield next(self)
|
|
229
298
|
|
|
@@ -232,11 +301,11 @@ class peekable(_peekable, Generic[_T]): # noqa: N801
|
|
|
232
301
|
|
|
233
302
|
|
|
234
303
|
@dataclass(kw_only=True, slots=True)
|
|
235
|
-
class Split
|
|
304
|
+
class Split[T]:
|
|
236
305
|
"""An iterable split into head/tail."""
|
|
237
306
|
|
|
238
|
-
head:
|
|
239
|
-
tail:
|
|
307
|
+
head: T
|
|
308
|
+
tail: T
|
|
240
309
|
|
|
241
310
|
@override
|
|
242
311
|
def __repr__(self) -> str:
|
|
@@ -250,15 +319,15 @@ class Split(Generic[_T]):
|
|
|
250
319
|
return f"{cls}(\n{joined}\n)"
|
|
251
320
|
|
|
252
321
|
|
|
253
|
-
def yield_splits(
|
|
254
|
-
iterable: Iterable[
|
|
322
|
+
def yield_splits[T](
|
|
323
|
+
iterable: Iterable[T],
|
|
255
324
|
head: int,
|
|
256
325
|
tail: int,
|
|
257
326
|
/,
|
|
258
327
|
*,
|
|
259
328
|
min_frac: float | None = None,
|
|
260
329
|
freq: int | None = None,
|
|
261
|
-
) -> Iterator[Split[Sequence[
|
|
330
|
+
) -> Iterator[Split[Sequence[T]]]:
|
|
262
331
|
"""Yield the splits of an iterable."""
|
|
263
332
|
it1 = _yield_splits1(iterable, head + tail)
|
|
264
333
|
it2 = _yield_splits2(it1, head, tail, min_frac=min_frac)
|
|
@@ -267,9 +336,9 @@ def yield_splits(
|
|
|
267
336
|
return islice(it3, 0, None, freq_use)
|
|
268
337
|
|
|
269
338
|
|
|
270
|
-
def _yield_splits1(
|
|
271
|
-
iterable: Iterable[
|
|
272
|
-
) -> Iterator[tuple[Literal["head", "body"], Sequence[
|
|
339
|
+
def _yield_splits1[T](
|
|
340
|
+
iterable: Iterable[T], total: int, /
|
|
341
|
+
) -> Iterator[tuple[Literal["head", "body"], Sequence[T]]]:
|
|
273
342
|
peek = peekable(iterable)
|
|
274
343
|
for i in range(1, total + 1):
|
|
275
344
|
if len(result := peek[:i]) < i:
|
|
@@ -283,14 +352,14 @@ def _yield_splits1(
|
|
|
283
352
|
break
|
|
284
353
|
|
|
285
354
|
|
|
286
|
-
def _yield_splits2(
|
|
287
|
-
iterable: Iterable[tuple[Literal["head", "body"], Sequence[
|
|
355
|
+
def _yield_splits2[T](
|
|
356
|
+
iterable: Iterable[tuple[Literal["head", "body"], Sequence[T]],],
|
|
288
357
|
head: int,
|
|
289
358
|
tail: int,
|
|
290
359
|
/,
|
|
291
360
|
*,
|
|
292
361
|
min_frac: float | None = None,
|
|
293
|
-
) -> Iterator[tuple[Iterable[
|
|
362
|
+
) -> Iterator[tuple[Iterable[T], int, int]]:
|
|
294
363
|
min_length = head if min_frac is None else min_frac * head
|
|
295
364
|
for kind, window in iterable:
|
|
296
365
|
len_win = len(window)
|
|
@@ -303,17 +372,17 @@ def _yield_splits2(
|
|
|
303
372
|
len_tail = max(len_win - head, 0)
|
|
304
373
|
if len_tail >= 1:
|
|
305
374
|
yield window, head, len_tail
|
|
306
|
-
case
|
|
375
|
+
case never:
|
|
307
376
|
assert_never(never)
|
|
308
377
|
|
|
309
378
|
|
|
310
|
-
def _yield_splits3(
|
|
311
|
-
iterable: Iterable[tuple[Iterable[
|
|
312
|
-
) -> Iterator[Split[Sequence[
|
|
379
|
+
def _yield_splits3[T](
|
|
380
|
+
iterable: Iterable[tuple[Iterable[T], int, int]], /
|
|
381
|
+
) -> Iterator[Split[Sequence[T]]]:
|
|
313
382
|
for window, len_head, len_tail in iterable:
|
|
314
383
|
head_win, tail_win = split_into(window, [len_head, len_tail])
|
|
315
384
|
yield cast(
|
|
316
|
-
"Split[Sequence[
|
|
385
|
+
"Split[Sequence[T]]", Split(head=list(head_win), tail=list(tail_win))
|
|
317
386
|
)
|
|
318
387
|
|
|
319
388
|
|