dycw-utilities 0.174.13__py3-none-any.whl → 0.174.15__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.13
3
+ Version: 0.174.15
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=MOUKZkLpGGyjxpjT7E-ok6qU-Ad2cKOMOyAdEHZoEGc,61
1
+ utilities/__init__.py,sha256=e_rDHgJr89spnVGWmvxwmI_TbjZtP1QsasPjzzI3FTs,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
@@ -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=2FvELtLUkr8H2rMRMRr5Ah5LA-p3pZy9PjMeA_ZTw_U,26362
84
+ utilities/subprocess.py,sha256=gsRaeBzUSINUBuMZTSz7F7rMdAmhUFCEhMQOEpS5Ro4,28897
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.13.dist-info/WHEEL,sha256=ZyFSCYkV2BrxH6-HRVRg3R9Fo7MALzer9KiPYqNxSbo,79
102
- dycw_utilities-0.174.13.dist-info/entry_points.txt,sha256=ykGI1ArwOPHqm2g5Cqh3ENdMxEej_a_FcOUov5EM5Oc,155
103
- dycw_utilities-0.174.13.dist-info/METADATA,sha256=BuUPpTMsSoLgdvmXiA-oOD_jsg6LYqlWsW_EFvJSTlM,1710
104
- dycw_utilities-0.174.13.dist-info/RECORD,,
101
+ dycw_utilities-0.174.15.dist-info/WHEEL,sha256=ZyFSCYkV2BrxH6-HRVRg3R9Fo7MALzer9KiPYqNxSbo,79
102
+ dycw_utilities-0.174.15.dist-info/entry_points.txt,sha256=ykGI1ArwOPHqm2g5Cqh3ENdMxEej_a_FcOUov5EM5Oc,155
103
+ dycw_utilities-0.174.15.dist-info/METADATA,sha256=-vZ1ta-ZjRzQ0WaZ7uRspE0DJQCqajno8r1UEp8gF2Y,1710
104
+ dycw_utilities-0.174.15.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.174.13"
3
+ __version__ = "0.174.15"
utilities/subprocess.py CHANGED
@@ -48,6 +48,7 @@ UPDATE_CA_CERTIFICATES: str = "update-ca-certificates"
48
48
 
49
49
 
50
50
  def apt_install_cmd(package: str, /) -> list[str]:
51
+ """Command to use 'apt' to install a package."""
51
52
  return ["apt", "install", "-y", package]
52
53
 
53
54
 
@@ -55,6 +56,7 @@ def apt_install_cmd(package: str, /) -> list[str]:
55
56
 
56
57
 
57
58
  def cat_cmd(path: PathLike, /) -> list[str]:
59
+ """Command to use 'cat' to concatenate and print files."""
58
60
  return ["cat", str(path)]
59
61
 
60
62
 
@@ -62,6 +64,7 @@ def cat_cmd(path: PathLike, /) -> list[str]:
62
64
 
63
65
 
64
66
  def cd_cmd(path: PathLike, /) -> list[str]:
67
+ """Command to use 'cd' to change working directory."""
65
68
  return ["cd", str(path)]
66
69
 
67
70
 
@@ -69,6 +72,7 @@ def cd_cmd(path: PathLike, /) -> list[str]:
69
72
 
70
73
 
71
74
  def chmod(path: PathLike, perms: PermissionsLike, /, *, sudo: bool = False) -> None:
75
+ """Change file mode."""
72
76
  if sudo: # pragma: no cover
73
77
  run(*sudo_cmd(*chmod_cmd(path, perms)))
74
78
  else:
@@ -79,6 +83,7 @@ def chmod(path: PathLike, perms: PermissionsLike, /, *, sudo: bool = False) -> N
79
83
 
80
84
 
81
85
  def chmod_cmd(path: PathLike, perms: PermissionsLike, /) -> list[str]:
86
+ """Command to use 'chmod' to change file mode."""
82
87
  return ["chmod", str(ensure_perms(perms)), str(path)]
83
88
 
84
89
 
@@ -93,6 +98,7 @@ def chown(
93
98
  user: str | int | None = None,
94
99
  group: str | int | None = None,
95
100
  ) -> None:
101
+ """Change file owner and/or group."""
96
102
  if sudo: # pragma: no cover
97
103
  match user, group:
98
104
  case None, None:
@@ -121,6 +127,7 @@ def chown(
121
127
  def chown_cmd(
122
128
  path: PathLike, /, *, user: str | int | None = None, group: str | int | None = None
123
129
  ) -> list[str]:
130
+ """Command to use 'chown' to change file owner and/or group."""
124
131
  match user, group:
125
132
  case None, None:
126
133
  raise ChownCmdError
@@ -145,7 +152,7 @@ class ChownCmdError(Exception):
145
152
  ##
146
153
 
147
154
 
148
- def copy_file(
155
+ def cp(
149
156
  src: PathLike,
150
157
  dest: PathLike,
151
158
  /,
@@ -155,7 +162,7 @@ def copy_file(
155
162
  owner: str | int | None = None,
156
163
  group: str | int | None = None,
157
164
  ) -> None:
158
- """Copy a file/directory from one location to another."""
165
+ """Copy a file/directory."""
159
166
  mkdir(dest, sudo=sudo, parent=True)
160
167
  if sudo:
161
168
  run(*sudo_cmd(*cp_cmd(src, dest)))
@@ -166,7 +173,7 @@ def copy_file(
166
173
  elif src.is_dir():
167
174
  _ = copytree(src, dest, dirs_exist_ok=True)
168
175
  else:
169
- raise CopyFileError(src=src, dest=dest)
176
+ raise CpError(src=src, dest=dest)
170
177
  if perms is not None:
171
178
  chmod(dest, perms, sudo=sudo)
172
179
  if (owner is not None) or (group is not None):
@@ -174,7 +181,7 @@ def copy_file(
174
181
 
175
182
 
176
183
  @dataclass(kw_only=True, slots=True)
177
- class CopyFileError(Exception):
184
+ class CpError(Exception):
178
185
  src: Path
179
186
  dest: Path
180
187
 
@@ -183,10 +190,8 @@ class CopyFileError(Exception):
183
190
  return f"Unable to copy {str(self.src)!r} to {str(self.dest)!r}; source does not exist"
184
191
 
185
192
 
186
- ##
187
-
188
-
189
193
  def cp_cmd(src: PathLike, dest: PathLike, /) -> list[str]:
194
+ """Command to use 'cp' to copy a file/directory."""
190
195
  return ["cp", "-r", str(src), str(dest)]
191
196
 
192
197
 
@@ -194,6 +199,7 @@ def cp_cmd(src: PathLike, dest: PathLike, /) -> list[str]:
194
199
 
195
200
 
196
201
  def echo_cmd(text: str, /) -> list[str]:
202
+ """Command to use 'echo' to write arguments to the standard output."""
197
203
  return ["echo", text]
198
204
 
199
205
 
@@ -203,6 +209,7 @@ def echo_cmd(text: str, /) -> list[str]:
203
209
  def expand_path(
204
210
  path: PathLike, /, *, subs: StrMapping | None = None, sudo: bool = False
205
211
  ) -> Path:
212
+ """Expand a path using `subprocess`."""
206
213
  if subs is not None:
207
214
  path = Template(str(path)).substitute(**subs)
208
215
  if sudo: # pragma: no cover
@@ -214,6 +221,7 @@ def expand_path(
214
221
 
215
222
 
216
223
  def git_clone_cmd(url: str, path: PathLike, /) -> list[str]:
224
+ """Command to use 'git clone' to clone a repository."""
217
225
  return ["git", "clone", "--recurse-submodules", url, str(path)]
218
226
 
219
227
 
@@ -221,6 +229,7 @@ def git_clone_cmd(url: str, path: PathLike, /) -> list[str]:
221
229
 
222
230
 
223
231
  def git_hard_reset_cmd(*, branch: str | None = None) -> list[str]:
232
+ """Command to use 'git hard-reset' to hard reset a repository."""
224
233
  branch_use = "master" if branch is None else branch
225
234
  return ["git", "hard-reset", branch_use]
226
235
 
@@ -229,6 +238,7 @@ def git_hard_reset_cmd(*, branch: str | None = None) -> list[str]:
229
238
 
230
239
 
231
240
  def maybe_parent(path: PathLike, /, *, parent: bool = False) -> Path:
241
+ """Get the parent of a path, if required."""
232
242
  path = Path(path)
233
243
  return path.parent if parent else path
234
244
 
@@ -236,15 +246,8 @@ def maybe_parent(path: PathLike, /, *, parent: bool = False) -> Path:
236
246
  ##
237
247
 
238
248
 
239
- def maybe_sudo_cmd(cmd: str, /, *args: str, sudo: bool = False) -> list[str]:
240
- parts: list[str] = [cmd, *args]
241
- return sudo_cmd(*parts) if sudo else parts
242
-
243
-
244
- ##
245
-
246
-
247
249
  def mkdir(path: PathLike, /, *, sudo: bool = False, parent: bool = False) -> None:
250
+ """Make a directory."""
248
251
  if sudo: # pragma: no cover
249
252
  run(*sudo_cmd(*mkdir_cmd(path, parent=parent)))
250
253
  else:
@@ -255,13 +258,14 @@ def mkdir(path: PathLike, /, *, sudo: bool = False, parent: bool = False) -> Non
255
258
 
256
259
 
257
260
  def mkdir_cmd(path: PathLike, /, *, parent: bool = False) -> list[str]:
261
+ """Command to use 'mv' to make a directory."""
258
262
  return ["mkdir", "-p", str(maybe_parent(path, parent=parent))]
259
263
 
260
264
 
261
265
  ##
262
266
 
263
267
 
264
- def move_file(
268
+ def mv(
265
269
  src: PathLike,
266
270
  dest: PathLike,
267
271
  /,
@@ -271,7 +275,7 @@ def move_file(
271
275
  owner: str | int | None = None,
272
276
  group: str | int | None = None,
273
277
  ) -> None:
274
- """Move a file/directory from one location to another."""
278
+ """Move a file/directory."""
275
279
  mkdir(dest, sudo=sudo, parent=True)
276
280
  if sudo:
277
281
  run(*sudo_cmd(*cp_cmd(src, dest)))
@@ -280,7 +284,7 @@ def move_file(
280
284
  if src.exists():
281
285
  _ = move(src, dest)
282
286
  else:
283
- raise MoveFileError(src=src, dest=dest)
287
+ raise MvFileError(src=src, dest=dest)
284
288
  if perms is not None:
285
289
  chmod(dest, perms, sudo=sudo)
286
290
  if (owner is not None) or (group is not None):
@@ -288,7 +292,7 @@ def move_file(
288
292
 
289
293
 
290
294
  @dataclass(kw_only=True, slots=True)
291
- class MoveFileError(Exception):
295
+ class MvFileError(Exception):
292
296
  src: Path
293
297
  dest: Path
294
298
 
@@ -297,17 +301,16 @@ class MoveFileError(Exception):
297
301
  return f"Unable to move {str(self.src)!r} to {str(self.dest)!r}; source does not exist"
298
302
 
299
303
 
300
- ##
301
-
302
-
303
304
  def mv_cmd(src: PathLike, dest: PathLike, /) -> list[str]:
305
+ """Command to use 'mv' to move a file/directory."""
304
306
  return ["mv", str(src), str(dest)]
305
307
 
306
308
 
307
309
  ##
308
310
 
309
311
 
310
- def remove(path: PathLike, /, *, sudo: bool = False) -> None:
312
+ def rm(path: PathLike, /, *, sudo: bool = False) -> None:
313
+ """Remove a file/directory."""
311
314
  if sudo: # pragma: no cover
312
315
  run(*sudo_cmd(*rm_cmd(path)))
313
316
  else:
@@ -318,10 +321,8 @@ def remove(path: PathLike, /, *, sudo: bool = False) -> None:
318
321
  rmtree(path, ignore_errors=True)
319
322
 
320
323
 
321
- ##
322
-
323
-
324
324
  def rm_cmd(path: PathLike, /) -> list[str]:
325
+ """Command to use 'rm' to remove a file/directory."""
325
326
  return ["rm", "-rf", str(path)]
326
327
 
327
328
 
@@ -347,6 +348,7 @@ def rsync(
347
348
  exclude: MaybeIterable[str] | None = None,
348
349
  chmod: str | None = None,
349
350
  ) -> None:
351
+ """Remote & local file copying."""
350
352
  mkdir_args = maybe_sudo_cmd(*mkdir_cmd(dest, parent=True), sudo=sudo) # skipif-ci
351
353
  ssh( # skipif-ci
352
354
  user,
@@ -391,9 +393,6 @@ def rsync(
391
393
  )
392
394
 
393
395
 
394
- ##
395
-
396
-
397
396
  def rsync_cmd(
398
397
  src_or_srcs: MaybeIterable[PathLike],
399
398
  user: str,
@@ -411,6 +410,7 @@ def rsync_cmd(
411
410
  sudo: bool = False,
412
411
  parent: bool = False,
413
412
  ) -> list[str]:
413
+ """Command to use 'rsync' to do remote & local file copying."""
414
414
  args: list[str] = ["rsync"]
415
415
  if archive:
416
416
  args.append("--archive")
@@ -568,6 +568,7 @@ def run(
568
568
  retry: Retry | None = None,
569
569
  logger: LoggerLike | None = None,
570
570
  ) -> str | None:
571
+ """Run a command in a subprocess."""
571
572
  args: list[str] = []
572
573
  if user is not None: # pragma: no cover
573
574
  args.extend(["su", "-", str(user)])
@@ -708,6 +709,7 @@ def _run_write_to_streams(text: str, /, *outputs: IO[str]) -> None:
708
709
 
709
710
 
710
711
  def set_hostname_cmd(hostname: str, /) -> list[str]:
712
+ """Command to set the system hostname."""
711
713
  return ["hostnamectl", "set-hostname", hostname]
712
714
 
713
715
 
@@ -827,6 +829,7 @@ def ssh(
827
829
  retry: Retry | None = None,
828
830
  logger: LoggerLike | None = None,
829
831
  ) -> str | None:
832
+ """Execute a command on a remote machine."""
830
833
  cmd_and_args = ssh_cmd( # skipif-ci
831
834
  user,
832
835
  hostname,
@@ -849,9 +852,6 @@ def ssh(
849
852
  )
850
853
 
851
854
 
852
- ##
853
-
854
-
855
855
  def ssh_cmd(
856
856
  user: str,
857
857
  hostname: str,
@@ -861,6 +861,7 @@ def ssh_cmd(
861
861
  host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
862
862
  strict_host_key_checking: bool = True,
863
863
  ) -> list[str]:
864
+ """Command to use 'ssh' to execute a command on a remote machine."""
864
865
  args: list[str] = ssh_opts_cmd(
865
866
  batch_mode=batch_mode,
866
867
  host_key_algorithms=host_key_algorithms,
@@ -869,15 +870,13 @@ def ssh_cmd(
869
870
  return [*args, f"{user}@{hostname}", *cmd_and_cmds_or_args]
870
871
 
871
872
 
872
- ##
873
-
874
-
875
873
  def ssh_opts_cmd(
876
874
  *,
877
875
  batch_mode: bool = True,
878
876
  host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
879
877
  strict_host_key_checking: bool = True,
880
878
  ) -> list[str]:
879
+ """Command to use prepare 'ssh' to execute a command on a remote machine."""
881
880
  args: list[str] = ["ssh"]
882
881
  if batch_mode:
883
882
  args.extend(["-o", "BatchMode=yes"])
@@ -891,6 +890,7 @@ def ssh_opts_cmd(
891
890
 
892
891
 
893
892
  def ssh_keygen_cmd(hostname: str, /) -> list[str]:
893
+ """Command to use 'ssh-keygen' to add a known host."""
894
894
  return ["ssh-keygen", "-f", "~/.ssh/known_hosts", "-R", hostname]
895
895
 
896
896
 
@@ -898,21 +898,64 @@ def ssh_keygen_cmd(hostname: str, /) -> list[str]:
898
898
 
899
899
 
900
900
  def sudo_cmd(cmd: str, /, *args: str) -> list[str]:
901
+ """Command to use 'sudo' to execute a command as another user."""
901
902
  return ["sudo", cmd, *args]
902
903
 
903
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
+
904
911
  ##
905
912
 
906
913
 
907
914
  def sudo_nopasswd_cmd(user: str, /) -> str:
915
+ """Command to allow a user to use password-free `sudo`."""
908
916
  return f"{user} ALL=(ALL) NOPASSWD: ALL"
909
917
 
910
918
 
911
919
  ##
912
920
 
913
921
 
914
- def symlink_cmd(src: PathLike, dest: PathLike, /) -> list[str]:
915
- 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(
942
+ path: PathLike, text: str, /, *, sudo: bool = False, append: bool = False
943
+ ) -> None:
944
+ """Use 'tee' to duplicate standard input."""
945
+ if sudo: # pragma: no cover
946
+ run(*sudo_cmd(*tee_cmd(path, append=append)), input=text)
947
+ else:
948
+ path = Path(path)
949
+ with path.open(mode="a" if append else "w") as fh:
950
+ _ = fh.write(text)
951
+
952
+
953
+ def tee_cmd(path: PathLike, /, *, append: bool = False) -> list[str]:
954
+ """Command to use 'tee' to duplicate standard input."""
955
+ args: list[str] = ["tee"]
956
+ if append:
957
+ args.append("-a")
958
+ return [*args, str(path)]
916
959
 
917
960
 
918
961
  ##
@@ -974,15 +1017,15 @@ __all__ = [
974
1017
  "RESTART_SSHD",
975
1018
  "UPDATE_CA_CERTIFICATES",
976
1019
  "ChownCmdError",
977
- "CopyFileError",
978
- "MoveFileError",
1020
+ "CpError",
1021
+ "MvFileError",
979
1022
  "apt_install_cmd",
980
1023
  "cd_cmd",
981
1024
  "chmod",
982
1025
  "chmod_cmd",
983
1026
  "chown",
984
1027
  "chown_cmd",
985
- "copy_file",
1028
+ "cp",
986
1029
  "cp_cmd",
987
1030
  "echo_cmd",
988
1031
  "expand_path",
@@ -992,9 +1035,9 @@ __all__ = [
992
1035
  "maybe_sudo_cmd",
993
1036
  "mkdir",
994
1037
  "mkdir_cmd",
995
- "move_file",
1038
+ "mv",
996
1039
  "mv_cmd",
997
- "remove",
1040
+ "rm",
998
1041
  "rm_cmd",
999
1042
  "rsync",
1000
1043
  "rsync_cmd",
@@ -1005,7 +1048,9 @@ __all__ = [
1005
1048
  "ssh_opts_cmd",
1006
1049
  "sudo_cmd",
1007
1050
  "sudo_nopasswd_cmd",
1051
+ "symlink",
1008
1052
  "symlink_cmd",
1053
+ "tee_cmd",
1009
1054
  "touch_cmd",
1010
1055
  "uv_run_cmd",
1011
1056
  "yield_ssh_temp_dir",