dycw-utilities 0.174.20__py3-none-any.whl → 0.175.7__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.20.dist-info → dycw_utilities-0.175.7.dist-info}/METADATA +4 -11
- {dycw_utilities-0.174.20.dist-info → dycw_utilities-0.175.7.dist-info}/RECORD +8 -9
- {dycw_utilities-0.174.20.dist-info → dycw_utilities-0.175.7.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 +246 -31
- utilities/aeventkit.py +0 -389
- {dycw_utilities-0.174.20.dist-info → dycw_utilities-0.175.7.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.7
|
|
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=B5bIogCgY2363uBTrXOQ-P5Y0xUozj2Nfbz2ZiV4uhs,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=pTmRcfsIraSfsM182R0gzqvDrBS7-_dRmwOABU1ZB14,41018
|
|
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.7.dist-info/WHEEL,sha256=RRVLqVugUmFOqBedBFAmA4bsgFcROUBiSUKlERi0Hcg,79
|
|
101
|
+
dycw_utilities-0.175.7.dist-info/entry_points.txt,sha256=cOGtKeJI0KXLSV7MJ8Dhc2G8jPgDcBDm53MVNJU4ycI,136
|
|
102
|
+
dycw_utilities-0.175.7.dist-info/METADATA,sha256=inDD1FvpQAWndmJ70WVWYhpWPrGG1MIhPIOR8Yfz98o,1442
|
|
103
|
+
dycw_utilities-0.175.7.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"
|
|
@@ -215,6 +217,13 @@ def echo_cmd(text: str, /) -> list[str]:
|
|
|
215
217
|
##
|
|
216
218
|
|
|
217
219
|
|
|
220
|
+
def env_cmds(env: StrStrMapping, /) -> list[str]:
|
|
221
|
+
return [f"{key}={value}" for key, value in env.items()]
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
##
|
|
225
|
+
|
|
226
|
+
|
|
218
227
|
def expand_path(
|
|
219
228
|
path: PathLike, /, *, subs: StrMapping | None = None, sudo: bool = False
|
|
220
229
|
) -> Path:
|
|
@@ -523,6 +532,7 @@ def rsync_many(
|
|
|
523
532
|
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
524
533
|
strict_host_key_checking: bool = True,
|
|
525
534
|
print: bool = False, # noqa: A002
|
|
535
|
+
exclude: MaybeIterable[str] | None = None,
|
|
526
536
|
) -> None:
|
|
527
537
|
cmds: list[list[str]] = [] # skipif-ci
|
|
528
538
|
with ( # skipif-ci
|
|
@@ -560,6 +570,7 @@ def rsync_many(
|
|
|
560
570
|
print=print,
|
|
561
571
|
retry=retry,
|
|
562
572
|
logger=logger,
|
|
573
|
+
exclude=exclude,
|
|
563
574
|
)
|
|
564
575
|
ssh(
|
|
565
576
|
user,
|
|
@@ -625,6 +636,7 @@ def run(
|
|
|
625
636
|
return_stdout: bool = False,
|
|
626
637
|
return_stderr: bool = False,
|
|
627
638
|
retry: Retry | None = None,
|
|
639
|
+
retry_skip: Callable[[int, str, str], bool] | None = None,
|
|
628
640
|
logger: LoggerLike | None = None,
|
|
629
641
|
) -> str: ...
|
|
630
642
|
@overload
|
|
@@ -645,6 +657,7 @@ def run(
|
|
|
645
657
|
return_stdout: Literal[True],
|
|
646
658
|
return_stderr: bool = False,
|
|
647
659
|
retry: Retry | None = None,
|
|
660
|
+
retry_skip: Callable[[int, str, str], bool] | None = None,
|
|
648
661
|
logger: LoggerLike | None = None,
|
|
649
662
|
) -> str: ...
|
|
650
663
|
@overload
|
|
@@ -665,6 +678,7 @@ def run(
|
|
|
665
678
|
return_stdout: bool = False,
|
|
666
679
|
return_stderr: Literal[True],
|
|
667
680
|
retry: Retry | None = None,
|
|
681
|
+
retry_skip: Callable[[int, str, str], bool] | None = None,
|
|
668
682
|
logger: LoggerLike | None = None,
|
|
669
683
|
) -> str: ...
|
|
670
684
|
@overload
|
|
@@ -685,6 +699,7 @@ def run(
|
|
|
685
699
|
return_stdout: Literal[False] = False,
|
|
686
700
|
return_stderr: Literal[False] = False,
|
|
687
701
|
retry: Retry | None = None,
|
|
702
|
+
retry_skip: Callable[[int, str, str], bool] | None = None,
|
|
688
703
|
logger: LoggerLike | None = None,
|
|
689
704
|
) -> None: ...
|
|
690
705
|
@overload
|
|
@@ -705,6 +720,7 @@ def run(
|
|
|
705
720
|
return_stdout: bool = False,
|
|
706
721
|
return_stderr: bool = False,
|
|
707
722
|
retry: Retry | None = None,
|
|
723
|
+
retry_skip: Callable[[int, str, str], bool] | None = None,
|
|
708
724
|
logger: LoggerLike | None = None,
|
|
709
725
|
) -> str | None: ...
|
|
710
726
|
def run(
|
|
@@ -724,6 +740,7 @@ def run(
|
|
|
724
740
|
return_stdout: bool = False,
|
|
725
741
|
return_stderr: bool = False,
|
|
726
742
|
retry: Retry | None = None,
|
|
743
|
+
retry_skip: Callable[[int, str, str], bool] | None = None,
|
|
727
744
|
logger: LoggerLike | None = None,
|
|
728
745
|
) -> str | None:
|
|
729
746
|
"""Run a command in a subprocess."""
|
|
@@ -781,14 +798,18 @@ def run(
|
|
|
781
798
|
case 0, False, False:
|
|
782
799
|
return None
|
|
783
800
|
case _, _, _:
|
|
784
|
-
if retry is None:
|
|
785
|
-
attempts = delta = None
|
|
786
|
-
else:
|
|
787
|
-
attempts, delta = retry
|
|
788
801
|
_ = stdout.seek(0)
|
|
789
802
|
stdout_text = stdout.read()
|
|
790
803
|
_ = stderr.seek(0)
|
|
791
804
|
stderr_text = stderr.read()
|
|
805
|
+
if (retry is None) or (
|
|
806
|
+
(retry is not None)
|
|
807
|
+
and (retry_skip is not None)
|
|
808
|
+
and retry_skip(return_code, stdout_text, stderr_text)
|
|
809
|
+
):
|
|
810
|
+
attempts = delta = None
|
|
811
|
+
else:
|
|
812
|
+
attempts, delta = retry
|
|
792
813
|
if logger is not None:
|
|
793
814
|
msg = strip_and_dedent(f"""
|
|
794
815
|
'run' failed with:
|
|
@@ -879,10 +900,12 @@ def ssh(
|
|
|
879
900
|
user: str,
|
|
880
901
|
hostname: str,
|
|
881
902
|
/,
|
|
882
|
-
*
|
|
903
|
+
*cmd_and_args: str,
|
|
883
904
|
batch_mode: bool = True,
|
|
884
905
|
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
885
906
|
strict_host_key_checking: bool = True,
|
|
907
|
+
port: int | None = None,
|
|
908
|
+
env: StrStrMapping | None = None,
|
|
886
909
|
input: str | None = None,
|
|
887
910
|
print: bool = False,
|
|
888
911
|
print_stdout: bool = False,
|
|
@@ -898,10 +921,12 @@ def ssh(
|
|
|
898
921
|
user: str,
|
|
899
922
|
hostname: str,
|
|
900
923
|
/,
|
|
901
|
-
*
|
|
924
|
+
*cmd_and_args: str,
|
|
902
925
|
batch_mode: bool = True,
|
|
903
926
|
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
904
927
|
strict_host_key_checking: bool = True,
|
|
928
|
+
port: int | None = None,
|
|
929
|
+
env: StrStrMapping | None = None,
|
|
905
930
|
input: str | None = None,
|
|
906
931
|
print: bool = False,
|
|
907
932
|
print_stdout: bool = False,
|
|
@@ -917,10 +942,12 @@ def ssh(
|
|
|
917
942
|
user: str,
|
|
918
943
|
hostname: str,
|
|
919
944
|
/,
|
|
920
|
-
*
|
|
945
|
+
*cmd_and_args: str,
|
|
921
946
|
batch_mode: bool = True,
|
|
922
947
|
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
923
948
|
strict_host_key_checking: bool = True,
|
|
949
|
+
port: int | None = None,
|
|
950
|
+
env: StrStrMapping | None = None,
|
|
924
951
|
input: str | None = None,
|
|
925
952
|
print: bool = False,
|
|
926
953
|
print_stdout: bool = False,
|
|
@@ -936,10 +963,12 @@ def ssh(
|
|
|
936
963
|
user: str,
|
|
937
964
|
hostname: str,
|
|
938
965
|
/,
|
|
939
|
-
*
|
|
966
|
+
*cmd_and_args: str,
|
|
940
967
|
batch_mode: bool = True,
|
|
941
968
|
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
942
969
|
strict_host_key_checking: bool = True,
|
|
970
|
+
port: int | None = None,
|
|
971
|
+
env: StrStrMapping | None = None,
|
|
943
972
|
input: str | None = None,
|
|
944
973
|
print: bool = False,
|
|
945
974
|
print_stdout: bool = False,
|
|
@@ -955,10 +984,12 @@ def ssh(
|
|
|
955
984
|
user: str,
|
|
956
985
|
hostname: str,
|
|
957
986
|
/,
|
|
958
|
-
*
|
|
987
|
+
*cmd_and_args: str,
|
|
959
988
|
batch_mode: bool = True,
|
|
960
989
|
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
961
990
|
strict_host_key_checking: bool = True,
|
|
991
|
+
port: int | None = None,
|
|
992
|
+
env: StrStrMapping | None = None,
|
|
962
993
|
input: str | None = None,
|
|
963
994
|
print: bool = False,
|
|
964
995
|
print_stdout: bool = False,
|
|
@@ -973,10 +1004,12 @@ def ssh(
|
|
|
973
1004
|
user: str,
|
|
974
1005
|
hostname: str,
|
|
975
1006
|
/,
|
|
976
|
-
*
|
|
1007
|
+
*cmd_and_args: str,
|
|
977
1008
|
batch_mode: bool = True,
|
|
978
1009
|
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
979
1010
|
strict_host_key_checking: bool = True,
|
|
1011
|
+
port: int | None = None,
|
|
1012
|
+
env: StrStrMapping | None = None,
|
|
980
1013
|
input: str | None = None, # noqa: A002
|
|
981
1014
|
print: bool = False, # noqa: A002
|
|
982
1015
|
print_stdout: bool = False,
|
|
@@ -988,44 +1021,89 @@ def ssh(
|
|
|
988
1021
|
logger: LoggerLike | None = None,
|
|
989
1022
|
) -> str | None:
|
|
990
1023
|
"""Execute a command on a remote machine."""
|
|
991
|
-
|
|
1024
|
+
run_cmd_and_args = ssh_cmd( # skipif-ci
|
|
992
1025
|
user,
|
|
993
1026
|
hostname,
|
|
994
|
-
*
|
|
1027
|
+
*cmd_and_args,
|
|
995
1028
|
batch_mode=batch_mode,
|
|
996
1029
|
host_key_algorithms=host_key_algorithms,
|
|
997
1030
|
strict_host_key_checking=strict_host_key_checking,
|
|
1031
|
+
port=port,
|
|
1032
|
+
env=env,
|
|
998
1033
|
)
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1034
|
+
try: # skipif-ci
|
|
1035
|
+
return run(
|
|
1036
|
+
*run_cmd_and_args,
|
|
1037
|
+
input=input,
|
|
1038
|
+
print=print,
|
|
1039
|
+
print_stdout=print_stdout,
|
|
1040
|
+
print_stderr=print_stderr,
|
|
1041
|
+
return_=return_,
|
|
1042
|
+
return_stdout=return_stdout,
|
|
1043
|
+
return_stderr=return_stderr,
|
|
1044
|
+
retry=retry,
|
|
1045
|
+
retry_skip=_ssh_retry_skip,
|
|
1046
|
+
logger=logger,
|
|
1047
|
+
)
|
|
1048
|
+
except CalledProcessError as error: # skipif-ci
|
|
1049
|
+
if not _ssh_is_strict_checking_error(error.stderr):
|
|
1050
|
+
raise
|
|
1051
|
+
ssh_keyscan(hostname, port=port)
|
|
1052
|
+
return ssh(
|
|
1053
|
+
user,
|
|
1054
|
+
hostname,
|
|
1055
|
+
*cmd_and_args,
|
|
1056
|
+
batch_mode=batch_mode,
|
|
1057
|
+
host_key_algorithms=host_key_algorithms,
|
|
1058
|
+
strict_host_key_checking=strict_host_key_checking,
|
|
1059
|
+
port=port,
|
|
1060
|
+
input=input,
|
|
1061
|
+
print=print,
|
|
1062
|
+
print_stdout=print_stdout,
|
|
1063
|
+
print_stderr=print_stderr,
|
|
1064
|
+
return_=return_,
|
|
1065
|
+
return_stdout=return_stdout,
|
|
1066
|
+
return_stderr=return_stderr,
|
|
1067
|
+
retry=retry,
|
|
1068
|
+
logger=logger,
|
|
1069
|
+
)
|
|
1070
|
+
|
|
1071
|
+
|
|
1072
|
+
def _ssh_retry_skip(return_code: int, stdout: str, stderr: str, /) -> bool:
|
|
1073
|
+
_ = (return_code, stdout)
|
|
1074
|
+
return _ssh_is_strict_checking_error(stderr)
|
|
1075
|
+
|
|
1076
|
+
|
|
1077
|
+
def _ssh_is_strict_checking_error(text: str, /) -> bool:
|
|
1078
|
+
match = search(
|
|
1079
|
+
"No ED25519 host key is known for .* and you have requested strict checking",
|
|
1080
|
+
text,
|
|
1010
1081
|
)
|
|
1082
|
+
return match is not None
|
|
1011
1083
|
|
|
1012
1084
|
|
|
1013
1085
|
def ssh_cmd(
|
|
1014
1086
|
user: str,
|
|
1015
1087
|
hostname: str,
|
|
1016
1088
|
/,
|
|
1017
|
-
*
|
|
1089
|
+
*cmd_and_args: str,
|
|
1018
1090
|
batch_mode: bool = True,
|
|
1019
1091
|
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
1020
1092
|
strict_host_key_checking: bool = True,
|
|
1093
|
+
port: int | None = None,
|
|
1094
|
+
env: StrStrMapping | None = None,
|
|
1021
1095
|
) -> list[str]:
|
|
1022
1096
|
"""Command to use 'ssh' to execute a command on a remote machine."""
|
|
1023
1097
|
args: list[str] = ssh_opts_cmd(
|
|
1024
1098
|
batch_mode=batch_mode,
|
|
1025
1099
|
host_key_algorithms=host_key_algorithms,
|
|
1026
1100
|
strict_host_key_checking=strict_host_key_checking,
|
|
1101
|
+
port=port,
|
|
1027
1102
|
)
|
|
1028
|
-
|
|
1103
|
+
args.append(f"{user}@{hostname}")
|
|
1104
|
+
if env is not None:
|
|
1105
|
+
args.extend(env_cmds(env))
|
|
1106
|
+
return [*args, *cmd_and_args]
|
|
1029
1107
|
|
|
1030
1108
|
|
|
1031
1109
|
def ssh_opts_cmd(
|
|
@@ -1033,6 +1111,7 @@ def ssh_opts_cmd(
|
|
|
1033
1111
|
batch_mode: bool = True,
|
|
1034
1112
|
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
1035
1113
|
strict_host_key_checking: bool = True,
|
|
1114
|
+
port: int | None = None,
|
|
1036
1115
|
) -> list[str]:
|
|
1037
1116
|
"""Command to use prepare 'ssh' to execute a command on a remote machine."""
|
|
1038
1117
|
args: list[str] = ["ssh"]
|
|
@@ -1041,15 +1120,47 @@ def ssh_opts_cmd(
|
|
|
1041
1120
|
args.extend(["-o", f"HostKeyAlgorithms={','.join(host_key_algorithms)}"])
|
|
1042
1121
|
if strict_host_key_checking:
|
|
1043
1122
|
args.extend(["-o", "StrictHostKeyChecking=yes"])
|
|
1123
|
+
if port is not None:
|
|
1124
|
+
args.extend(["-p", str(port)])
|
|
1044
1125
|
return [*args, "-T"]
|
|
1045
1126
|
|
|
1046
1127
|
|
|
1047
1128
|
##
|
|
1048
1129
|
|
|
1049
1130
|
|
|
1050
|
-
def
|
|
1051
|
-
|
|
1052
|
-
|
|
1131
|
+
def ssh_keyscan(
|
|
1132
|
+
hostname: str, /, *, path: PathLike = KNOWN_HOSTS, port: int | None = None
|
|
1133
|
+
) -> None:
|
|
1134
|
+
"""Add a known host."""
|
|
1135
|
+
ssh_keygen_remove(hostname, path=path) # skipif-ci
|
|
1136
|
+
mkdir(path, parent=True) # skipif-ci
|
|
1137
|
+
with Path(path).open(mode="a") as fh: # skipif-ci
|
|
1138
|
+
_ = fh.write(run(*ssh_keyscan_cmd(hostname, port=port), return_=True))
|
|
1139
|
+
|
|
1140
|
+
|
|
1141
|
+
def ssh_keyscan_cmd(hostname: str, /, *, port: int | None = None) -> list[str]:
|
|
1142
|
+
"""Command to use 'ssh-keyscan' to add a known host."""
|
|
1143
|
+
args: list[str] = ["ssh-keyscan"]
|
|
1144
|
+
if port is not None:
|
|
1145
|
+
args.extend(["-p", str(port)])
|
|
1146
|
+
return [*args, "-q", "-t", "ed25519", hostname]
|
|
1147
|
+
|
|
1148
|
+
|
|
1149
|
+
##
|
|
1150
|
+
|
|
1151
|
+
|
|
1152
|
+
def ssh_keygen_remove(hostname: str, /, *, path: PathLike = KNOWN_HOSTS) -> None:
|
|
1153
|
+
"""Remove a known host."""
|
|
1154
|
+
path = Path(path)
|
|
1155
|
+
if path.exists():
|
|
1156
|
+
run(*ssh_keygen_remove_cmd(hostname, path=path))
|
|
1157
|
+
|
|
1158
|
+
|
|
1159
|
+
def ssh_keygen_remove_cmd(
|
|
1160
|
+
hostname: str, /, *, path: PathLike = KNOWN_HOSTS
|
|
1161
|
+
) -> list[str]:
|
|
1162
|
+
"""Command to use 'ssh-keygen' to remove a known host."""
|
|
1163
|
+
return ["ssh-keygen", "-f", str(path), "-R", hostname]
|
|
1053
1164
|
|
|
1054
1165
|
|
|
1055
1166
|
##
|
|
@@ -1132,9 +1243,108 @@ def touch_cmd(path: PathLike, /) -> list[str]:
|
|
|
1132
1243
|
##
|
|
1133
1244
|
|
|
1134
1245
|
|
|
1135
|
-
|
|
1246
|
+
@overload
|
|
1247
|
+
def uv_run(
|
|
1248
|
+
module: str,
|
|
1249
|
+
/,
|
|
1250
|
+
*args: str,
|
|
1251
|
+
cwd: PathLike | None = None,
|
|
1252
|
+
print: bool = False,
|
|
1253
|
+
print_stdout: bool = False,
|
|
1254
|
+
print_stderr: bool = False,
|
|
1255
|
+
return_: Literal[True],
|
|
1256
|
+
return_stdout: bool = False,
|
|
1257
|
+
return_stderr: bool = False,
|
|
1258
|
+
retry: Retry | None = None,
|
|
1259
|
+
logger: LoggerLike | None = None,
|
|
1260
|
+
) -> str: ...
|
|
1261
|
+
@overload
|
|
1262
|
+
def uv_run(
|
|
1263
|
+
module: str,
|
|
1264
|
+
/,
|
|
1265
|
+
*args: str,
|
|
1266
|
+
cwd: PathLike | None = None,
|
|
1267
|
+
print: bool = False,
|
|
1268
|
+
print_stdout: bool = False,
|
|
1269
|
+
print_stderr: bool = False,
|
|
1270
|
+
return_: bool = False,
|
|
1271
|
+
return_stdout: Literal[True],
|
|
1272
|
+
return_stderr: bool = False,
|
|
1273
|
+
retry: Retry | None = None,
|
|
1274
|
+
logger: LoggerLike | None = None,
|
|
1275
|
+
) -> str: ...
|
|
1276
|
+
@overload
|
|
1277
|
+
def uv_run(
|
|
1278
|
+
module: str,
|
|
1279
|
+
/,
|
|
1280
|
+
*args: str,
|
|
1281
|
+
cwd: PathLike | None = None,
|
|
1282
|
+
print: bool = False,
|
|
1283
|
+
print_stdout: bool = False,
|
|
1284
|
+
print_stderr: bool = False,
|
|
1285
|
+
return_: bool = False,
|
|
1286
|
+
return_stdout: bool = False,
|
|
1287
|
+
return_stderr: Literal[True],
|
|
1288
|
+
retry: Retry | None = None,
|
|
1289
|
+
logger: LoggerLike | None = None,
|
|
1290
|
+
) -> str: ...
|
|
1291
|
+
@overload
|
|
1292
|
+
def uv_run(
|
|
1293
|
+
module: str,
|
|
1294
|
+
/,
|
|
1295
|
+
*args: str,
|
|
1296
|
+
cwd: PathLike | None = None,
|
|
1297
|
+
print: bool = False,
|
|
1298
|
+
print_stdout: bool = False,
|
|
1299
|
+
print_stderr: bool = False,
|
|
1300
|
+
return_: Literal[False] = False,
|
|
1301
|
+
return_stdout: Literal[False] = False,
|
|
1302
|
+
return_stderr: Literal[False] = False,
|
|
1303
|
+
retry: Retry | None = None,
|
|
1304
|
+
logger: LoggerLike | None = None,
|
|
1305
|
+
) -> None: ...
|
|
1306
|
+
@overload
|
|
1307
|
+
def uv_run(
|
|
1308
|
+
module: str,
|
|
1309
|
+
/,
|
|
1310
|
+
*args: str,
|
|
1311
|
+
cwd: PathLike | None = None,
|
|
1312
|
+
print: bool = False,
|
|
1313
|
+
print_stdout: bool = False,
|
|
1314
|
+
print_stderr: bool = False,
|
|
1315
|
+
return_: bool = False,
|
|
1316
|
+
return_stdout: bool = False,
|
|
1317
|
+
return_stderr: bool = False,
|
|
1318
|
+
retry: Retry | None = None,
|
|
1319
|
+
logger: LoggerLike | None = None,
|
|
1320
|
+
) -> str | None: ...
|
|
1321
|
+
def uv_run(
|
|
1322
|
+
module: str,
|
|
1323
|
+
/,
|
|
1324
|
+
*args: str,
|
|
1325
|
+
cwd: PathLike | None = None,
|
|
1326
|
+
print: bool = False, # noqa: A002
|
|
1327
|
+
print_stdout: bool = False,
|
|
1328
|
+
print_stderr: bool = False,
|
|
1329
|
+
return_: bool = False,
|
|
1330
|
+
return_stdout: bool = False,
|
|
1331
|
+
return_stderr: bool = False,
|
|
1332
|
+
retry: Retry | None = None,
|
|
1333
|
+
logger: LoggerLike | None = None,
|
|
1334
|
+
) -> str | None:
|
|
1136
1335
|
"""Run a command or script."""
|
|
1137
|
-
run(
|
|
1336
|
+
return run( # pragma: no cover
|
|
1337
|
+
*uv_run_cmd(module, *args),
|
|
1338
|
+
cwd=cwd,
|
|
1339
|
+
print=print,
|
|
1340
|
+
print_stdout=print_stdout,
|
|
1341
|
+
print_stderr=print_stderr,
|
|
1342
|
+
return_=return_,
|
|
1343
|
+
return_stdout=return_stdout,
|
|
1344
|
+
return_stderr=return_stderr,
|
|
1345
|
+
retry=retry,
|
|
1346
|
+
logger=logger,
|
|
1347
|
+
)
|
|
1138
1348
|
|
|
1139
1349
|
|
|
1140
1350
|
def uv_run_cmd(module: str, /, *args: str) -> list[str]:
|
|
@@ -1215,6 +1425,7 @@ __all__ = [
|
|
|
1215
1425
|
"cp",
|
|
1216
1426
|
"cp_cmd",
|
|
1217
1427
|
"echo_cmd",
|
|
1428
|
+
"env_cmds",
|
|
1218
1429
|
"expand_path",
|
|
1219
1430
|
"git_branch_current",
|
|
1220
1431
|
"git_checkout",
|
|
@@ -1236,6 +1447,10 @@ __all__ = [
|
|
|
1236
1447
|
"set_hostname_cmd",
|
|
1237
1448
|
"ssh",
|
|
1238
1449
|
"ssh_cmd",
|
|
1450
|
+
"ssh_keygen_remove",
|
|
1451
|
+
"ssh_keygen_remove_cmd",
|
|
1452
|
+
"ssh_keyscan",
|
|
1453
|
+
"ssh_keyscan_cmd",
|
|
1239
1454
|
"ssh_opts_cmd",
|
|
1240
1455
|
"sudo_cmd",
|
|
1241
1456
|
"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
|