dycw-utilities 0.174.3__tar.gz → 0.174.5__tar.gz

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.
Files changed (102) hide show
  1. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/PKG-INFO +1 -1
  2. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/pyproject.toml +2 -2
  3. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/__init__.py +1 -1
  4. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/docker.py +29 -4
  5. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/subprocess.py +34 -18
  6. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/types.py +5 -0
  7. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/README.md +0 -0
  8. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/aeventkit.py +0 -0
  9. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/altair.py +0 -0
  10. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/asyncio.py +0 -0
  11. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/atomicwrites.py +0 -0
  12. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/atools.py +0 -0
  13. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/cachetools.py +0 -0
  14. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/click.py +0 -0
  15. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/concurrent.py +0 -0
  16. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/contextlib.py +0 -0
  17. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/contextvars.py +0 -0
  18. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/cryptography.py +0 -0
  19. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/cvxpy.py +0 -0
  20. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/dataclasses.py +0 -0
  21. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/enum.py +0 -0
  22. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/errors.py +0 -0
  23. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/fastapi.py +0 -0
  24. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/fpdf2.py +0 -0
  25. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/functions.py +0 -0
  26. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/functools.py +0 -0
  27. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/getpass.py +0 -0
  28. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/git.py +0 -0
  29. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/grp.py +0 -0
  30. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/gzip.py +0 -0
  31. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/hashlib.py +0 -0
  32. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/http.py +0 -0
  33. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/hypothesis.py +0 -0
  34. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/importlib.py +0 -0
  35. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/inflect.py +0 -0
  36. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/ipython.py +0 -0
  37. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/iterables.py +0 -0
  38. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/jinja2.py +0 -0
  39. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/json.py +0 -0
  40. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/jupyter.py +0 -0
  41. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/libcst.py +0 -0
  42. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/lightweight_charts.py +0 -0
  43. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/logging.py +0 -0
  44. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/math.py +0 -0
  45. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/memory_profiler.py +0 -0
  46. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/modules.py +0 -0
  47. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/more_itertools.py +0 -0
  48. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/numpy.py +0 -0
  49. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/operator.py +0 -0
  50. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/optuna.py +0 -0
  51. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/orjson.py +0 -0
  52. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/os.py +0 -0
  53. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/parse.py +0 -0
  54. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/pathlib.py +0 -0
  55. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/pickle.py +0 -0
  56. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/platform.py +0 -0
  57. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/polars.py +0 -0
  58. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/polars_ols.py +0 -0
  59. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/postgres.py +0 -0
  60. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/pottery.py +0 -0
  61. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/pqdm.py +0 -0
  62. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/psutil.py +0 -0
  63. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/pwd.py +0 -0
  64. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/py.typed +0 -0
  65. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/pydantic.py +0 -0
  66. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/pydantic_settings.py +0 -0
  67. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/pydantic_settings_sops.py +0 -0
  68. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/pyinstrument.py +0 -0
  69. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/pytest.py +0 -0
  70. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/pytest_plugins/__init__.py +0 -0
  71. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/pytest_plugins/pytest_randomly.py +0 -0
  72. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/pytest_plugins/pytest_regressions.py +0 -0
  73. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/pytest_regressions.py +0 -0
  74. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/random.py +0 -0
  75. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/re.py +0 -0
  76. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/redis.py +0 -0
  77. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/reprlib.py +0 -0
  78. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/scipy.py +0 -0
  79. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/sentinel.py +0 -0
  80. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/shelve.py +0 -0
  81. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/shutil.py +0 -0
  82. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/slack_sdk.py +0 -0
  83. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/socket.py +0 -0
  84. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/sqlalchemy.py +0 -0
  85. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/sqlalchemy_polars.py +0 -0
  86. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/statsmodels.py +0 -0
  87. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/string.py +0 -0
  88. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/tempfile.py +0 -0
  89. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/testbook.py +0 -0
  90. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/text.py +0 -0
  91. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/threading.py +0 -0
  92. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/timer.py +0 -0
  93. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/traceback.py +0 -0
  94. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/typing.py +0 -0
  95. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/tzdata.py +0 -0
  96. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/tzlocal.py +0 -0
  97. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/uuid.py +0 -0
  98. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/version.py +0 -0
  99. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/warnings.py +0 -0
  100. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/whenever.py +0 -0
  101. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/zipfile.py +0 -0
  102. {dycw_utilities-0.174.3 → dycw_utilities-0.174.5}/src/utilities/zoneinfo.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: dycw-utilities
3
- Version: 0.174.3
3
+ Version: 0.174.5
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
@@ -101,7 +101,7 @@
101
101
  name = "dycw-utilities"
102
102
  readme = "README.md"
103
103
  requires-python = ">= 3.12"
104
- version = "0.174.3"
104
+ version = "0.174.5"
105
105
 
106
106
  [project.entry-points.pytest11]
107
107
  pytest-randomly = "utilities.pytest_plugins.pytest_randomly"
@@ -135,7 +135,7 @@
135
135
  # bump-my-version
136
136
  [tool.bumpversion]
137
137
  allow_dirty = true
138
- current_version = "0.174.3"
138
+ current_version = "0.174.5"
139
139
 
140
140
  [[tool.bumpversion.files]]
141
141
  filename = "src/utilities/__init__.py"
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.174.3"
3
+ __version__ = "0.174.5"
@@ -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.logging import to_logger
8
9
  from utilities.subprocess import (
9
10
  MKTEMP_DIR_CMD,
10
11
  maybe_sudo_cmd,
@@ -17,7 +18,7 @@ from utilities.subprocess import (
17
18
  if TYPE_CHECKING:
18
19
  from collections.abc import Iterator
19
20
 
20
- from utilities.types import LoggerLike, PathLike, StrStrMapping
21
+ from utilities.types import LoggerLike, PathLike, Retry, StrStrMapping
21
22
 
22
23
 
23
24
  @overload
@@ -106,6 +107,7 @@ def docker_exec(
106
107
  return_: Literal[True],
107
108
  return_stdout: bool = False,
108
109
  return_stderr: bool = False,
110
+ retry: Retry | None = None,
109
111
  logger: LoggerLike | None = None,
110
112
  **env_kwargs: str,
111
113
  ) -> str: ...
@@ -125,6 +127,7 @@ def docker_exec(
125
127
  return_: bool = False,
126
128
  return_stdout: Literal[True],
127
129
  return_stderr: bool = False,
130
+ retry: Retry | None = None,
128
131
  logger: LoggerLike | None = None,
129
132
  **env_kwargs: str,
130
133
  ) -> str: ...
@@ -144,6 +147,7 @@ def docker_exec(
144
147
  return_: bool = False,
145
148
  return_stdout: bool = False,
146
149
  return_stderr: Literal[True],
150
+ retry: Retry | None = None,
147
151
  logger: LoggerLike | None = None,
148
152
  **env_kwargs: str,
149
153
  ) -> str: ...
@@ -163,6 +167,7 @@ def docker_exec(
163
167
  return_: Literal[False] = False,
164
168
  return_stdout: Literal[False] = False,
165
169
  return_stderr: Literal[False] = False,
170
+ retry: Retry | None = None,
166
171
  logger: LoggerLike | None = None,
167
172
  **env_kwargs: str,
168
173
  ) -> None: ...
@@ -182,6 +187,7 @@ def docker_exec(
182
187
  return_: bool = False,
183
188
  return_stdout: bool = False,
184
189
  return_stderr: bool = False,
190
+ retry: Retry | None = None,
185
191
  logger: LoggerLike | None = None,
186
192
  **env_kwargs: str,
187
193
  ) -> str | None: ...
@@ -200,6 +206,7 @@ def docker_exec(
200
206
  return_: bool = False,
201
207
  return_stdout: bool = False,
202
208
  return_stderr: bool = False,
209
+ retry: Retry | None = None,
203
210
  logger: LoggerLike | None = None,
204
211
  **env_kwargs: str,
205
212
  ) -> str | None:
@@ -222,6 +229,7 @@ def docker_exec(
222
229
  return_=return_,
223
230
  return_stdout=return_stdout,
224
231
  return_stderr=return_stderr,
232
+ retry=retry,
225
233
  logger=logger,
226
234
  )
227
235
 
@@ -253,15 +261,32 @@ def docker_exec_cmd(
253
261
 
254
262
  @contextmanager
255
263
  def yield_docker_temp_dir(
256
- container: str, /, *, user: str | None = None, logger: LoggerLike | None = None
264
+ container: str,
265
+ /,
266
+ *,
267
+ user: str | None = None,
268
+ retry: Retry | None = None,
269
+ logger: LoggerLike | None = None,
270
+ keep: bool = False,
257
271
  ) -> Iterator[Path]:
258
272
  path = Path( # skipif-ci
259
- docker_exec(container, *MKTEMP_DIR_CMD, user=user, return_=True, logger=logger)
273
+ docker_exec(
274
+ container,
275
+ *MKTEMP_DIR_CMD,
276
+ user=user,
277
+ return_=True,
278
+ retry=retry,
279
+ logger=logger,
280
+ )
260
281
  )
261
282
  try: # skipif-ci
262
283
  yield path
263
284
  finally: # skipif-ci
264
- docker_exec(container, *rm_cmd(path), user=user, logger=logger)
285
+ if keep:
286
+ if logger is not None:
287
+ to_logger(logger).info("Keeping temporary directory '%s'...", path)
288
+ else:
289
+ docker_exec(container, *rm_cmd(path), user=user, retry=retry, logger=logger)
265
290
 
266
291
 
267
292
  __all__ = ["docker_cp_cmd", "docker_exec", "docker_exec_cmd", "yield_docker_temp_dir"]
@@ -13,22 +13,24 @@ from typing import IO, TYPE_CHECKING, Literal, assert_never, overload
13
13
  from utilities.errors import ImpossibleCaseError
14
14
  from utilities.logging import to_logger
15
15
  from utilities.text import strip_and_dedent
16
- from utilities.types import Delta
17
16
  from utilities.whenever import to_seconds
18
17
 
19
18
  if TYPE_CHECKING:
20
19
  from collections.abc import Iterator
21
20
 
22
- from utilities.types import LoggerLike, PathLike, StrMapping, StrStrMapping
21
+ from utilities.types import LoggerLike, PathLike, Retry, StrMapping, StrStrMapping
23
22
 
24
23
 
25
- type _Retry = tuple[int, Delta | None]
26
24
  _HOST_KEY_ALGORITHMS = ["ssh-ed25519"]
27
25
  BASH_LC = ["bash", "-lc"]
28
26
  BASH_LS = ["bash", "-ls"]
29
27
  MKTEMP_DIR_CMD = ["mktemp", "-d"]
30
28
 
31
29
 
30
+ def cp_cmd(src: PathLike, dest: PathLike, /) -> list[str]:
31
+ return ["cp", "-r", str(src), str(dest)]
32
+
33
+
32
34
  def echo_cmd(text: str, /) -> list[str]:
33
35
  return ["echo", text]
34
36
 
@@ -62,6 +64,10 @@ def mkdir_cmd(path: PathLike, /, *, parent: bool = False) -> list[str]:
62
64
  return ["mkdir", "-p", str(path_use)]
63
65
 
64
66
 
67
+ def mv_cmd(src: PathLike, dest: PathLike, /) -> list[str]:
68
+ return ["mv", str(src), str(dest)]
69
+
70
+
65
71
  def rm_cmd(path: PathLike, /) -> list[str]:
66
72
  return ["rm", "-rf", str(path)]
67
73
 
@@ -83,7 +89,7 @@ def run(
83
89
  return_: Literal[True],
84
90
  return_stdout: bool = False,
85
91
  return_stderr: bool = False,
86
- retry: _Retry | None = None,
92
+ retry: Retry | None = None,
87
93
  logger: LoggerLike | None = None,
88
94
  ) -> str: ...
89
95
  @overload
@@ -103,7 +109,7 @@ def run(
103
109
  return_: bool = False,
104
110
  return_stdout: Literal[True],
105
111
  return_stderr: bool = False,
106
- retry: _Retry | None = None,
112
+ retry: Retry | None = None,
107
113
  logger: LoggerLike | None = None,
108
114
  ) -> str: ...
109
115
  @overload
@@ -123,7 +129,7 @@ def run(
123
129
  return_: bool = False,
124
130
  return_stdout: bool = False,
125
131
  return_stderr: Literal[True],
126
- retry: _Retry | None = None,
132
+ retry: Retry | None = None,
127
133
  logger: LoggerLike | None = None,
128
134
  ) -> str: ...
129
135
  @overload
@@ -143,7 +149,7 @@ def run(
143
149
  return_: Literal[False] = False,
144
150
  return_stdout: Literal[False] = False,
145
151
  return_stderr: Literal[False] = False,
146
- retry: _Retry | None = None,
152
+ retry: Retry | None = None,
147
153
  logger: LoggerLike | None = None,
148
154
  ) -> None: ...
149
155
  @overload
@@ -163,7 +169,7 @@ def run(
163
169
  return_: bool = False,
164
170
  return_stdout: bool = False,
165
171
  return_stderr: bool = False,
166
- retry: _Retry | None = None,
172
+ retry: Retry | None = None,
167
173
  logger: LoggerLike | None = None,
168
174
  ) -> str | None: ...
169
175
  def run(
@@ -182,7 +188,7 @@ def run(
182
188
  return_: bool = False,
183
189
  return_stdout: bool = False,
184
190
  return_stderr: bool = False,
185
- retry: _Retry | None = None,
191
+ retry: Retry | None = None,
186
192
  logger: LoggerLike | None = None,
187
193
  ) -> str | None:
188
194
  args: list[str] = []
@@ -337,7 +343,7 @@ def ssh(
337
343
  return_: Literal[True],
338
344
  return_stdout: bool = False,
339
345
  return_stderr: bool = False,
340
- retry: _Retry | None = None,
346
+ retry: Retry | None = None,
341
347
  logger: LoggerLike | None = None,
342
348
  ) -> str: ...
343
349
  @overload
@@ -356,7 +362,7 @@ def ssh(
356
362
  return_: bool = False,
357
363
  return_stdout: Literal[True],
358
364
  return_stderr: bool = False,
359
- retry: _Retry | None = None,
365
+ retry: Retry | None = None,
360
366
  logger: LoggerLike | None = None,
361
367
  ) -> str: ...
362
368
  @overload
@@ -375,7 +381,7 @@ def ssh(
375
381
  return_: bool = False,
376
382
  return_stdout: bool = False,
377
383
  return_stderr: Literal[True],
378
- retry: _Retry | None = None,
384
+ retry: Retry | None = None,
379
385
  logger: LoggerLike | None = None,
380
386
  ) -> str: ...
381
387
  @overload
@@ -394,7 +400,7 @@ def ssh(
394
400
  return_: Literal[False] = False,
395
401
  return_stdout: Literal[False] = False,
396
402
  return_stderr: Literal[False] = False,
397
- retry: _Retry | None = None,
403
+ retry: Retry | None = None,
398
404
  logger: LoggerLike | None = None,
399
405
  ) -> None: ...
400
406
  @overload
@@ -413,7 +419,7 @@ def ssh(
413
419
  return_: bool = False,
414
420
  return_stdout: bool = False,
415
421
  return_stderr: bool = False,
416
- retry: _Retry | None = None,
422
+ retry: Retry | None = None,
417
423
  logger: LoggerLike | None = None,
418
424
  ) -> str | None: ...
419
425
  def ssh(
@@ -431,7 +437,7 @@ def ssh(
431
437
  return_: bool = False,
432
438
  return_stdout: bool = False,
433
439
  return_stderr: bool = False,
434
- retry: _Retry | None = None,
440
+ retry: Retry | None = None,
435
441
  logger: LoggerLike | None = None,
436
442
  ) -> str | None:
437
443
  cmd_and_args = ssh_cmd( # skipif-ci
@@ -484,9 +490,17 @@ def touch_cmd(path: PathLike, /) -> list[str]:
484
490
 
485
491
  @contextmanager
486
492
  def yield_ssh_temp_dir(
487
- user: str, hostname: str, /, *, keep: bool = False, logger: LoggerLike | None = None
493
+ user: str,
494
+ hostname: str,
495
+ /,
496
+ *,
497
+ retry: Retry | None = None,
498
+ logger: LoggerLike | None = None,
499
+ keep: bool = False,
488
500
  ) -> Iterator[Path]:
489
- path = Path(ssh(user, hostname, *MKTEMP_DIR_CMD, return_=True))
501
+ path = Path(
502
+ ssh(user, hostname, *MKTEMP_DIR_CMD, return_=True, retry=retry, logger=logger)
503
+ )
490
504
  try:
491
505
  yield path
492
506
  finally:
@@ -494,18 +508,20 @@ def yield_ssh_temp_dir(
494
508
  if logger is not None:
495
509
  to_logger(logger).info("Keeping temporary directory '%s'...", path)
496
510
  else:
497
- ssh(user, hostname, *rm_cmd(path))
511
+ ssh(user, hostname, *rm_cmd(path), retry=retry, logger=logger)
498
512
 
499
513
 
500
514
  __all__ = [
501
515
  "BASH_LC",
502
516
  "BASH_LS",
503
517
  "MKTEMP_DIR_CMD",
518
+ "cp_cmd",
504
519
  "echo_cmd",
505
520
  "expand_path",
506
521
  "maybe_sudo_cmd",
507
522
  "mkdir",
508
523
  "mkdir_cmd",
524
+ "mv_cmd",
509
525
  "rm_cmd",
510
526
  "run",
511
527
  "ssh",
@@ -231,6 +231,10 @@ type Seed = int | float | str | bytes | bytearray | Random
231
231
  type PatternLike = MaybeStr[Pattern[str]]
232
232
 
233
233
 
234
+ # retry
235
+ type Retry = tuple[int, Delta | None]
236
+
237
+
234
238
  # text
235
239
  type MaybeCallableStr = MaybeCallable[str]
236
240
 
@@ -332,6 +336,7 @@ __all__ = [
332
336
  "PathLike",
333
337
  "PatternLike",
334
338
  "PlainDateTimeLike",
339
+ "Retry",
335
340
  "Seed",
336
341
  "SequenceStr",
337
342
  "SerializeObjectExtra",