dycw-utilities 0.174.5__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.5
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=HSEHPwrjbITUKM0rHSI676yh7VgQMNCB5ksk8o1Hb8M,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=r91VteJ7I1upuiWxRT2kvm17xffS5eXMZ3CJXdmZT4s,15686
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.5.dist-info/WHEEL,sha256=ZyFSCYkV2BrxH6-HRVRg3R9Fo7MALzer9KiPYqNxSbo,79
101
- dycw_utilities-0.174.5.dist-info/entry_points.txt,sha256=ykGI1ArwOPHqm2g5Cqh3ENdMxEej_a_FcOUov5EM5Oc,155
102
- dycw_utilities-0.174.5.dist-info/METADATA,sha256=qYrj2g20dkkvXEdb1v43eAPpIoIS2vJOu4nM5QEFG54,1709
103
- dycw_utilities-0.174.5.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.5"
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,52 @@ 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"
28
72
 
29
73
 
30
74
  def cp_cmd(src: PathLike, dest: PathLike, /) -> list[str]:
@@ -45,6 +89,15 @@ def expand_path(
45
89
  return Path(path).expanduser()
46
90
 
47
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
+
48
101
  def maybe_sudo_cmd(cmd: str, /, *args: str, sudo: bool = False) -> list[str]:
49
102
  parts: list[str] = [cmd, *args]
50
103
  return sudo_cmd(*parts) if sudo else parts
@@ -224,8 +277,8 @@ def run(
224
277
  if proc.stderr is None: # pragma: no cover
225
278
  raise ImpossibleCaseError(case=[f"{proc.stderr=}"])
226
279
  with (
227
- _yield_write(proc.stdout, *stdout_outputs),
228
- _yield_write(proc.stderr, *stderr_outputs),
280
+ _run_yield_write(proc.stdout, *stdout_outputs),
281
+ _run_yield_write(proc.stderr, *stderr_outputs),
229
282
  ):
230
283
  if input is not None:
231
284
  _ = proc.stdin.write(input)
@@ -307,8 +360,8 @@ def run(
307
360
 
308
361
 
309
362
  @contextmanager
310
- def _yield_write(input_: IO[str], /, *outputs: IO[str]) -> Iterator[None]:
311
- 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)
312
365
  thread.start()
313
366
  try:
314
367
  yield
@@ -316,17 +369,21 @@ def _yield_write(input_: IO[str], /, *outputs: IO[str]) -> Iterator[None]:
316
369
  thread.join()
317
370
 
318
371
 
319
- def _run_target(input_: IO[str], /, *outputs: IO[str]) -> None:
372
+ def _run_daemon_target(input_: IO[str], /, *outputs: IO[str]) -> None:
320
373
  with input_:
321
374
  for text in iter(input_.readline, ""):
322
- _write_to_streams(text, *outputs)
375
+ _run_write_to_streams(text, *outputs)
323
376
 
324
377
 
325
- def _write_to_streams(text: str, /, *outputs: IO[str]) -> None:
378
+ def _run_write_to_streams(text: str, /, *outputs: IO[str]) -> None:
326
379
  for output in outputs:
327
380
  _ = output.write(text)
328
381
 
329
382
 
383
+ def set_hostname_cmd(hostname: str, /) -> list[str]:
384
+ return ["hostnamectl", "set-hostname", hostname]
385
+
386
+
330
387
  @overload
331
388
  def ssh(
332
389
  user: str,
@@ -480,14 +537,41 @@ def ssh_cmd(
480
537
  return [*args, "-T", f"{user}@{hostname}", *cmd_and_cmds_or_args]
481
538
 
482
539
 
540
+ def ssh_keygen_cmd(hostname: str, /) -> list[str]:
541
+ return ["ssh-keygen", "-f", "~/.ssh/known_hosts", "-R", hostname]
542
+
543
+
483
544
  def sudo_cmd(cmd: str, /, *args: str) -> list[str]:
484
545
  return ["sudo", cmd, *args]
485
546
 
486
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
+
487
556
  def touch_cmd(path: PathLike, /) -> list[str]:
488
557
  return ["touch", str(path)]
489
558
 
490
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
+
491
575
  @contextmanager
492
576
  def yield_ssh_temp_dir(
493
577
  user: str,
@@ -512,21 +596,35 @@ def yield_ssh_temp_dir(
512
596
 
513
597
 
514
598
  __all__ = [
599
+ "APT_UPDATE",
515
600
  "BASH_LC",
516
601
  "BASH_LS",
517
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",
518
610
  "cp_cmd",
519
611
  "echo_cmd",
520
612
  "expand_path",
613
+ "git_clone_cmd",
614
+ "git_hard_reset_cmd",
521
615
  "maybe_sudo_cmd",
522
616
  "mkdir",
523
617
  "mkdir_cmd",
524
618
  "mv_cmd",
525
619
  "rm_cmd",
526
620
  "run",
621
+ "set_hostname_cmd",
527
622
  "ssh",
528
623
  "ssh_cmd",
529
624
  "sudo_cmd",
625
+ "sudo_nopasswd_cmd",
626
+ "symlink_cmd",
530
627
  "touch_cmd",
628
+ "uv_run_cmd",
531
629
  "yield_ssh_temp_dir",
532
630
  ]