dycw-utilities 0.138.5__py3-none-any.whl → 0.138.7__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.138.5
3
+ Version: 0.138.7
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -1,4 +1,4 @@
1
- utilities/__init__.py,sha256=8_NgkEVVSVQiH0KSjlNRZwW_d9Lb2cnfj8c1HrNPmGA,60
1
+ utilities/__init__.py,sha256=Y_4wr1MFLRZ5ZwdjuZ0h_gSie-eMamrUJhqXhpN6Eo4,60
2
2
  utilities/aiolimiter.py,sha256=mD0wEiqMgwpty4XTbawFpnkkmJS6R4JRsVXFUaoitSU,628
3
3
  utilities/altair.py,sha256=HeZBVUocjkrTNwwKrClppsIqgNFF-ykv05HfZSoHYno,9104
4
4
  utilities/asyncio.py,sha256=dcGeKQzjLBXxKzZkVIk5oZsFXEcynVbRB9iNB5XEDZk,38526
@@ -42,7 +42,7 @@ utilities/optuna.py,sha256=C-fhWYiXHVPo1l8QctYkFJ4DyhbSrGorzP1dJb_qvd8,1933
42
42
  utilities/orjson.py,sha256=WWV2QukCIuwT8OAOtmKhLhxezXPVbeA_fQCucmGmbRA,37106
43
43
  utilities/os.py,sha256=yMNAKMyY8oFgQ1yN3TQYnwa5-A_FXz4tCDbhIctQHSs,3736
44
44
  utilities/parse.py,sha256=JcJn5yXKhIWXBCwgBdPsyu7Hvcuw6kyEdqvaebCaI9k,17951
45
- utilities/pathlib.py,sha256=yE85i5nlXXdW9HFE2KzjmP2yyWhnZYQkMZ9TjQ8UQRk,4454
45
+ utilities/pathlib.py,sha256=1QTfoMze_RYX0wrbm9F9FSaZNfO8n1bSFD60-E1Vmc0,6912
46
46
  utilities/period.py,sha256=6jEff_qAiE7xdFaQ1DnKgNf10D2wHhzt7hQXCBoKlgc,6842
47
47
  utilities/pickle.py,sha256=MBT2xZCsv0pH868IXLGKnlcqNx2IRVKYNpRcqiQQqxw,653
48
48
  utilities/platform.py,sha256=5uCKRf_ij7ukJDcbnNfhY2ay9fbrpiNLRO1t2QvcwqQ,2825
@@ -55,7 +55,8 @@ utilities/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
55
  utilities/pydantic.py,sha256=CmxCi4sukeHM3JGjJ1Rbp8UAvcx4MZapLg10mFYJ-nk,1771
56
56
  utilities/pyinstrument.py,sha256=HrTGJ2niUAHUFMSN3im9BeedC0faq2DqoFccDxPpsP8,884
57
57
  utilities/pytest.py,sha256=f6Yl2vHimin2Ulg0aUsNhdWmf6dC1Hs6nDTFOCOpRJI,8029
58
- utilities/pytest_regressions.py,sha256=YI55B7EtLjhz7zPJZ6NK9bWrxrKCKabWZJe1cwcbA5o,5082
58
+ utilities/pytest_regressions.py,sha256=X5fN5MjRyTHWV_xe-K5a3f01I1FX0qcCMvtIQeQ93yE,4176
59
+ utilities/pytest_regressions_plugin.py,sha256=kIuQx36EzamLmakQruZjd4ak9PUKcRnrnoBJvgYCiFo,1412
59
60
  utilities/python_dotenv.py,sha256=dYooRYwqrvhSoZWuiVbCiKUWiS-M5b5yv2zDWGYPEvI,3209
60
61
  utilities/random.py,sha256=YWYzWxQDeyJRiuHGnO1OxF6dDucpq7qc1tH_ealwCRg,4130
61
62
  utilities/re.py,sha256=6qxeV0rQZaBDKWcB7apSBmxtg_XzoGY-EdegTkMn-ZY,4578
@@ -87,7 +88,8 @@ utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
87
88
  utilities/whenever.py,sha256=R5d9UCNCdAOyjwLUmfH2Vn8Ykee8OHQi2skRTFfbZMM,20492
88
89
  utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
89
90
  utilities/zoneinfo.py,sha256=oEH-nL3t4h9uawyZqWDtNtDAl6M-CLpLYGI_nI6DulM,1971
90
- dycw_utilities-0.138.5.dist-info/METADATA,sha256=49xR0EeDJd73mcxgxDir4zIZrsvAyW3Cb-iorojBolY,1638
91
- dycw_utilities-0.138.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
92
- dycw_utilities-0.138.5.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
93
- dycw_utilities-0.138.5.dist-info/RECORD,,
91
+ dycw_utilities-0.138.7.dist-info/METADATA,sha256=6Xtr3iRQVgzBZqhlaQk5tw2c0Dnpt51AU2A86z1qdAE,1638
92
+ dycw_utilities-0.138.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
93
+ dycw_utilities-0.138.7.dist-info/entry_points.txt,sha256=uLj5QWWVXv8tnMaRX3ZGYpt7w1xzLWU6LxbFhELEpkc,68
94
+ dycw_utilities-0.138.7.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
95
+ dycw_utilities-0.138.7.dist-info/RECORD,,
@@ -0,0 +1,2 @@
1
+ [pytest11]
2
+ pytest-regressions = utilities.pytest_regressions_plugin
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.138.5"
3
+ __version__ = "0.138.7"
utilities/pathlib.py CHANGED
@@ -9,7 +9,7 @@ from os.path import expandvars
9
9
  from pathlib import Path
10
10
  from re import IGNORECASE, search
11
11
  from subprocess import PIPE, CalledProcessError, check_output
12
- from typing import TYPE_CHECKING, assert_never, overload, override
12
+ from typing import TYPE_CHECKING, Literal, assert_never, overload, override
13
13
 
14
14
  from utilities.errors import ImpossibleCaseError
15
15
  from utilities.sentinel import Sentinel, sentinel
@@ -132,6 +132,85 @@ class GetRootError(Exception):
132
132
  ##
133
133
 
134
134
 
135
+ type _GetTailDisambiguate = Literal["raise", "earlier", "later"]
136
+
137
+
138
+ def get_tail(
139
+ path: PathLike, root: PathLike, /, *, disambiguate: _GetTailDisambiguate = "raise"
140
+ ) -> Path:
141
+ """Get the tail of a path following a root match."""
142
+ path_parts, root_parts = [Path(p).parts for p in [path, root]]
143
+ len_path, len_root = map(len, [path_parts, root_parts])
144
+ if len_root > len_path:
145
+ raise _GetTailLengthError(path=path, root=root, len_root=len_root)
146
+ candidates = {
147
+ i + len_root: path_parts[i : i + len_root]
148
+ for i in range(len_path + 1 - len_root)
149
+ }
150
+ matches = {k: v for k, v in candidates.items() if v == root_parts}
151
+ match len(matches), disambiguate:
152
+ case 0, _:
153
+ raise _GetTailEmptyError(path=path, root=root)
154
+ case 1, _:
155
+ return _get_tail_core(path, next(iter(matches)))
156
+ case _, "raise":
157
+ first, second, *_ = matches
158
+ raise _GetTailNonUniqueError(
159
+ path=path,
160
+ root=root,
161
+ first=_get_tail_core(path, first),
162
+ second=_get_tail_core(path, second),
163
+ )
164
+ case _, "earlier":
165
+ return _get_tail_core(path, next(iter(matches)))
166
+ case _, "later":
167
+ return _get_tail_core(path, next(iter(reversed(matches))))
168
+ case _ as never:
169
+ assert_never(never)
170
+
171
+
172
+ def _get_tail_core(path: PathLike, i: int, /) -> Path:
173
+ parts = Path(path).parts
174
+ return Path(*parts[i:])
175
+
176
+
177
+ @dataclass(kw_only=True, slots=True)
178
+ class GetTailError(Exception):
179
+ path: PathLike
180
+ root: PathLike
181
+
182
+
183
+ @dataclass(kw_only=True, slots=True)
184
+ class _GetTailLengthError(GetTailError):
185
+ len_root: int
186
+
187
+ @override
188
+ def __str__(self) -> str:
189
+ return f"Unable to get the tail of {str(self.path)!r} with root of length {self.len_root}"
190
+
191
+
192
+ @dataclass(kw_only=True, slots=True)
193
+ class _GetTailEmptyError(GetTailError):
194
+ @override
195
+ def __str__(self) -> str:
196
+ return (
197
+ f"Unable to get the tail of {str(self.path)!r} with root {str(self.root)!r}"
198
+ )
199
+
200
+
201
+ @dataclass(kw_only=True, slots=True)
202
+ class _GetTailNonUniqueError(GetTailError):
203
+ first: Path
204
+ second: Path
205
+
206
+ @override
207
+ def __str__(self) -> str:
208
+ return f"Path {str(self.path)!r} must contain exactly one tail with root {str(self.root)!r}; got {str(self.first)!r}, {str(self.second)!r} and perhaps more"
209
+
210
+
211
+ ##
212
+
213
+
135
214
  def is_sub_path(x: PathLike, y: PathLike, /, *, strict: bool = False) -> bool:
136
215
  """Check if a path is a sub path of another."""
137
216
  x, y = [Path(i).resolve() for i in [x, y]]
@@ -162,9 +241,11 @@ def temp_cwd(path: PathLike, /) -> Iterator[None]:
162
241
 
163
242
  __all__ = [
164
243
  "PWD",
244
+ "GetTailError",
165
245
  "ensure_suffix",
166
246
  "expand_path",
167
247
  "get_path",
248
+ "get_tail",
168
249
  "is_sub_path",
169
250
  "list_dir",
170
251
  "temp_cwd",
@@ -6,13 +6,10 @@ from pathlib import Path
6
6
  from shutil import copytree
7
7
  from typing import TYPE_CHECKING, Any, assert_never
8
8
 
9
- from pytest import fixture
10
9
  from pytest_regressions.file_regression import FileRegressionFixture
11
10
 
12
11
  from utilities.functions import ensure_str
13
12
  from utilities.operator import is_equal
14
- from utilities.pathlib import get_root
15
- from utilities.pytest import node_id_to_path
16
13
 
17
14
  if TYPE_CHECKING:
18
15
  from polars import DataFrame, Series
@@ -21,9 +18,6 @@ if TYPE_CHECKING:
21
18
  from utilities.types import PathLike, StrMapping
22
19
 
23
20
 
24
- _PATH_TESTS = Path("src", "tests")
25
-
26
-
27
21
  ##
28
22
 
29
23
 
@@ -84,15 +78,6 @@ class OrjsonRegressionFixture:
84
78
  assert is_equal(left, right), f"{left=}, {right=}"
85
79
 
86
80
 
87
- @fixture
88
- def orjson_regression(
89
- *, request: FixtureRequest, tmp_path: Path
90
- ) -> OrjsonRegressionFixture:
91
- """Instance of the `OrjsonRegressionFixture`."""
92
- path = _get_path(request)
93
- return OrjsonRegressionFixture(path, request, tmp_path)
94
-
95
-
96
81
  ##
97
82
 
98
83
 
@@ -139,26 +124,4 @@ class PolarsRegressionFixture:
139
124
  self._fixture.check(data, suffix=suffix)
140
125
 
141
126
 
142
- @fixture
143
- def polars_regression(
144
- *, request: FixtureRequest, tmp_path: Path
145
- ) -> PolarsRegressionFixture:
146
- """Instance of the `PolarsRegressionFixture`."""
147
- path = _get_path(request)
148
- return PolarsRegressionFixture(path, request, tmp_path)
149
-
150
-
151
- ##
152
-
153
-
154
- def _get_path(request: FixtureRequest, /) -> Path:
155
- tail = node_id_to_path(request.node.nodeid, head=_PATH_TESTS)
156
- return get_root().joinpath(_PATH_TESTS, "regressions", tail)
157
-
158
-
159
- __all__ = [
160
- "OrjsonRegressionFixture",
161
- "PolarsRegressionFixture",
162
- "orjson_regression",
163
- "polars_regression",
164
- ]
127
+ __all__ = ["OrjsonRegressionFixture", "PolarsRegressionFixture"]
@@ -0,0 +1,51 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import TYPE_CHECKING
5
+
6
+ if TYPE_CHECKING:
7
+ from pytest import FixtureRequest
8
+
9
+ from utilities.pytest_regressions import (
10
+ OrjsonRegressionFixture,
11
+ PolarsRegressionFixture,
12
+ )
13
+
14
+
15
+ try:
16
+ from pytest import fixture
17
+ except ModuleNotFoundError:
18
+ pass
19
+ else:
20
+
21
+ @fixture
22
+ def orjson_regression(
23
+ *, request: FixtureRequest, tmp_path: Path
24
+ ) -> OrjsonRegressionFixture:
25
+ """Instance of the `OrjsonRegressionFixture`."""
26
+ from utilities.pytest_regressions import OrjsonRegressionFixture
27
+
28
+ path = _get_path(request)
29
+ return OrjsonRegressionFixture(path, request, tmp_path)
30
+
31
+ @fixture
32
+ def polars_regression(
33
+ *, request: FixtureRequest, tmp_path: Path
34
+ ) -> PolarsRegressionFixture:
35
+ """Instance of the `PolarsRegressionFixture`."""
36
+ from utilities.pytest_regressions import PolarsRegressionFixture
37
+
38
+ path = _get_path(request)
39
+ return PolarsRegressionFixture(path, request, tmp_path)
40
+
41
+
42
+ def _get_path(request: FixtureRequest, /) -> Path:
43
+ from utilities.pathlib import get_root
44
+ from utilities.pytest import node_id_to_path
45
+
46
+ head = Path("src", "tests")
47
+ tail = node_id_to_path(request.node.nodeid, head=head)
48
+ return get_root().joinpath(head, "regressions", tail)
49
+
50
+
51
+ __all__ = ["orjson_regression", "polars_regression"]