dycw-utilities 0.174.16__py3-none-any.whl → 0.174.18__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.174.16.dist-info → dycw_utilities-0.174.18.dist-info}/METADATA +1 -1
- {dycw_utilities-0.174.16.dist-info → dycw_utilities-0.174.18.dist-info}/RECORD +7 -7
- {dycw_utilities-0.174.16.dist-info → dycw_utilities-0.174.18.dist-info}/WHEEL +1 -1
- utilities/__init__.py +1 -1
- utilities/docker.py +1 -0
- utilities/subprocess.py +182 -12
- {dycw_utilities-0.174.16.dist-info → dycw_utilities-0.174.18.dist-info}/entry_points.txt +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
utilities/__init__.py,sha256=
|
|
1
|
+
utilities/__init__.py,sha256=2xupJL_-0RN3mhQDSD7NIAJegYc_Pr3NKdbT8LPHrvI,61
|
|
2
2
|
utilities/aeventkit.py,sha256=OmDBhYGgbsKrB7cdC5FFpJHUatX9O76eTeKVVTksp2Y,12673
|
|
3
3
|
utilities/altair.py,sha256=rUK99g9x6CYDDfiZrf-aTx5fSRbL1Q8ctgKORowzXHg,9060
|
|
4
4
|
utilities/asyncio.py,sha256=aJySVxBY0gqsIYnoNmH7-1r8djKuf4vSsU69VCD08t8,16772
|
|
@@ -12,7 +12,7 @@ utilities/contextvars.py,sha256=J8OhC7jqozAGYOCe2KUWysbPXNGe5JYz3HfaY_mIs08,883
|
|
|
12
12
|
utilities/cryptography.py,sha256=5PFrzsNUGHay91dFgYnDKwYprXxahrBqztmUqViRzBk,956
|
|
13
13
|
utilities/cvxpy.py,sha256=Rv1-fD-XYerosCavRF8Pohop2DBkU3AlFaGTfD8AEAA,13776
|
|
14
14
|
utilities/dataclasses.py,sha256=xbU3QN1GFy7RC6hIJRZIeUZm7YRlodrgEWmahWG6k2g,32465
|
|
15
|
-
utilities/docker.py,sha256=
|
|
15
|
+
utilities/docker.py,sha256=N__PKd3cnSRsXNEMHMLdLneLdyzfbr2ESkElcwrovvQ,7940
|
|
16
16
|
utilities/enum.py,sha256=5l6pwZD1cjSlVW4ss-zBPspWvrbrYrdtJWcg6f5_J5w,5781
|
|
17
17
|
utilities/errors.py,sha256=mFlDGSM0LI1jZ1pbqwLAH3ttLZ2JVIxyZLojw8tGVZU,1479
|
|
18
18
|
utilities/fastapi.py,sha256=TqyKvBjiMS594sXPjrz-KRTLMb3l3D3rZ1zAYV7GfOk,1454
|
|
@@ -81,7 +81,7 @@ utilities/sqlalchemy.py,sha256=HQYpd7LFxdTF5WYVWYtCJeEBI71EJm7ytvCGyAH9B-U,37163
|
|
|
81
81
|
utilities/sqlalchemy_polars.py,sha256=JCGhB37raSR7fqeWV5dTsciRTMVzIdVT9YSqKT0piT0,13370
|
|
82
82
|
utilities/statsmodels.py,sha256=koyiBHvpMcSiBfh99wFUfSggLNx7cuAw3rwyfAhoKpQ,3410
|
|
83
83
|
utilities/string.py,sha256=shmBK87zZwzGyixuNuXCiUbqzfeZ9xlrFwz6JTaRvDk,582
|
|
84
|
-
utilities/subprocess.py,sha256=
|
|
84
|
+
utilities/subprocess.py,sha256=q0k-Fl8iRKX9UvqtNgshUcrv-tAEzJbeyoQWiWEdql8,34298
|
|
85
85
|
utilities/tempfile.py,sha256=Lx6qa16lL1XVH6WdmD_G9vlN6gLI8nrIurxmsFkPKvg,3022
|
|
86
86
|
utilities/testbook.py,sha256=j1KmaVbrX9VrbeMgtPh5gk55myAsn3dyRUn7jGbPbRk,1294
|
|
87
87
|
utilities/text.py,sha256=7SvwcSR2l_5cOrm1samGnR4C-ZI6qyFLHLzSpO1zeHQ,13958
|
|
@@ -98,7 +98,7 @@ utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
|
|
|
98
98
|
utilities/whenever.py,sha256=F4ek0-OBWxHYrZdmoZt76N2RnNyKY5KrEHt7rqO4AQE,60183
|
|
99
99
|
utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
|
|
100
100
|
utilities/zoneinfo.py,sha256=tdIScrTB2-B-LH0ukb1HUXKooLknOfJNwHk10MuMYvA,3619
|
|
101
|
-
dycw_utilities-0.174.
|
|
102
|
-
dycw_utilities-0.174.
|
|
103
|
-
dycw_utilities-0.174.
|
|
104
|
-
dycw_utilities-0.174.
|
|
101
|
+
dycw_utilities-0.174.18.dist-info/WHEEL,sha256=RRVLqVugUmFOqBedBFAmA4bsgFcROUBiSUKlERi0Hcg,79
|
|
102
|
+
dycw_utilities-0.174.18.dist-info/entry_points.txt,sha256=ykGI1ArwOPHqm2g5Cqh3ENdMxEej_a_FcOUov5EM5Oc,155
|
|
103
|
+
dycw_utilities-0.174.18.dist-info/METADATA,sha256=lsjOEHDTftfTew-cUGjw81uggqGrWLEXHVN3snWSV80,1710
|
|
104
|
+
dycw_utilities-0.174.18.dist-info/RECORD,,
|
utilities/__init__.py
CHANGED
utilities/docker.py
CHANGED
utilities/subprocess.py
CHANGED
|
@@ -17,7 +17,8 @@ from typing import IO, TYPE_CHECKING, Literal, assert_never, overload, override
|
|
|
17
17
|
from utilities.errors import ImpossibleCaseError
|
|
18
18
|
from utilities.iterables import always_iterable
|
|
19
19
|
from utilities.logging import to_logger
|
|
20
|
-
from utilities.permissions import ensure_perms
|
|
20
|
+
from utilities.permissions import Permissions, ensure_perms
|
|
21
|
+
from utilities.tempfile import TemporaryDirectory
|
|
21
22
|
from utilities.text import strip_and_dedent
|
|
22
23
|
from utilities.whenever import to_seconds
|
|
23
24
|
|
|
@@ -220,6 +221,16 @@ def expand_path(
|
|
|
220
221
|
##
|
|
221
222
|
|
|
222
223
|
|
|
224
|
+
def git_clone(
|
|
225
|
+
url: str, path: PathLike, /, *, sudo: bool = False, branch: str | None = None
|
|
226
|
+
) -> None:
|
|
227
|
+
"""Clone a repository."""
|
|
228
|
+
rm(path, sudo=sudo)
|
|
229
|
+
run(*maybe_sudo_cmd(*git_clone_cmd(url, path), sudo=sudo))
|
|
230
|
+
if branch is not None:
|
|
231
|
+
run(*maybe_sudo_cmd(*git_hard_reset_cmd(branch=branch), sudo=sudo), cwd=path)
|
|
232
|
+
|
|
233
|
+
|
|
223
234
|
def git_clone_cmd(url: str, path: PathLike, /) -> list[str]:
|
|
224
235
|
"""Command to use 'git clone' to clone a repository."""
|
|
225
236
|
return ["git", "clone", "--recurse-submodules", url, str(path)]
|
|
@@ -346,7 +357,7 @@ def rsync(
|
|
|
346
357
|
chown_user: str | None = None,
|
|
347
358
|
chown_group: str | None = None,
|
|
348
359
|
exclude: MaybeIterable[str] | None = None,
|
|
349
|
-
chmod:
|
|
360
|
+
chmod: PermissionsLike | None = None,
|
|
350
361
|
) -> None:
|
|
351
362
|
"""Remote & local file copying."""
|
|
352
363
|
mkdir_args = maybe_sudo_cmd(*mkdir_cmd(dest, parent=True), sudo=sudo) # skipif-ci
|
|
@@ -361,13 +372,13 @@ def rsync(
|
|
|
361
372
|
retry=retry,
|
|
362
373
|
logger=logger,
|
|
363
374
|
)
|
|
364
|
-
|
|
375
|
+
srcs = list(always_iterable(src_or_srcs)) # skipif-ci
|
|
365
376
|
rsync_args = rsync_cmd( # skipif-ci
|
|
366
|
-
|
|
377
|
+
srcs,
|
|
367
378
|
user,
|
|
368
379
|
hostname,
|
|
369
380
|
dest,
|
|
370
|
-
archive=is_dir,
|
|
381
|
+
archive=any(Path(s).is_dir() for s in srcs),
|
|
371
382
|
chown_user=chown_user,
|
|
372
383
|
chown_group=chown_group,
|
|
373
384
|
exclude=exclude,
|
|
@@ -375,7 +386,6 @@ def rsync(
|
|
|
375
386
|
host_key_algorithms=host_key_algorithms,
|
|
376
387
|
strict_host_key_checking=strict_host_key_checking,
|
|
377
388
|
sudo=sudo,
|
|
378
|
-
parent=is_dir,
|
|
379
389
|
)
|
|
380
390
|
run(*rsync_args, print=print, retry=retry, logger=logger) # skipif-ci
|
|
381
391
|
if chmod is not None: # skipif-ci
|
|
@@ -408,7 +418,6 @@ def rsync_cmd(
|
|
|
408
418
|
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
409
419
|
strict_host_key_checking: bool = True,
|
|
410
420
|
sudo: bool = False,
|
|
411
|
-
parent: bool = False,
|
|
412
421
|
) -> list[str]:
|
|
413
422
|
"""Command to use 'rsync' to do remote & local file copying."""
|
|
414
423
|
args: list[str] = ["rsync"]
|
|
@@ -438,12 +447,141 @@ def rsync_cmd(
|
|
|
438
447
|
args.extend(["--rsh", join(rsh_args)])
|
|
439
448
|
if sudo:
|
|
440
449
|
args.extend(["--rsync-path", join(sudo_cmd("rsync"))])
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
450
|
+
srcs = list(always_iterable(src_or_srcs)) # do not Path()
|
|
451
|
+
if len(srcs) == 0:
|
|
452
|
+
raise RsyncCmdNoSourcesError(user=user, hostname=hostname, dest=dest)
|
|
453
|
+
missing = [s for s in srcs if not Path(s).exists()]
|
|
454
|
+
if len(missing) >= 1:
|
|
455
|
+
raise RsyncCmdSourcesNotFoundError(
|
|
456
|
+
sources=missing, user=user, hostname=hostname, dest=dest
|
|
457
|
+
)
|
|
458
|
+
return [*args, *map(str, srcs), f"{user}@{hostname}:{dest}"]
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
@dataclass(kw_only=True, slots=True)
|
|
462
|
+
class RsyncCmdError(Exception):
|
|
463
|
+
user: str
|
|
464
|
+
hostname: str
|
|
465
|
+
dest: PathLike
|
|
466
|
+
|
|
467
|
+
@override
|
|
468
|
+
def __str__(self) -> str:
|
|
469
|
+
return f"No sources selected to send to {self.user}@{self.hostname}:{self.dest}"
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
@dataclass(kw_only=True, slots=True)
|
|
473
|
+
class RsyncCmdNoSourcesError(RsyncCmdError):
|
|
474
|
+
@override
|
|
475
|
+
def __str__(self) -> str:
|
|
476
|
+
return f"No sources selected to send to {self.user}@{self.hostname}:{self.dest}"
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
@dataclass(kw_only=True, slots=True)
|
|
480
|
+
class RsyncCmdSourcesNotFoundError(RsyncCmdError):
|
|
481
|
+
sources: list[PathLike]
|
|
482
|
+
|
|
483
|
+
@override
|
|
484
|
+
def __str__(self) -> str:
|
|
485
|
+
desc = ", ".join(map(repr, map(str, self.sources)))
|
|
486
|
+
return f"Sources selected to send to {self.user}@{self.hostname}:{self.dest} but not found: {desc}"
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
##
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def rsync_many(
|
|
493
|
+
user: str,
|
|
494
|
+
hostname: str,
|
|
495
|
+
/,
|
|
496
|
+
*items: tuple[PathLike, PathLike]
|
|
497
|
+
| tuple[Literal["sudo"], PathLike, PathLike]
|
|
498
|
+
| tuple[PathLike, PathLike, PermissionsLike],
|
|
499
|
+
retry: Retry | None = None,
|
|
500
|
+
logger: LoggerLike | None = None,
|
|
501
|
+
keep: bool = False,
|
|
502
|
+
batch_mode: bool = True,
|
|
503
|
+
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
504
|
+
strict_host_key_checking: bool = True,
|
|
505
|
+
print: bool = False, # noqa: A002
|
|
506
|
+
) -> None:
|
|
507
|
+
cmds: list[list[str]] = [] # skipif-ci
|
|
508
|
+
with ( # skipif-ci
|
|
509
|
+
TemporaryDirectory() as temp_src,
|
|
510
|
+
yield_ssh_temp_dir(
|
|
511
|
+
user, hostname, retry=retry, logger=logger, keep=keep
|
|
512
|
+
) as temp_dest,
|
|
513
|
+
):
|
|
514
|
+
for item in items:
|
|
515
|
+
match item:
|
|
516
|
+
case Path() | str() as src, Path() | str() as dest:
|
|
517
|
+
cmds.extend(_rsync_many_prepare(src, dest, temp_src, temp_dest))
|
|
518
|
+
case "sudo", Path() | str() as src, Path() | str() as dest:
|
|
519
|
+
cmds.extend(
|
|
520
|
+
_rsync_many_prepare(src, dest, temp_src, temp_dest, sudo=True)
|
|
521
|
+
)
|
|
522
|
+
case (
|
|
523
|
+
Path() | str() as src,
|
|
524
|
+
Path() | str() as dest,
|
|
525
|
+
Permissions() | int() | str() as perms,
|
|
526
|
+
):
|
|
527
|
+
cmds.extend(
|
|
528
|
+
_rsync_many_prepare(src, dest, temp_src, temp_dest, perms=perms)
|
|
529
|
+
)
|
|
530
|
+
case never:
|
|
531
|
+
assert_never(never)
|
|
532
|
+
rsync(
|
|
533
|
+
f"{temp_src}/",
|
|
534
|
+
user,
|
|
535
|
+
hostname,
|
|
536
|
+
temp_dest,
|
|
537
|
+
batch_mode=batch_mode,
|
|
538
|
+
host_key_algorithms=host_key_algorithms,
|
|
539
|
+
strict_host_key_checking=strict_host_key_checking,
|
|
540
|
+
print=print,
|
|
541
|
+
retry=retry,
|
|
542
|
+
logger=logger,
|
|
543
|
+
)
|
|
544
|
+
ssh(
|
|
545
|
+
user,
|
|
546
|
+
hostname,
|
|
547
|
+
*BASH_LS,
|
|
548
|
+
input="\n".join(map(join, cmds)),
|
|
549
|
+
print=print,
|
|
550
|
+
retry=retry,
|
|
551
|
+
logger=logger,
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
def _rsync_many_prepare(
|
|
556
|
+
src: PathLike,
|
|
557
|
+
dest: PathLike,
|
|
558
|
+
temp_src: PathLike,
|
|
559
|
+
temp_dest: PathLike,
|
|
560
|
+
/,
|
|
561
|
+
*,
|
|
562
|
+
sudo: bool = False,
|
|
563
|
+
perms: PermissionsLike | None = None,
|
|
564
|
+
) -> list[list[str]]:
|
|
565
|
+
dest, temp_src, temp_dest = map(Path, [dest, temp_src, temp_dest])
|
|
566
|
+
n = len(list(temp_src.iterdir()))
|
|
567
|
+
name = str(n)
|
|
568
|
+
match src:
|
|
569
|
+
case Path():
|
|
570
|
+
cp(src, temp_src / name)
|
|
571
|
+
case str():
|
|
572
|
+
if Path(src).exists():
|
|
573
|
+
cp(src, temp_src / name)
|
|
574
|
+
else:
|
|
575
|
+
tee(temp_src / name, src)
|
|
576
|
+
case never:
|
|
577
|
+
assert_never(never)
|
|
578
|
+
cmds: list[list[str]] = [
|
|
579
|
+
maybe_sudo_cmd(*mkdir_cmd(dest, parent=True), sudo=sudo),
|
|
580
|
+
maybe_sudo_cmd(*cp_cmd(temp_dest / name, dest), sudo=sudo),
|
|
446
581
|
]
|
|
582
|
+
if perms is not None:
|
|
583
|
+
cmds.append(maybe_sudo_cmd(*chmod_cmd(dest, perms), sudo=sudo))
|
|
584
|
+
return cmds
|
|
447
585
|
|
|
448
586
|
|
|
449
587
|
##
|
|
@@ -961,14 +1099,26 @@ def tee_cmd(path: PathLike, /, *, append: bool = False) -> list[str]:
|
|
|
961
1099
|
##
|
|
962
1100
|
|
|
963
1101
|
|
|
1102
|
+
def touch(path: PathLike, /, *, sudo: bool = False) -> None:
|
|
1103
|
+
"""Change file access and modification times."""
|
|
1104
|
+
run(*maybe_sudo_cmd(*touch_cmd(path), sudo=sudo))
|
|
1105
|
+
|
|
1106
|
+
|
|
964
1107
|
def touch_cmd(path: PathLike, /) -> list[str]:
|
|
1108
|
+
"""Command to use 'touch' to change file access and modification times."""
|
|
965
1109
|
return ["touch", str(path)]
|
|
966
1110
|
|
|
967
1111
|
|
|
968
1112
|
##
|
|
969
1113
|
|
|
970
1114
|
|
|
1115
|
+
def uv_run(module: str, /, *args: str) -> None:
|
|
1116
|
+
"""Run a command or script."""
|
|
1117
|
+
run(*uv_run_cmd(module, *args)) # pragma: no cover
|
|
1118
|
+
|
|
1119
|
+
|
|
971
1120
|
def uv_run_cmd(module: str, /, *args: str) -> list[str]:
|
|
1121
|
+
"""Command to use 'uv' to run a command or script."""
|
|
972
1122
|
return [
|
|
973
1123
|
"uv",
|
|
974
1124
|
"run",
|
|
@@ -986,6 +1136,17 @@ def uv_run_cmd(module: str, /, *args: str) -> list[str]:
|
|
|
986
1136
|
##
|
|
987
1137
|
|
|
988
1138
|
|
|
1139
|
+
@contextmanager
|
|
1140
|
+
def yield_git_repo(url: str, /, *, branch: str | None = None) -> Iterator[Path]:
|
|
1141
|
+
"""Yield a temporary git repository."""
|
|
1142
|
+
with TemporaryDirectory() as temp_dir:
|
|
1143
|
+
git_clone(url, temp_dir, branch=branch)
|
|
1144
|
+
yield temp_dir
|
|
1145
|
+
|
|
1146
|
+
|
|
1147
|
+
##
|
|
1148
|
+
|
|
1149
|
+
|
|
989
1150
|
@contextmanager
|
|
990
1151
|
def yield_ssh_temp_dir(
|
|
991
1152
|
user: str,
|
|
@@ -996,6 +1157,7 @@ def yield_ssh_temp_dir(
|
|
|
996
1157
|
logger: LoggerLike | None = None,
|
|
997
1158
|
keep: bool = False,
|
|
998
1159
|
) -> Iterator[Path]:
|
|
1160
|
+
"""Yield a temporary directory on a remote machine."""
|
|
999
1161
|
path = Path( # skipif-ci
|
|
1000
1162
|
ssh(user, hostname, *MKTEMP_DIR_CMD, return_=True, retry=retry, logger=logger)
|
|
1001
1163
|
)
|
|
@@ -1019,6 +1181,9 @@ __all__ = [
|
|
|
1019
1181
|
"ChownCmdError",
|
|
1020
1182
|
"CpError",
|
|
1021
1183
|
"MvFileError",
|
|
1184
|
+
"RsyncCmdError",
|
|
1185
|
+
"RsyncCmdNoSourcesError",
|
|
1186
|
+
"RsyncCmdSourcesNotFoundError",
|
|
1022
1187
|
"apt_install_cmd",
|
|
1023
1188
|
"cd_cmd",
|
|
1024
1189
|
"chmod",
|
|
@@ -1029,6 +1194,7 @@ __all__ = [
|
|
|
1029
1194
|
"cp_cmd",
|
|
1030
1195
|
"echo_cmd",
|
|
1031
1196
|
"expand_path",
|
|
1197
|
+
"git_clone",
|
|
1032
1198
|
"git_clone_cmd",
|
|
1033
1199
|
"git_hard_reset_cmd",
|
|
1034
1200
|
"maybe_parent",
|
|
@@ -1041,6 +1207,7 @@ __all__ = [
|
|
|
1041
1207
|
"rm_cmd",
|
|
1042
1208
|
"rsync",
|
|
1043
1209
|
"rsync_cmd",
|
|
1210
|
+
"rsync_many",
|
|
1044
1211
|
"run",
|
|
1045
1212
|
"set_hostname_cmd",
|
|
1046
1213
|
"ssh",
|
|
@@ -1051,7 +1218,10 @@ __all__ = [
|
|
|
1051
1218
|
"symlink",
|
|
1052
1219
|
"symlink_cmd",
|
|
1053
1220
|
"tee_cmd",
|
|
1221
|
+
"touch",
|
|
1054
1222
|
"touch_cmd",
|
|
1223
|
+
"uv_run",
|
|
1055
1224
|
"uv_run_cmd",
|
|
1225
|
+
"yield_git_repo",
|
|
1056
1226
|
"yield_ssh_temp_dir",
|
|
1057
1227
|
]
|
|
File without changes
|