dycw-utilities 0.174.4__py3-none-any.whl → 0.174.6__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.3
2
2
  Name: dycw-utilities
3
- Version: 0.174.4
3
+ Version: 0.174.6
4
4
  Author: Derek Wan
5
5
  Author-email: Derek Wan <d.wan@icloud.com>
6
6
  Requires-Dist: atomicwrites>=1.4.1,<1.5
@@ -1,4 +1,4 @@
1
- utilities/__init__.py,sha256=IPAfPsoQRCu4p2HN4WqlBZlivN_FHMOLCjrLLfdu4EQ,60
1
+ utilities/__init__.py,sha256=3pVEXocwWUcnyWJHWq_I_o96RALb2ltauJoGiOsfki8,60
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
@@ -80,7 +80,7 @@ utilities/sqlalchemy.py,sha256=HQYpd7LFxdTF5WYVWYtCJeEBI71EJm7ytvCGyAH9B-U,37163
80
80
  utilities/sqlalchemy_polars.py,sha256=JCGhB37raSR7fqeWV5dTsciRTMVzIdVT9YSqKT0piT0,13370
81
81
  utilities/statsmodels.py,sha256=koyiBHvpMcSiBfh99wFUfSggLNx7cuAw3rwyfAhoKpQ,3410
82
82
  utilities/string.py,sha256=shmBK87zZwzGyixuNuXCiUbqzfeZ9xlrFwz6JTaRvDk,582
83
- utilities/subprocess.py,sha256=CWcaF5UUrR4JaXXTV-IwN9-4QFBN_bzl0AUileP2DJM,15452
83
+ utilities/subprocess.py,sha256=qrPDBTVKy_RMTnNFD4mQ7iYlNncXQiDBGJhffh6K0uc,18216
84
84
  utilities/tempfile.py,sha256=Lx6qa16lL1XVH6WdmD_G9vlN6gLI8nrIurxmsFkPKvg,3022
85
85
  utilities/testbook.py,sha256=j1KmaVbrX9VrbeMgtPh5gk55myAsn3dyRUn7jGbPbRk,1294
86
86
  utilities/text.py,sha256=7SvwcSR2l_5cOrm1samGnR4C-ZI6qyFLHLzSpO1zeHQ,13958
@@ -97,7 +97,7 @@ utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
97
97
  utilities/whenever.py,sha256=F4ek0-OBWxHYrZdmoZt76N2RnNyKY5KrEHt7rqO4AQE,60183
98
98
  utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
99
99
  utilities/zoneinfo.py,sha256=tdIScrTB2-B-LH0ukb1HUXKooLknOfJNwHk10MuMYvA,3619
100
- dycw_utilities-0.174.4.dist-info/WHEEL,sha256=ZyFSCYkV2BrxH6-HRVRg3R9Fo7MALzer9KiPYqNxSbo,79
101
- dycw_utilities-0.174.4.dist-info/entry_points.txt,sha256=ykGI1ArwOPHqm2g5Cqh3ENdMxEej_a_FcOUov5EM5Oc,155
102
- dycw_utilities-0.174.4.dist-info/METADATA,sha256=4OEtGyGgCm1RvAqPsNsps7YbqYlfIc-AqnrcY2GguJQ,1709
103
- dycw_utilities-0.174.4.dist-info/RECORD,,
100
+ dycw_utilities-0.174.6.dist-info/WHEEL,sha256=ZyFSCYkV2BrxH6-HRVRg3R9Fo7MALzer9KiPYqNxSbo,79
101
+ dycw_utilities-0.174.6.dist-info/entry_points.txt,sha256=ykGI1ArwOPHqm2g5Cqh3ENdMxEej_a_FcOUov5EM5Oc,155
102
+ dycw_utilities-0.174.6.dist-info/METADATA,sha256=9UHGH9P99oGq0OZI0ERbOeM_X2x3M2EO6C3-xo16np4,1709
103
+ dycw_utilities-0.174.6.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.174.4"
3
+ __version__ = "0.174.6"
utilities/subprocess.py CHANGED
@@ -2,13 +2,14 @@ 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
7
8
  from string import Template
8
9
  from subprocess import PIPE, CalledProcessError, Popen
9
10
  from threading import Thread
10
11
  from time import sleep
11
- from typing import IO, TYPE_CHECKING, Literal, assert_never, overload
12
+ from typing import IO, TYPE_CHECKING, Literal, assert_never, overload, override
12
13
 
13
14
  from utilities.errors import ImpossibleCaseError
14
15
  from utilities.logging import to_logger
@@ -22,9 +23,56 @@ if TYPE_CHECKING:
22
23
 
23
24
 
24
25
  _HOST_KEY_ALGORITHMS = ["ssh-ed25519"]
26
+ APT_UPDATE = ["apt", "update", "-y"]
25
27
  BASH_LC = ["bash", "-lc"]
26
28
  BASH_LS = ["bash", "-ls"]
27
29
  MKTEMP_DIR_CMD = ["mktemp", "-d"]
30
+ RESTART_SSHD = ["systemctl", "restart", "sshd"]
31
+ UPDATE_CA_CERTIFICATES: str = "update-ca-certificates"
32
+
33
+
34
+ def apt_install_cmd(package: str, /) -> list[str]:
35
+ return ["apt", "install", "-y", package]
36
+
37
+
38
+ def cat_cmd(path: PathLike, /) -> list[str]:
39
+ return ["cat", str(path)]
40
+
41
+
42
+ def cd_cmd(path: PathLike, /) -> list[str]:
43
+ return ["cd", str(path)]
44
+
45
+
46
+ def chmod_cmd(path: PathLike, mode: str, /) -> list[str]:
47
+ return ["chmod", mode, str(path)]
48
+
49
+
50
+ def chown_cmd(
51
+ path: PathLike, /, *, user: str | None = None, group: str | None = None
52
+ ) -> list[str]:
53
+ match user, group:
54
+ case None, None:
55
+ raise ChownCmdError
56
+ case str(), None:
57
+ ownership = "user"
58
+ case None, str():
59
+ ownership = f":{group}"
60
+ case str(), str():
61
+ ownership = f"{user}:{group}"
62
+ case never:
63
+ assert_never(never)
64
+ return ["chown", ownership, str(path)]
65
+
66
+
67
+ @dataclass(kw_only=True, slots=True)
68
+ class ChownCmdError(Exception):
69
+ @override
70
+ def __str__(self) -> str:
71
+ return "At least one of 'user' and/or 'group' must be given; got None"
72
+
73
+
74
+ def cp_cmd(src: PathLike, dest: PathLike, /) -> list[str]:
75
+ return ["cp", "-r", str(src), str(dest)]
28
76
 
29
77
 
30
78
  def echo_cmd(text: str, /) -> list[str]:
@@ -41,6 +89,15 @@ def expand_path(
41
89
  return Path(path).expanduser()
42
90
 
43
91
 
92
+ def git_clone_cmd(url: str, path: PathLike, /) -> list[str]:
93
+ return ["git", "clone", "--recurse-submodules", url, str(path)]
94
+
95
+
96
+ def git_hard_reset_cmd(*, branch: str | None = None) -> list[str]:
97
+ branch_use = "master" if branch is None else branch
98
+ return ["git", "hard-reset", branch_use]
99
+
100
+
44
101
  def maybe_sudo_cmd(cmd: str, /, *args: str, sudo: bool = False) -> list[str]:
45
102
  parts: list[str] = [cmd, *args]
46
103
  return sudo_cmd(*parts) if sudo else parts
@@ -60,6 +117,10 @@ def mkdir_cmd(path: PathLike, /, *, parent: bool = False) -> list[str]:
60
117
  return ["mkdir", "-p", str(path_use)]
61
118
 
62
119
 
120
+ def mv_cmd(src: PathLike, dest: PathLike, /) -> list[str]:
121
+ return ["mv", str(src), str(dest)]
122
+
123
+
63
124
  def rm_cmd(path: PathLike, /) -> list[str]:
64
125
  return ["rm", "-rf", str(path)]
65
126
 
@@ -216,8 +277,8 @@ def run(
216
277
  if proc.stderr is None: # pragma: no cover
217
278
  raise ImpossibleCaseError(case=[f"{proc.stderr=}"])
218
279
  with (
219
- _yield_write(proc.stdout, *stdout_outputs),
220
- _yield_write(proc.stderr, *stderr_outputs),
280
+ _run_yield_write(proc.stdout, *stdout_outputs),
281
+ _run_yield_write(proc.stderr, *stderr_outputs),
221
282
  ):
222
283
  if input is not None:
223
284
  _ = proc.stdin.write(input)
@@ -299,8 +360,8 @@ def run(
299
360
 
300
361
 
301
362
  @contextmanager
302
- def _yield_write(input_: IO[str], /, *outputs: IO[str]) -> Iterator[None]:
303
- thread = Thread(target=_run_target, args=(input_, *outputs), daemon=True)
363
+ def _run_yield_write(input_: IO[str], /, *outputs: IO[str]) -> Iterator[None]:
364
+ thread = Thread(target=_run_daemon_target, args=(input_, *outputs), daemon=True)
304
365
  thread.start()
305
366
  try:
306
367
  yield
@@ -308,17 +369,21 @@ def _yield_write(input_: IO[str], /, *outputs: IO[str]) -> Iterator[None]:
308
369
  thread.join()
309
370
 
310
371
 
311
- def _run_target(input_: IO[str], /, *outputs: IO[str]) -> None:
372
+ def _run_daemon_target(input_: IO[str], /, *outputs: IO[str]) -> None:
312
373
  with input_:
313
374
  for text in iter(input_.readline, ""):
314
- _write_to_streams(text, *outputs)
375
+ _run_write_to_streams(text, *outputs)
315
376
 
316
377
 
317
- def _write_to_streams(text: str, /, *outputs: IO[str]) -> None:
378
+ def _run_write_to_streams(text: str, /, *outputs: IO[str]) -> None:
318
379
  for output in outputs:
319
380
  _ = output.write(text)
320
381
 
321
382
 
383
+ def set_hostname_cmd(hostname: str, /) -> list[str]:
384
+ return ["hostnamectl", "set-hostname", hostname]
385
+
386
+
322
387
  @overload
323
388
  def ssh(
324
389
  user: str,
@@ -472,14 +537,41 @@ def ssh_cmd(
472
537
  return [*args, "-T", f"{user}@{hostname}", *cmd_and_cmds_or_args]
473
538
 
474
539
 
540
+ def ssh_keygen_cmd(hostname: str, /) -> list[str]:
541
+ return ["ssh-keygen", "-f", "~/.ssh/known_hosts", "-R", hostname]
542
+
543
+
475
544
  def sudo_cmd(cmd: str, /, *args: str) -> list[str]:
476
545
  return ["sudo", cmd, *args]
477
546
 
478
547
 
548
+ def sudo_nopasswd_cmd(user: str, /) -> str:
549
+ return f"{user} ALL=(ALL) NOPASSWD: ALL"
550
+
551
+
552
+ def symlink_cmd(src: PathLike, dest: PathLike, /) -> list[str]:
553
+ return ["ln", "-s", str(src), str(dest)]
554
+
555
+
479
556
  def touch_cmd(path: PathLike, /) -> list[str]:
480
557
  return ["touch", str(path)]
481
558
 
482
559
 
560
+ def uv_run_cmd(module: str, /, *args: str) -> list[str]:
561
+ return [
562
+ "uv",
563
+ "run",
564
+ "--no-dev",
565
+ "--active",
566
+ "--prerelease=disallow",
567
+ "--managed-python",
568
+ "python",
569
+ "-m",
570
+ module,
571
+ *args,
572
+ ]
573
+
574
+
483
575
  @contextmanager
484
576
  def yield_ssh_temp_dir(
485
577
  user: str,
@@ -504,19 +596,35 @@ def yield_ssh_temp_dir(
504
596
 
505
597
 
506
598
  __all__ = [
599
+ "APT_UPDATE",
507
600
  "BASH_LC",
508
601
  "BASH_LS",
509
602
  "MKTEMP_DIR_CMD",
603
+ "RESTART_SSHD",
604
+ "UPDATE_CA_CERTIFICATES",
605
+ "ChownCmdError",
606
+ "apt_install_cmd",
607
+ "cd_cmd",
608
+ "chmod_cmd",
609
+ "chown_cmd",
610
+ "cp_cmd",
510
611
  "echo_cmd",
511
612
  "expand_path",
613
+ "git_clone_cmd",
614
+ "git_hard_reset_cmd",
512
615
  "maybe_sudo_cmd",
513
616
  "mkdir",
514
617
  "mkdir_cmd",
618
+ "mv_cmd",
515
619
  "rm_cmd",
516
620
  "run",
621
+ "set_hostname_cmd",
517
622
  "ssh",
518
623
  "ssh_cmd",
519
624
  "sudo_cmd",
625
+ "sudo_nopasswd_cmd",
626
+ "symlink_cmd",
520
627
  "touch_cmd",
628
+ "uv_run_cmd",
521
629
  "yield_ssh_temp_dir",
522
630
  ]