dycw-utilities 0.174.19__py3-none-any.whl → 0.175.6__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.
- {dycw_utilities-0.174.19.dist-info → dycw_utilities-0.175.6.dist-info}/METADATA +4 -11
- {dycw_utilities-0.174.19.dist-info → dycw_utilities-0.175.6.dist-info}/RECORD +8 -9
- {dycw_utilities-0.174.19.dist-info → dycw_utilities-0.175.6.dist-info}/entry_points.txt +0 -2
- utilities/__init__.py +1 -1
- utilities/altair.py +3 -1
- utilities/pytest_regressions.py +25 -5
- utilities/subprocess.py +226 -32
- utilities/aeventkit.py +0 -389
- {dycw_utilities-0.174.19.dist-info → dycw_utilities-0.175.6.dist-info}/WHEEL +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: dycw-utilities
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.175.6
|
|
4
|
+
Summary: Miscellaneous Python utilities
|
|
4
5
|
Author: Derek Wan
|
|
5
6
|
Author-email: Derek Wan <d.wan@icloud.com>
|
|
6
7
|
Requires-Dist: atomicwrites>=1.4.1,<1.5
|
|
@@ -28,14 +29,6 @@ Provides-Extra: logging
|
|
|
28
29
|
Provides-Extra: test
|
|
29
30
|
Description-Content-Type: text/markdown
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
# `python-utilities`
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
[All the Python functions I don't want to write twice.](https://github.com/nvim-lua/plenary.nvim)
|
|
36
|
-
|
|
37
|
-
## Installation
|
|
38
|
-
|
|
39
|
-
- `pip install dycw-utilities`
|
|
40
|
-
|
|
41
|
-
or with [extras](https://github.com/dycw/python-utilities/blob/master/pyproject.toml).
|
|
34
|
+
Miscellaneous Python utilities
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
utilities/__init__.py,sha256=
|
|
2
|
-
utilities/
|
|
3
|
-
utilities/altair.py,sha256=rUK99g9x6CYDDfiZrf-aTx5fSRbL1Q8ctgKORowzXHg,9060
|
|
1
|
+
utilities/__init__.py,sha256=7y6XPRHYRyZjE6Vfy185IzZfDUJT8hoQcrfKUT3_4RU,60
|
|
2
|
+
utilities/altair.py,sha256=TLfRFbG9HwG7SLXoJ-v0r-t49ZaGgTQZD82cpjVi4vs,9085
|
|
4
3
|
utilities/asyncio.py,sha256=aJySVxBY0gqsIYnoNmH7-1r8djKuf4vSsU69VCD08t8,16772
|
|
5
4
|
utilities/atomicwrites.py,sha256=tPo6r-Rypd9u99u66B9z86YBPpnLrlHtwox_8Z7T34Y,5790
|
|
6
5
|
utilities/atools.py,sha256=6neeCcgXxK2dlsc0xp15Za7nSucbCgFtAJepGI_-WXU,2549
|
|
@@ -66,7 +65,7 @@ utilities/pytest.py,sha256=9HHwYgZQe6CRF0ekHQEFH05gmoP4Ne0V54RrtUNDfi4,10524
|
|
|
66
65
|
utilities/pytest_plugins/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
|
|
67
66
|
utilities/pytest_plugins/pytest_randomly.py,sha256=B1qYVlExGOxTywq2r1SMi5o7btHLk2PNdY_b1p98dkE,409
|
|
68
67
|
utilities/pytest_plugins/pytest_regressions.py,sha256=mnHYBfdprz50UGVkVzV1bZERZN5CFfoF8YbokGxdFwU,1639
|
|
69
|
-
utilities/pytest_regressions.py,sha256=
|
|
68
|
+
utilities/pytest_regressions.py,sha256=tJxW38u-zpoyjW1N4zogBx4V_07r-ibDInddcEUyXmc,4763
|
|
70
69
|
utilities/random.py,sha256=hZlH4gnAtoaofWswuJYjcygejrY8db4CzP-z_adO2Mo,4165
|
|
71
70
|
utilities/re.py,sha256=S4h-DLL6ScMPqjboZ_uQ1BVTJajrqV06r_81D--_HCE,4573
|
|
72
71
|
utilities/redis.py,sha256=gybjqKea33Jy50n4dHTS14JdquqHaJqHF2dixQljYWQ,30172
|
|
@@ -81,7 +80,7 @@ utilities/sqlalchemy.py,sha256=HQYpd7LFxdTF5WYVWYtCJeEBI71EJm7ytvCGyAH9B-U,37163
|
|
|
81
80
|
utilities/sqlalchemy_polars.py,sha256=JCGhB37raSR7fqeWV5dTsciRTMVzIdVT9YSqKT0piT0,13370
|
|
82
81
|
utilities/statsmodels.py,sha256=koyiBHvpMcSiBfh99wFUfSggLNx7cuAw3rwyfAhoKpQ,3410
|
|
83
82
|
utilities/string.py,sha256=shmBK87zZwzGyixuNuXCiUbqzfeZ9xlrFwz6JTaRvDk,582
|
|
84
|
-
utilities/subprocess.py,sha256=
|
|
83
|
+
utilities/subprocess.py,sha256=C9H8GiV4gsNRRJY5mo8_2DcNqfeonxcfT5o6icQeSkg,40599
|
|
85
84
|
utilities/tempfile.py,sha256=Lx6qa16lL1XVH6WdmD_G9vlN6gLI8nrIurxmsFkPKvg,3022
|
|
86
85
|
utilities/testbook.py,sha256=j1KmaVbrX9VrbeMgtPh5gk55myAsn3dyRUn7jGbPbRk,1294
|
|
87
86
|
utilities/text.py,sha256=7SvwcSR2l_5cOrm1samGnR4C-ZI6qyFLHLzSpO1zeHQ,13958
|
|
@@ -98,7 +97,7 @@ utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
|
|
|
98
97
|
utilities/whenever.py,sha256=F4ek0-OBWxHYrZdmoZt76N2RnNyKY5KrEHt7rqO4AQE,60183
|
|
99
98
|
utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
|
|
100
99
|
utilities/zoneinfo.py,sha256=tdIScrTB2-B-LH0ukb1HUXKooLknOfJNwHk10MuMYvA,3619
|
|
101
|
-
dycw_utilities-0.
|
|
102
|
-
dycw_utilities-0.
|
|
103
|
-
dycw_utilities-0.
|
|
104
|
-
dycw_utilities-0.
|
|
100
|
+
dycw_utilities-0.175.6.dist-info/WHEEL,sha256=RRVLqVugUmFOqBedBFAmA4bsgFcROUBiSUKlERi0Hcg,79
|
|
101
|
+
dycw_utilities-0.175.6.dist-info/entry_points.txt,sha256=cOGtKeJI0KXLSV7MJ8Dhc2G8jPgDcBDm53MVNJU4ycI,136
|
|
102
|
+
dycw_utilities-0.175.6.dist-info/METADATA,sha256=761YNYgBWP5Xs0xN_xnVhdet11rc3wj-eIQfF_XoqNk,1442
|
|
103
|
+
dycw_utilities-0.175.6.dist-info/RECORD,,
|
utilities/__init__.py
CHANGED
utilities/altair.py
CHANGED
|
@@ -145,7 +145,9 @@ def plot_dataframes(
|
|
|
145
145
|
]
|
|
146
146
|
zoom = selection_interval(bind="scales", encodings=["x"])
|
|
147
147
|
chart = (
|
|
148
|
-
|
|
148
|
+
vconcat_charts(*layers)
|
|
149
|
+
.add_params(zoom)
|
|
150
|
+
.resolve_scale(color="independent", x="shared")
|
|
149
151
|
)
|
|
150
152
|
if title is not None:
|
|
151
153
|
chart = chart.properties(title=title)
|
utilities/pytest_regressions.py
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from contextlib import suppress
|
|
4
|
+
from dataclasses import dataclass
|
|
4
5
|
from json import loads
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from shutil import copytree
|
|
7
|
-
from typing import TYPE_CHECKING, Any, assert_never
|
|
8
|
+
from typing import TYPE_CHECKING, Any, assert_never, override
|
|
8
9
|
|
|
9
10
|
from pytest_regressions.file_regression import FileRegressionFixture
|
|
10
11
|
|
|
11
12
|
from utilities.functions import ensure_str
|
|
12
13
|
from utilities.operator import is_equal
|
|
14
|
+
from utilities.reprlib import get_repr
|
|
13
15
|
|
|
14
16
|
if TYPE_CHECKING:
|
|
15
17
|
from polars import DataFrame, Series
|
|
@@ -70,10 +72,28 @@ class OrjsonRegressionFixture:
|
|
|
70
72
|
check_fn=self._check_fn,
|
|
71
73
|
)
|
|
72
74
|
|
|
73
|
-
def _check_fn(self,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
def _check_fn(self, path_obtained: Path, path_existing: Path, /) -> None:
|
|
76
|
+
obtained = loads(path_obtained.read_text())
|
|
77
|
+
existing = loads(path_existing.read_text())
|
|
78
|
+
if not is_equal(obtained, existing):
|
|
79
|
+
raise OrjsonRegressionError(
|
|
80
|
+
path_obtained=path_obtained,
|
|
81
|
+
path_existing=path_existing,
|
|
82
|
+
obtained=obtained,
|
|
83
|
+
existing=existing,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass(kw_only=True, slots=True)
|
|
88
|
+
class OrjsonRegressionError(Exception):
|
|
89
|
+
path_obtained: Path
|
|
90
|
+
path_existing: Path
|
|
91
|
+
obtained: Any
|
|
92
|
+
existing: Any
|
|
93
|
+
|
|
94
|
+
@override
|
|
95
|
+
def __str__(self) -> str:
|
|
96
|
+
return f"Obtained object (at {str(self.path_obtained)!r}) and existing object (at {str(self.path_existing)!r}) differ; got {get_repr(self.obtained)} and {get_repr(self.existing)}"
|
|
77
97
|
|
|
78
98
|
|
|
79
99
|
##
|
utilities/subprocess.py
CHANGED
|
@@ -6,6 +6,7 @@ from contextlib import contextmanager
|
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from io import StringIO
|
|
8
8
|
from pathlib import Path
|
|
9
|
+
from re import search
|
|
9
10
|
from shlex import join
|
|
10
11
|
from shutil import copyfile, copytree, move, rmtree
|
|
11
12
|
from string import Template
|
|
@@ -23,7 +24,7 @@ from utilities.text import strip_and_dedent
|
|
|
23
24
|
from utilities.whenever import to_seconds
|
|
24
25
|
|
|
25
26
|
if TYPE_CHECKING:
|
|
26
|
-
from collections.abc import Iterator
|
|
27
|
+
from collections.abc import Callable, Iterator
|
|
27
28
|
|
|
28
29
|
from utilities.permissions import PermissionsLike
|
|
29
30
|
from utilities.types import (
|
|
@@ -41,6 +42,7 @@ APT_UPDATE = ["apt", "update", "-y"]
|
|
|
41
42
|
BASH_LC = ["bash", "-lc"]
|
|
42
43
|
BASH_LS = ["bash", "-ls"]
|
|
43
44
|
GIT_BRANCH_SHOW_CURRENT = ["git", "branch", "--show-current"]
|
|
45
|
+
KNOWN_HOSTS = Path.home() / ".ssh/known_hosts"
|
|
44
46
|
MKTEMP_DIR_CMD = ["mktemp", "-d"]
|
|
45
47
|
RESTART_SSHD = ["systemctl", "restart", "sshd"]
|
|
46
48
|
UPDATE_CA_CERTIFICATES: str = "update-ca-certificates"
|
|
@@ -49,6 +51,13 @@ UPDATE_CA_CERTIFICATES: str = "update-ca-certificates"
|
|
|
49
51
|
##
|
|
50
52
|
|
|
51
53
|
|
|
54
|
+
def apt_install(package: str, /, *, update: bool = False, sudo: bool = False) -> None:
|
|
55
|
+
"""Install a package."""
|
|
56
|
+
if update: # pragma: no cover
|
|
57
|
+
run(*maybe_sudo_cmd(*APT_UPDATE, sudo=sudo))
|
|
58
|
+
run(*maybe_sudo_cmd(*apt_install_cmd(package), sudo=sudo))
|
|
59
|
+
|
|
60
|
+
|
|
52
61
|
def apt_install_cmd(package: str, /) -> list[str]:
|
|
53
62
|
"""Command to use 'apt' to install a package."""
|
|
54
63
|
return ["apt", "install", "-y", package]
|
|
@@ -250,7 +259,7 @@ def git_clone(
|
|
|
250
259
|
rm(path, sudo=sudo)
|
|
251
260
|
run(*maybe_sudo_cmd(*git_clone_cmd(url, path), sudo=sudo))
|
|
252
261
|
if branch is not None:
|
|
253
|
-
|
|
262
|
+
git_checkout(branch, path)
|
|
254
263
|
|
|
255
264
|
|
|
256
265
|
def git_clone_cmd(url: str, path: PathLike, /) -> list[str]:
|
|
@@ -261,15 +270,6 @@ def git_clone_cmd(url: str, path: PathLike, /) -> list[str]:
|
|
|
261
270
|
##
|
|
262
271
|
|
|
263
272
|
|
|
264
|
-
def git_hard_reset_cmd(*, branch: str | None = None) -> list[str]:
|
|
265
|
-
"""Command to use 'git hard-reset' to hard reset a repository."""
|
|
266
|
-
branch_use = "master" if branch is None else branch
|
|
267
|
-
return ["git", "hard-reset", branch_use]
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
##
|
|
271
|
-
|
|
272
|
-
|
|
273
273
|
def maybe_parent(path: PathLike, /, *, parent: bool = False) -> Path:
|
|
274
274
|
"""Get the parent of a path, if required."""
|
|
275
275
|
path = Path(path)
|
|
@@ -525,6 +525,7 @@ def rsync_many(
|
|
|
525
525
|
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
526
526
|
strict_host_key_checking: bool = True,
|
|
527
527
|
print: bool = False, # noqa: A002
|
|
528
|
+
exclude: MaybeIterable[str] | None = None,
|
|
528
529
|
) -> None:
|
|
529
530
|
cmds: list[list[str]] = [] # skipif-ci
|
|
530
531
|
with ( # skipif-ci
|
|
@@ -562,6 +563,7 @@ def rsync_many(
|
|
|
562
563
|
print=print,
|
|
563
564
|
retry=retry,
|
|
564
565
|
logger=logger,
|
|
566
|
+
exclude=exclude,
|
|
565
567
|
)
|
|
566
568
|
ssh(
|
|
567
569
|
user,
|
|
@@ -627,6 +629,7 @@ def run(
|
|
|
627
629
|
return_stdout: bool = False,
|
|
628
630
|
return_stderr: bool = False,
|
|
629
631
|
retry: Retry | None = None,
|
|
632
|
+
retry_skip: Callable[[int, str, str], bool] | None = None,
|
|
630
633
|
logger: LoggerLike | None = None,
|
|
631
634
|
) -> str: ...
|
|
632
635
|
@overload
|
|
@@ -647,6 +650,7 @@ def run(
|
|
|
647
650
|
return_stdout: Literal[True],
|
|
648
651
|
return_stderr: bool = False,
|
|
649
652
|
retry: Retry | None = None,
|
|
653
|
+
retry_skip: Callable[[int, str, str], bool] | None = None,
|
|
650
654
|
logger: LoggerLike | None = None,
|
|
651
655
|
) -> str: ...
|
|
652
656
|
@overload
|
|
@@ -667,6 +671,7 @@ def run(
|
|
|
667
671
|
return_stdout: bool = False,
|
|
668
672
|
return_stderr: Literal[True],
|
|
669
673
|
retry: Retry | None = None,
|
|
674
|
+
retry_skip: Callable[[int, str, str], bool] | None = None,
|
|
670
675
|
logger: LoggerLike | None = None,
|
|
671
676
|
) -> str: ...
|
|
672
677
|
@overload
|
|
@@ -687,6 +692,7 @@ def run(
|
|
|
687
692
|
return_stdout: Literal[False] = False,
|
|
688
693
|
return_stderr: Literal[False] = False,
|
|
689
694
|
retry: Retry | None = None,
|
|
695
|
+
retry_skip: Callable[[int, str, str], bool] | None = None,
|
|
690
696
|
logger: LoggerLike | None = None,
|
|
691
697
|
) -> None: ...
|
|
692
698
|
@overload
|
|
@@ -707,6 +713,7 @@ def run(
|
|
|
707
713
|
return_stdout: bool = False,
|
|
708
714
|
return_stderr: bool = False,
|
|
709
715
|
retry: Retry | None = None,
|
|
716
|
+
retry_skip: Callable[[int, str, str], bool] | None = None,
|
|
710
717
|
logger: LoggerLike | None = None,
|
|
711
718
|
) -> str | None: ...
|
|
712
719
|
def run(
|
|
@@ -726,6 +733,7 @@ def run(
|
|
|
726
733
|
return_stdout: bool = False,
|
|
727
734
|
return_stderr: bool = False,
|
|
728
735
|
retry: Retry | None = None,
|
|
736
|
+
retry_skip: Callable[[int, str, str], bool] | None = None,
|
|
729
737
|
logger: LoggerLike | None = None,
|
|
730
738
|
) -> str | None:
|
|
731
739
|
"""Run a command in a subprocess."""
|
|
@@ -783,14 +791,18 @@ def run(
|
|
|
783
791
|
case 0, False, False:
|
|
784
792
|
return None
|
|
785
793
|
case _, _, _:
|
|
786
|
-
if retry is None:
|
|
787
|
-
attempts = delta = None
|
|
788
|
-
else:
|
|
789
|
-
attempts, delta = retry
|
|
790
794
|
_ = stdout.seek(0)
|
|
791
795
|
stdout_text = stdout.read()
|
|
792
796
|
_ = stderr.seek(0)
|
|
793
797
|
stderr_text = stderr.read()
|
|
798
|
+
if (retry is None) or (
|
|
799
|
+
(retry is not None)
|
|
800
|
+
and (retry_skip is not None)
|
|
801
|
+
and retry_skip(return_code, stdout_text, stderr_text)
|
|
802
|
+
):
|
|
803
|
+
attempts = delta = None
|
|
804
|
+
else:
|
|
805
|
+
attempts, delta = retry
|
|
794
806
|
if logger is not None:
|
|
795
807
|
msg = strip_and_dedent(f"""
|
|
796
808
|
'run' failed with:
|
|
@@ -885,6 +897,7 @@ def ssh(
|
|
|
885
897
|
batch_mode: bool = True,
|
|
886
898
|
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
887
899
|
strict_host_key_checking: bool = True,
|
|
900
|
+
port: int | None = None,
|
|
888
901
|
input: str | None = None,
|
|
889
902
|
print: bool = False,
|
|
890
903
|
print_stdout: bool = False,
|
|
@@ -904,6 +917,7 @@ def ssh(
|
|
|
904
917
|
batch_mode: bool = True,
|
|
905
918
|
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
906
919
|
strict_host_key_checking: bool = True,
|
|
920
|
+
port: int | None = None,
|
|
907
921
|
input: str | None = None,
|
|
908
922
|
print: bool = False,
|
|
909
923
|
print_stdout: bool = False,
|
|
@@ -923,6 +937,7 @@ def ssh(
|
|
|
923
937
|
batch_mode: bool = True,
|
|
924
938
|
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
925
939
|
strict_host_key_checking: bool = True,
|
|
940
|
+
port: int | None = None,
|
|
926
941
|
input: str | None = None,
|
|
927
942
|
print: bool = False,
|
|
928
943
|
print_stdout: bool = False,
|
|
@@ -942,6 +957,7 @@ def ssh(
|
|
|
942
957
|
batch_mode: bool = True,
|
|
943
958
|
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
944
959
|
strict_host_key_checking: bool = True,
|
|
960
|
+
port: int | None = None,
|
|
945
961
|
input: str | None = None,
|
|
946
962
|
print: bool = False,
|
|
947
963
|
print_stdout: bool = False,
|
|
@@ -961,6 +977,7 @@ def ssh(
|
|
|
961
977
|
batch_mode: bool = True,
|
|
962
978
|
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
963
979
|
strict_host_key_checking: bool = True,
|
|
980
|
+
port: int | None = None,
|
|
964
981
|
input: str | None = None,
|
|
965
982
|
print: bool = False,
|
|
966
983
|
print_stdout: bool = False,
|
|
@@ -979,6 +996,7 @@ def ssh(
|
|
|
979
996
|
batch_mode: bool = True,
|
|
980
997
|
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
981
998
|
strict_host_key_checking: bool = True,
|
|
999
|
+
port: int | None = None,
|
|
982
1000
|
input: str | None = None, # noqa: A002
|
|
983
1001
|
print: bool = False, # noqa: A002
|
|
984
1002
|
print_stdout: bool = False,
|
|
@@ -997,19 +1015,57 @@ def ssh(
|
|
|
997
1015
|
batch_mode=batch_mode,
|
|
998
1016
|
host_key_algorithms=host_key_algorithms,
|
|
999
1017
|
strict_host_key_checking=strict_host_key_checking,
|
|
1018
|
+
port=port,
|
|
1000
1019
|
)
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1020
|
+
try: # skipif-ci
|
|
1021
|
+
return run(
|
|
1022
|
+
*cmd_and_args,
|
|
1023
|
+
input=input,
|
|
1024
|
+
print=print,
|
|
1025
|
+
print_stdout=print_stdout,
|
|
1026
|
+
print_stderr=print_stderr,
|
|
1027
|
+
return_=return_,
|
|
1028
|
+
return_stdout=return_stdout,
|
|
1029
|
+
return_stderr=return_stderr,
|
|
1030
|
+
retry=retry,
|
|
1031
|
+
retry_skip=_ssh_retry_skip,
|
|
1032
|
+
logger=logger,
|
|
1033
|
+
)
|
|
1034
|
+
except CalledProcessError as error: # skipif-ci
|
|
1035
|
+
if not _ssh_is_strict_checking_error(error.stderr):
|
|
1036
|
+
raise
|
|
1037
|
+
ssh_keyscan(hostname, port=port)
|
|
1038
|
+
return ssh(
|
|
1039
|
+
user,
|
|
1040
|
+
hostname,
|
|
1041
|
+
*cmd_and_cmds_or_args,
|
|
1042
|
+
batch_mode=batch_mode,
|
|
1043
|
+
host_key_algorithms=host_key_algorithms,
|
|
1044
|
+
strict_host_key_checking=strict_host_key_checking,
|
|
1045
|
+
port=port,
|
|
1046
|
+
input=input,
|
|
1047
|
+
print=print,
|
|
1048
|
+
print_stdout=print_stdout,
|
|
1049
|
+
print_stderr=print_stderr,
|
|
1050
|
+
return_=return_,
|
|
1051
|
+
return_stdout=return_stdout,
|
|
1052
|
+
return_stderr=return_stderr,
|
|
1053
|
+
retry=retry,
|
|
1054
|
+
logger=logger,
|
|
1055
|
+
)
|
|
1056
|
+
|
|
1057
|
+
|
|
1058
|
+
def _ssh_retry_skip(return_code: int, stdout: str, stderr: str, /) -> bool:
|
|
1059
|
+
_ = (return_code, stdout)
|
|
1060
|
+
return _ssh_is_strict_checking_error(stderr)
|
|
1061
|
+
|
|
1062
|
+
|
|
1063
|
+
def _ssh_is_strict_checking_error(text: str, /) -> bool:
|
|
1064
|
+
match = search(
|
|
1065
|
+
"No ED25519 host key is known for .* and you have requested strict checking",
|
|
1066
|
+
text,
|
|
1012
1067
|
)
|
|
1068
|
+
return match is not None
|
|
1013
1069
|
|
|
1014
1070
|
|
|
1015
1071
|
def ssh_cmd(
|
|
@@ -1020,12 +1076,14 @@ def ssh_cmd(
|
|
|
1020
1076
|
batch_mode: bool = True,
|
|
1021
1077
|
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
1022
1078
|
strict_host_key_checking: bool = True,
|
|
1079
|
+
port: int | None = None,
|
|
1023
1080
|
) -> list[str]:
|
|
1024
1081
|
"""Command to use 'ssh' to execute a command on a remote machine."""
|
|
1025
1082
|
args: list[str] = ssh_opts_cmd(
|
|
1026
1083
|
batch_mode=batch_mode,
|
|
1027
1084
|
host_key_algorithms=host_key_algorithms,
|
|
1028
1085
|
strict_host_key_checking=strict_host_key_checking,
|
|
1086
|
+
port=port,
|
|
1029
1087
|
)
|
|
1030
1088
|
return [*args, f"{user}@{hostname}", *cmd_and_cmds_or_args]
|
|
1031
1089
|
|
|
@@ -1035,6 +1093,7 @@ def ssh_opts_cmd(
|
|
|
1035
1093
|
batch_mode: bool = True,
|
|
1036
1094
|
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
1037
1095
|
strict_host_key_checking: bool = True,
|
|
1096
|
+
port: int | None = None,
|
|
1038
1097
|
) -> list[str]:
|
|
1039
1098
|
"""Command to use prepare 'ssh' to execute a command on a remote machine."""
|
|
1040
1099
|
args: list[str] = ["ssh"]
|
|
@@ -1043,15 +1102,47 @@ def ssh_opts_cmd(
|
|
|
1043
1102
|
args.extend(["-o", f"HostKeyAlgorithms={','.join(host_key_algorithms)}"])
|
|
1044
1103
|
if strict_host_key_checking:
|
|
1045
1104
|
args.extend(["-o", "StrictHostKeyChecking=yes"])
|
|
1105
|
+
if port is not None:
|
|
1106
|
+
args.extend(["-p", str(port)])
|
|
1046
1107
|
return [*args, "-T"]
|
|
1047
1108
|
|
|
1048
1109
|
|
|
1049
1110
|
##
|
|
1050
1111
|
|
|
1051
1112
|
|
|
1052
|
-
def
|
|
1053
|
-
|
|
1054
|
-
|
|
1113
|
+
def ssh_keyscan(
|
|
1114
|
+
hostname: str, /, *, path: PathLike = KNOWN_HOSTS, port: int | None = None
|
|
1115
|
+
) -> None:
|
|
1116
|
+
"""Add a known host."""
|
|
1117
|
+
ssh_keygen_remove(hostname, path=path) # skipif-ci
|
|
1118
|
+
mkdir(path, parent=True) # skipif-ci
|
|
1119
|
+
with Path(path).open(mode="a") as fh: # skipif-ci
|
|
1120
|
+
_ = fh.write(run(*ssh_keyscan_cmd(hostname, port=port), return_=True))
|
|
1121
|
+
|
|
1122
|
+
|
|
1123
|
+
def ssh_keyscan_cmd(hostname: str, /, *, port: int | None = None) -> list[str]:
|
|
1124
|
+
"""Command to use 'ssh-keyscan' to add a known host."""
|
|
1125
|
+
args: list[str] = ["ssh-keyscan"]
|
|
1126
|
+
if port is not None:
|
|
1127
|
+
args.extend(["-p", str(port)])
|
|
1128
|
+
return [*args, "-q", "-t", "ed25519", hostname]
|
|
1129
|
+
|
|
1130
|
+
|
|
1131
|
+
##
|
|
1132
|
+
|
|
1133
|
+
|
|
1134
|
+
def ssh_keygen_remove(hostname: str, /, *, path: PathLike = KNOWN_HOSTS) -> None:
|
|
1135
|
+
"""Remove a known host."""
|
|
1136
|
+
path = Path(path)
|
|
1137
|
+
if path.exists():
|
|
1138
|
+
run(*ssh_keygen_remove_cmd(hostname, path=path))
|
|
1139
|
+
|
|
1140
|
+
|
|
1141
|
+
def ssh_keygen_remove_cmd(
|
|
1142
|
+
hostname: str, /, *, path: PathLike = KNOWN_HOSTS
|
|
1143
|
+
) -> list[str]:
|
|
1144
|
+
"""Command to use 'ssh-keygen' to remove a known host."""
|
|
1145
|
+
return ["ssh-keygen", "-f", str(path), "-R", hostname]
|
|
1055
1146
|
|
|
1056
1147
|
|
|
1057
1148
|
##
|
|
@@ -1134,9 +1225,108 @@ def touch_cmd(path: PathLike, /) -> list[str]:
|
|
|
1134
1225
|
##
|
|
1135
1226
|
|
|
1136
1227
|
|
|
1137
|
-
|
|
1228
|
+
@overload
|
|
1229
|
+
def uv_run(
|
|
1230
|
+
module: str,
|
|
1231
|
+
/,
|
|
1232
|
+
*args: str,
|
|
1233
|
+
cwd: PathLike | None = None,
|
|
1234
|
+
print: bool = False,
|
|
1235
|
+
print_stdout: bool = False,
|
|
1236
|
+
print_stderr: bool = False,
|
|
1237
|
+
return_: Literal[True],
|
|
1238
|
+
return_stdout: bool = False,
|
|
1239
|
+
return_stderr: bool = False,
|
|
1240
|
+
retry: Retry | None = None,
|
|
1241
|
+
logger: LoggerLike | None = None,
|
|
1242
|
+
) -> str: ...
|
|
1243
|
+
@overload
|
|
1244
|
+
def uv_run(
|
|
1245
|
+
module: str,
|
|
1246
|
+
/,
|
|
1247
|
+
*args: str,
|
|
1248
|
+
cwd: PathLike | None = None,
|
|
1249
|
+
print: bool = False,
|
|
1250
|
+
print_stdout: bool = False,
|
|
1251
|
+
print_stderr: bool = False,
|
|
1252
|
+
return_: bool = False,
|
|
1253
|
+
return_stdout: Literal[True],
|
|
1254
|
+
return_stderr: bool = False,
|
|
1255
|
+
retry: Retry | None = None,
|
|
1256
|
+
logger: LoggerLike | None = None,
|
|
1257
|
+
) -> str: ...
|
|
1258
|
+
@overload
|
|
1259
|
+
def uv_run(
|
|
1260
|
+
module: str,
|
|
1261
|
+
/,
|
|
1262
|
+
*args: str,
|
|
1263
|
+
cwd: PathLike | None = None,
|
|
1264
|
+
print: bool = False,
|
|
1265
|
+
print_stdout: bool = False,
|
|
1266
|
+
print_stderr: bool = False,
|
|
1267
|
+
return_: bool = False,
|
|
1268
|
+
return_stdout: bool = False,
|
|
1269
|
+
return_stderr: Literal[True],
|
|
1270
|
+
retry: Retry | None = None,
|
|
1271
|
+
logger: LoggerLike | None = None,
|
|
1272
|
+
) -> str: ...
|
|
1273
|
+
@overload
|
|
1274
|
+
def uv_run(
|
|
1275
|
+
module: str,
|
|
1276
|
+
/,
|
|
1277
|
+
*args: str,
|
|
1278
|
+
cwd: PathLike | None = None,
|
|
1279
|
+
print: bool = False,
|
|
1280
|
+
print_stdout: bool = False,
|
|
1281
|
+
print_stderr: bool = False,
|
|
1282
|
+
return_: Literal[False] = False,
|
|
1283
|
+
return_stdout: Literal[False] = False,
|
|
1284
|
+
return_stderr: Literal[False] = False,
|
|
1285
|
+
retry: Retry | None = None,
|
|
1286
|
+
logger: LoggerLike | None = None,
|
|
1287
|
+
) -> None: ...
|
|
1288
|
+
@overload
|
|
1289
|
+
def uv_run(
|
|
1290
|
+
module: str,
|
|
1291
|
+
/,
|
|
1292
|
+
*args: str,
|
|
1293
|
+
cwd: PathLike | None = None,
|
|
1294
|
+
print: bool = False,
|
|
1295
|
+
print_stdout: bool = False,
|
|
1296
|
+
print_stderr: bool = False,
|
|
1297
|
+
return_: bool = False,
|
|
1298
|
+
return_stdout: bool = False,
|
|
1299
|
+
return_stderr: bool = False,
|
|
1300
|
+
retry: Retry | None = None,
|
|
1301
|
+
logger: LoggerLike | None = None,
|
|
1302
|
+
) -> str | None: ...
|
|
1303
|
+
def uv_run(
|
|
1304
|
+
module: str,
|
|
1305
|
+
/,
|
|
1306
|
+
*args: str,
|
|
1307
|
+
cwd: PathLike | None = None,
|
|
1308
|
+
print: bool = False, # noqa: A002
|
|
1309
|
+
print_stdout: bool = False,
|
|
1310
|
+
print_stderr: bool = False,
|
|
1311
|
+
return_: bool = False,
|
|
1312
|
+
return_stdout: bool = False,
|
|
1313
|
+
return_stderr: bool = False,
|
|
1314
|
+
retry: Retry | None = None,
|
|
1315
|
+
logger: LoggerLike | None = None,
|
|
1316
|
+
) -> str | None:
|
|
1138
1317
|
"""Run a command or script."""
|
|
1139
|
-
run(
|
|
1318
|
+
return run( # pragma: no cover
|
|
1319
|
+
*uv_run_cmd(module, *args),
|
|
1320
|
+
cwd=cwd,
|
|
1321
|
+
print=print,
|
|
1322
|
+
print_stdout=print_stdout,
|
|
1323
|
+
print_stderr=print_stderr,
|
|
1324
|
+
return_=return_,
|
|
1325
|
+
return_stdout=return_stdout,
|
|
1326
|
+
return_stderr=return_stderr,
|
|
1327
|
+
retry=retry,
|
|
1328
|
+
logger=logger,
|
|
1329
|
+
)
|
|
1140
1330
|
|
|
1141
1331
|
|
|
1142
1332
|
def uv_run_cmd(module: str, /, *args: str) -> list[str]:
|
|
@@ -1207,6 +1397,7 @@ __all__ = [
|
|
|
1207
1397
|
"RsyncCmdError",
|
|
1208
1398
|
"RsyncCmdNoSourcesError",
|
|
1209
1399
|
"RsyncCmdSourcesNotFoundError",
|
|
1400
|
+
"apt_install",
|
|
1210
1401
|
"apt_install_cmd",
|
|
1211
1402
|
"cd_cmd",
|
|
1212
1403
|
"chmod",
|
|
@@ -1222,7 +1413,6 @@ __all__ = [
|
|
|
1222
1413
|
"git_checkout_cmd",
|
|
1223
1414
|
"git_clone",
|
|
1224
1415
|
"git_clone_cmd",
|
|
1225
|
-
"git_hard_reset_cmd",
|
|
1226
1416
|
"maybe_parent",
|
|
1227
1417
|
"maybe_sudo_cmd",
|
|
1228
1418
|
"mkdir",
|
|
@@ -1238,6 +1428,10 @@ __all__ = [
|
|
|
1238
1428
|
"set_hostname_cmd",
|
|
1239
1429
|
"ssh",
|
|
1240
1430
|
"ssh_cmd",
|
|
1431
|
+
"ssh_keygen_remove",
|
|
1432
|
+
"ssh_keygen_remove_cmd",
|
|
1433
|
+
"ssh_keyscan",
|
|
1434
|
+
"ssh_keyscan_cmd",
|
|
1241
1435
|
"ssh_opts_cmd",
|
|
1242
1436
|
"sudo_cmd",
|
|
1243
1437
|
"sudo_nopasswd_cmd",
|
utilities/aeventkit.py
DELETED
|
@@ -1,389 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from dataclasses import dataclass
|
|
4
|
-
from functools import wraps
|
|
5
|
-
from inspect import iscoroutinefunction
|
|
6
|
-
from typing import TYPE_CHECKING, Any, Self, assert_never, cast, override
|
|
7
|
-
|
|
8
|
-
from eventkit import (
|
|
9
|
-
Constant,
|
|
10
|
-
Count,
|
|
11
|
-
DropWhile,
|
|
12
|
-
Enumerate,
|
|
13
|
-
Event,
|
|
14
|
-
Filter,
|
|
15
|
-
Fork,
|
|
16
|
-
Iterate,
|
|
17
|
-
Map,
|
|
18
|
-
Pack,
|
|
19
|
-
Partial,
|
|
20
|
-
PartialRight,
|
|
21
|
-
Pluck,
|
|
22
|
-
Skip,
|
|
23
|
-
Star,
|
|
24
|
-
Take,
|
|
25
|
-
TakeUntil,
|
|
26
|
-
TakeWhile,
|
|
27
|
-
Timestamp,
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
from utilities.functions import apply_decorators
|
|
31
|
-
from utilities.iterables import always_iterable
|
|
32
|
-
from utilities.logging import to_logger
|
|
33
|
-
|
|
34
|
-
if TYPE_CHECKING:
|
|
35
|
-
from collections.abc import Callable
|
|
36
|
-
|
|
37
|
-
from utilities.types import Coro, LoggerLike, MaybeCoro, MaybeIterable, TypeLike
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
##
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def add_listener[E: Event, F: Callable](
|
|
44
|
-
event: E,
|
|
45
|
-
listener: Callable[..., MaybeCoro[None]],
|
|
46
|
-
/,
|
|
47
|
-
*,
|
|
48
|
-
error: Callable[[Event, BaseException], MaybeCoro[None]] | None = None,
|
|
49
|
-
ignore: TypeLike[BaseException] | None = None,
|
|
50
|
-
logger: LoggerLike | None = None,
|
|
51
|
-
decorators: MaybeIterable[Callable[[F], F]] | None = None,
|
|
52
|
-
done: Callable[..., MaybeCoro[None]] | None = None,
|
|
53
|
-
keep_ref: bool = False,
|
|
54
|
-
) -> E:
|
|
55
|
-
"""Connect a listener to an event."""
|
|
56
|
-
lifted = lift_listener(
|
|
57
|
-
listener,
|
|
58
|
-
event,
|
|
59
|
-
error=error,
|
|
60
|
-
ignore=ignore,
|
|
61
|
-
logger=logger,
|
|
62
|
-
decorators=decorators,
|
|
63
|
-
)
|
|
64
|
-
return cast("E", event.connect(lifted, done=done, keep_ref=keep_ref))
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
##
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
@dataclass(repr=False, kw_only=True)
|
|
71
|
-
class LiftedEvent[F: Callable[..., MaybeCoro[None]]]:
|
|
72
|
-
"""A lifted version of `Event`."""
|
|
73
|
-
|
|
74
|
-
event: Event
|
|
75
|
-
|
|
76
|
-
def name(self) -> str:
|
|
77
|
-
return self.event.name() # pragma: no cover
|
|
78
|
-
|
|
79
|
-
def done(self) -> bool:
|
|
80
|
-
return self.event.done() # pragma: no cover
|
|
81
|
-
|
|
82
|
-
def set_done(self) -> None:
|
|
83
|
-
self.event.set_done() # pragma: no cover
|
|
84
|
-
|
|
85
|
-
def value(self) -> Any:
|
|
86
|
-
return self.event.value() # pragma: no cover
|
|
87
|
-
|
|
88
|
-
def connect[F2: Callable](
|
|
89
|
-
self,
|
|
90
|
-
listener: F,
|
|
91
|
-
/,
|
|
92
|
-
*,
|
|
93
|
-
error: Callable[[Event, BaseException], MaybeCoro[None]] | None = None,
|
|
94
|
-
ignore: TypeLike[BaseException] | None = None,
|
|
95
|
-
logger: LoggerLike | None = None,
|
|
96
|
-
decorators: MaybeIterable[Callable[[F2], F2]] | None = None,
|
|
97
|
-
done: Callable[..., MaybeCoro[None]] | None = None,
|
|
98
|
-
keep_ref: bool = False,
|
|
99
|
-
) -> Event:
|
|
100
|
-
return add_listener(
|
|
101
|
-
self.event,
|
|
102
|
-
listener,
|
|
103
|
-
error=error,
|
|
104
|
-
ignore=ignore,
|
|
105
|
-
logger=logger,
|
|
106
|
-
decorators=decorators,
|
|
107
|
-
done=done,
|
|
108
|
-
keep_ref=keep_ref,
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
def disconnect(
|
|
112
|
-
self, listener: Any, /, *, error: Any = None, done: Any = None
|
|
113
|
-
) -> Any:
|
|
114
|
-
return self.event.disconnect( # pragma: no cover
|
|
115
|
-
listener, error=error, done=done
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
def disconnect_obj(self, obj: Any, /) -> None:
|
|
119
|
-
self.event.disconnect_obj(obj) # pragma: no cover
|
|
120
|
-
|
|
121
|
-
def emit(self, *args: Any) -> None:
|
|
122
|
-
self.event.emit(*args) # pragma: no cover
|
|
123
|
-
|
|
124
|
-
def emit_threadsafe(self, *args: Any) -> None:
|
|
125
|
-
self.event.emit_threadsafe(*args) # pragma: no cover
|
|
126
|
-
|
|
127
|
-
def clear(self) -> None:
|
|
128
|
-
self.event.clear() # pragma: no cover
|
|
129
|
-
|
|
130
|
-
def run(self) -> list[Any]:
|
|
131
|
-
return self.event.run() # pragma: no cover
|
|
132
|
-
|
|
133
|
-
def pipe(self, *targets: Event) -> Event:
|
|
134
|
-
return self.event.pipe(*targets) # pragma: no cover
|
|
135
|
-
|
|
136
|
-
def fork(self, *targets: Event) -> Fork:
|
|
137
|
-
return self.event.fork(*targets) # pragma: no cover
|
|
138
|
-
|
|
139
|
-
def set_source(self, source: Any, /) -> None:
|
|
140
|
-
self.event.set_source(source) # pragma: no cover
|
|
141
|
-
|
|
142
|
-
def _onFinalize(self, ref: Any) -> None: # noqa: N802
|
|
143
|
-
self.event._onFinalize(ref) # noqa: SLF001 # pragma: no cover
|
|
144
|
-
|
|
145
|
-
async def aiter(self, *, skip_to_last: bool = False, tuples: bool = False) -> Any:
|
|
146
|
-
async for i in self.event.aiter( # pragma: no cover
|
|
147
|
-
skip_to_last=skip_to_last, tuples=tuples
|
|
148
|
-
):
|
|
149
|
-
yield i
|
|
150
|
-
|
|
151
|
-
__iadd__ = connect
|
|
152
|
-
__isub__ = disconnect
|
|
153
|
-
__call__ = emit
|
|
154
|
-
__or__ = pipe
|
|
155
|
-
|
|
156
|
-
@override
|
|
157
|
-
def __repr__(self) -> str:
|
|
158
|
-
return self.event.__repr__() # pragma: no cover
|
|
159
|
-
|
|
160
|
-
def __len__(self) -> int:
|
|
161
|
-
return self.event.__len__() # pragma: no cover
|
|
162
|
-
|
|
163
|
-
def __bool__(self) -> bool:
|
|
164
|
-
return self.event.__bool__() # pragma: no cover
|
|
165
|
-
|
|
166
|
-
def __getitem__(self, fork_targets: Any, /) -> Fork:
|
|
167
|
-
return self.event.__getitem__(fork_targets) # pragma: no cover
|
|
168
|
-
|
|
169
|
-
def __await__(self) -> Any:
|
|
170
|
-
return self.event.__await__() # pragma: no cover
|
|
171
|
-
|
|
172
|
-
def __aiter__(self) -> Any:
|
|
173
|
-
return self.event.aiter() # pragma: no cover
|
|
174
|
-
|
|
175
|
-
def __contains__(self, c: Any, /) -> bool:
|
|
176
|
-
return self.event.__contains__(c) # pragma: no cover
|
|
177
|
-
|
|
178
|
-
@override
|
|
179
|
-
def __reduce__(self) -> Any:
|
|
180
|
-
return self.event.__reduce__() # pragma: no cover
|
|
181
|
-
|
|
182
|
-
def filter(self, *, predicate: Any = bool) -> Filter:
|
|
183
|
-
return self.event.filter(predicate=predicate) # pragma: no cover
|
|
184
|
-
|
|
185
|
-
def skip(self, *, count: int = 1) -> Skip:
|
|
186
|
-
return self.event.skip(count=count) # pragma: no cover
|
|
187
|
-
|
|
188
|
-
def take(self, *, count: int = 1) -> Take:
|
|
189
|
-
return self.event.take(count=count) # pragma: no cover
|
|
190
|
-
|
|
191
|
-
def takewhile(self, *, predicate: Any = bool) -> TakeWhile:
|
|
192
|
-
return self.event.takewhile(predicate=predicate) # pragma: no cover
|
|
193
|
-
|
|
194
|
-
def dropwhile(self, *, predicate: Any = lambda x: not x) -> DropWhile: # pyright: ignore[reportUnknownLambdaType]
|
|
195
|
-
return self.event.dropwhile(predicate=predicate) # pragma: no cover
|
|
196
|
-
|
|
197
|
-
def takeuntil(self, notifier: Event, /) -> TakeUntil:
|
|
198
|
-
return self.event.takeuntil(notifier) # pragma: no cover
|
|
199
|
-
|
|
200
|
-
def constant(self, constant: Any, /) -> Constant:
|
|
201
|
-
return self.event.constant(constant) # pragma: no cover
|
|
202
|
-
|
|
203
|
-
def iterate(self, it: Any, /) -> Iterate:
|
|
204
|
-
return self.event.iterate(it) # pragma: no cover
|
|
205
|
-
|
|
206
|
-
def count(self, *, start: int = 0, step: int = 1) -> Count:
|
|
207
|
-
return self.event.count(start=start, step=step) # pragma: no cover
|
|
208
|
-
|
|
209
|
-
def enumerate(self, *, start: int = 0, step: int = 1) -> Enumerate:
|
|
210
|
-
return self.event.enumerate(start=start, step=step) # pragma: no cover
|
|
211
|
-
|
|
212
|
-
def timestamp(self) -> Timestamp:
|
|
213
|
-
return self.event.timestamp() # pragma: no cover
|
|
214
|
-
|
|
215
|
-
def partial(self, *left_args: Any) -> Partial:
|
|
216
|
-
return self.event.partial(*left_args) # pragma: no cover
|
|
217
|
-
|
|
218
|
-
def partial_right(self, *right_args: Any) -> PartialRight:
|
|
219
|
-
return self.event.partial_right(*right_args) # pragma: no cover
|
|
220
|
-
|
|
221
|
-
def star(self) -> Star:
|
|
222
|
-
return self.event.star() # pragma: no cover
|
|
223
|
-
|
|
224
|
-
def pack(self) -> Pack:
|
|
225
|
-
return self.event.pack() # pragma: no cover
|
|
226
|
-
|
|
227
|
-
def pluck(self, *selections: int | str) -> Pluck:
|
|
228
|
-
return self.event.pluck(*selections) # pragma: no cover
|
|
229
|
-
|
|
230
|
-
def map(
|
|
231
|
-
self,
|
|
232
|
-
func: Any,
|
|
233
|
-
/,
|
|
234
|
-
*,
|
|
235
|
-
timeout: float | None = None,
|
|
236
|
-
ordered: bool = True,
|
|
237
|
-
task_limit: int | None = None,
|
|
238
|
-
) -> Map:
|
|
239
|
-
return self.event.map( # pragma: no cover
|
|
240
|
-
func, timeout=timeout, ordered=ordered, task_limit=task_limit
|
|
241
|
-
)
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
##
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
class TypedEvent[F: Callable[..., MaybeCoro[None]]](Event):
|
|
248
|
-
"""A typed version of `Event`."""
|
|
249
|
-
|
|
250
|
-
@override
|
|
251
|
-
def connect[F2: Callable](
|
|
252
|
-
self,
|
|
253
|
-
listener: F,
|
|
254
|
-
error: Callable[[Self, BaseException], MaybeCoro[None]] | None = None,
|
|
255
|
-
done: Callable[[Self], MaybeCoro[None]] | None = None,
|
|
256
|
-
keep_ref: bool = False,
|
|
257
|
-
*,
|
|
258
|
-
ignore: TypeLike[BaseException] | None = None,
|
|
259
|
-
logger: LoggerLike | None = None,
|
|
260
|
-
decorators: MaybeIterable[Callable[[F2], F2]] | None = None,
|
|
261
|
-
) -> Self:
|
|
262
|
-
lifted = lift_listener(
|
|
263
|
-
listener,
|
|
264
|
-
self,
|
|
265
|
-
error=cast(
|
|
266
|
-
"Callable[[Event, BaseException], MaybeCoro[None]] | None", error
|
|
267
|
-
),
|
|
268
|
-
ignore=ignore,
|
|
269
|
-
logger=logger,
|
|
270
|
-
decorators=decorators,
|
|
271
|
-
)
|
|
272
|
-
return cast(
|
|
273
|
-
"Self", super().connect(lifted, error=error, done=done, keep_ref=keep_ref)
|
|
274
|
-
)
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
##
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
def lift_listener[F1: Callable[..., MaybeCoro[None]], F2: Callable](
|
|
281
|
-
listener: F1,
|
|
282
|
-
event: Event,
|
|
283
|
-
/,
|
|
284
|
-
*,
|
|
285
|
-
error: Callable[[Event, BaseException], MaybeCoro[None]] | None = None,
|
|
286
|
-
ignore: TypeLike[BaseException] | None = None,
|
|
287
|
-
logger: LoggerLike | None = None,
|
|
288
|
-
decorators: MaybeIterable[Callable[[F2], F2]] | None = None,
|
|
289
|
-
) -> F1:
|
|
290
|
-
match error, bool(iscoroutinefunction(listener)):
|
|
291
|
-
case None, False:
|
|
292
|
-
listener_typed = cast("Callable[..., None]", listener)
|
|
293
|
-
|
|
294
|
-
@wraps(listener)
|
|
295
|
-
def listener_no_error_sync(*args: Any, **kwargs: Any) -> None:
|
|
296
|
-
try:
|
|
297
|
-
listener_typed(*args, **kwargs)
|
|
298
|
-
except Exception as exc: # noqa: BLE001
|
|
299
|
-
if (ignore is not None) and isinstance(exc, ignore):
|
|
300
|
-
return
|
|
301
|
-
to_logger(logger).exception("")
|
|
302
|
-
|
|
303
|
-
lifted = listener_no_error_sync
|
|
304
|
-
|
|
305
|
-
case None, True:
|
|
306
|
-
listener_typed = cast("Callable[..., Coro[None]]", listener)
|
|
307
|
-
|
|
308
|
-
@wraps(listener)
|
|
309
|
-
async def listener_no_error_async(*args: Any, **kwargs: Any) -> None:
|
|
310
|
-
try:
|
|
311
|
-
await listener_typed(*args, **kwargs)
|
|
312
|
-
except Exception as exc: # noqa: BLE001
|
|
313
|
-
if (ignore is not None) and isinstance(exc, ignore):
|
|
314
|
-
return
|
|
315
|
-
to_logger(logger).exception("")
|
|
316
|
-
|
|
317
|
-
lifted = listener_no_error_async
|
|
318
|
-
case _, _:
|
|
319
|
-
match bool(iscoroutinefunction(listener)), bool(iscoroutinefunction(error)):
|
|
320
|
-
case False, False:
|
|
321
|
-
listener_typed = cast("Callable[..., None]", listener)
|
|
322
|
-
error_typed = cast("Callable[[Event, Exception], None]", error)
|
|
323
|
-
|
|
324
|
-
@wraps(listener)
|
|
325
|
-
def listener_have_error_sync(*args: Any, **kwargs: Any) -> None:
|
|
326
|
-
try:
|
|
327
|
-
listener_typed(*args, **kwargs)
|
|
328
|
-
except Exception as exc: # noqa: BLE001
|
|
329
|
-
if (ignore is not None) and isinstance(exc, ignore):
|
|
330
|
-
return
|
|
331
|
-
error_typed(event, exc)
|
|
332
|
-
|
|
333
|
-
lifted = listener_have_error_sync
|
|
334
|
-
case False, True:
|
|
335
|
-
listener_typed = cast("Callable[..., None]", listener)
|
|
336
|
-
error_typed = cast(
|
|
337
|
-
"Callable[[Event, Exception], Coro[None]]", error
|
|
338
|
-
)
|
|
339
|
-
raise LiftListenerError(listener=listener_typed, error=error_typed)
|
|
340
|
-
case True, _:
|
|
341
|
-
listener_typed = cast("Callable[..., Coro[None]]", listener)
|
|
342
|
-
|
|
343
|
-
@wraps(listener)
|
|
344
|
-
async def listener_have_error_async(
|
|
345
|
-
*args: Any, **kwargs: Any
|
|
346
|
-
) -> None:
|
|
347
|
-
try:
|
|
348
|
-
await listener_typed(*args, **kwargs)
|
|
349
|
-
except Exception as exc: # noqa: BLE001
|
|
350
|
-
if (ignore is not None) and isinstance(exc, ignore):
|
|
351
|
-
return None
|
|
352
|
-
if iscoroutinefunction(error):
|
|
353
|
-
error_typed = cast(
|
|
354
|
-
"Callable[[Event, Exception], Coro[None]]", error
|
|
355
|
-
)
|
|
356
|
-
return await error_typed(event, exc)
|
|
357
|
-
error_typed = cast(
|
|
358
|
-
"Callable[[Event, Exception], None]", error
|
|
359
|
-
)
|
|
360
|
-
error_typed(event, exc)
|
|
361
|
-
|
|
362
|
-
lifted = listener_have_error_async
|
|
363
|
-
case never:
|
|
364
|
-
assert_never(never)
|
|
365
|
-
case never:
|
|
366
|
-
assert_never(never)
|
|
367
|
-
|
|
368
|
-
if decorators is not None:
|
|
369
|
-
lifted = apply_decorators(lifted, *always_iterable(decorators))
|
|
370
|
-
return cast("F1", lifted)
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
@dataclass(kw_only=True, slots=True)
|
|
374
|
-
class LiftListenerError(Exception):
|
|
375
|
-
listener: Callable[..., None]
|
|
376
|
-
error: Callable[[Event, Exception], Coro[None]]
|
|
377
|
-
|
|
378
|
-
@override
|
|
379
|
-
def __str__(self) -> str:
|
|
380
|
-
return f"Synchronous listener {self.listener} cannot be paired with an asynchronous error handler {self.error}"
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
__all__ = [
|
|
384
|
-
"LiftListenerError",
|
|
385
|
-
"LiftedEvent",
|
|
386
|
-
"TypedEvent",
|
|
387
|
-
"add_listener",
|
|
388
|
-
"lift_listener",
|
|
389
|
-
]
|
|
File without changes
|