dycw-utilities 0.174.12__py3-none-any.whl → 0.174.14__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.12
3
+ Version: 0.174.14
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=VFU3kmpNxjyAnXuMdW-ygHCxl3CNmUA0rP-wy1y_uzg,61
1
+ utilities/__init__.py,sha256=wElh7rM2Gp4Qe3xG2KlBo0z_G6fFiYwVzZ10q9vTQac,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
@@ -47,7 +47,7 @@ utilities/orjson.py,sha256=T_0SlK811ysg46d3orvIPY3JpBa4FRMpP2wlPQo7-gU,41854
47
47
  utilities/os.py,sha256=kjKKSQfnRqFTTZ315iavaaGd3gGuYNoSWlxVLCJjyQs,4852
48
48
  utilities/parse.py,sha256=g7Qm9eBOIeDId2tGA021CIaeF6jp1TI8rx4srdvlyoo,17937
49
49
  utilities/pathlib.py,sha256=EKZn-wWxH7MEWFrQGqHIoB-GJzyXeiEj8iDIgvkr8Wk,9325
50
- utilities/permissions.py,sha256=u-1PMJGC8PDbZltR4SozBuKKX5yIubmXPuiKrBx3IKU,8943
50
+ utilities/permissions.py,sha256=vLXlWztSVYffbrxptne7ksj6dU1HLekm4fEvS4ny_4Q,8944
51
51
  utilities/pickle.py,sha256=MBT2xZCsv0pH868IXLGKnlcqNx2IRVKYNpRcqiQQqxw,653
52
52
  utilities/platform.py,sha256=0pYO5v7L2sU5UN87zHhEEhTKsZ9NIEM8N6UCr0F7bLY,2778
53
53
  utilities/polars.py,sha256=cNFBLWgOMUAp_Sz4xtlto17uZswZRrcfQYC95QKyaY4,87483
@@ -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=dIBguvLB0WepJKqgBxlzF6r8z9HW5JiGKLg-eslpuec,22549
84
+ utilities/subprocess.py,sha256=tYfdY7CdVc91rs9AZ-KSwLvKyNNoPunDOWLgCtwuTvs,28490
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.12.dist-info/WHEEL,sha256=ZyFSCYkV2BrxH6-HRVRg3R9Fo7MALzer9KiPYqNxSbo,79
102
- dycw_utilities-0.174.12.dist-info/entry_points.txt,sha256=ykGI1ArwOPHqm2g5Cqh3ENdMxEej_a_FcOUov5EM5Oc,155
103
- dycw_utilities-0.174.12.dist-info/METADATA,sha256=Cf8kQqLOY8YDH8nG9Y7WcGf6xJFGxfDIMC1Amw13isc,1710
104
- dycw_utilities-0.174.12.dist-info/RECORD,,
101
+ dycw_utilities-0.174.14.dist-info/WHEEL,sha256=ZyFSCYkV2BrxH6-HRVRg3R9Fo7MALzer9KiPYqNxSbo,79
102
+ dycw_utilities-0.174.14.dist-info/entry_points.txt,sha256=ykGI1ArwOPHqm2g5Cqh3ENdMxEej_a_FcOUov5EM5Oc,155
103
+ dycw_utilities-0.174.14.dist-info/METADATA,sha256=nHCDEWfo7tFV9RLiNWEE9IqfJAvwPWvuv6rn07LnXpw,1710
104
+ dycw_utilities-0.174.14.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.174.12"
3
+ __version__ = "0.174.14"
utilities/permissions.py CHANGED
@@ -25,6 +25,7 @@ from utilities.sentinel import Sentinel, sentinel
25
25
  if TYPE_CHECKING:
26
26
  from utilities.types import PathLike
27
27
 
28
+
28
29
  type PermissionsLike = Permissions | int | str
29
30
 
30
31
 
utilities/subprocess.py CHANGED
@@ -1,11 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import shutil
3
4
  import sys
4
5
  from contextlib import contextmanager
5
6
  from dataclasses import dataclass
6
7
  from io import StringIO
7
8
  from pathlib import Path
8
9
  from shlex import join
10
+ from shutil import copyfile, copytree, move, rmtree
9
11
  from string import Template
10
12
  from subprocess import PIPE, CalledProcessError, Popen
11
13
  from threading import Thread
@@ -15,12 +17,14 @@ from typing import IO, TYPE_CHECKING, Literal, assert_never, overload, override
15
17
  from utilities.errors import ImpossibleCaseError
16
18
  from utilities.iterables import always_iterable
17
19
  from utilities.logging import to_logger
20
+ from utilities.permissions import ensure_perms
18
21
  from utilities.text import strip_and_dedent
19
22
  from utilities.whenever import to_seconds
20
23
 
21
24
  if TYPE_CHECKING:
22
25
  from collections.abc import Iterator
23
26
 
27
+ from utilities.permissions import PermissionsLike
24
28
  from utilities.types import (
25
29
  LoggerLike,
26
30
  MaybeIterable,
@@ -44,6 +48,7 @@ UPDATE_CA_CERTIFICATES: str = "update-ca-certificates"
44
48
 
45
49
 
46
50
  def apt_install_cmd(package: str, /) -> list[str]:
51
+ """Command to use 'apt' to install a package."""
47
52
  return ["apt", "install", "-y", package]
48
53
 
49
54
 
@@ -51,6 +56,7 @@ def apt_install_cmd(package: str, /) -> list[str]:
51
56
 
52
57
 
53
58
  def cat_cmd(path: PathLike, /) -> list[str]:
59
+ """Command to use 'cat' to concatenate and print files."""
54
60
  return ["cat", str(path)]
55
61
 
56
62
 
@@ -58,30 +64,78 @@ def cat_cmd(path: PathLike, /) -> list[str]:
58
64
 
59
65
 
60
66
  def cd_cmd(path: PathLike, /) -> list[str]:
67
+ """Command to use 'cd' to change working directory."""
61
68
  return ["cd", str(path)]
62
69
 
63
70
 
64
71
  ##
65
72
 
66
73
 
67
- def chmod_cmd(path: PathLike, mode: str, /) -> list[str]:
68
- return ["chmod", mode, str(path)]
74
+ def chmod(path: PathLike, perms: PermissionsLike, /, *, sudo: bool = False) -> None:
75
+ """Change file mode."""
76
+ if sudo: # pragma: no cover
77
+ run(*sudo_cmd(*chmod_cmd(path, perms)))
78
+ else:
79
+ Path(path).chmod(int(ensure_perms(perms)))
80
+
81
+
82
+ ##
83
+
84
+
85
+ def chmod_cmd(path: PathLike, perms: PermissionsLike, /) -> list[str]:
86
+ """Command to use 'chmod' to change file mode."""
87
+ return ["chmod", str(ensure_perms(perms)), str(path)]
88
+
89
+
90
+ ##
91
+
92
+
93
+ def chown(
94
+ path: PathLike,
95
+ /,
96
+ *,
97
+ sudo: bool = False,
98
+ user: str | int | None = None,
99
+ group: str | int | None = None,
100
+ ) -> None:
101
+ """Change file owner and/or group."""
102
+ if sudo: # pragma: no cover
103
+ match user, group:
104
+ case None, None:
105
+ ...
106
+ case str() | int() | None, str() | int() | None:
107
+ run(*sudo_cmd(*chown_cmd(path, user=user, group=group)))
108
+ case never:
109
+ assert_never(never)
110
+ else:
111
+ match user, group:
112
+ case None, None:
113
+ ...
114
+ case str() | int(), None:
115
+ shutil.chown(path, user, group)
116
+ case None, str() | int():
117
+ shutil.chown(path, user, group)
118
+ case str() | int(), str() | int():
119
+ shutil.chown(path, user, group)
120
+ case never:
121
+ assert_never(never)
69
122
 
70
123
 
71
124
  ##
72
125
 
73
126
 
74
127
  def chown_cmd(
75
- path: PathLike, /, *, user: str | None = None, group: str | None = None
128
+ path: PathLike, /, *, user: str | int | None = None, group: str | int | None = None
76
129
  ) -> list[str]:
130
+ """Command to use 'chown' to change file owner and/or group."""
77
131
  match user, group:
78
132
  case None, None:
79
133
  raise ChownCmdError
80
- case str(), None:
134
+ case str() | int(), None:
81
135
  ownership = "user"
82
- case None, str():
136
+ case None, str() | int():
83
137
  ownership = f":{group}"
84
- case str(), str():
138
+ case str() | int(), str() | int():
85
139
  ownership = f"{user}:{group}"
86
140
  case never:
87
141
  assert_never(never)
@@ -98,7 +152,46 @@ class ChownCmdError(Exception):
98
152
  ##
99
153
 
100
154
 
155
+ def cp(
156
+ src: PathLike,
157
+ dest: PathLike,
158
+ /,
159
+ *,
160
+ sudo: bool = False,
161
+ perms: PermissionsLike | None = None,
162
+ owner: str | int | None = None,
163
+ group: str | int | None = None,
164
+ ) -> None:
165
+ """Copy a file/directory."""
166
+ mkdir(dest, sudo=sudo, parent=True)
167
+ if sudo:
168
+ run(*sudo_cmd(*cp_cmd(src, dest)))
169
+ else:
170
+ src, dest = map(Path, [src, dest])
171
+ if src.is_file():
172
+ _ = copyfile(src, dest)
173
+ elif src.is_dir():
174
+ _ = copytree(src, dest, dirs_exist_ok=True)
175
+ else:
176
+ raise CpError(src=src, dest=dest)
177
+ if perms is not None:
178
+ chmod(dest, perms, sudo=sudo)
179
+ if (owner is not None) or (group is not None):
180
+ chown(dest, sudo=sudo, user=owner, group=group)
181
+
182
+
183
+ @dataclass(kw_only=True, slots=True)
184
+ class CpError(Exception):
185
+ src: Path
186
+ dest: Path
187
+
188
+ @override
189
+ def __str__(self) -> str:
190
+ return f"Unable to copy {str(self.src)!r} to {str(self.dest)!r}; source does not exist"
191
+
192
+
101
193
  def cp_cmd(src: PathLike, dest: PathLike, /) -> list[str]:
194
+ """Command to use 'cp' to copy a file/directory."""
102
195
  return ["cp", "-r", str(src), str(dest)]
103
196
 
104
197
 
@@ -106,6 +199,7 @@ def cp_cmd(src: PathLike, dest: PathLike, /) -> list[str]:
106
199
 
107
200
 
108
201
  def echo_cmd(text: str, /) -> list[str]:
202
+ """Command to use 'echo' to write arguments to the standard output."""
109
203
  return ["echo", text]
110
204
 
111
205
 
@@ -115,6 +209,7 @@ def echo_cmd(text: str, /) -> list[str]:
115
209
  def expand_path(
116
210
  path: PathLike, /, *, subs: StrMapping | None = None, sudo: bool = False
117
211
  ) -> Path:
212
+ """Expand a path using `subprocess`."""
118
213
  if subs is not None:
119
214
  path = Template(str(path)).substitute(**subs)
120
215
  if sudo: # pragma: no cover
@@ -126,6 +221,7 @@ def expand_path(
126
221
 
127
222
 
128
223
  def git_clone_cmd(url: str, path: PathLike, /) -> list[str]:
224
+ """Command to use 'git clone' to clone a repository."""
129
225
  return ["git", "clone", "--recurse-submodules", url, str(path)]
130
226
 
131
227
 
@@ -133,6 +229,7 @@ def git_clone_cmd(url: str, path: PathLike, /) -> list[str]:
133
229
 
134
230
 
135
231
  def git_hard_reset_cmd(*, branch: str | None = None) -> list[str]:
232
+ """Command to use 'git hard-reset' to hard reset a repository."""
136
233
  branch_use = "master" if branch is None else branch
137
234
  return ["git", "hard-reset", branch_use]
138
235
 
@@ -141,6 +238,7 @@ def git_hard_reset_cmd(*, branch: str | None = None) -> list[str]:
141
238
 
142
239
 
143
240
  def maybe_parent(path: PathLike, /, *, parent: bool = False) -> Path:
241
+ """Get the parent of a path, if required."""
144
242
  path = Path(path)
145
243
  return path.parent if parent else path
146
244
 
@@ -148,15 +246,8 @@ def maybe_parent(path: PathLike, /, *, parent: bool = False) -> Path:
148
246
  ##
149
247
 
150
248
 
151
- def maybe_sudo_cmd(cmd: str, /, *args: str, sudo: bool = False) -> list[str]:
152
- parts: list[str] = [cmd, *args]
153
- return sudo_cmd(*parts) if sudo else parts
154
-
155
-
156
- ##
157
-
158
-
159
249
  def mkdir(path: PathLike, /, *, sudo: bool = False, parent: bool = False) -> None:
250
+ """Make a directory."""
160
251
  if sudo: # pragma: no cover
161
252
  run(*sudo_cmd(*mkdir_cmd(path, parent=parent)))
162
253
  else:
@@ -167,20 +258,71 @@ def mkdir(path: PathLike, /, *, sudo: bool = False, parent: bool = False) -> Non
167
258
 
168
259
 
169
260
  def mkdir_cmd(path: PathLike, /, *, parent: bool = False) -> list[str]:
261
+ """Command to use 'mv' to make a directory."""
170
262
  return ["mkdir", "-p", str(maybe_parent(path, parent=parent))]
171
263
 
172
264
 
173
265
  ##
174
266
 
175
267
 
268
+ def mv(
269
+ src: PathLike,
270
+ dest: PathLike,
271
+ /,
272
+ *,
273
+ sudo: bool = False,
274
+ perms: PermissionsLike | None = None,
275
+ owner: str | int | None = None,
276
+ group: str | int | None = None,
277
+ ) -> None:
278
+ """Move a file/directory."""
279
+ mkdir(dest, sudo=sudo, parent=True)
280
+ if sudo:
281
+ run(*sudo_cmd(*cp_cmd(src, dest)))
282
+ else:
283
+ src, dest = map(Path, [src, dest])
284
+ if src.exists():
285
+ _ = move(src, dest)
286
+ else:
287
+ raise MvFileError(src=src, dest=dest)
288
+ if perms is not None:
289
+ chmod(dest, perms, sudo=sudo)
290
+ if (owner is not None) or (group is not None):
291
+ chown(dest, sudo=sudo, user=owner, group=group)
292
+
293
+
294
+ @dataclass(kw_only=True, slots=True)
295
+ class MvFileError(Exception):
296
+ src: Path
297
+ dest: Path
298
+
299
+ @override
300
+ def __str__(self) -> str:
301
+ return f"Unable to move {str(self.src)!r} to {str(self.dest)!r}; source does not exist"
302
+
303
+
176
304
  def mv_cmd(src: PathLike, dest: PathLike, /) -> list[str]:
305
+ """Command to use 'mv' to move a file/directory."""
177
306
  return ["mv", str(src), str(dest)]
178
307
 
179
308
 
180
309
  ##
181
310
 
182
311
 
312
+ def rm(path: PathLike, /, *, sudo: bool = False) -> None:
313
+ """Remove a file/directory."""
314
+ if sudo: # pragma: no cover
315
+ run(*sudo_cmd(*rm_cmd(path)))
316
+ else:
317
+ path = Path(path)
318
+ if path.is_file():
319
+ path.unlink(missing_ok=True)
320
+ elif path.is_dir():
321
+ rmtree(path, ignore_errors=True)
322
+
323
+
183
324
  def rm_cmd(path: PathLike, /) -> list[str]:
325
+ """Command to use 'rm' to remove a file/directory."""
184
326
  return ["rm", "-rf", str(path)]
185
327
 
186
328
 
@@ -206,6 +348,7 @@ def rsync(
206
348
  exclude: MaybeIterable[str] | None = None,
207
349
  chmod: str | None = None,
208
350
  ) -> None:
351
+ """Remote & local file copying."""
209
352
  mkdir_args = maybe_sudo_cmd(*mkdir_cmd(dest, parent=True), sudo=sudo) # skipif-ci
210
353
  ssh( # skipif-ci
211
354
  user,
@@ -250,9 +393,6 @@ def rsync(
250
393
  )
251
394
 
252
395
 
253
- ##
254
-
255
-
256
396
  def rsync_cmd(
257
397
  src_or_srcs: MaybeIterable[PathLike],
258
398
  user: str,
@@ -270,6 +410,7 @@ def rsync_cmd(
270
410
  sudo: bool = False,
271
411
  parent: bool = False,
272
412
  ) -> list[str]:
413
+ """Command to use 'rsync' to do remote & local file copying."""
273
414
  args: list[str] = ["rsync"]
274
415
  if archive:
275
416
  args.append("--archive")
@@ -427,6 +568,7 @@ def run(
427
568
  retry: Retry | None = None,
428
569
  logger: LoggerLike | None = None,
429
570
  ) -> str | None:
571
+ """Run a command in a subprocess."""
430
572
  args: list[str] = []
431
573
  if user is not None: # pragma: no cover
432
574
  args.extend(["su", "-", str(user)])
@@ -567,6 +709,7 @@ def _run_write_to_streams(text: str, /, *outputs: IO[str]) -> None:
567
709
 
568
710
 
569
711
  def set_hostname_cmd(hostname: str, /) -> list[str]:
712
+ """Command to set the system hostname."""
570
713
  return ["hostnamectl", "set-hostname", hostname]
571
714
 
572
715
 
@@ -686,6 +829,7 @@ def ssh(
686
829
  retry: Retry | None = None,
687
830
  logger: LoggerLike | None = None,
688
831
  ) -> str | None:
832
+ """Execute a command on a remote machine."""
689
833
  cmd_and_args = ssh_cmd( # skipif-ci
690
834
  user,
691
835
  hostname,
@@ -708,9 +852,6 @@ def ssh(
708
852
  )
709
853
 
710
854
 
711
- ##
712
-
713
-
714
855
  def ssh_cmd(
715
856
  user: str,
716
857
  hostname: str,
@@ -720,6 +861,7 @@ def ssh_cmd(
720
861
  host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
721
862
  strict_host_key_checking: bool = True,
722
863
  ) -> list[str]:
864
+ """Command to use 'ssh' to execute a command on a remote machine."""
723
865
  args: list[str] = ssh_opts_cmd(
724
866
  batch_mode=batch_mode,
725
867
  host_key_algorithms=host_key_algorithms,
@@ -728,15 +870,13 @@ def ssh_cmd(
728
870
  return [*args, f"{user}@{hostname}", *cmd_and_cmds_or_args]
729
871
 
730
872
 
731
- ##
732
-
733
-
734
873
  def ssh_opts_cmd(
735
874
  *,
736
875
  batch_mode: bool = True,
737
876
  host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
738
877
  strict_host_key_checking: bool = True,
739
878
  ) -> list[str]:
879
+ """Command to use prepare 'ssh' to execute a command on a remote machine."""
740
880
  args: list[str] = ["ssh"]
741
881
  if batch_mode:
742
882
  args.extend(["-o", "BatchMode=yes"])
@@ -750,6 +890,7 @@ def ssh_opts_cmd(
750
890
 
751
891
 
752
892
  def ssh_keygen_cmd(hostname: str, /) -> list[str]:
893
+ """Command to use 'ssh-keygen' to add a known host."""
753
894
  return ["ssh-keygen", "-f", "~/.ssh/known_hosts", "-R", hostname]
754
895
 
755
896
 
@@ -757,21 +898,52 @@ def ssh_keygen_cmd(hostname: str, /) -> list[str]:
757
898
 
758
899
 
759
900
  def sudo_cmd(cmd: str, /, *args: str) -> list[str]:
901
+ """Command to use 'sudo' to execute a command as another user."""
760
902
  return ["sudo", cmd, *args]
761
903
 
762
904
 
905
+ def maybe_sudo_cmd(cmd: str, /, *args: str, sudo: bool = False) -> list[str]:
906
+ """Command to use 'sudo' to execute a command as another user, if required."""
907
+ parts: list[str] = [cmd, *args]
908
+ return sudo_cmd(*parts) if sudo else parts
909
+
910
+
763
911
  ##
764
912
 
765
913
 
766
914
  def sudo_nopasswd_cmd(user: str, /) -> str:
915
+ """Command to allow a user to use password-free `sudo`."""
767
916
  return f"{user} ALL=(ALL) NOPASSWD: ALL"
768
917
 
769
918
 
770
919
  ##
771
920
 
772
921
 
773
- def symlink_cmd(src: PathLike, dest: PathLike, /) -> list[str]:
774
- return ["ln", "-s", str(src), str(dest)]
922
+ def symlink(targret: PathLike, link: PathLike, /, *, sudo: bool = False) -> None:
923
+ """Make a symbolic link."""
924
+ rm(link, sudo=sudo)
925
+ mkdir(link, sudo=sudo, parent=True)
926
+ if sudo: # pragma: no cover
927
+ run(*sudo_cmd(*symlink_cmd(targret, link)))
928
+ else:
929
+ targret, link = map(Path, [targret, link])
930
+ link.symlink_to(targret)
931
+
932
+
933
+ def symlink_cmd(target: PathLike, link: PathLike, /) -> list[str]:
934
+ """Command to use 'symlink' to make a symbolic link."""
935
+ return ["ln", "-s", str(target), str(link)]
936
+
937
+
938
+ ##
939
+
940
+
941
+ def tee_cmd(*, append: bool = False) -> list[str]:
942
+ """Command to use 'tee' to duplicate standard input."""
943
+ args: list[str] = ["tee"]
944
+ if append:
945
+ args.append("-a")
946
+ return args
775
947
 
776
948
 
777
949
  ##
@@ -833,10 +1005,15 @@ __all__ = [
833
1005
  "RESTART_SSHD",
834
1006
  "UPDATE_CA_CERTIFICATES",
835
1007
  "ChownCmdError",
1008
+ "CpError",
1009
+ "MvFileError",
836
1010
  "apt_install_cmd",
837
1011
  "cd_cmd",
1012
+ "chmod",
838
1013
  "chmod_cmd",
1014
+ "chown",
839
1015
  "chown_cmd",
1016
+ "cp",
840
1017
  "cp_cmd",
841
1018
  "echo_cmd",
842
1019
  "expand_path",
@@ -846,7 +1023,9 @@ __all__ = [
846
1023
  "maybe_sudo_cmd",
847
1024
  "mkdir",
848
1025
  "mkdir_cmd",
1026
+ "mv",
849
1027
  "mv_cmd",
1028
+ "rm",
850
1029
  "rm_cmd",
851
1030
  "rsync",
852
1031
  "rsync_cmd",
@@ -857,7 +1036,9 @@ __all__ = [
857
1036
  "ssh_opts_cmd",
858
1037
  "sudo_cmd",
859
1038
  "sudo_nopasswd_cmd",
1039
+ "symlink",
860
1040
  "symlink_cmd",
1041
+ "tee_cmd",
861
1042
  "touch_cmd",
862
1043
  "uv_run_cmd",
863
1044
  "yield_ssh_temp_dir",