dycw-utilities 0.174.6__tar.gz → 0.174.7__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.6 → dycw_utilities-0.174.7}/PKG-INFO +1 -1
  2. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/pyproject.toml +2 -2
  3. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/__init__.py +1 -1
  4. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/subprocess.py +146 -7
  5. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/README.md +0 -0
  6. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/aeventkit.py +0 -0
  7. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/altair.py +0 -0
  8. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/asyncio.py +0 -0
  9. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/atomicwrites.py +0 -0
  10. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/atools.py +0 -0
  11. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/cachetools.py +0 -0
  12. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/click.py +0 -0
  13. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/concurrent.py +0 -0
  14. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/contextlib.py +0 -0
  15. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/contextvars.py +0 -0
  16. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/cryptography.py +0 -0
  17. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/cvxpy.py +0 -0
  18. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/dataclasses.py +0 -0
  19. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/docker.py +0 -0
  20. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/enum.py +0 -0
  21. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/errors.py +0 -0
  22. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/fastapi.py +0 -0
  23. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/fpdf2.py +0 -0
  24. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/functions.py +0 -0
  25. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/functools.py +0 -0
  26. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/getpass.py +0 -0
  27. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/git.py +0 -0
  28. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/grp.py +0 -0
  29. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/gzip.py +0 -0
  30. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/hashlib.py +0 -0
  31. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/http.py +0 -0
  32. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/hypothesis.py +0 -0
  33. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/importlib.py +0 -0
  34. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/inflect.py +0 -0
  35. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/ipython.py +0 -0
  36. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/iterables.py +0 -0
  37. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/jinja2.py +0 -0
  38. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/json.py +0 -0
  39. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/jupyter.py +0 -0
  40. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/libcst.py +0 -0
  41. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/lightweight_charts.py +0 -0
  42. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/logging.py +0 -0
  43. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/math.py +0 -0
  44. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/memory_profiler.py +0 -0
  45. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/modules.py +0 -0
  46. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/more_itertools.py +0 -0
  47. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/numpy.py +0 -0
  48. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/operator.py +0 -0
  49. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/optuna.py +0 -0
  50. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/orjson.py +0 -0
  51. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/os.py +0 -0
  52. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/parse.py +0 -0
  53. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/pathlib.py +0 -0
  54. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/pickle.py +0 -0
  55. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/platform.py +0 -0
  56. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/polars.py +0 -0
  57. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/polars_ols.py +0 -0
  58. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/postgres.py +0 -0
  59. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/pottery.py +0 -0
  60. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/pqdm.py +0 -0
  61. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/psutil.py +0 -0
  62. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/pwd.py +0 -0
  63. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/py.typed +0 -0
  64. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/pydantic.py +0 -0
  65. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/pydantic_settings.py +0 -0
  66. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/pydantic_settings_sops.py +0 -0
  67. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/pyinstrument.py +0 -0
  68. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/pytest.py +0 -0
  69. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/pytest_plugins/__init__.py +0 -0
  70. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/pytest_plugins/pytest_randomly.py +0 -0
  71. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/pytest_plugins/pytest_regressions.py +0 -0
  72. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/pytest_regressions.py +0 -0
  73. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/random.py +0 -0
  74. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/re.py +0 -0
  75. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/redis.py +0 -0
  76. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/reprlib.py +0 -0
  77. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/scipy.py +0 -0
  78. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/sentinel.py +0 -0
  79. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/shelve.py +0 -0
  80. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/shutil.py +0 -0
  81. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/slack_sdk.py +0 -0
  82. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/socket.py +0 -0
  83. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/sqlalchemy.py +0 -0
  84. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/sqlalchemy_polars.py +0 -0
  85. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/statsmodels.py +0 -0
  86. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/string.py +0 -0
  87. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/tempfile.py +0 -0
  88. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/testbook.py +0 -0
  89. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/text.py +0 -0
  90. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/threading.py +0 -0
  91. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/timer.py +0 -0
  92. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/traceback.py +0 -0
  93. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/types.py +0 -0
  94. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/typing.py +0 -0
  95. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/tzdata.py +0 -0
  96. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/tzlocal.py +0 -0
  97. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/uuid.py +0 -0
  98. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/version.py +0 -0
  99. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/warnings.py +0 -0
  100. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/whenever.py +0 -0
  101. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/src/utilities/zipfile.py +0 -0
  102. {dycw_utilities-0.174.6 → dycw_utilities-0.174.7}/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.6
3
+ Version: 0.174.7
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.6"
104
+ version = "0.174.7"
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.6"
138
+ current_version = "0.174.7"
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.6"
3
+ __version__ = "0.174.7"
@@ -5,6 +5,7 @@ from contextlib import contextmanager
5
5
  from dataclasses import dataclass
6
6
  from io import StringIO
7
7
  from pathlib import Path
8
+ from shlex import join, quote
8
9
  from string import Template
9
10
  from subprocess import PIPE, CalledProcessError, Popen
10
11
  from threading import Thread
@@ -12,6 +13,7 @@ from time import sleep
12
13
  from typing import IO, TYPE_CHECKING, Literal, assert_never, overload, override
13
14
 
14
15
  from utilities.errors import ImpossibleCaseError
16
+ from utilities.iterables import always_iterable
15
17
  from utilities.logging import to_logger
16
18
  from utilities.text import strip_and_dedent
17
19
  from utilities.whenever import to_seconds
@@ -19,7 +21,14 @@ from utilities.whenever import to_seconds
19
21
  if TYPE_CHECKING:
20
22
  from collections.abc import Iterator
21
23
 
22
- from utilities.types import LoggerLike, PathLike, Retry, StrMapping, StrStrMapping
24
+ from utilities.types import (
25
+ LoggerLike,
26
+ MaybeIterable,
27
+ PathLike,
28
+ Retry,
29
+ StrMapping,
30
+ StrStrMapping,
31
+ )
23
32
 
24
33
 
25
34
  _HOST_KEY_ALGORITHMS = ["ssh-ed25519"]
@@ -113,8 +122,13 @@ def mkdir(path: PathLike, /, *, sudo: bool = False, parent: bool = False) -> Non
113
122
 
114
123
 
115
124
  def mkdir_cmd(path: PathLike, /, *, parent: bool = False) -> list[str]:
116
- path_use = f"$(dirname {path})" if parent else path
117
- return ["mkdir", "-p", str(path_use)]
125
+ args: list[str] = ["mkdir", "-p"]
126
+ quoted = quote(str(path))
127
+ if parent:
128
+ args.append(f"$(dirname {quoted})")
129
+ else:
130
+ args.append(quoted)
131
+ return args
118
132
 
119
133
 
120
134
  def mv_cmd(src: PathLike, dest: PathLike, /) -> list[str]:
@@ -125,6 +139,114 @@ def rm_cmd(path: PathLike, /) -> list[str]:
125
139
  return ["rm", "-rf", str(path)]
126
140
 
127
141
 
142
+ def rsync(
143
+ src_or_srcs: MaybeIterable[PathLike],
144
+ user: str,
145
+ hostname: str,
146
+ dest: PathLike,
147
+ /,
148
+ *,
149
+ sudo: bool = False,
150
+ batch_mode: bool = True,
151
+ host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
152
+ strict_host_key_checking: bool = True,
153
+ print: bool = False, # noqa: A002
154
+ retry: Retry | None = None,
155
+ logger: LoggerLike | None = None,
156
+ archive: bool = False,
157
+ chown_user: str | None = None,
158
+ chown_group: str | None = None,
159
+ exclude: MaybeIterable[str] | None = None,
160
+ chmod: str | None = None,
161
+ ) -> None:
162
+ mkdir_args = maybe_sudo_cmd(*mkdir_cmd(dest, parent=True), sudo=sudo) # skipif-ci
163
+ ssh( # skipif-ci
164
+ user,
165
+ hostname,
166
+ *mkdir_args,
167
+ batch_mode=batch_mode,
168
+ host_key_algorithms=host_key_algorithms,
169
+ strict_host_key_checking=strict_host_key_checking,
170
+ print=print,
171
+ retry=retry,
172
+ logger=logger,
173
+ )
174
+ rsync_args = rsync_cmd( # skipif-ci
175
+ src_or_srcs,
176
+ user,
177
+ hostname,
178
+ dest,
179
+ archive=archive,
180
+ chown_user=chown_user,
181
+ chown_group=chown_group,
182
+ exclude=exclude,
183
+ batch_mode=batch_mode,
184
+ host_key_algorithms=host_key_algorithms,
185
+ strict_host_key_checking=strict_host_key_checking,
186
+ sudo=sudo,
187
+ )
188
+ run(*rsync_args, print=print, retry=retry, logger=logger) # skipif-ci
189
+ if chmod is not None: # skipif-ci
190
+ chmod_args = maybe_sudo_cmd(*chmod_cmd(dest, chmod), sudo=sudo)
191
+ ssh(
192
+ user,
193
+ hostname,
194
+ *chmod_args,
195
+ batch_mode=batch_mode,
196
+ host_key_algorithms=host_key_algorithms,
197
+ strict_host_key_checking=strict_host_key_checking,
198
+ print=print,
199
+ retry=retry,
200
+ logger=logger,
201
+ )
202
+
203
+
204
+ def rsync_cmd(
205
+ src_or_srcs: MaybeIterable[PathLike],
206
+ user: str,
207
+ hostname: str,
208
+ dest: PathLike,
209
+ /,
210
+ *,
211
+ archive: bool = False,
212
+ chown_user: str | None = None,
213
+ chown_group: str | None = None,
214
+ exclude: MaybeIterable[str] | None = None,
215
+ batch_mode: bool = True,
216
+ host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
217
+ strict_host_key_checking: bool = True,
218
+ sudo: bool = False,
219
+ ) -> list[str]:
220
+ args: list[str] = ["rsync"]
221
+ if archive:
222
+ args.append("--archive")
223
+ args.append("--checksum")
224
+ match chown_user, chown_group:
225
+ case None, None:
226
+ ...
227
+ case str(), None:
228
+ args.extend(["--chown", chown_user])
229
+ case None, str():
230
+ args.extend(["--chown", f":{chown_group}"])
231
+ case str(), str():
232
+ args.extend(["--chown", f"{chown_user}:{chown_group}"])
233
+ case never:
234
+ assert_never(never)
235
+ args.append("--compress")
236
+ if exclude is not None:
237
+ for exclude_i in always_iterable(exclude):
238
+ args.extend(["--exclude", exclude_i])
239
+ rsh_args: list[str] = ssh_opts_cmd(
240
+ batch_mode=batch_mode,
241
+ host_key_algorithms=host_key_algorithms,
242
+ strict_host_key_checking=strict_host_key_checking,
243
+ )
244
+ args.extend(["--rsh", join(rsh_args)])
245
+ if sudo:
246
+ args.extend(["--rsync-path", join(sudo_cmd("rsync"))])
247
+ return [*args, *map(str, always_iterable(src_or_srcs)), f"{user}@{hostname}:{dest}"]
248
+
249
+
128
250
  @overload
129
251
  def run(
130
252
  cmd: str,
@@ -527,6 +649,20 @@ def ssh_cmd(
527
649
  batch_mode: bool = True,
528
650
  host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
529
651
  strict_host_key_checking: bool = True,
652
+ ) -> list[str]:
653
+ args: list[str] = ssh_opts_cmd(
654
+ batch_mode=batch_mode,
655
+ host_key_algorithms=host_key_algorithms,
656
+ strict_host_key_checking=strict_host_key_checking,
657
+ )
658
+ return [*args, f"{user}@{hostname}", *cmd_and_cmds_or_args]
659
+
660
+
661
+ def ssh_opts_cmd(
662
+ *,
663
+ batch_mode: bool = True,
664
+ host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
665
+ strict_host_key_checking: bool = True,
530
666
  ) -> list[str]:
531
667
  args: list[str] = ["ssh"]
532
668
  if batch_mode:
@@ -534,7 +670,7 @@ def ssh_cmd(
534
670
  args.extend(["-o", f"HostKeyAlgorithms={','.join(host_key_algorithms)}"])
535
671
  if strict_host_key_checking:
536
672
  args.extend(["-o", "StrictHostKeyChecking=yes"])
537
- return [*args, "-T", f"{user}@{hostname}", *cmd_and_cmds_or_args]
673
+ return [*args, "-T"]
538
674
 
539
675
 
540
676
  def ssh_keygen_cmd(hostname: str, /) -> list[str]:
@@ -582,12 +718,12 @@ def yield_ssh_temp_dir(
582
718
  logger: LoggerLike | None = None,
583
719
  keep: bool = False,
584
720
  ) -> Iterator[Path]:
585
- path = Path(
721
+ path = Path( # skipif-ci
586
722
  ssh(user, hostname, *MKTEMP_DIR_CMD, return_=True, retry=retry, logger=logger)
587
723
  )
588
- try:
724
+ try: # skipif-ci
589
725
  yield path
590
- finally:
726
+ finally: # skipif-ci
591
727
  if keep:
592
728
  if logger is not None:
593
729
  to_logger(logger).info("Keeping temporary directory '%s'...", path)
@@ -617,10 +753,13 @@ __all__ = [
617
753
  "mkdir_cmd",
618
754
  "mv_cmd",
619
755
  "rm_cmd",
756
+ "rsync",
757
+ "rsync_cmd",
620
758
  "run",
621
759
  "set_hostname_cmd",
622
760
  "ssh",
623
761
  "ssh_cmd",
762
+ "ssh_opts_cmd",
624
763
  "sudo_cmd",
625
764
  "sudo_nopasswd_cmd",
626
765
  "symlink_cmd",