dycw-utilities 0.174.5__tar.gz → 0.174.7__tar.gz
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.174.5 → dycw_utilities-0.174.7}/PKG-INFO +1 -1
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/pyproject.toml +2 -2
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/__init__.py +1 -1
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/subprocess.py +252 -15
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/README.md +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/aeventkit.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/altair.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/asyncio.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/atomicwrites.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/atools.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/cachetools.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/click.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/concurrent.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/contextlib.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/contextvars.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/cryptography.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/cvxpy.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/dataclasses.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/docker.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/enum.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/errors.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/fastapi.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/fpdf2.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/functions.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/functools.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/getpass.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/git.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/grp.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/gzip.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/hashlib.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/http.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/hypothesis.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/importlib.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/inflect.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/ipython.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/iterables.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/jinja2.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/json.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/jupyter.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/libcst.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/lightweight_charts.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/logging.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/math.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/memory_profiler.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/modules.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/more_itertools.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/numpy.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/operator.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/optuna.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/orjson.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/os.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/parse.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/pathlib.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/pickle.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/platform.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/polars.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/polars_ols.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/postgres.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/pottery.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/pqdm.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/psutil.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/pwd.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/py.typed +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/pydantic.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/pydantic_settings.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/pydantic_settings_sops.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/pyinstrument.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/pytest.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/pytest_plugins/__init__.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/pytest_plugins/pytest_randomly.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/pytest_plugins/pytest_regressions.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/pytest_regressions.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/random.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/re.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/redis.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/reprlib.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/scipy.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/sentinel.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/shelve.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/shutil.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/slack_sdk.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/socket.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/sqlalchemy.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/sqlalchemy_polars.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/statsmodels.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/string.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/tempfile.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/testbook.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/text.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/threading.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/timer.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/traceback.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/types.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/typing.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/tzdata.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/tzlocal.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/uuid.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/version.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/warnings.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/whenever.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/zipfile.py +0 -0
- {dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/zoneinfo.py +0 -0
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
name = "dycw-utilities"
|
|
102
102
|
readme = "README.md"
|
|
103
103
|
requires-python = ">= 3.12"
|
|
104
|
-
version = "0.174.
|
|
104
|
+
version = "0.174.7"
|
|
105
105
|
|
|
106
106
|
[project.entry-points.pytest11]
|
|
107
107
|
pytest-randomly = "utilities.pytest_plugins.pytest_randomly"
|
|
@@ -135,7 +135,7 @@
|
|
|
135
135
|
# bump-my-version
|
|
136
136
|
[tool.bumpversion]
|
|
137
137
|
allow_dirty = true
|
|
138
|
-
current_version = "0.174.
|
|
138
|
+
current_version = "0.174.7"
|
|
139
139
|
|
|
140
140
|
[[tool.bumpversion.files]]
|
|
141
141
|
filename = "src/utilities/__init__.py"
|
|
@@ -2,15 +2,18 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
4
|
from contextlib import contextmanager
|
|
5
|
+
from dataclasses import dataclass
|
|
5
6
|
from io import StringIO
|
|
6
7
|
from pathlib import Path
|
|
8
|
+
from shlex import join, quote
|
|
7
9
|
from string import Template
|
|
8
10
|
from subprocess import PIPE, CalledProcessError, Popen
|
|
9
11
|
from threading import Thread
|
|
10
12
|
from time import sleep
|
|
11
|
-
from typing import IO, TYPE_CHECKING, Literal, assert_never, overload
|
|
13
|
+
from typing import IO, TYPE_CHECKING, Literal, assert_never, overload, override
|
|
12
14
|
|
|
13
15
|
from utilities.errors import ImpossibleCaseError
|
|
16
|
+
from utilities.iterables import always_iterable
|
|
14
17
|
from utilities.logging import to_logger
|
|
15
18
|
from utilities.text import strip_and_dedent
|
|
16
19
|
from utilities.whenever import to_seconds
|
|
@@ -18,13 +21,63 @@ from utilities.whenever import to_seconds
|
|
|
18
21
|
if TYPE_CHECKING:
|
|
19
22
|
from collections.abc import Iterator
|
|
20
23
|
|
|
21
|
-
from utilities.types import
|
|
24
|
+
from utilities.types import (
|
|
25
|
+
LoggerLike,
|
|
26
|
+
MaybeIterable,
|
|
27
|
+
PathLike,
|
|
28
|
+
Retry,
|
|
29
|
+
StrMapping,
|
|
30
|
+
StrStrMapping,
|
|
31
|
+
)
|
|
22
32
|
|
|
23
33
|
|
|
24
34
|
_HOST_KEY_ALGORITHMS = ["ssh-ed25519"]
|
|
35
|
+
APT_UPDATE = ["apt", "update", "-y"]
|
|
25
36
|
BASH_LC = ["bash", "-lc"]
|
|
26
37
|
BASH_LS = ["bash", "-ls"]
|
|
27
38
|
MKTEMP_DIR_CMD = ["mktemp", "-d"]
|
|
39
|
+
RESTART_SSHD = ["systemctl", "restart", "sshd"]
|
|
40
|
+
UPDATE_CA_CERTIFICATES: str = "update-ca-certificates"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def apt_install_cmd(package: str, /) -> list[str]:
|
|
44
|
+
return ["apt", "install", "-y", package]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def cat_cmd(path: PathLike, /) -> list[str]:
|
|
48
|
+
return ["cat", str(path)]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def cd_cmd(path: PathLike, /) -> list[str]:
|
|
52
|
+
return ["cd", str(path)]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def chmod_cmd(path: PathLike, mode: str, /) -> list[str]:
|
|
56
|
+
return ["chmod", mode, str(path)]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def chown_cmd(
|
|
60
|
+
path: PathLike, /, *, user: str | None = None, group: str | None = None
|
|
61
|
+
) -> list[str]:
|
|
62
|
+
match user, group:
|
|
63
|
+
case None, None:
|
|
64
|
+
raise ChownCmdError
|
|
65
|
+
case str(), None:
|
|
66
|
+
ownership = "user"
|
|
67
|
+
case None, str():
|
|
68
|
+
ownership = f":{group}"
|
|
69
|
+
case str(), str():
|
|
70
|
+
ownership = f"{user}:{group}"
|
|
71
|
+
case never:
|
|
72
|
+
assert_never(never)
|
|
73
|
+
return ["chown", ownership, str(path)]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass(kw_only=True, slots=True)
|
|
77
|
+
class ChownCmdError(Exception):
|
|
78
|
+
@override
|
|
79
|
+
def __str__(self) -> str:
|
|
80
|
+
return "At least one of 'user' and/or 'group' must be given; got None"
|
|
28
81
|
|
|
29
82
|
|
|
30
83
|
def cp_cmd(src: PathLike, dest: PathLike, /) -> list[str]:
|
|
@@ -45,6 +98,15 @@ def expand_path(
|
|
|
45
98
|
return Path(path).expanduser()
|
|
46
99
|
|
|
47
100
|
|
|
101
|
+
def git_clone_cmd(url: str, path: PathLike, /) -> list[str]:
|
|
102
|
+
return ["git", "clone", "--recurse-submodules", url, str(path)]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def git_hard_reset_cmd(*, branch: str | None = None) -> list[str]:
|
|
106
|
+
branch_use = "master" if branch is None else branch
|
|
107
|
+
return ["git", "hard-reset", branch_use]
|
|
108
|
+
|
|
109
|
+
|
|
48
110
|
def maybe_sudo_cmd(cmd: str, /, *args: str, sudo: bool = False) -> list[str]:
|
|
49
111
|
parts: list[str] = [cmd, *args]
|
|
50
112
|
return sudo_cmd(*parts) if sudo else parts
|
|
@@ -60,8 +122,13 @@ def mkdir(path: PathLike, /, *, sudo: bool = False, parent: bool = False) -> Non
|
|
|
60
122
|
|
|
61
123
|
|
|
62
124
|
def mkdir_cmd(path: PathLike, /, *, parent: bool = False) -> list[str]:
|
|
63
|
-
|
|
64
|
-
|
|
125
|
+
args: list[str] = ["mkdir", "-p"]
|
|
126
|
+
quoted = quote(str(path))
|
|
127
|
+
if parent:
|
|
128
|
+
args.append(f"$(dirname {quoted})")
|
|
129
|
+
else:
|
|
130
|
+
args.append(quoted)
|
|
131
|
+
return args
|
|
65
132
|
|
|
66
133
|
|
|
67
134
|
def mv_cmd(src: PathLike, dest: PathLike, /) -> list[str]:
|
|
@@ -72,6 +139,114 @@ def rm_cmd(path: PathLike, /) -> list[str]:
|
|
|
72
139
|
return ["rm", "-rf", str(path)]
|
|
73
140
|
|
|
74
141
|
|
|
142
|
+
def rsync(
|
|
143
|
+
src_or_srcs: MaybeIterable[PathLike],
|
|
144
|
+
user: str,
|
|
145
|
+
hostname: str,
|
|
146
|
+
dest: PathLike,
|
|
147
|
+
/,
|
|
148
|
+
*,
|
|
149
|
+
sudo: bool = False,
|
|
150
|
+
batch_mode: bool = True,
|
|
151
|
+
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
152
|
+
strict_host_key_checking: bool = True,
|
|
153
|
+
print: bool = False, # noqa: A002
|
|
154
|
+
retry: Retry | None = None,
|
|
155
|
+
logger: LoggerLike | None = None,
|
|
156
|
+
archive: bool = False,
|
|
157
|
+
chown_user: str | None = None,
|
|
158
|
+
chown_group: str | None = None,
|
|
159
|
+
exclude: MaybeIterable[str] | None = None,
|
|
160
|
+
chmod: str | None = None,
|
|
161
|
+
) -> None:
|
|
162
|
+
mkdir_args = maybe_sudo_cmd(*mkdir_cmd(dest, parent=True), sudo=sudo) # skipif-ci
|
|
163
|
+
ssh( # skipif-ci
|
|
164
|
+
user,
|
|
165
|
+
hostname,
|
|
166
|
+
*mkdir_args,
|
|
167
|
+
batch_mode=batch_mode,
|
|
168
|
+
host_key_algorithms=host_key_algorithms,
|
|
169
|
+
strict_host_key_checking=strict_host_key_checking,
|
|
170
|
+
print=print,
|
|
171
|
+
retry=retry,
|
|
172
|
+
logger=logger,
|
|
173
|
+
)
|
|
174
|
+
rsync_args = rsync_cmd( # skipif-ci
|
|
175
|
+
src_or_srcs,
|
|
176
|
+
user,
|
|
177
|
+
hostname,
|
|
178
|
+
dest,
|
|
179
|
+
archive=archive,
|
|
180
|
+
chown_user=chown_user,
|
|
181
|
+
chown_group=chown_group,
|
|
182
|
+
exclude=exclude,
|
|
183
|
+
batch_mode=batch_mode,
|
|
184
|
+
host_key_algorithms=host_key_algorithms,
|
|
185
|
+
strict_host_key_checking=strict_host_key_checking,
|
|
186
|
+
sudo=sudo,
|
|
187
|
+
)
|
|
188
|
+
run(*rsync_args, print=print, retry=retry, logger=logger) # skipif-ci
|
|
189
|
+
if chmod is not None: # skipif-ci
|
|
190
|
+
chmod_args = maybe_sudo_cmd(*chmod_cmd(dest, chmod), sudo=sudo)
|
|
191
|
+
ssh(
|
|
192
|
+
user,
|
|
193
|
+
hostname,
|
|
194
|
+
*chmod_args,
|
|
195
|
+
batch_mode=batch_mode,
|
|
196
|
+
host_key_algorithms=host_key_algorithms,
|
|
197
|
+
strict_host_key_checking=strict_host_key_checking,
|
|
198
|
+
print=print,
|
|
199
|
+
retry=retry,
|
|
200
|
+
logger=logger,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def rsync_cmd(
|
|
205
|
+
src_or_srcs: MaybeIterable[PathLike],
|
|
206
|
+
user: str,
|
|
207
|
+
hostname: str,
|
|
208
|
+
dest: PathLike,
|
|
209
|
+
/,
|
|
210
|
+
*,
|
|
211
|
+
archive: bool = False,
|
|
212
|
+
chown_user: str | None = None,
|
|
213
|
+
chown_group: str | None = None,
|
|
214
|
+
exclude: MaybeIterable[str] | None = None,
|
|
215
|
+
batch_mode: bool = True,
|
|
216
|
+
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
217
|
+
strict_host_key_checking: bool = True,
|
|
218
|
+
sudo: bool = False,
|
|
219
|
+
) -> list[str]:
|
|
220
|
+
args: list[str] = ["rsync"]
|
|
221
|
+
if archive:
|
|
222
|
+
args.append("--archive")
|
|
223
|
+
args.append("--checksum")
|
|
224
|
+
match chown_user, chown_group:
|
|
225
|
+
case None, None:
|
|
226
|
+
...
|
|
227
|
+
case str(), None:
|
|
228
|
+
args.extend(["--chown", chown_user])
|
|
229
|
+
case None, str():
|
|
230
|
+
args.extend(["--chown", f":{chown_group}"])
|
|
231
|
+
case str(), str():
|
|
232
|
+
args.extend(["--chown", f"{chown_user}:{chown_group}"])
|
|
233
|
+
case never:
|
|
234
|
+
assert_never(never)
|
|
235
|
+
args.append("--compress")
|
|
236
|
+
if exclude is not None:
|
|
237
|
+
for exclude_i in always_iterable(exclude):
|
|
238
|
+
args.extend(["--exclude", exclude_i])
|
|
239
|
+
rsh_args: list[str] = ssh_opts_cmd(
|
|
240
|
+
batch_mode=batch_mode,
|
|
241
|
+
host_key_algorithms=host_key_algorithms,
|
|
242
|
+
strict_host_key_checking=strict_host_key_checking,
|
|
243
|
+
)
|
|
244
|
+
args.extend(["--rsh", join(rsh_args)])
|
|
245
|
+
if sudo:
|
|
246
|
+
args.extend(["--rsync-path", join(sudo_cmd("rsync"))])
|
|
247
|
+
return [*args, *map(str, always_iterable(src_or_srcs)), f"{user}@{hostname}:{dest}"]
|
|
248
|
+
|
|
249
|
+
|
|
75
250
|
@overload
|
|
76
251
|
def run(
|
|
77
252
|
cmd: str,
|
|
@@ -224,8 +399,8 @@ def run(
|
|
|
224
399
|
if proc.stderr is None: # pragma: no cover
|
|
225
400
|
raise ImpossibleCaseError(case=[f"{proc.stderr=}"])
|
|
226
401
|
with (
|
|
227
|
-
|
|
228
|
-
|
|
402
|
+
_run_yield_write(proc.stdout, *stdout_outputs),
|
|
403
|
+
_run_yield_write(proc.stderr, *stderr_outputs),
|
|
229
404
|
):
|
|
230
405
|
if input is not None:
|
|
231
406
|
_ = proc.stdin.write(input)
|
|
@@ -307,8 +482,8 @@ def run(
|
|
|
307
482
|
|
|
308
483
|
|
|
309
484
|
@contextmanager
|
|
310
|
-
def
|
|
311
|
-
thread = Thread(target=
|
|
485
|
+
def _run_yield_write(input_: IO[str], /, *outputs: IO[str]) -> Iterator[None]:
|
|
486
|
+
thread = Thread(target=_run_daemon_target, args=(input_, *outputs), daemon=True)
|
|
312
487
|
thread.start()
|
|
313
488
|
try:
|
|
314
489
|
yield
|
|
@@ -316,17 +491,21 @@ def _yield_write(input_: IO[str], /, *outputs: IO[str]) -> Iterator[None]:
|
|
|
316
491
|
thread.join()
|
|
317
492
|
|
|
318
493
|
|
|
319
|
-
def
|
|
494
|
+
def _run_daemon_target(input_: IO[str], /, *outputs: IO[str]) -> None:
|
|
320
495
|
with input_:
|
|
321
496
|
for text in iter(input_.readline, ""):
|
|
322
|
-
|
|
497
|
+
_run_write_to_streams(text, *outputs)
|
|
323
498
|
|
|
324
499
|
|
|
325
|
-
def
|
|
500
|
+
def _run_write_to_streams(text: str, /, *outputs: IO[str]) -> None:
|
|
326
501
|
for output in outputs:
|
|
327
502
|
_ = output.write(text)
|
|
328
503
|
|
|
329
504
|
|
|
505
|
+
def set_hostname_cmd(hostname: str, /) -> list[str]:
|
|
506
|
+
return ["hostnamectl", "set-hostname", hostname]
|
|
507
|
+
|
|
508
|
+
|
|
330
509
|
@overload
|
|
331
510
|
def ssh(
|
|
332
511
|
user: str,
|
|
@@ -470,6 +649,20 @@ def ssh_cmd(
|
|
|
470
649
|
batch_mode: bool = True,
|
|
471
650
|
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
472
651
|
strict_host_key_checking: bool = True,
|
|
652
|
+
) -> list[str]:
|
|
653
|
+
args: list[str] = ssh_opts_cmd(
|
|
654
|
+
batch_mode=batch_mode,
|
|
655
|
+
host_key_algorithms=host_key_algorithms,
|
|
656
|
+
strict_host_key_checking=strict_host_key_checking,
|
|
657
|
+
)
|
|
658
|
+
return [*args, f"{user}@{hostname}", *cmd_and_cmds_or_args]
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
def ssh_opts_cmd(
|
|
662
|
+
*,
|
|
663
|
+
batch_mode: bool = True,
|
|
664
|
+
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
665
|
+
strict_host_key_checking: bool = True,
|
|
473
666
|
) -> list[str]:
|
|
474
667
|
args: list[str] = ["ssh"]
|
|
475
668
|
if batch_mode:
|
|
@@ -477,17 +670,44 @@ def ssh_cmd(
|
|
|
477
670
|
args.extend(["-o", f"HostKeyAlgorithms={','.join(host_key_algorithms)}"])
|
|
478
671
|
if strict_host_key_checking:
|
|
479
672
|
args.extend(["-o", "StrictHostKeyChecking=yes"])
|
|
480
|
-
return [*args, "-T"
|
|
673
|
+
return [*args, "-T"]
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
def ssh_keygen_cmd(hostname: str, /) -> list[str]:
|
|
677
|
+
return ["ssh-keygen", "-f", "~/.ssh/known_hosts", "-R", hostname]
|
|
481
678
|
|
|
482
679
|
|
|
483
680
|
def sudo_cmd(cmd: str, /, *args: str) -> list[str]:
|
|
484
681
|
return ["sudo", cmd, *args]
|
|
485
682
|
|
|
486
683
|
|
|
684
|
+
def sudo_nopasswd_cmd(user: str, /) -> str:
|
|
685
|
+
return f"{user} ALL=(ALL) NOPASSWD: ALL"
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
def symlink_cmd(src: PathLike, dest: PathLike, /) -> list[str]:
|
|
689
|
+
return ["ln", "-s", str(src), str(dest)]
|
|
690
|
+
|
|
691
|
+
|
|
487
692
|
def touch_cmd(path: PathLike, /) -> list[str]:
|
|
488
693
|
return ["touch", str(path)]
|
|
489
694
|
|
|
490
695
|
|
|
696
|
+
def uv_run_cmd(module: str, /, *args: str) -> list[str]:
|
|
697
|
+
return [
|
|
698
|
+
"uv",
|
|
699
|
+
"run",
|
|
700
|
+
"--no-dev",
|
|
701
|
+
"--active",
|
|
702
|
+
"--prerelease=disallow",
|
|
703
|
+
"--managed-python",
|
|
704
|
+
"python",
|
|
705
|
+
"-m",
|
|
706
|
+
module,
|
|
707
|
+
*args,
|
|
708
|
+
]
|
|
709
|
+
|
|
710
|
+
|
|
491
711
|
@contextmanager
|
|
492
712
|
def yield_ssh_temp_dir(
|
|
493
713
|
user: str,
|
|
@@ -498,12 +718,12 @@ def yield_ssh_temp_dir(
|
|
|
498
718
|
logger: LoggerLike | None = None,
|
|
499
719
|
keep: bool = False,
|
|
500
720
|
) -> Iterator[Path]:
|
|
501
|
-
path = Path(
|
|
721
|
+
path = Path( # skipif-ci
|
|
502
722
|
ssh(user, hostname, *MKTEMP_DIR_CMD, return_=True, retry=retry, logger=logger)
|
|
503
723
|
)
|
|
504
|
-
try:
|
|
724
|
+
try: # skipif-ci
|
|
505
725
|
yield path
|
|
506
|
-
finally:
|
|
726
|
+
finally: # skipif-ci
|
|
507
727
|
if keep:
|
|
508
728
|
if logger is not None:
|
|
509
729
|
to_logger(logger).info("Keeping temporary directory '%s'...", path)
|
|
@@ -512,21 +732,38 @@ def yield_ssh_temp_dir(
|
|
|
512
732
|
|
|
513
733
|
|
|
514
734
|
__all__ = [
|
|
735
|
+
"APT_UPDATE",
|
|
515
736
|
"BASH_LC",
|
|
516
737
|
"BASH_LS",
|
|
517
738
|
"MKTEMP_DIR_CMD",
|
|
739
|
+
"RESTART_SSHD",
|
|
740
|
+
"UPDATE_CA_CERTIFICATES",
|
|
741
|
+
"ChownCmdError",
|
|
742
|
+
"apt_install_cmd",
|
|
743
|
+
"cd_cmd",
|
|
744
|
+
"chmod_cmd",
|
|
745
|
+
"chown_cmd",
|
|
518
746
|
"cp_cmd",
|
|
519
747
|
"echo_cmd",
|
|
520
748
|
"expand_path",
|
|
749
|
+
"git_clone_cmd",
|
|
750
|
+
"git_hard_reset_cmd",
|
|
521
751
|
"maybe_sudo_cmd",
|
|
522
752
|
"mkdir",
|
|
523
753
|
"mkdir_cmd",
|
|
524
754
|
"mv_cmd",
|
|
525
755
|
"rm_cmd",
|
|
756
|
+
"rsync",
|
|
757
|
+
"rsync_cmd",
|
|
526
758
|
"run",
|
|
759
|
+
"set_hostname_cmd",
|
|
527
760
|
"ssh",
|
|
528
761
|
"ssh_cmd",
|
|
762
|
+
"ssh_opts_cmd",
|
|
529
763
|
"sudo_cmd",
|
|
764
|
+
"sudo_nopasswd_cmd",
|
|
765
|
+
"symlink_cmd",
|
|
530
766
|
"touch_cmd",
|
|
767
|
+
"uv_run_cmd",
|
|
531
768
|
"yield_ssh_temp_dir",
|
|
532
769
|
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/pytest_plugins/pytest_randomly.py
RENAMED
|
File without changes
|
{dycw_utilities-0.174.5 → dycw_utilities-0.174.7}/src/utilities/pytest_plugins/pytest_regressions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|