dycw-utilities 0.174.20__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.20.dist-info → dycw_utilities-0.175.6.dist-info}/METADATA +4 -11
- {dycw_utilities-0.174.20.dist-info → dycw_utilities-0.175.6.dist-info}/RECORD +8 -9
- {dycw_utilities-0.174.20.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 +217 -21
- utilities/aeventkit.py +0 -389
- {dycw_utilities-0.174.20.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"
|
|
@@ -523,6 +525,7 @@ def rsync_many(
|
|
|
523
525
|
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
524
526
|
strict_host_key_checking: bool = True,
|
|
525
527
|
print: bool = False, # noqa: A002
|
|
528
|
+
exclude: MaybeIterable[str] | None = None,
|
|
526
529
|
) -> None:
|
|
527
530
|
cmds: list[list[str]] = [] # skipif-ci
|
|
528
531
|
with ( # skipif-ci
|
|
@@ -560,6 +563,7 @@ def rsync_many(
|
|
|
560
563
|
print=print,
|
|
561
564
|
retry=retry,
|
|
562
565
|
logger=logger,
|
|
566
|
+
exclude=exclude,
|
|
563
567
|
)
|
|
564
568
|
ssh(
|
|
565
569
|
user,
|
|
@@ -625,6 +629,7 @@ def run(
|
|
|
625
629
|
return_stdout: bool = False,
|
|
626
630
|
return_stderr: bool = False,
|
|
627
631
|
retry: Retry | None = None,
|
|
632
|
+
retry_skip: Callable[[int, str, str], bool] | None = None,
|
|
628
633
|
logger: LoggerLike | None = None,
|
|
629
634
|
) -> str: ...
|
|
630
635
|
@overload
|
|
@@ -645,6 +650,7 @@ def run(
|
|
|
645
650
|
return_stdout: Literal[True],
|
|
646
651
|
return_stderr: bool = False,
|
|
647
652
|
retry: Retry | None = None,
|
|
653
|
+
retry_skip: Callable[[int, str, str], bool] | None = None,
|
|
648
654
|
logger: LoggerLike | None = None,
|
|
649
655
|
) -> str: ...
|
|
650
656
|
@overload
|
|
@@ -665,6 +671,7 @@ def run(
|
|
|
665
671
|
return_stdout: bool = False,
|
|
666
672
|
return_stderr: Literal[True],
|
|
667
673
|
retry: Retry | None = None,
|
|
674
|
+
retry_skip: Callable[[int, str, str], bool] | None = None,
|
|
668
675
|
logger: LoggerLike | None = None,
|
|
669
676
|
) -> str: ...
|
|
670
677
|
@overload
|
|
@@ -685,6 +692,7 @@ def run(
|
|
|
685
692
|
return_stdout: Literal[False] = False,
|
|
686
693
|
return_stderr: Literal[False] = False,
|
|
687
694
|
retry: Retry | None = None,
|
|
695
|
+
retry_skip: Callable[[int, str, str], bool] | None = None,
|
|
688
696
|
logger: LoggerLike | None = None,
|
|
689
697
|
) -> None: ...
|
|
690
698
|
@overload
|
|
@@ -705,6 +713,7 @@ def run(
|
|
|
705
713
|
return_stdout: bool = False,
|
|
706
714
|
return_stderr: bool = False,
|
|
707
715
|
retry: Retry | None = None,
|
|
716
|
+
retry_skip: Callable[[int, str, str], bool] | None = None,
|
|
708
717
|
logger: LoggerLike | None = None,
|
|
709
718
|
) -> str | None: ...
|
|
710
719
|
def run(
|
|
@@ -724,6 +733,7 @@ def run(
|
|
|
724
733
|
return_stdout: bool = False,
|
|
725
734
|
return_stderr: bool = False,
|
|
726
735
|
retry: Retry | None = None,
|
|
736
|
+
retry_skip: Callable[[int, str, str], bool] | None = None,
|
|
727
737
|
logger: LoggerLike | None = None,
|
|
728
738
|
) -> str | None:
|
|
729
739
|
"""Run a command in a subprocess."""
|
|
@@ -781,14 +791,18 @@ def run(
|
|
|
781
791
|
case 0, False, False:
|
|
782
792
|
return None
|
|
783
793
|
case _, _, _:
|
|
784
|
-
if retry is None:
|
|
785
|
-
attempts = delta = None
|
|
786
|
-
else:
|
|
787
|
-
attempts, delta = retry
|
|
788
794
|
_ = stdout.seek(0)
|
|
789
795
|
stdout_text = stdout.read()
|
|
790
796
|
_ = stderr.seek(0)
|
|
791
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
|
|
792
806
|
if logger is not None:
|
|
793
807
|
msg = strip_and_dedent(f"""
|
|
794
808
|
'run' failed with:
|
|
@@ -883,6 +897,7 @@ def ssh(
|
|
|
883
897
|
batch_mode: bool = True,
|
|
884
898
|
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
885
899
|
strict_host_key_checking: bool = True,
|
|
900
|
+
port: int | None = None,
|
|
886
901
|
input: str | None = None,
|
|
887
902
|
print: bool = False,
|
|
888
903
|
print_stdout: bool = False,
|
|
@@ -902,6 +917,7 @@ def ssh(
|
|
|
902
917
|
batch_mode: bool = True,
|
|
903
918
|
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
904
919
|
strict_host_key_checking: bool = True,
|
|
920
|
+
port: int | None = None,
|
|
905
921
|
input: str | None = None,
|
|
906
922
|
print: bool = False,
|
|
907
923
|
print_stdout: bool = False,
|
|
@@ -921,6 +937,7 @@ def ssh(
|
|
|
921
937
|
batch_mode: bool = True,
|
|
922
938
|
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
923
939
|
strict_host_key_checking: bool = True,
|
|
940
|
+
port: int | None = None,
|
|
924
941
|
input: str | None = None,
|
|
925
942
|
print: bool = False,
|
|
926
943
|
print_stdout: bool = False,
|
|
@@ -940,6 +957,7 @@ def ssh(
|
|
|
940
957
|
batch_mode: bool = True,
|
|
941
958
|
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
942
959
|
strict_host_key_checking: bool = True,
|
|
960
|
+
port: int | None = None,
|
|
943
961
|
input: str | None = None,
|
|
944
962
|
print: bool = False,
|
|
945
963
|
print_stdout: bool = False,
|
|
@@ -959,6 +977,7 @@ def ssh(
|
|
|
959
977
|
batch_mode: bool = True,
|
|
960
978
|
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
961
979
|
strict_host_key_checking: bool = True,
|
|
980
|
+
port: int | None = None,
|
|
962
981
|
input: str | None = None,
|
|
963
982
|
print: bool = False,
|
|
964
983
|
print_stdout: bool = False,
|
|
@@ -977,6 +996,7 @@ def ssh(
|
|
|
977
996
|
batch_mode: bool = True,
|
|
978
997
|
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
979
998
|
strict_host_key_checking: bool = True,
|
|
999
|
+
port: int | None = None,
|
|
980
1000
|
input: str | None = None, # noqa: A002
|
|
981
1001
|
print: bool = False, # noqa: A002
|
|
982
1002
|
print_stdout: bool = False,
|
|
@@ -995,19 +1015,57 @@ def ssh(
|
|
|
995
1015
|
batch_mode=batch_mode,
|
|
996
1016
|
host_key_algorithms=host_key_algorithms,
|
|
997
1017
|
strict_host_key_checking=strict_host_key_checking,
|
|
1018
|
+
port=port,
|
|
998
1019
|
)
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
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,
|
|
1010
1067
|
)
|
|
1068
|
+
return match is not None
|
|
1011
1069
|
|
|
1012
1070
|
|
|
1013
1071
|
def ssh_cmd(
|
|
@@ -1018,12 +1076,14 @@ def ssh_cmd(
|
|
|
1018
1076
|
batch_mode: bool = True,
|
|
1019
1077
|
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
1020
1078
|
strict_host_key_checking: bool = True,
|
|
1079
|
+
port: int | None = None,
|
|
1021
1080
|
) -> list[str]:
|
|
1022
1081
|
"""Command to use 'ssh' to execute a command on a remote machine."""
|
|
1023
1082
|
args: list[str] = ssh_opts_cmd(
|
|
1024
1083
|
batch_mode=batch_mode,
|
|
1025
1084
|
host_key_algorithms=host_key_algorithms,
|
|
1026
1085
|
strict_host_key_checking=strict_host_key_checking,
|
|
1086
|
+
port=port,
|
|
1027
1087
|
)
|
|
1028
1088
|
return [*args, f"{user}@{hostname}", *cmd_and_cmds_or_args]
|
|
1029
1089
|
|
|
@@ -1033,6 +1093,7 @@ def ssh_opts_cmd(
|
|
|
1033
1093
|
batch_mode: bool = True,
|
|
1034
1094
|
host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS,
|
|
1035
1095
|
strict_host_key_checking: bool = True,
|
|
1096
|
+
port: int | None = None,
|
|
1036
1097
|
) -> list[str]:
|
|
1037
1098
|
"""Command to use prepare 'ssh' to execute a command on a remote machine."""
|
|
1038
1099
|
args: list[str] = ["ssh"]
|
|
@@ -1041,15 +1102,47 @@ def ssh_opts_cmd(
|
|
|
1041
1102
|
args.extend(["-o", f"HostKeyAlgorithms={','.join(host_key_algorithms)}"])
|
|
1042
1103
|
if strict_host_key_checking:
|
|
1043
1104
|
args.extend(["-o", "StrictHostKeyChecking=yes"])
|
|
1105
|
+
if port is not None:
|
|
1106
|
+
args.extend(["-p", str(port)])
|
|
1044
1107
|
return [*args, "-T"]
|
|
1045
1108
|
|
|
1046
1109
|
|
|
1047
1110
|
##
|
|
1048
1111
|
|
|
1049
1112
|
|
|
1050
|
-
def
|
|
1051
|
-
|
|
1052
|
-
|
|
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]
|
|
1053
1146
|
|
|
1054
1147
|
|
|
1055
1148
|
##
|
|
@@ -1132,9 +1225,108 @@ def touch_cmd(path: PathLike, /) -> list[str]:
|
|
|
1132
1225
|
##
|
|
1133
1226
|
|
|
1134
1227
|
|
|
1135
|
-
|
|
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:
|
|
1136
1317
|
"""Run a command or script."""
|
|
1137
|
-
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
|
+
)
|
|
1138
1330
|
|
|
1139
1331
|
|
|
1140
1332
|
def uv_run_cmd(module: str, /, *args: str) -> list[str]:
|
|
@@ -1236,6 +1428,10 @@ __all__ = [
|
|
|
1236
1428
|
"set_hostname_cmd",
|
|
1237
1429
|
"ssh",
|
|
1238
1430
|
"ssh_cmd",
|
|
1431
|
+
"ssh_keygen_remove",
|
|
1432
|
+
"ssh_keygen_remove_cmd",
|
|
1433
|
+
"ssh_keyscan",
|
|
1434
|
+
"ssh_keyscan_cmd",
|
|
1239
1435
|
"ssh_opts_cmd",
|
|
1240
1436
|
"sudo_cmd",
|
|
1241
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
|