dycw-utilities 0.174.0__tar.gz → 0.174.2__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.0 → dycw_utilities-0.174.2}/PKG-INFO +1 -1
  2. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/pyproject.toml +2 -2
  3. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/__init__.py +1 -1
  4. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/subprocess.py +98 -11
  5. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/README.md +0 -0
  6. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/aeventkit.py +0 -0
  7. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/altair.py +0 -0
  8. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/asyncio.py +0 -0
  9. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/atomicwrites.py +0 -0
  10. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/atools.py +0 -0
  11. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/cachetools.py +0 -0
  12. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/click.py +0 -0
  13. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/concurrent.py +0 -0
  14. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/contextlib.py +0 -0
  15. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/contextvars.py +0 -0
  16. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/cryptography.py +0 -0
  17. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/cvxpy.py +0 -0
  18. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/dataclasses.py +0 -0
  19. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/docker.py +0 -0
  20. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/enum.py +0 -0
  21. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/errors.py +0 -0
  22. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/fastapi.py +0 -0
  23. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/fpdf2.py +0 -0
  24. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/functions.py +0 -0
  25. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/functools.py +0 -0
  26. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/getpass.py +0 -0
  27. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/git.py +0 -0
  28. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/grp.py +0 -0
  29. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/gzip.py +0 -0
  30. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/hashlib.py +0 -0
  31. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/http.py +0 -0
  32. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/hypothesis.py +0 -0
  33. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/importlib.py +0 -0
  34. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/inflect.py +0 -0
  35. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/ipython.py +0 -0
  36. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/iterables.py +0 -0
  37. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/jinja2.py +0 -0
  38. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/json.py +0 -0
  39. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/jupyter.py +0 -0
  40. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/libcst.py +0 -0
  41. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/lightweight_charts.py +0 -0
  42. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/logging.py +0 -0
  43. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/math.py +0 -0
  44. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/memory_profiler.py +0 -0
  45. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/modules.py +0 -0
  46. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/more_itertools.py +0 -0
  47. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/numpy.py +0 -0
  48. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/operator.py +0 -0
  49. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/optuna.py +0 -0
  50. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/orjson.py +0 -0
  51. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/os.py +0 -0
  52. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/parse.py +0 -0
  53. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/pathlib.py +0 -0
  54. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/pickle.py +0 -0
  55. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/platform.py +0 -0
  56. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/polars.py +0 -0
  57. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/polars_ols.py +0 -0
  58. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/postgres.py +0 -0
  59. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/pottery.py +0 -0
  60. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/pqdm.py +0 -0
  61. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/psutil.py +0 -0
  62. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/pwd.py +0 -0
  63. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/py.typed +0 -0
  64. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/pydantic.py +0 -0
  65. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/pydantic_settings.py +0 -0
  66. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/pydantic_settings_sops.py +0 -0
  67. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/pyinstrument.py +0 -0
  68. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/pytest.py +0 -0
  69. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/pytest_plugins/__init__.py +0 -0
  70. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/pytest_plugins/pytest_randomly.py +0 -0
  71. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/pytest_plugins/pytest_regressions.py +0 -0
  72. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/pytest_regressions.py +0 -0
  73. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/random.py +0 -0
  74. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/re.py +0 -0
  75. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/redis.py +0 -0
  76. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/reprlib.py +0 -0
  77. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/scipy.py +0 -0
  78. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/sentinel.py +0 -0
  79. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/shelve.py +0 -0
  80. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/shutil.py +0 -0
  81. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/slack_sdk.py +0 -0
  82. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/socket.py +0 -0
  83. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/sqlalchemy.py +0 -0
  84. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/sqlalchemy_polars.py +0 -0
  85. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/statsmodels.py +0 -0
  86. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/string.py +0 -0
  87. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/tempfile.py +0 -0
  88. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/testbook.py +0 -0
  89. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/text.py +0 -0
  90. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/threading.py +0 -0
  91. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/timer.py +0 -0
  92. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/traceback.py +0 -0
  93. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/types.py +0 -0
  94. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/typing.py +0 -0
  95. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/tzdata.py +0 -0
  96. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/tzlocal.py +0 -0
  97. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/uuid.py +0 -0
  98. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/version.py +0 -0
  99. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/warnings.py +0 -0
  100. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/whenever.py +0 -0
  101. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/src/utilities/zipfile.py +0 -0
  102. {dycw_utilities-0.174.0 → dycw_utilities-0.174.2}/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.0
3
+ Version: 0.174.2
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.0"
104
+ version = "0.174.2"
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.0"
138
+ current_version = "0.174.2"
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.0"
3
+ __version__ = "0.174.2"
@@ -7,6 +7,7 @@ from pathlib import Path
7
7
  from string import Template
8
8
  from subprocess import PIPE, CalledProcessError, Popen
9
9
  from threading import Thread
10
+ from time import sleep
10
11
  from typing import IO, TYPE_CHECKING, Literal, assert_never, overload
11
12
 
12
13
  from utilities.errors import ImpossibleCaseError
@@ -16,6 +17,8 @@ from utilities.text import strip_and_dedent
16
17
  if TYPE_CHECKING:
17
18
  from collections.abc import Iterator
18
19
 
20
+ from whenever import TimeDelta
21
+
19
22
  from utilities.types import LoggerLike, PathLike, StrMapping, StrStrMapping
20
23
 
21
24
 
@@ -300,6 +303,7 @@ def ssh(
300
303
  return_stdout: bool = False,
301
304
  return_stderr: bool = False,
302
305
  logger: LoggerLike | None = None,
306
+ retry: tuple[int, TimeDelta] | None = None,
303
307
  ) -> str: ...
304
308
  @overload
305
309
  def ssh(
@@ -318,6 +322,7 @@ def ssh(
318
322
  return_stdout: Literal[True],
319
323
  return_stderr: bool = False,
320
324
  logger: LoggerLike | None = None,
325
+ retry: tuple[int, TimeDelta] | None = None,
321
326
  ) -> str: ...
322
327
  @overload
323
328
  def ssh(
@@ -336,6 +341,7 @@ def ssh(
336
341
  return_stdout: bool = False,
337
342
  return_stderr: Literal[True],
338
343
  logger: LoggerLike | None = None,
344
+ retry: tuple[int, TimeDelta] | None = None,
339
345
  ) -> str: ...
340
346
  @overload
341
347
  def ssh(
@@ -354,7 +360,27 @@ def ssh(
354
360
  return_stdout: Literal[False] = False,
355
361
  return_stderr: Literal[False] = False,
356
362
  logger: LoggerLike | None = None,
363
+ retry: tuple[int, TimeDelta] | None = None,
357
364
  ) -> None: ...
365
+ @overload
366
+ def ssh(
367
+ user: str,
368
+ hostname: str,
369
+ /,
370
+ *cmd_and_cmds_or_args: str,
371
+ batch_mode: bool = True,
372
+ host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
373
+ strict_host_key_checking: bool = True,
374
+ input: str | None = None,
375
+ print: bool = False,
376
+ print_stdout: bool = False,
377
+ print_stderr: bool = False,
378
+ return_: bool = False,
379
+ return_stdout: bool = False,
380
+ return_stderr: bool = False,
381
+ logger: LoggerLike | None = None,
382
+ retry: tuple[int, TimeDelta] | None = None,
383
+ ) -> str | None: ...
358
384
  def ssh(
359
385
  user: str,
360
386
  hostname: str,
@@ -371,6 +397,7 @@ def ssh(
371
397
  return_stdout: bool = False,
372
398
  return_stderr: bool = False,
373
399
  logger: LoggerLike | None = None,
400
+ retry: tuple[int, TimeDelta] | None = None,
374
401
  ) -> str | None:
375
402
  cmd_and_args = ssh_cmd( # skipif-ci
376
403
  user,
@@ -380,17 +407,61 @@ def ssh(
380
407
  host_key_algorithms=host_key_algorithms,
381
408
  strict_host_key_checking=strict_host_key_checking,
382
409
  )
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
- )
410
+ try: # skipif-ci
411
+ return run(
412
+ *cmd_and_args,
413
+ input=input,
414
+ print=print,
415
+ print_stdout=print_stdout,
416
+ print_stderr=print_stderr,
417
+ return_=return_,
418
+ return_stdout=return_stdout,
419
+ return_stderr=return_stderr,
420
+ logger=logger,
421
+ )
422
+ except CalledProcessError as error: # skipif-ci
423
+ if retry is None:
424
+ raise
425
+ attempts, delta = retry
426
+ if attempts <= 0:
427
+ raise
428
+ if logger is not None:
429
+ msg = strip_and_dedent(f"""
430
+ 'ssh' failed with:
431
+ - user = {user}
432
+ - hostname = {hostname}
433
+ - cmd_and_cmds_or_args = {cmd_and_cmds_or_args}
434
+ - batch_mode = {batch_mode}
435
+ - host_key_algorithms = {host_key_algorithms}
436
+ - strict_host_key_checking = {strict_host_key_checking}
437
+ - input = {input}
438
+
439
+ -- stdout ---------------------------------------------------------------------
440
+ {error.stdout}-------------------------------------------------------------------------------
441
+ -- stderr ---------------------------------------------------------------------
442
+ {error.stderr}-------------------------------------------------------------------------------
443
+
444
+ Retrying {attempts} more time(s) after {delta}...
445
+ """)
446
+ to_logger(logger).error(msg)
447
+ sleep(delta.in_seconds())
448
+ return ssh(
449
+ user,
450
+ hostname,
451
+ *cmd_and_cmds_or_args,
452
+ batch_mode=batch_mode,
453
+ host_key_algorithms=host_key_algorithms,
454
+ strict_host_key_checking=strict_host_key_checking,
455
+ input=input,
456
+ print=print,
457
+ print_stdout=print_stdout,
458
+ print_stderr=print_stderr,
459
+ return_=return_,
460
+ return_stdout=return_stdout,
461
+ return_stderr=return_stderr,
462
+ logger=logger,
463
+ retry=(attempts - 1, delta),
464
+ )
394
465
 
395
466
 
396
467
  def ssh_cmd(
@@ -419,6 +490,21 @@ def touch_cmd(path: PathLike, /) -> list[str]:
419
490
  return ["touch", str(path)]
420
491
 
421
492
 
493
+ @contextmanager
494
+ def yield_ssh_temp_dir(
495
+ user: str, hostname: str, /, *, keep: bool = False, logger: LoggerLike | None = None
496
+ ) -> Iterator[Path]:
497
+ path = Path(ssh(user, hostname, *MKTEMP_DIR_CMD, return_=True))
498
+ try:
499
+ yield path
500
+ finally:
501
+ if keep:
502
+ if logger is not None:
503
+ to_logger(logger).info("Keeping temporary directory '%s'...", path)
504
+ else:
505
+ ssh(user, hostname, *rm_cmd(path))
506
+
507
+
422
508
  __all__ = [
423
509
  "BASH_LC",
424
510
  "BASH_LS",
@@ -434,4 +520,5 @@ __all__ = [
434
520
  "ssh_cmd",
435
521
  "sudo_cmd",
436
522
  "touch_cmd",
523
+ "yield_ssh_temp_dir",
437
524
  ]