dycw-utilities 0.173.6__py3-none-any.whl → 0.174.0__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.173.6
3
+ Version: 0.174.0
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=B07lof4TvEtS0ztTgbMuH09MDCT2tmK8ItKOiAlMvZQ,60
1
+ utilities/__init__.py,sha256=SAizpT7mlxrqXLUp3CAijczSi6mCMJcL-XzIlkyvEKk,60
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
@@ -12,7 +12,7 @@ utilities/contextvars.py,sha256=J8OhC7jqozAGYOCe2KUWysbPXNGe5JYz3HfaY_mIs08,883
12
12
  utilities/cryptography.py,sha256=5PFrzsNUGHay91dFgYnDKwYprXxahrBqztmUqViRzBk,956
13
13
  utilities/cvxpy.py,sha256=Rv1-fD-XYerosCavRF8Pohop2DBkU3AlFaGTfD8AEAA,13776
14
14
  utilities/dataclasses.py,sha256=xbU3QN1GFy7RC6hIJRZIeUZm7YRlodrgEWmahWG6k2g,32465
15
- utilities/docker.py,sha256=6WNMuhsFudj67tjvxLyhHyaInUwXmxGUzDuL-M-32vM,7255
15
+ utilities/docker.py,sha256=bLMXK1WCCoyVg92Yc4BNfaCXMaCIHRKEvqL2DiHTX4s,7270
16
16
  utilities/enum.py,sha256=5l6pwZD1cjSlVW4ss-zBPspWvrbrYrdtJWcg6f5_J5w,5781
17
17
  utilities/errors.py,sha256=mFlDGSM0LI1jZ1pbqwLAH3ttLZ2JVIxyZLojw8tGVZU,1479
18
18
  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=e7IL48srBhiOpLKwfFTtvX0YgcDI39cfo8BSjRHsLIs,10165
83
+ utilities/subprocess.py,sha256=WQ7MCIFbSHfscNgSAujdQb1iTw0ZtSojySdmoiZcP3w,12582
84
84
  utilities/tempfile.py,sha256=Lx6qa16lL1XVH6WdmD_G9vlN6gLI8nrIurxmsFkPKvg,3022
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.173.6.dist-info/WHEEL,sha256=ZyFSCYkV2BrxH6-HRVRg3R9Fo7MALzer9KiPYqNxSbo,79
101
- dycw_utilities-0.173.6.dist-info/entry_points.txt,sha256=ykGI1ArwOPHqm2g5Cqh3ENdMxEej_a_FcOUov5EM5Oc,155
102
- dycw_utilities-0.173.6.dist-info/METADATA,sha256=zZXrDZEFMywcbh_qCp4Rx04wtgYCKsEz2xb9o7xLcGE,1709
103
- dycw_utilities-0.173.6.dist-info/RECORD,,
100
+ dycw_utilities-0.174.0.dist-info/WHEEL,sha256=ZyFSCYkV2BrxH6-HRVRg3R9Fo7MALzer9KiPYqNxSbo,79
101
+ dycw_utilities-0.174.0.dist-info/entry_points.txt,sha256=ykGI1ArwOPHqm2g5Cqh3ENdMxEej_a_FcOUov5EM5Oc,155
102
+ dycw_utilities-0.174.0.dist-info/METADATA,sha256=s1r1jGAcrQUx0YBQBK-2L4K2op5q7VlQkywW4FHbz8Y,1709
103
+ dycw_utilities-0.174.0.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.173.6"
3
+ __version__ = "0.174.0"
utilities/docker.py CHANGED
@@ -7,7 +7,6 @@ from typing import TYPE_CHECKING, Literal, overload
7
7
  from utilities.errors import ImpossibleCaseError
8
8
  from utilities.subprocess import (
9
9
  MKTEMP_DIR_CMD,
10
- bash_cmd_and_args,
11
10
  maybe_sudo_cmd,
12
11
  mkdir,
13
12
  mkdir_cmd,
@@ -100,7 +99,7 @@ def docker_exec(
100
99
  env: StrStrMapping | None = None,
101
100
  user: str | None = None,
102
101
  workdir: PathLike | None = None,
103
- bash: bool = False,
102
+ input: str | None = None,
104
103
  print: bool = False,
105
104
  print_stdout: bool = False,
106
105
  print_stderr: bool = False,
@@ -119,7 +118,7 @@ def docker_exec(
119
118
  env: StrStrMapping | None = None,
120
119
  user: str | None = None,
121
120
  workdir: PathLike | None = None,
122
- bash: bool = False,
121
+ input: str | None = None,
123
122
  print: bool = False,
124
123
  print_stdout: bool = False,
125
124
  print_stderr: bool = False,
@@ -138,7 +137,7 @@ def docker_exec(
138
137
  env: StrStrMapping | None = None,
139
138
  user: str | None = None,
140
139
  workdir: PathLike | None = None,
141
- bash: bool = False,
140
+ input: str | None = None,
142
141
  print: bool = False,
143
142
  print_stdout: bool = False,
144
143
  print_stderr: bool = False,
@@ -157,7 +156,7 @@ def docker_exec(
157
156
  env: StrStrMapping | None = None,
158
157
  user: str | None = None,
159
158
  workdir: PathLike | None = None,
160
- bash: bool = False,
159
+ input: str | None = None,
161
160
  print: bool = False,
162
161
  print_stdout: bool = False,
163
162
  print_stderr: bool = False,
@@ -176,7 +175,7 @@ def docker_exec(
176
175
  env: StrStrMapping | None = None,
177
176
  user: str | None = None,
178
177
  workdir: PathLike | None = None,
179
- bash: bool = False,
178
+ input: str | None = None,
180
179
  print: bool = False,
181
180
  print_stdout: bool = False,
182
181
  print_stderr: bool = False,
@@ -194,7 +193,7 @@ def docker_exec(
194
193
  env: StrStrMapping | None = None,
195
194
  user: str | None = None,
196
195
  workdir: PathLike | None = None,
197
- bash: bool = False,
196
+ input: str | None = None, # noqa: A002
198
197
  print: bool = False, # noqa: A002
199
198
  print_stdout: bool = False,
200
199
  print_stderr: bool = False,
@@ -209,13 +208,14 @@ def docker_exec(
209
208
  cmd,
210
209
  *cmds_or_args,
211
210
  env=env,
211
+ interactive=input is not None,
212
212
  user=user,
213
213
  workdir=workdir,
214
- bash=bash,
215
214
  **env_kwargs,
216
215
  )
217
216
  return run( # skipif-ci
218
217
  *cmd_and_args,
218
+ input=input,
219
219
  print=print,
220
220
  print_stdout=print_stdout,
221
221
  print_stderr=print_stderr,
@@ -232,9 +232,9 @@ def docker_exec_cmd(
232
232
  /,
233
233
  *cmds_or_args: str,
234
234
  env: StrStrMapping | None = None,
235
+ interactive: bool = False,
235
236
  user: str | None = None,
236
237
  workdir: PathLike | None = None,
237
- bash: bool = False,
238
238
  **env_kwargs: str,
239
239
  ) -> list[str]:
240
240
  """Build a command for `docker exec`."""
@@ -242,16 +242,13 @@ def docker_exec_cmd(
242
242
  mapping: dict[str, str] = ({} if env is None else dict(env)) | env_kwargs
243
243
  for key, value in mapping.items():
244
244
  args.extend(["--env", f"{key}={value}"])
245
+ if interactive:
246
+ args.append("--interactive")
245
247
  if user is not None:
246
248
  args.extend(["--user", user])
247
249
  if workdir is not None:
248
250
  args.extend(["--workdir", str(workdir)])
249
- args.append(container)
250
- if bash:
251
- args.extend(bash_cmd_and_args(cmd, *cmds_or_args))
252
- else:
253
- args.extend([cmd, *cmds_or_args])
254
- return args
251
+ return [*args, container, cmd, *cmds_or_args]
255
252
 
256
253
 
257
254
  @contextmanager
utilities/subprocess.py CHANGED
@@ -20,13 +20,11 @@ if TYPE_CHECKING:
20
20
 
21
21
 
22
22
  _HOST_KEY_ALGORITHMS = ["ssh-ed25519"]
23
+ BASH_LC = ["bash", "-lc"]
24
+ BASH_LS = ["bash", "-ls"]
23
25
  MKTEMP_DIR_CMD = ["mktemp", "-d"]
24
26
 
25
27
 
26
- def bash_cmd_and_args(cmd: str, /, *cmds: str) -> list[str]:
27
- return ["bash", "-lc", "\n".join([cmd, *cmds])]
28
-
29
-
30
28
  def echo_cmd(text: str, /) -> list[str]:
31
29
  return ["echo", text]
32
30
 
@@ -69,7 +67,6 @@ def run(
69
67
  cmd: str,
70
68
  /,
71
69
  *cmds_or_args: str,
72
- bash: bool = False,
73
70
  user: str | int | None = None,
74
71
  executable: str | None = None,
75
72
  shell: bool = False,
@@ -89,7 +86,6 @@ def run(
89
86
  cmd: str,
90
87
  /,
91
88
  *cmds_or_args: str,
92
- bash: bool = False,
93
89
  user: str | int | None = None,
94
90
  executable: str | None = None,
95
91
  shell: bool = False,
@@ -109,7 +105,6 @@ def run(
109
105
  cmd: str,
110
106
  /,
111
107
  *cmds_or_args: str,
112
- bash: bool = False,
113
108
  user: str | int | None = None,
114
109
  executable: str | None = None,
115
110
  shell: bool = False,
@@ -129,7 +124,6 @@ def run(
129
124
  cmd: str,
130
125
  /,
131
126
  *cmds_or_args: str,
132
- bash: bool = False,
133
127
  user: str | int | None = None,
134
128
  executable: str | None = None,
135
129
  shell: bool = False,
@@ -149,7 +143,6 @@ def run(
149
143
  cmd: str,
150
144
  /,
151
145
  *cmds_or_args: str,
152
- bash: bool = False,
153
146
  user: str | int | None = None,
154
147
  executable: str | None = None,
155
148
  shell: bool = False,
@@ -168,7 +161,6 @@ def run(
168
161
  cmd: str,
169
162
  /,
170
163
  *cmds_or_args: str,
171
- bash: bool = False,
172
164
  user: str | int | None = None,
173
165
  executable: str | None = None,
174
166
  shell: bool = False,
@@ -183,22 +175,10 @@ def run(
183
175
  return_stderr: bool = False,
184
176
  logger: LoggerLike | None = None,
185
177
  ) -> str | None:
186
- match bash, user:
187
- case False, user_use:
188
- args: list[str] = [cmd, *cmds_or_args]
189
- case True, None:
190
- args: list[str] = bash_cmd_and_args(cmd, *cmds_or_args)
191
- user_use = None
192
- case True, str() | int(): # skipif-ci-or-mac
193
- args: list[str] = [
194
- "su",
195
- "-",
196
- str(user),
197
- *bash_cmd_and_args(cmd, *cmds_or_args),
198
- ]
199
- user_use = None
200
- case never:
201
- assert_never(never)
178
+ args: list[str] = []
179
+ if user is not None:
180
+ args.extend(["su", "-", str(user)])
181
+ args.extend([cmd, *cmds_or_args])
202
182
  buffer = StringIO()
203
183
  stdout = StringIO()
204
184
  stderr = StringIO()
@@ -208,33 +188,33 @@ def run(
208
188
  stderr_outputs: list[IO[str]] = [buffer, stderr]
209
189
  if print or print_stderr:
210
190
  stderr_outputs.append(sys.stderr)
211
- inputted = False
212
191
  with Popen(
213
192
  args,
214
193
  bufsize=1,
215
194
  executable=executable,
216
- stdin=None if input is None else PIPE,
195
+ stdin=PIPE,
217
196
  stdout=PIPE,
218
197
  stderr=PIPE,
219
198
  shell=shell,
220
199
  cwd=cwd,
221
200
  env=env,
222
201
  text=True,
223
- user=user_use,
202
+ user=user,
224
203
  ) as proc:
204
+ if proc.stdin is None: # pragma: no cover
205
+ raise ImpossibleCaseError(case=[f"{proc.stdin=}"])
225
206
  if proc.stdout is None: # pragma: no cover
226
207
  raise ImpossibleCaseError(case=[f"{proc.stdout=}"])
227
208
  if proc.stderr is None: # pragma: no cover
228
209
  raise ImpossibleCaseError(case=[f"{proc.stderr=}"])
229
- if (input is not None) and not inputted:
230
- inputted = True
231
- stdout_i, stderr_i = proc.communicate(input=input)
232
- _write_to_streams(stdout_i, *stdout_outputs)
233
- _write_to_streams(stderr_i, *stderr_outputs)
234
210
  with (
235
- _yield_write(proc.stdout, *stdout_outputs),
236
- _yield_write(proc.stderr, *stderr_outputs),
211
+ _yield_write(proc.stdout, "proc.stdout", *stdout_outputs),
212
+ _yield_write(proc.stderr, "proc.stderr", *stderr_outputs),
237
213
  ):
214
+ if input is not None:
215
+ _ = proc.stdin.write(input)
216
+ proc.stdin.flush()
217
+ proc.stdin.close()
238
218
  return_code = proc.wait()
239
219
  match return_code, return_ or return_stdout, return_ or return_stderr:
240
220
  case 0, True, True:
@@ -258,7 +238,6 @@ def run(
258
238
  'run' failed with:
259
239
  - cmd = {cmd}
260
240
  - cmds_or_args = {cmds_or_args}
261
- - bash = {bash}
262
241
  - user = {user}
263
242
  - executable = {executable}
264
243
  - shell = {shell}
@@ -280,8 +259,8 @@ def run(
280
259
 
281
260
 
282
261
  @contextmanager
283
- def _yield_write(input_: IO[str], /, *outputs: IO[str]) -> Iterator[None]:
284
- thread = Thread(target=_run_target, args=(input_, *outputs), daemon=True)
262
+ def _yield_write(input_: IO[str], desc: str, /, *outputs: IO[str]) -> Iterator[None]:
263
+ thread = Thread(target=_run_target, args=(input_, desc, *outputs), daemon=True)
285
264
  thread.start()
286
265
  try:
287
266
  yield
@@ -289,10 +268,14 @@ def _yield_write(input_: IO[str], /, *outputs: IO[str]) -> Iterator[None]:
289
268
  thread.join()
290
269
 
291
270
 
292
- def _run_target(input_: IO[str], /, *outputs: IO[str]) -> None:
293
- with input_:
294
- for text in iter(input_.readline, ""):
295
- _write_to_streams(text, *outputs)
271
+ def _run_target(input_: IO[str], desc: str, /, *outputs: IO[str]) -> None:
272
+ try:
273
+ with input_:
274
+ for text in iter(input_.readline, ""):
275
+ _write_to_streams(text, *outputs)
276
+ except ValueError:
277
+ _ = sys.stderr.write(f"Failed to write to {desc!r}...")
278
+ raise
296
279
 
297
280
 
298
281
  def _write_to_streams(text: str, /, *outputs: IO[str]) -> None:
@@ -300,16 +283,124 @@ def _write_to_streams(text: str, /, *outputs: IO[str]) -> None:
300
283
  _ = output.write(text)
301
284
 
302
285
 
286
+ @overload
287
+ def ssh(
288
+ user: str,
289
+ hostname: str,
290
+ /,
291
+ *cmd_and_cmds_or_args: str,
292
+ batch_mode: bool = True,
293
+ host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
294
+ strict_host_key_checking: bool = True,
295
+ input: str | None = None,
296
+ print: bool = False,
297
+ print_stdout: bool = False,
298
+ print_stderr: bool = False,
299
+ return_: Literal[True],
300
+ return_stdout: bool = False,
301
+ return_stderr: bool = False,
302
+ logger: LoggerLike | None = None,
303
+ ) -> str: ...
304
+ @overload
305
+ def ssh(
306
+ user: str,
307
+ hostname: str,
308
+ /,
309
+ *cmd_and_cmds_or_args: str,
310
+ batch_mode: bool = True,
311
+ host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
312
+ strict_host_key_checking: bool = True,
313
+ input: str | None = None,
314
+ print: bool = False,
315
+ print_stdout: bool = False,
316
+ print_stderr: bool = False,
317
+ return_: bool = False,
318
+ return_stdout: Literal[True],
319
+ return_stderr: bool = False,
320
+ logger: LoggerLike | None = None,
321
+ ) -> str: ...
322
+ @overload
323
+ def ssh(
324
+ user: str,
325
+ hostname: str,
326
+ /,
327
+ *cmd_and_cmds_or_args: str,
328
+ batch_mode: bool = True,
329
+ host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
330
+ strict_host_key_checking: bool = True,
331
+ input: str | None = None,
332
+ print: bool = False,
333
+ print_stdout: bool = False,
334
+ print_stderr: bool = False,
335
+ return_: bool = False,
336
+ return_stdout: bool = False,
337
+ return_stderr: Literal[True],
338
+ logger: LoggerLike | None = None,
339
+ ) -> str: ...
340
+ @overload
341
+ def ssh(
342
+ user: str,
343
+ hostname: str,
344
+ /,
345
+ *cmd_and_cmds_or_args: str,
346
+ batch_mode: bool = True,
347
+ host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
348
+ strict_host_key_checking: bool = True,
349
+ input: str | None = None,
350
+ print: bool = False,
351
+ print_stdout: bool = False,
352
+ print_stderr: bool = False,
353
+ return_: Literal[False] = False,
354
+ return_stdout: Literal[False] = False,
355
+ return_stderr: Literal[False] = False,
356
+ logger: LoggerLike | None = None,
357
+ ) -> None: ...
358
+ def ssh(
359
+ user: str,
360
+ hostname: str,
361
+ /,
362
+ *cmd_and_cmds_or_args: str,
363
+ batch_mode: bool = True,
364
+ host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
365
+ strict_host_key_checking: bool = True,
366
+ input: str | None = None, # noqa: A002
367
+ print: bool = False, # noqa: A002
368
+ print_stdout: bool = False,
369
+ print_stderr: bool = False,
370
+ return_: bool = False,
371
+ return_stdout: bool = False,
372
+ return_stderr: bool = False,
373
+ logger: LoggerLike | None = None,
374
+ ) -> str | None:
375
+ cmd_and_args = ssh_cmd( # skipif-ci
376
+ user,
377
+ hostname,
378
+ *cmd_and_cmds_or_args,
379
+ batch_mode=batch_mode,
380
+ host_key_algorithms=host_key_algorithms,
381
+ strict_host_key_checking=strict_host_key_checking,
382
+ )
383
+ return run( # skipif-ci
384
+ *cmd_and_args,
385
+ input=input,
386
+ print=print,
387
+ print_stdout=print_stdout,
388
+ print_stderr=print_stderr,
389
+ return_=return_,
390
+ return_stdout=return_stdout,
391
+ return_stderr=return_stderr,
392
+ logger=logger,
393
+ )
394
+
395
+
303
396
  def ssh_cmd(
304
397
  user: str,
305
398
  hostname: str,
306
- cmd: str,
307
399
  /,
308
- *cmds_or_args: str,
400
+ *cmd_and_cmds_or_args: str,
309
401
  batch_mode: bool = True,
310
402
  host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
311
403
  strict_host_key_checking: bool = True,
312
- bash: bool = False,
313
404
  ) -> list[str]:
314
405
  args: list[str] = ["ssh"]
315
406
  if batch_mode:
@@ -317,12 +408,7 @@ def ssh_cmd(
317
408
  args.extend(["-o", f"HostKeyAlgorithms={','.join(host_key_algorithms)}"])
318
409
  if strict_host_key_checking:
319
410
  args.extend(["-o", "StrictHostKeyChecking=yes"])
320
- args.append(f"{user}@{hostname}")
321
- if bash:
322
- args.extend(bash_cmd_and_args(cmd, *cmds_or_args))
323
- else:
324
- args.extend([cmd, *cmds_or_args])
325
- return args
411
+ return [*args, "-T", f"{user}@{hostname}", *cmd_and_cmds_or_args]
326
412
 
327
413
 
328
414
  def sudo_cmd(cmd: str, /, *args: str) -> list[str]:
@@ -334,8 +420,9 @@ def touch_cmd(path: PathLike, /) -> list[str]:
334
420
 
335
421
 
336
422
  __all__ = [
423
+ "BASH_LC",
424
+ "BASH_LS",
337
425
  "MKTEMP_DIR_CMD",
338
- "bash_cmd_and_args",
339
426
  "echo_cmd",
340
427
  "expand_path",
341
428
  "maybe_sudo_cmd",
@@ -343,6 +430,7 @@ __all__ = [
343
430
  "mkdir_cmd",
344
431
  "rm_cmd",
345
432
  "run",
433
+ "ssh",
346
434
  "ssh_cmd",
347
435
  "sudo_cmd",
348
436
  "touch_cmd",