dycw-utilities 0.175.28__py3-none-any.whl → 0.175.30__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.175.28
3
+ Version: 0.175.30
4
4
  Summary: Miscellaneous Python utilities
5
5
  Author: Derek Wan
6
6
  Author-email: Derek Wan <d.wan@icloud.com>
@@ -1,4 +1,4 @@
1
- utilities/__init__.py,sha256=k4kaN7LN6ZF6oNv47GuGm1AaLmVrNa8LJBL_8oBnlMY,61
1
+ utilities/__init__.py,sha256=MtsJJrbSJrW256hO7llrT3qk9914hd3MBaFYuonK4KU,61
2
2
  utilities/altair.py,sha256=TLfRFbG9HwG7SLXoJ-v0r-t49ZaGgTQZD82cpjVi4vs,9085
3
3
  utilities/asyncio.py,sha256=aJySVxBY0gqsIYnoNmH7-1r8djKuf4vSsU69VCD08t8,16772
4
4
  utilities/atomicwrites.py,sha256=tPo6r-Rypd9u99u66B9z86YBPpnLrlHtwox_8Z7T34Y,5790
@@ -11,7 +11,7 @@ utilities/contextvars.py,sha256=J8OhC7jqozAGYOCe2KUWysbPXNGe5JYz3HfaY_mIs08,883
11
11
  utilities/cryptography.py,sha256=5PFrzsNUGHay91dFgYnDKwYprXxahrBqztmUqViRzBk,956
12
12
  utilities/cvxpy.py,sha256=Rv1-fD-XYerosCavRF8Pohop2DBkU3AlFaGTfD8AEAA,13776
13
13
  utilities/dataclasses.py,sha256=xbU3QN1GFy7RC6hIJRZIeUZm7YRlodrgEWmahWG6k2g,32465
14
- utilities/docker.py,sha256=N__PKd3cnSRsXNEMHMLdLneLdyzfbr2ESkElcwrovvQ,7940
14
+ utilities/docker.py,sha256=nzeuR5-OQKL2yhtY-SSqLS2ftl-iev0cUrshwEMwB2k,9808
15
15
  utilities/enum.py,sha256=5l6pwZD1cjSlVW4ss-zBPspWvrbrYrdtJWcg6f5_J5w,5781
16
16
  utilities/errors.py,sha256=mFlDGSM0LI1jZ1pbqwLAH3ttLZ2JVIxyZLojw8tGVZU,1479
17
17
  utilities/fastapi.py,sha256=TqyKvBjiMS594sXPjrz-KRTLMb3l3D3rZ1zAYV7GfOk,1454
@@ -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=f-bVgM-8dGYuqmfegp7y9JtU1WP3FX4AaU2munpeD-U,52304
83
+ utilities/subprocess.py,sha256=ei0OD73S0WX7ZqHCQp9UB3hY9OWbAUVIrLoz9niIfQQ,53031
84
84
  utilities/tempfile.py,sha256=a3_M1QyxGZql_VcGkBOQBeWbbkItjgkfIpVyzU1UAic,3843
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.175.28.dist-info/WHEEL,sha256=RRVLqVugUmFOqBedBFAmA4bsgFcROUBiSUKlERi0Hcg,79
101
- dycw_utilities-0.175.28.dist-info/entry_points.txt,sha256=cOGtKeJI0KXLSV7MJ8Dhc2G8jPgDcBDm53MVNJU4ycI,136
102
- dycw_utilities-0.175.28.dist-info/METADATA,sha256=k8PYEVjHbdSwrVP6nAsY8ptu8MEenozvTPprPs3Pgwk,1443
103
- dycw_utilities-0.175.28.dist-info/RECORD,,
100
+ dycw_utilities-0.175.30.dist-info/WHEEL,sha256=RRVLqVugUmFOqBedBFAmA4bsgFcROUBiSUKlERi0Hcg,79
101
+ dycw_utilities-0.175.30.dist-info/entry_points.txt,sha256=cOGtKeJI0KXLSV7MJ8Dhc2G8jPgDcBDm53MVNJU4ycI,136
102
+ dycw_utilities-0.175.30.dist-info/METADATA,sha256=X2Hiaa6YhG9ZkFu1AfxlEDbXKDKYepqcVZylTh5hVds,1443
103
+ dycw_utilities-0.175.30.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.175.28"
3
+ __version__ = "0.175.30"
utilities/docker.py CHANGED
@@ -5,6 +5,7 @@ from pathlib import Path
5
5
  from typing import TYPE_CHECKING, Literal, overload
6
6
 
7
7
  from utilities.errors import ImpossibleCaseError
8
+ from utilities.iterables import always_iterable
8
9
  from utilities.logging import to_logger
9
10
  from utilities.subprocess import (
10
11
  MKTEMP_DIR_CMD,
@@ -18,7 +19,60 @@ from utilities.subprocess import (
18
19
  if TYPE_CHECKING:
19
20
  from collections.abc import Iterator
20
21
 
21
- from utilities.types import LoggerLike, PathLike, Retry, StrStrMapping
22
+ from utilities.types import (
23
+ LoggerLike,
24
+ MaybeIterable,
25
+ PathLike,
26
+ Retry,
27
+ StrStrMapping,
28
+ )
29
+
30
+
31
+ def docker_compose_down(*, files: MaybeIterable[PathLike] | None = None) -> None:
32
+ """Stop and remove containers."""
33
+ run(*docker_compose_down_cmd(files=files)) # pragma: no cover
34
+
35
+
36
+ def docker_compose_down_cmd(
37
+ *, files: MaybeIterable[PathLike] | None = None
38
+ ) -> list[str]:
39
+ """Command to use 'docker compose down' to stop and remove containers."""
40
+ return _docker_compose_cmd("down", files=files)
41
+
42
+
43
+ def docker_compose_pull(*, files: MaybeIterable[PathLike] | None = None) -> None:
44
+ """Pull service images."""
45
+ run(*docker_compose_pull_cmd(files=files)) # pragma: no cover
46
+
47
+
48
+ def docker_compose_pull_cmd(
49
+ *, files: MaybeIterable[PathLike] | None = None
50
+ ) -> list[str]:
51
+ """Command to use 'docker compose pull' to pull service images."""
52
+ return _docker_compose_cmd("pull", files=files)
53
+
54
+
55
+ def docker_compose_up(*, files: MaybeIterable[PathLike] | None = None) -> None:
56
+ """Create and start containers."""
57
+ run(*docker_compose_up_cmd(files=files)) # pragma: no cover
58
+
59
+
60
+ def docker_compose_up_cmd(*, files: MaybeIterable[PathLike] | None = None) -> list[str]:
61
+ """Command to use 'docker compose up' to create and start containers."""
62
+ return _docker_compose_cmd("up", files=files)
63
+
64
+
65
+ def _docker_compose_cmd(
66
+ cmd: str, /, *, files: MaybeIterable[PathLike] | None = None
67
+ ) -> list[str]:
68
+ args: list[str] = ["docker", "compose"]
69
+ if files is not None:
70
+ for file in always_iterable(files):
71
+ args.extend(["--file", str(file)])
72
+ return [*args, cmd]
73
+
74
+
75
+ ##
22
76
 
23
77
 
24
78
  @overload
@@ -47,48 +101,39 @@ def docker_cp(
47
101
  sudo: bool = False,
48
102
  logger: LoggerLike | None = None,
49
103
  ) -> None:
104
+ """Copy between a container and the local file system."""
50
105
  match src, dest: # skipif-ci
51
106
  case Path() | str(), (str() as cont, Path() | str() as dest_path):
52
107
  docker_exec(
53
108
  cont, *maybe_sudo_cmd(*mkdir_cmd(dest_path, parent=True), sudo=sudo)
54
109
  )
55
- run(*docker_cp_cmd(src, dest, sudo=sudo), logger=logger)
110
+ run(*maybe_sudo_cmd(*docker_cp_cmd(src, dest), sudo=sudo), logger=logger)
56
111
  case (str(), Path() | str()), Path() | str():
57
112
  mkdir(dest, parent=True, sudo=sudo)
58
- run(*docker_cp_cmd(src, dest, sudo=sudo), logger=logger)
113
+ run(*maybe_sudo_cmd(*docker_cp_cmd(src, dest), sudo=sudo), logger=logger)
59
114
  case _: # pragma: no cover
60
115
  raise ImpossibleCaseError(case=[f"{src}", f"{dest=}"])
61
116
 
62
117
 
63
118
  @overload
64
- def docker_cp_cmd(
65
- src: tuple[str, PathLike], dest: PathLike, /, *, sudo: bool = False
66
- ) -> list[str]: ...
119
+ def docker_cp_cmd(src: tuple[str, PathLike], dest: PathLike, /) -> list[str]: ...
67
120
  @overload
121
+ def docker_cp_cmd(src: PathLike, dest: tuple[str, PathLike], /) -> list[str]: ...
68
122
  def docker_cp_cmd(
69
- src: PathLike, dest: tuple[str, PathLike], /, *, sudo: bool = False
70
- ) -> list[str]: ...
71
- def docker_cp_cmd(
72
- src: PathLike | tuple[str, PathLike],
73
- dest: PathLike | tuple[str, PathLike],
74
- /,
75
- *,
76
- sudo: bool = False,
123
+ src: PathLike | tuple[str, PathLike], dest: PathLike | tuple[str, PathLike], /
77
124
  ) -> list[str]:
125
+ """Command to use 'docker cp' to copy between a container and the local file system."""
126
+ args: list[str] = ["docker", "cp"]
78
127
  match src, dest:
79
- case (Path() | str()) as src_use, (
80
- str() as dest_cont,
81
- Path() | str() as dest_path,
82
- ):
83
- dest_use = f"{dest_cont}:{dest_path}"
84
- case (str() as src_cont, (Path() | str()) as src_path), (
85
- Path() | str() as dest_use
86
- ):
87
- src_use = f"{src_cont}:{src_path}"
128
+ case ((Path() | str()), (str() as cont, Path() | str() as path)):
129
+ return [*args, str(src), f"{cont}:{path}"]
130
+ case (str() as cont, (Path() | str()) as path), (Path() | str() as dest):
131
+ return [*args, f"{cont}:{path}", str(dest)]
88
132
  case _: # pragma: no cover
89
133
  raise ImpossibleCaseError(case=[f"{src}", f"{dest=}"])
90
- parts: list[str] = ["docker", "cp", str(src_use), str(dest_use)]
91
- return maybe_sudo_cmd(*parts, sudo=sudo)
134
+
135
+
136
+ ##
92
137
 
93
138
 
94
139
  @overload
@@ -210,6 +255,7 @@ def docker_exec(
210
255
  logger: LoggerLike | None = None,
211
256
  **env_kwargs: str,
212
257
  ) -> str | None:
258
+ """Execute a command in a container."""
213
259
  cmd_and_args = docker_exec_cmd( # skipif-ci
214
260
  container,
215
261
  cmd,
@@ -245,7 +291,7 @@ def docker_exec_cmd(
245
291
  workdir: PathLike | None = None,
246
292
  **env_kwargs: str,
247
293
  ) -> list[str]:
248
- """Build a command for `docker exec`."""
294
+ """Command to use `docker exec` to execute a command in a container."""
249
295
  args: list[str] = ["docker", "exec"]
250
296
  mapping: dict[str, str] = ({} if env is None else dict(env)) | env_kwargs
251
297
  for key, value in mapping.items():
@@ -259,6 +305,9 @@ def docker_exec_cmd(
259
305
  return [*args, container, cmd, *cmds_or_args]
260
306
 
261
307
 
308
+ ##
309
+
310
+
262
311
  @contextmanager
263
312
  def yield_docker_temp_dir(
264
313
  container: str,
@@ -290,4 +339,16 @@ def yield_docker_temp_dir(
290
339
  docker_exec(container, *rm_cmd(path), user=user, retry=retry, logger=logger)
291
340
 
292
341
 
293
- __all__ = ["docker_cp_cmd", "docker_exec", "docker_exec_cmd", "yield_docker_temp_dir"]
342
+ __all__ = [
343
+ "docker_compose_down",
344
+ "docker_compose_down_cmd",
345
+ "docker_compose_pull",
346
+ "docker_compose_pull_cmd",
347
+ "docker_compose_up",
348
+ "docker_compose_up_cmd",
349
+ "docker_cp",
350
+ "docker_cp_cmd",
351
+ "docker_exec",
352
+ "docker_exec_cmd",
353
+ "yield_docker_temp_dir",
354
+ ]
utilities/subprocess.py CHANGED
@@ -86,9 +86,10 @@ def apt_install(
86
86
  """Install packages."""
87
87
  if update: # pragma: no cover
88
88
  apt_update(sudo=sudo)
89
- run( # pragma: no cover
90
- *maybe_sudo_cmd(*apt_install_cmd(package, *packages), sudo=sudo)
89
+ args = maybe_sudo_cmd( # pragma: no cover
90
+ *apt_install_cmd(package, *packages), sudo=sudo
91
91
  )
92
+ run(*args) # pragma: no cover
92
93
 
93
94
 
94
95
  def apt_install_cmd(package: str, /, *packages: str) -> list[str]:
@@ -101,9 +102,10 @@ def apt_install_cmd(package: str, /, *packages: str) -> list[str]:
101
102
 
102
103
  def apt_remove(package: str, /, *packages: str, sudo: bool = False) -> None:
103
104
  """Remove a package."""
104
- run( # pragma: no cover
105
- *maybe_sudo_cmd(*apt_remove_cmd(package, *packages), sudo=sudo)
105
+ args = maybe_sudo_cmd( # pragma: no cover
106
+ *apt_remove_cmd(package, *packages), sudo=sudo
106
107
  )
108
+ run(*args) # pragma: no cover
107
109
 
108
110
 
109
111
  def apt_remove_cmd(package: str, /, *packages: str) -> list[str]:
@@ -146,6 +148,29 @@ def cd_cmd(path: PathLike, /) -> list[str]:
146
148
  ##
147
149
 
148
150
 
151
+ def chattr(
152
+ path: PathLike, /, *, immutable: bool | None = None, sudo: bool = False
153
+ ) -> None:
154
+ """Change file attributes."""
155
+ args = maybe_sudo_cmd( # pragma: no cover
156
+ *chattr_cmd(path, immutable=immutable), sudo=sudo
157
+ )
158
+ run(*args) # pragma: no cover
159
+
160
+
161
+ def chattr_cmd(path: PathLike, /, *, immutable: bool | None = None) -> list[str]:
162
+ """Command to use 'chattr' to change file attributes."""
163
+ args: list[str] = ["chattr"]
164
+ if immutable is True:
165
+ args.append("+i")
166
+ elif immutable is False:
167
+ args.append("-i")
168
+ return [*args, str(path)]
169
+
170
+
171
+ ##
172
+
173
+
149
174
  def chmod(path: PathLike, perms: PermissionsLike, /, *, sudo: bool = False) -> None:
150
175
  """Change file mode."""
151
176
  if sudo: # pragma: no cover
@@ -223,9 +248,8 @@ class ChownCmdError(Exception):
223
248
 
224
249
  def chpasswd(user_name: str, password: str, /, *, sudo: bool = False) -> None:
225
250
  """Update passwords."""
226
- run( # pragma: no cover
227
- *maybe_sudo_cmd(CHPASSWD, sudo=sudo), input=f"{user_name}:{password}"
228
- )
251
+ args = maybe_sudo_cmd(CHPASSWD, sudo=sudo) # pragma: no cover
252
+ run(*args, input=f"{user_name}:{password}") # pragma: no cover
229
253
 
230
254
 
231
255
  ##
@@ -421,8 +445,8 @@ def curl(
421
445
  logger: LoggerLike | None = None,
422
446
  ) -> str | None:
423
447
  """Transfer a URL."""
424
- args = maybe_sudo_cmd(
425
- *curl_cmd( # skipif-ci
448
+ args = maybe_sudo_cmd( # skipif-ci
449
+ *curl_cmd(
426
450
  url,
427
451
  fail=fail,
428
452
  location=location,
@@ -1860,6 +1884,8 @@ __all__ = [
1860
1884
  "apt_update",
1861
1885
  "cat",
1862
1886
  "cd_cmd",
1887
+ "chattr",
1888
+ "chattr_cmd",
1863
1889
  "chmod",
1864
1890
  "chmod_cmd",
1865
1891
  "chown",