dycw-utilities 0.174.12__py3-none-any.whl → 0.175.31__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.
Potentially problematic release.
This version of dycw-utilities might be problematic. Click here for more details.
- {dycw_utilities-0.174.12.dist-info → dycw_utilities-0.175.31.dist-info}/METADATA +4 -11
- {dycw_utilities-0.174.12.dist-info → dycw_utilities-0.175.31.dist-info}/RECORD +18 -19
- {dycw_utilities-0.174.12.dist-info → dycw_utilities-0.175.31.dist-info}/WHEEL +1 -1
- {dycw_utilities-0.174.12.dist-info → dycw_utilities-0.175.31.dist-info}/entry_points.txt +0 -2
- utilities/__init__.py +1 -1
- utilities/altair.py +3 -1
- utilities/click.py +1 -2
- utilities/docker.py +117 -28
- utilities/functions.py +1 -1
- utilities/hypothesis.py +1 -1
- utilities/importlib.py +17 -1
- utilities/pathlib.py +1 -1
- utilities/permissions.py +1 -0
- utilities/platform.py +1 -1
- utilities/polars.py +2 -0
- utilities/pytest_regressions.py +25 -5
- utilities/subprocess.py +1191 -108
- utilities/tempfile.py +49 -16
- utilities/aeventkit.py +0 -389
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: dycw-utilities
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.175.31
|
|
4
|
+
Summary: Miscellaneous Python utilities
|
|
4
5
|
Author: Derek Wan
|
|
5
6
|
Author-email: Derek Wan <d.wan@icloud.com>
|
|
6
7
|
Requires-Dist: atomicwrites>=1.4.1,<1.5
|
|
@@ -28,14 +29,6 @@ Provides-Extra: logging
|
|
|
28
29
|
Provides-Extra: test
|
|
29
30
|
Description-Content-Type: text/markdown
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
# `python-utilities`
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
[All the Python functions I don't want to write twice.](https://github.com/nvim-lua/plenary.nvim)
|
|
36
|
-
|
|
37
|
-
## Installation
|
|
38
|
-
|
|
39
|
-
- `pip install dycw-utilities`
|
|
40
|
-
|
|
41
|
-
or with [extras](https://github.com/dycw/python-utilities/blob/master/pyproject.toml).
|
|
34
|
+
Miscellaneous Python utilities
|
|
@@ -1,23 +1,22 @@
|
|
|
1
|
-
utilities/__init__.py,sha256=
|
|
2
|
-
utilities/
|
|
3
|
-
utilities/altair.py,sha256=rUK99g9x6CYDDfiZrf-aTx5fSRbL1Q8ctgKORowzXHg,9060
|
|
1
|
+
utilities/__init__.py,sha256=y-0IecU_rQ9B_0aVstJ6gMlcyWn2-Hw_v-RcSXW8pWY,61
|
|
2
|
+
utilities/altair.py,sha256=TLfRFbG9HwG7SLXoJ-v0r-t49ZaGgTQZD82cpjVi4vs,9085
|
|
4
3
|
utilities/asyncio.py,sha256=aJySVxBY0gqsIYnoNmH7-1r8djKuf4vSsU69VCD08t8,16772
|
|
5
4
|
utilities/atomicwrites.py,sha256=tPo6r-Rypd9u99u66B9z86YBPpnLrlHtwox_8Z7T34Y,5790
|
|
6
5
|
utilities/atools.py,sha256=6neeCcgXxK2dlsc0xp15Za7nSucbCgFtAJepGI_-WXU,2549
|
|
7
6
|
utilities/cachetools.py,sha256=2S9LMHIunDYMIu8JGI7OLN04sQ7-xZGdEdP1Li0vksA,2775
|
|
8
|
-
utilities/click.py,sha256
|
|
7
|
+
utilities/click.py,sha256=ScLzBLoBp8Si5YjgB18A0IVMAR-r4sGUnVfJbAaru98,19191
|
|
9
8
|
utilities/concurrent.py,sha256=fHeW2SZ_TEMfFY0C8pyQI6aPlnecvx9x6SuUwBWj_JY,2853
|
|
10
9
|
utilities/contextlib.py,sha256=iP7R2tIm6ZsbfLD5ks6UKBYwj50e9gBI8AkpLN-chro,7476
|
|
11
10
|
utilities/contextvars.py,sha256=J8OhC7jqozAGYOCe2KUWysbPXNGe5JYz3HfaY_mIs08,883
|
|
12
11
|
utilities/cryptography.py,sha256=5PFrzsNUGHay91dFgYnDKwYprXxahrBqztmUqViRzBk,956
|
|
13
12
|
utilities/cvxpy.py,sha256=Rv1-fD-XYerosCavRF8Pohop2DBkU3AlFaGTfD8AEAA,13776
|
|
14
13
|
utilities/dataclasses.py,sha256=xbU3QN1GFy7RC6hIJRZIeUZm7YRlodrgEWmahWG6k2g,32465
|
|
15
|
-
utilities/docker.py,sha256=
|
|
14
|
+
utilities/docker.py,sha256=IOOdxAqfJfy1SXwy3e4VaAR9ag42W0HTcNcQz7DJPF8,10501
|
|
16
15
|
utilities/enum.py,sha256=5l6pwZD1cjSlVW4ss-zBPspWvrbrYrdtJWcg6f5_J5w,5781
|
|
17
16
|
utilities/errors.py,sha256=mFlDGSM0LI1jZ1pbqwLAH3ttLZ2JVIxyZLojw8tGVZU,1479
|
|
18
17
|
utilities/fastapi.py,sha256=TqyKvBjiMS594sXPjrz-KRTLMb3l3D3rZ1zAYV7GfOk,1454
|
|
19
18
|
utilities/fpdf2.py,sha256=dSiYz0FJTD2sQuxpxqFWwwIe2-p6Y7oTB9Tv0Jajit0,1866
|
|
20
|
-
utilities/functions.py,sha256=
|
|
19
|
+
utilities/functions.py,sha256=18Zda7nTloARdcEudH8YJ4e13xAdWShAGhPNN4w2Gyc,21498
|
|
21
20
|
utilities/functools.py,sha256=I00ru2gQPakZw2SHVeKIKXfTv741655s6HI0lUoE0D4,1552
|
|
22
21
|
utilities/getpass.py,sha256=DfN5UgMAtFCqS3dSfFHUfqIMZX2shXvwphOz_6J6f6A,103
|
|
23
22
|
utilities/git.py,sha256=U1RFvCTANGENgx9wVBDvllioqBQZM2ns12ivKhOsaO4,414
|
|
@@ -25,8 +24,8 @@ utilities/grp.py,sha256=1vV3gNR9dQsl1vtUtvC_2qgVdQzm7O8lLMSh56cTbeg,694
|
|
|
25
24
|
utilities/gzip.py,sha256=fkGP3KdsBfXlstodT4wtlp-PwNyUsogpbDCVVVGdsm4,781
|
|
26
25
|
utilities/hashlib.py,sha256=SVTgtguur0P4elppvzOBbLEjVM3Pea0eWB61yg2ilxo,309
|
|
27
26
|
utilities/http.py,sha256=TsavEfHlRtlLaeV21Z6KZh0qbPw-kvD1zsQdZ7Kep5Q,977
|
|
28
|
-
utilities/hypothesis.py,sha256=
|
|
29
|
-
utilities/importlib.py,sha256=
|
|
27
|
+
utilities/hypothesis.py,sha256=NUu30pl5kjL3tzo-m8SMRwTqLAmTWK-_Sau2NemJcQo,46773
|
|
28
|
+
utilities/importlib.py,sha256=SkVVtIjVC7bjJ36doXnmnmFiYe5tLbip4YAfYJj8Ycg,892
|
|
30
29
|
utilities/inflect.py,sha256=v7YkOWSu8NAmVghPcf4F3YBZQoJCS47_DLf9jbfWIs0,581
|
|
31
30
|
utilities/ipython.py,sha256=V2oMYHvEKvlNBzxDXdLvKi48oUq2SclRg5xasjaXStw,763
|
|
32
31
|
utilities/iterables.py,sha256=t2TsW-K3rVlS6y4_tqcc1fk9RwJV-bi7G_VwduMABK0,42558
|
|
@@ -46,11 +45,11 @@ utilities/optuna.py,sha256=C-fhWYiXHVPo1l8QctYkFJ4DyhbSrGorzP1dJb_qvd8,1933
|
|
|
46
45
|
utilities/orjson.py,sha256=T_0SlK811ysg46d3orvIPY3JpBa4FRMpP2wlPQo7-gU,41854
|
|
47
46
|
utilities/os.py,sha256=kjKKSQfnRqFTTZ315iavaaGd3gGuYNoSWlxVLCJjyQs,4852
|
|
48
47
|
utilities/parse.py,sha256=g7Qm9eBOIeDId2tGA021CIaeF6jp1TI8rx4srdvlyoo,17937
|
|
49
|
-
utilities/pathlib.py,sha256=
|
|
50
|
-
utilities/permissions.py,sha256=
|
|
48
|
+
utilities/pathlib.py,sha256=N4Ip8R9eCM-6GfvxUJ3T9oQIle2C2P52F-13BCFRdTg,9345
|
|
49
|
+
utilities/permissions.py,sha256=vLXlWztSVYffbrxptne7ksj6dU1HLekm4fEvS4ny_4Q,8944
|
|
51
50
|
utilities/pickle.py,sha256=MBT2xZCsv0pH868IXLGKnlcqNx2IRVKYNpRcqiQQqxw,653
|
|
52
|
-
utilities/platform.py,sha256=
|
|
53
|
-
utilities/polars.py,sha256=
|
|
51
|
+
utilities/platform.py,sha256=R3ldt2-DlI7la9ng6Rxt1CThd2lL0Ai2tC0TbabtCC0,2800
|
|
52
|
+
utilities/polars.py,sha256=JPzN4UqQDC7R4IXsIuXEIXRiwHSrkiSZcD8UOfwGPuE,87535
|
|
54
53
|
utilities/polars_ols.py,sha256=LNTFNLPuYW7fcAHymlbnams_DhitToblYvib3mhKbwI,5615
|
|
55
54
|
utilities/postgres.py,sha256=g3tEwTI8TdmiCbRME61ffQ0xaibdpXPu8mJOOHvjPKc,12532
|
|
56
55
|
utilities/pottery.py,sha256=nA0SsF9irvfC0tk68YAr08tuL9lGRSlBKihSx7Ibk84,3963
|
|
@@ -66,7 +65,7 @@ utilities/pytest.py,sha256=9HHwYgZQe6CRF0ekHQEFH05gmoP4Ne0V54RrtUNDfi4,10524
|
|
|
66
65
|
utilities/pytest_plugins/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
|
|
67
66
|
utilities/pytest_plugins/pytest_randomly.py,sha256=B1qYVlExGOxTywq2r1SMi5o7btHLk2PNdY_b1p98dkE,409
|
|
68
67
|
utilities/pytest_plugins/pytest_regressions.py,sha256=mnHYBfdprz50UGVkVzV1bZERZN5CFfoF8YbokGxdFwU,1639
|
|
69
|
-
utilities/pytest_regressions.py,sha256=
|
|
68
|
+
utilities/pytest_regressions.py,sha256=tJxW38u-zpoyjW1N4zogBx4V_07r-ibDInddcEUyXmc,4763
|
|
70
69
|
utilities/random.py,sha256=hZlH4gnAtoaofWswuJYjcygejrY8db4CzP-z_adO2Mo,4165
|
|
71
70
|
utilities/re.py,sha256=S4h-DLL6ScMPqjboZ_uQ1BVTJajrqV06r_81D--_HCE,4573
|
|
72
71
|
utilities/redis.py,sha256=gybjqKea33Jy50n4dHTS14JdquqHaJqHF2dixQljYWQ,30172
|
|
@@ -81,8 +80,8 @@ utilities/sqlalchemy.py,sha256=HQYpd7LFxdTF5WYVWYtCJeEBI71EJm7ytvCGyAH9B-U,37163
|
|
|
81
80
|
utilities/sqlalchemy_polars.py,sha256=JCGhB37raSR7fqeWV5dTsciRTMVzIdVT9YSqKT0piT0,13370
|
|
82
81
|
utilities/statsmodels.py,sha256=koyiBHvpMcSiBfh99wFUfSggLNx7cuAw3rwyfAhoKpQ,3410
|
|
83
82
|
utilities/string.py,sha256=shmBK87zZwzGyixuNuXCiUbqzfeZ9xlrFwz6JTaRvDk,582
|
|
84
|
-
utilities/subprocess.py,sha256=
|
|
85
|
-
utilities/tempfile.py,sha256=
|
|
83
|
+
utilities/subprocess.py,sha256=ei0OD73S0WX7ZqHCQp9UB3hY9OWbAUVIrLoz9niIfQQ,53031
|
|
84
|
+
utilities/tempfile.py,sha256=a3_M1QyxGZql_VcGkBOQBeWbbkItjgkfIpVyzU1UAic,3843
|
|
86
85
|
utilities/testbook.py,sha256=j1KmaVbrX9VrbeMgtPh5gk55myAsn3dyRUn7jGbPbRk,1294
|
|
87
86
|
utilities/text.py,sha256=7SvwcSR2l_5cOrm1samGnR4C-ZI6qyFLHLzSpO1zeHQ,13958
|
|
88
87
|
utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
|
|
@@ -98,7 +97,7 @@ utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
|
|
|
98
97
|
utilities/whenever.py,sha256=F4ek0-OBWxHYrZdmoZt76N2RnNyKY5KrEHt7rqO4AQE,60183
|
|
99
98
|
utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
|
|
100
99
|
utilities/zoneinfo.py,sha256=tdIScrTB2-B-LH0ukb1HUXKooLknOfJNwHk10MuMYvA,3619
|
|
101
|
-
dycw_utilities-0.
|
|
102
|
-
dycw_utilities-0.
|
|
103
|
-
dycw_utilities-0.
|
|
104
|
-
dycw_utilities-0.
|
|
100
|
+
dycw_utilities-0.175.31.dist-info/WHEEL,sha256=RRVLqVugUmFOqBedBFAmA4bsgFcROUBiSUKlERi0Hcg,79
|
|
101
|
+
dycw_utilities-0.175.31.dist-info/entry_points.txt,sha256=cOGtKeJI0KXLSV7MJ8Dhc2G8jPgDcBDm53MVNJU4ycI,136
|
|
102
|
+
dycw_utilities-0.175.31.dist-info/METADATA,sha256=WX68a6sQoOamTeIZsrXvDdI_VKItf-Jm3Cam7p1jR_U,1443
|
|
103
|
+
dycw_utilities-0.175.31.dist-info/RECORD,,
|
utilities/__init__.py
CHANGED
utilities/altair.py
CHANGED
|
@@ -145,7 +145,9 @@ def plot_dataframes(
|
|
|
145
145
|
]
|
|
146
146
|
zoom = selection_interval(bind="scales", encodings=["x"])
|
|
147
147
|
chart = (
|
|
148
|
-
|
|
148
|
+
vconcat_charts(*layers)
|
|
149
|
+
.add_params(zoom)
|
|
150
|
+
.resolve_scale(color="independent", x="shared")
|
|
149
151
|
)
|
|
150
152
|
if title is not None:
|
|
151
153
|
chart = chart.properties(title=title)
|
utilities/click.py
CHANGED
|
@@ -210,8 +210,7 @@ class EnumPartial[E: enum.Enum](ParamType):
|
|
|
210
210
|
self.fail(str(error), param, ctx)
|
|
211
211
|
if enum in self._members:
|
|
212
212
|
return enum
|
|
213
|
-
self.fail(f"{enum.value!r} is not a selected member")
|
|
214
|
-
return None
|
|
213
|
+
return self.fail(f"{enum.value!r} is not a selected member")
|
|
215
214
|
|
|
216
215
|
@override
|
|
217
216
|
def get_metavar(self, param: Parameter, ctx: Context) -> str | None:
|
utilities/docker.py
CHANGED
|
@@ -5,6 +5,7 @@ from pathlib import Path
|
|
|
5
5
|
from typing import TYPE_CHECKING, Literal, overload
|
|
6
6
|
|
|
7
7
|
from utilities.errors import ImpossibleCaseError
|
|
8
|
+
from utilities.iterables import always_iterable
|
|
8
9
|
from utilities.logging import to_logger
|
|
9
10
|
from utilities.subprocess import (
|
|
10
11
|
MKTEMP_DIR_CMD,
|
|
@@ -18,7 +19,87 @@ from utilities.subprocess import (
|
|
|
18
19
|
if TYPE_CHECKING:
|
|
19
20
|
from collections.abc import Iterator
|
|
20
21
|
|
|
21
|
-
from utilities.types import
|
|
22
|
+
from utilities.types import (
|
|
23
|
+
LoggerLike,
|
|
24
|
+
MaybeIterable,
|
|
25
|
+
PathLike,
|
|
26
|
+
Retry,
|
|
27
|
+
StrStrMapping,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def docker_compose_down(
|
|
32
|
+
*,
|
|
33
|
+
files: MaybeIterable[PathLike] | None = None,
|
|
34
|
+
print: bool = False, # noqa: A002
|
|
35
|
+
print_stdout: bool = False,
|
|
36
|
+
print_stderr: bool = False,
|
|
37
|
+
) -> None:
|
|
38
|
+
"""Stop and remove containers."""
|
|
39
|
+
args = docker_compose_down_cmd(files=files) # pragma: no cover
|
|
40
|
+
run( # pragma: no cover
|
|
41
|
+
*args, print=print, print_stdout=print_stdout, print_stderr=print_stderr
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def docker_compose_down_cmd(
|
|
46
|
+
*, files: MaybeIterable[PathLike] | None = None
|
|
47
|
+
) -> list[str]:
|
|
48
|
+
"""Command to use 'docker compose down' to stop and remove containers."""
|
|
49
|
+
return _docker_compose_cmd("down", files=files)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def docker_compose_pull(
|
|
53
|
+
*,
|
|
54
|
+
files: MaybeIterable[PathLike] | None = None,
|
|
55
|
+
print: bool = False, # noqa: A002
|
|
56
|
+
print_stdout: bool = False,
|
|
57
|
+
print_stderr: bool = False,
|
|
58
|
+
) -> None:
|
|
59
|
+
"""Pull service images."""
|
|
60
|
+
args = docker_compose_pull_cmd(files=files) # pragma: no cover
|
|
61
|
+
run( # pragma: no cover
|
|
62
|
+
*args, print=print, print_stdout=print_stdout, print_stderr=print_stderr
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def docker_compose_pull_cmd(
|
|
67
|
+
*, files: MaybeIterable[PathLike] | None = None
|
|
68
|
+
) -> list[str]:
|
|
69
|
+
"""Command to use 'docker compose pull' to pull service images."""
|
|
70
|
+
return _docker_compose_cmd("pull", files=files)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def docker_compose_up(
|
|
74
|
+
*,
|
|
75
|
+
files: MaybeIterable[PathLike] | None = None,
|
|
76
|
+
print: bool = False, # noqa: A002
|
|
77
|
+
print_stdout: bool = False,
|
|
78
|
+
print_stderr: bool = False,
|
|
79
|
+
) -> None:
|
|
80
|
+
"""Create and start containers."""
|
|
81
|
+
args = docker_compose_up_cmd(files=files) # pragma: no cover
|
|
82
|
+
run( # pragma: no cover
|
|
83
|
+
*args, print=print, print_stdout=print_stdout, print_stderr=print_stderr
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def docker_compose_up_cmd(*, files: MaybeIterable[PathLike] | None = None) -> list[str]:
|
|
88
|
+
"""Command to use 'docker compose up' to create and start containers."""
|
|
89
|
+
return _docker_compose_cmd("up", files=files)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _docker_compose_cmd(
|
|
93
|
+
cmd: str, /, *, files: MaybeIterable[PathLike] | None = None
|
|
94
|
+
) -> list[str]:
|
|
95
|
+
args: list[str] = ["docker", "compose"]
|
|
96
|
+
if files is not None:
|
|
97
|
+
for file in always_iterable(files):
|
|
98
|
+
args.extend(["--file", str(file)])
|
|
99
|
+
return [*args, cmd]
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
##
|
|
22
103
|
|
|
23
104
|
|
|
24
105
|
@overload
|
|
@@ -47,48 +128,39 @@ def docker_cp(
|
|
|
47
128
|
sudo: bool = False,
|
|
48
129
|
logger: LoggerLike | None = None,
|
|
49
130
|
) -> None:
|
|
50
|
-
|
|
131
|
+
"""Copy between a container and the local file system."""
|
|
132
|
+
match src, dest: # skipif-ci
|
|
51
133
|
case Path() | str(), (str() as cont, Path() | str() as dest_path):
|
|
52
134
|
docker_exec(
|
|
53
135
|
cont, *maybe_sudo_cmd(*mkdir_cmd(dest_path, parent=True), sudo=sudo)
|
|
54
136
|
)
|
|
55
|
-
run(*docker_cp_cmd(src, dest, sudo=sudo), logger=logger)
|
|
137
|
+
run(*maybe_sudo_cmd(*docker_cp_cmd(src, dest), sudo=sudo), logger=logger)
|
|
56
138
|
case (str(), Path() | str()), Path() | str():
|
|
57
139
|
mkdir(dest, parent=True, sudo=sudo)
|
|
58
|
-
run(*docker_cp_cmd(src, dest, sudo=sudo), logger=logger)
|
|
140
|
+
run(*maybe_sudo_cmd(*docker_cp_cmd(src, dest), sudo=sudo), logger=logger)
|
|
59
141
|
case _: # pragma: no cover
|
|
60
142
|
raise ImpossibleCaseError(case=[f"{src}", f"{dest=}"])
|
|
61
143
|
|
|
62
144
|
|
|
63
145
|
@overload
|
|
64
|
-
def docker_cp_cmd(
|
|
65
|
-
src: tuple[str, PathLike], dest: PathLike, /, *, sudo: bool = False
|
|
66
|
-
) -> list[str]: ...
|
|
146
|
+
def docker_cp_cmd(src: tuple[str, PathLike], dest: PathLike, /) -> list[str]: ...
|
|
67
147
|
@overload
|
|
148
|
+
def docker_cp_cmd(src: PathLike, dest: tuple[str, PathLike], /) -> list[str]: ...
|
|
68
149
|
def docker_cp_cmd(
|
|
69
|
-
src: PathLike
|
|
70
|
-
) -> list[str]: ...
|
|
71
|
-
def docker_cp_cmd(
|
|
72
|
-
src: PathLike | tuple[str, PathLike],
|
|
73
|
-
dest: PathLike | tuple[str, PathLike],
|
|
74
|
-
/,
|
|
75
|
-
*,
|
|
76
|
-
sudo: bool = False,
|
|
150
|
+
src: PathLike | tuple[str, PathLike], dest: PathLike | tuple[str, PathLike], /
|
|
77
151
|
) -> list[str]:
|
|
152
|
+
"""Command to use 'docker cp' to copy between a container and the local file system."""
|
|
153
|
+
args: list[str] = ["docker", "cp"]
|
|
78
154
|
match src, dest:
|
|
79
|
-
case (Path() | str()) as
|
|
80
|
-
str()
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
dest_use = f"{dest_cont}:{dest_path}"
|
|
84
|
-
case (str() as src_cont, (Path() | str()) as src_path), (
|
|
85
|
-
Path() | str() as dest_use
|
|
86
|
-
):
|
|
87
|
-
src_use = f"{src_cont}:{src_path}"
|
|
155
|
+
case ((Path() | str()), (str() as cont, Path() | str() as path)):
|
|
156
|
+
return [*args, str(src), f"{cont}:{path}"]
|
|
157
|
+
case (str() as cont, (Path() | str()) as path), (Path() | str() as dest):
|
|
158
|
+
return [*args, f"{cont}:{path}", str(dest)]
|
|
88
159
|
case _: # pragma: no cover
|
|
89
160
|
raise ImpossibleCaseError(case=[f"{src}", f"{dest=}"])
|
|
90
|
-
|
|
91
|
-
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
##
|
|
92
164
|
|
|
93
165
|
|
|
94
166
|
@overload
|
|
@@ -210,6 +282,7 @@ def docker_exec(
|
|
|
210
282
|
logger: LoggerLike | None = None,
|
|
211
283
|
**env_kwargs: str,
|
|
212
284
|
) -> str | None:
|
|
285
|
+
"""Execute a command in a container."""
|
|
213
286
|
cmd_and_args = docker_exec_cmd( # skipif-ci
|
|
214
287
|
container,
|
|
215
288
|
cmd,
|
|
@@ -245,7 +318,7 @@ def docker_exec_cmd(
|
|
|
245
318
|
workdir: PathLike | None = None,
|
|
246
319
|
**env_kwargs: str,
|
|
247
320
|
) -> list[str]:
|
|
248
|
-
"""
|
|
321
|
+
"""Command to use `docker exec` to execute a command in a container."""
|
|
249
322
|
args: list[str] = ["docker", "exec"]
|
|
250
323
|
mapping: dict[str, str] = ({} if env is None else dict(env)) | env_kwargs
|
|
251
324
|
for key, value in mapping.items():
|
|
@@ -259,6 +332,9 @@ def docker_exec_cmd(
|
|
|
259
332
|
return [*args, container, cmd, *cmds_or_args]
|
|
260
333
|
|
|
261
334
|
|
|
335
|
+
##
|
|
336
|
+
|
|
337
|
+
|
|
262
338
|
@contextmanager
|
|
263
339
|
def yield_docker_temp_dir(
|
|
264
340
|
container: str,
|
|
@@ -269,6 +345,7 @@ def yield_docker_temp_dir(
|
|
|
269
345
|
logger: LoggerLike | None = None,
|
|
270
346
|
keep: bool = False,
|
|
271
347
|
) -> Iterator[Path]:
|
|
348
|
+
"""Yield a temporary directory in a Docker container."""
|
|
272
349
|
path = Path( # skipif-ci
|
|
273
350
|
docker_exec(
|
|
274
351
|
container,
|
|
@@ -289,4 +366,16 @@ def yield_docker_temp_dir(
|
|
|
289
366
|
docker_exec(container, *rm_cmd(path), user=user, retry=retry, logger=logger)
|
|
290
367
|
|
|
291
368
|
|
|
292
|
-
__all__ = [
|
|
369
|
+
__all__ = [
|
|
370
|
+
"docker_compose_down",
|
|
371
|
+
"docker_compose_down_cmd",
|
|
372
|
+
"docker_compose_pull",
|
|
373
|
+
"docker_compose_pull_cmd",
|
|
374
|
+
"docker_compose_up",
|
|
375
|
+
"docker_compose_up_cmd",
|
|
376
|
+
"docker_cp",
|
|
377
|
+
"docker_cp_cmd",
|
|
378
|
+
"docker_exec",
|
|
379
|
+
"docker_exec_cmd",
|
|
380
|
+
"yield_docker_temp_dir",
|
|
381
|
+
]
|
utilities/functions.py
CHANGED
|
@@ -693,7 +693,7 @@ def second[U](pair: tuple[Any, U], /) -> U:
|
|
|
693
693
|
|
|
694
694
|
def skip_if_optimize[**P](func: Callable[P, None], /) -> Callable[P, None]:
|
|
695
695
|
"""Skip a function if we are in the optimized mode."""
|
|
696
|
-
if __debug__:
|
|
696
|
+
if __debug__: # pragma: no cover
|
|
697
697
|
return func
|
|
698
698
|
|
|
699
699
|
@wraps(func)
|
utilities/hypothesis.py
CHANGED
|
@@ -1040,7 +1040,7 @@ def setup_hypothesis_profiles(
|
|
|
1040
1040
|
assert_never(never)
|
|
1041
1041
|
|
|
1042
1042
|
phases = {Phase.explicit, Phase.reuse, Phase.generate, Phase.target}
|
|
1043
|
-
if "HYPOTHESIS_NO_SHRINK" not in environ:
|
|
1043
|
+
if "HYPOTHESIS_NO_SHRINK" not in environ: # pragma: no cover
|
|
1044
1044
|
phases.add(Phase.shrink)
|
|
1045
1045
|
for profile in Profile:
|
|
1046
1046
|
try:
|
utilities/importlib.py
CHANGED
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import importlib.resources
|
|
3
4
|
from importlib import import_module
|
|
4
5
|
from importlib.util import find_spec
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from utilities.errors import ImpossibleCaseError
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from importlib.resources import Anchor
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def files(*, anchor: Anchor | None = None) -> Path:
|
|
16
|
+
"""Get the path for an anchor."""
|
|
17
|
+
path = importlib.resources.files(anchor)
|
|
18
|
+
if isinstance(path, Path):
|
|
19
|
+
return path
|
|
20
|
+
raise ImpossibleCaseError(case=[f"{path=}"]) # pragma: no cover
|
|
5
21
|
|
|
6
22
|
|
|
7
23
|
def is_valid_import(module: str, /, *, name: str | None = None) -> bool:
|
|
@@ -15,4 +31,4 @@ def is_valid_import(module: str, /, *, name: str | None = None) -> bool:
|
|
|
15
31
|
return hasattr(mod, name)
|
|
16
32
|
|
|
17
33
|
|
|
18
|
-
__all__ = ["is_valid_import"]
|
|
34
|
+
__all__ = ["files", "is_valid_import"]
|
utilities/pathlib.py
CHANGED
|
@@ -127,7 +127,7 @@ class GetRepoRootError(Exception): ...
|
|
|
127
127
|
class _GetRepoRootGitNotFoundError(GetRepoRootError):
|
|
128
128
|
@override
|
|
129
129
|
def __str__(self) -> str:
|
|
130
|
-
return "'git' not found"
|
|
130
|
+
return "'git' not found" # pragma: no cover
|
|
131
131
|
|
|
132
132
|
|
|
133
133
|
@dataclass(kw_only=True, slots=True)
|
utilities/permissions.py
CHANGED
utilities/platform.py
CHANGED
|
@@ -16,7 +16,7 @@ System = Literal["windows", "mac", "linux"]
|
|
|
16
16
|
def get_system() -> System:
|
|
17
17
|
"""Get the system/OS name."""
|
|
18
18
|
sys = system()
|
|
19
|
-
if sys == "Windows":
|
|
19
|
+
if sys == "Windows": # skipif-not-windows
|
|
20
20
|
return "windows"
|
|
21
21
|
if sys == "Darwin": # skipif-not-macos
|
|
22
22
|
return "mac"
|
utilities/polars.py
CHANGED
utilities/pytest_regressions.py
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from contextlib import suppress
|
|
4
|
+
from dataclasses import dataclass
|
|
4
5
|
from json import loads
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from shutil import copytree
|
|
7
|
-
from typing import TYPE_CHECKING, Any, assert_never
|
|
8
|
+
from typing import TYPE_CHECKING, Any, assert_never, override
|
|
8
9
|
|
|
9
10
|
from pytest_regressions.file_regression import FileRegressionFixture
|
|
10
11
|
|
|
11
12
|
from utilities.functions import ensure_str
|
|
12
13
|
from utilities.operator import is_equal
|
|
14
|
+
from utilities.reprlib import get_repr
|
|
13
15
|
|
|
14
16
|
if TYPE_CHECKING:
|
|
15
17
|
from polars import DataFrame, Series
|
|
@@ -70,10 +72,28 @@ class OrjsonRegressionFixture:
|
|
|
70
72
|
check_fn=self._check_fn,
|
|
71
73
|
)
|
|
72
74
|
|
|
73
|
-
def _check_fn(self,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
def _check_fn(self, path_obtained: Path, path_existing: Path, /) -> None:
|
|
76
|
+
obtained = loads(path_obtained.read_text())
|
|
77
|
+
existing = loads(path_existing.read_text())
|
|
78
|
+
if not is_equal(obtained, existing):
|
|
79
|
+
raise OrjsonRegressionError(
|
|
80
|
+
path_obtained=path_obtained,
|
|
81
|
+
path_existing=path_existing,
|
|
82
|
+
obtained=obtained,
|
|
83
|
+
existing=existing,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass(kw_only=True, slots=True)
|
|
88
|
+
class OrjsonRegressionError(Exception):
|
|
89
|
+
path_obtained: Path
|
|
90
|
+
path_existing: Path
|
|
91
|
+
obtained: Any
|
|
92
|
+
existing: Any
|
|
93
|
+
|
|
94
|
+
@override
|
|
95
|
+
def __str__(self) -> str:
|
|
96
|
+
return f"Obtained object (at {str(self.path_obtained)!r}) and existing object (at {str(self.path_existing)!r}) differ; got {get_repr(self.obtained)} and {get_repr(self.existing)}"
|
|
77
97
|
|
|
78
98
|
|
|
79
99
|
##
|