ominfra 0.0.0.dev154__py3-none-any.whl → 0.0.0.dev156__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- ominfra/manage/bootstrap.py +4 -0
- ominfra/manage/bootstrap_.py +5 -0
- ominfra/manage/commands/inject.py +8 -11
- ominfra/manage/commands/{execution.py → local.py} +1 -5
- ominfra/manage/commands/ping.py +23 -0
- ominfra/manage/commands/subprocess.py +3 -4
- ominfra/manage/commands/types.py +8 -0
- ominfra/manage/deploy/apps.py +72 -0
- ominfra/manage/deploy/config.py +8 -0
- ominfra/manage/deploy/git.py +136 -0
- ominfra/manage/deploy/inject.py +21 -0
- ominfra/manage/deploy/paths.py +81 -28
- ominfra/manage/deploy/types.py +13 -0
- ominfra/manage/deploy/venvs.py +66 -0
- ominfra/manage/inject.py +20 -4
- ominfra/manage/main.py +15 -27
- ominfra/manage/remote/_main.py +1 -1
- ominfra/manage/remote/config.py +0 -2
- ominfra/manage/remote/connection.py +7 -24
- ominfra/manage/remote/execution.py +1 -1
- ominfra/manage/remote/inject.py +3 -14
- ominfra/manage/remote/spawning.py +2 -2
- ominfra/manage/system/commands.py +22 -2
- ominfra/manage/system/config.py +3 -1
- ominfra/manage/system/inject.py +16 -6
- ominfra/manage/system/packages.py +38 -14
- ominfra/manage/system/platforms.py +72 -0
- ominfra/manage/targets/__init__.py +0 -0
- ominfra/manage/targets/connection.py +150 -0
- ominfra/manage/targets/inject.py +42 -0
- ominfra/manage/targets/targets.py +87 -0
- ominfra/scripts/journald2aws.py +205 -134
- ominfra/scripts/manage.py +2192 -734
- ominfra/scripts/supervisor.py +187 -25
- ominfra/supervisor/configs.py +163 -18
- {ominfra-0.0.0.dev154.dist-info → ominfra-0.0.0.dev156.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev154.dist-info → ominfra-0.0.0.dev156.dist-info}/RECORD +42 -31
- ominfra/manage/system/types.py +0 -5
- /ominfra/manage/{commands → deploy}/interp.py +0 -0
- {ominfra-0.0.0.dev154.dist-info → ominfra-0.0.0.dev156.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev154.dist-info → ominfra-0.0.0.dev156.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev154.dist-info → ominfra-0.0.0.dev156.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev154.dist-info → ominfra-0.0.0.dev156.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,150 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import abc
|
3
|
+
import contextlib
|
4
|
+
import dataclasses as dc
|
5
|
+
import typing as ta
|
6
|
+
|
7
|
+
from omlish.lite.check import check
|
8
|
+
|
9
|
+
from ..bootstrap import MainBootstrap
|
10
|
+
from ..commands.base import CommandExecutor
|
11
|
+
from ..commands.local import LocalCommandExecutor
|
12
|
+
from ..remote.connection import InProcessRemoteExecutionConnector
|
13
|
+
from ..remote.connection import PyremoteRemoteExecutionConnector
|
14
|
+
from ..remote.spawning import RemoteSpawning
|
15
|
+
from .targets import DockerManageTarget
|
16
|
+
from .targets import InProcessManageTarget
|
17
|
+
from .targets import LocalManageTarget
|
18
|
+
from .targets import ManageTarget
|
19
|
+
from .targets import SshManageTarget
|
20
|
+
from .targets import SubprocessManageTarget
|
21
|
+
|
22
|
+
|
23
|
+
##
|
24
|
+
|
25
|
+
|
26
|
+
class ManageTargetConnector(abc.ABC):
|
27
|
+
@abc.abstractmethod
|
28
|
+
def connect(self, tgt: ManageTarget) -> ta.AsyncContextManager[CommandExecutor]:
|
29
|
+
raise NotImplementedError
|
30
|
+
|
31
|
+
|
32
|
+
##
|
33
|
+
|
34
|
+
|
35
|
+
ManageTargetConnectorMap = ta.NewType('ManageTargetConnectorMap', ta.Mapping[ta.Type[ManageTarget], ManageTargetConnector]) # noqa
|
36
|
+
|
37
|
+
|
38
|
+
@dc.dataclass(frozen=True)
|
39
|
+
class TypeSwitchedManageTargetConnector(ManageTargetConnector):
|
40
|
+
connectors: ManageTargetConnectorMap
|
41
|
+
|
42
|
+
def get_connector(self, ty: ta.Type[ManageTarget]) -> ManageTargetConnector:
|
43
|
+
for k, v in self.connectors.items():
|
44
|
+
if issubclass(ty, k):
|
45
|
+
return v
|
46
|
+
raise KeyError(ty)
|
47
|
+
|
48
|
+
def connect(self, tgt: ManageTarget) -> ta.AsyncContextManager[CommandExecutor]:
|
49
|
+
return self.get_connector(type(tgt)).connect(tgt)
|
50
|
+
|
51
|
+
|
52
|
+
##
|
53
|
+
|
54
|
+
|
55
|
+
@dc.dataclass(frozen=True)
|
56
|
+
class LocalManageTargetConnector(ManageTargetConnector):
|
57
|
+
_local_executor: LocalCommandExecutor
|
58
|
+
_in_process_connector: InProcessRemoteExecutionConnector
|
59
|
+
_pyremote_connector: PyremoteRemoteExecutionConnector
|
60
|
+
_bootstrap: MainBootstrap
|
61
|
+
|
62
|
+
@contextlib.asynccontextmanager
|
63
|
+
async def connect(self, tgt: ManageTarget) -> ta.AsyncGenerator[CommandExecutor, None]:
|
64
|
+
lmt = check.isinstance(tgt, LocalManageTarget)
|
65
|
+
|
66
|
+
if isinstance(lmt, InProcessManageTarget):
|
67
|
+
imt = check.isinstance(lmt, InProcessManageTarget)
|
68
|
+
|
69
|
+
if imt.mode == InProcessManageTarget.Mode.DIRECT:
|
70
|
+
yield self._local_executor
|
71
|
+
|
72
|
+
elif imt.mode == InProcessManageTarget.Mode.FAKE_REMOTE:
|
73
|
+
async with self._in_process_connector.connect() as rce:
|
74
|
+
yield rce
|
75
|
+
|
76
|
+
else:
|
77
|
+
raise TypeError(imt.mode)
|
78
|
+
|
79
|
+
elif isinstance(lmt, SubprocessManageTarget):
|
80
|
+
async with self._pyremote_connector.connect(
|
81
|
+
RemoteSpawning.Target(
|
82
|
+
python=lmt.python,
|
83
|
+
),
|
84
|
+
self._bootstrap,
|
85
|
+
) as rce:
|
86
|
+
yield rce
|
87
|
+
|
88
|
+
else:
|
89
|
+
raise TypeError(lmt)
|
90
|
+
|
91
|
+
|
92
|
+
##
|
93
|
+
|
94
|
+
|
95
|
+
@dc.dataclass(frozen=True)
|
96
|
+
class DockerManageTargetConnector(ManageTargetConnector):
|
97
|
+
_pyremote_connector: PyremoteRemoteExecutionConnector
|
98
|
+
_bootstrap: MainBootstrap
|
99
|
+
|
100
|
+
@contextlib.asynccontextmanager
|
101
|
+
async def connect(self, tgt: ManageTarget) -> ta.AsyncGenerator[CommandExecutor, None]:
|
102
|
+
dmt = check.isinstance(tgt, DockerManageTarget)
|
103
|
+
|
104
|
+
sh_parts: ta.List[str] = ['docker']
|
105
|
+
if dmt.image is not None:
|
106
|
+
sh_parts.extend(['run', '-i', dmt.image])
|
107
|
+
elif dmt.container_id is not None:
|
108
|
+
sh_parts.extend(['exec', dmt.container_id])
|
109
|
+
else:
|
110
|
+
raise ValueError(dmt)
|
111
|
+
|
112
|
+
async with self._pyremote_connector.connect(
|
113
|
+
RemoteSpawning.Target(
|
114
|
+
shell=' '.join(sh_parts),
|
115
|
+
python=dmt.python,
|
116
|
+
),
|
117
|
+
self._bootstrap,
|
118
|
+
) as rce:
|
119
|
+
yield rce
|
120
|
+
|
121
|
+
|
122
|
+
##
|
123
|
+
|
124
|
+
|
125
|
+
@dc.dataclass(frozen=True)
|
126
|
+
class SshManageTargetConnector(ManageTargetConnector):
|
127
|
+
_pyremote_connector: PyremoteRemoteExecutionConnector
|
128
|
+
_bootstrap: MainBootstrap
|
129
|
+
|
130
|
+
@contextlib.asynccontextmanager
|
131
|
+
async def connect(self, tgt: ManageTarget) -> ta.AsyncGenerator[CommandExecutor, None]:
|
132
|
+
smt = check.isinstance(tgt, SshManageTarget)
|
133
|
+
|
134
|
+
sh_parts: ta.List[str] = ['ssh']
|
135
|
+
if smt.key_file is not None:
|
136
|
+
sh_parts.extend(['-i', smt.key_file])
|
137
|
+
addr = check.not_none(smt.host)
|
138
|
+
if smt.username is not None:
|
139
|
+
addr = f'{smt.username}@{addr}'
|
140
|
+
sh_parts.append(addr)
|
141
|
+
|
142
|
+
async with self._pyremote_connector.connect(
|
143
|
+
RemoteSpawning.Target(
|
144
|
+
shell=' '.join(sh_parts),
|
145
|
+
shell_quote=True,
|
146
|
+
python=smt.python,
|
147
|
+
),
|
148
|
+
self._bootstrap,
|
149
|
+
) as rce:
|
150
|
+
yield rce
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from omlish.lite.inject import Injector
|
5
|
+
from omlish.lite.inject import InjectorBindingOrBindings
|
6
|
+
from omlish.lite.inject import InjectorBindings
|
7
|
+
from omlish.lite.inject import inj
|
8
|
+
|
9
|
+
from .connection import DockerManageTargetConnector
|
10
|
+
from .connection import LocalManageTargetConnector
|
11
|
+
from .connection import ManageTargetConnector
|
12
|
+
from .connection import ManageTargetConnectorMap
|
13
|
+
from .connection import SshManageTargetConnector
|
14
|
+
from .connection import TypeSwitchedManageTargetConnector
|
15
|
+
from .targets import DockerManageTarget
|
16
|
+
from .targets import LocalManageTarget
|
17
|
+
from .targets import SshManageTarget
|
18
|
+
|
19
|
+
|
20
|
+
def bind_targets() -> InjectorBindings:
|
21
|
+
lst: ta.List[InjectorBindingOrBindings] = [
|
22
|
+
inj.bind(LocalManageTargetConnector, singleton=True),
|
23
|
+
inj.bind(DockerManageTargetConnector, singleton=True),
|
24
|
+
inj.bind(SshManageTargetConnector, singleton=True),
|
25
|
+
|
26
|
+
inj.bind(TypeSwitchedManageTargetConnector, singleton=True),
|
27
|
+
inj.bind(ManageTargetConnector, to_key=TypeSwitchedManageTargetConnector),
|
28
|
+
]
|
29
|
+
|
30
|
+
#
|
31
|
+
|
32
|
+
def provide_manage_target_connector_map(injector: Injector) -> ManageTargetConnectorMap:
|
33
|
+
return ManageTargetConnectorMap({
|
34
|
+
LocalManageTarget: injector[LocalManageTargetConnector],
|
35
|
+
DockerManageTarget: injector[DockerManageTargetConnector],
|
36
|
+
SshManageTarget: injector[SshManageTargetConnector],
|
37
|
+
})
|
38
|
+
lst.append(inj.bind(provide_manage_target_connector_map, singleton=True))
|
39
|
+
|
40
|
+
#
|
41
|
+
|
42
|
+
return inj.as_bindings(*lst)
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
"""
|
3
|
+
It's desugaring. Subprocess and locals are only leafs. Retain an origin?
|
4
|
+
** TWO LAYERS ** - ManageTarget is user-facing, ConnectorTarget is bound, internal
|
5
|
+
"""
|
6
|
+
import abc
|
7
|
+
import dataclasses as dc
|
8
|
+
import enum
|
9
|
+
import typing as ta
|
10
|
+
|
11
|
+
from omlish.lite.check import check
|
12
|
+
|
13
|
+
|
14
|
+
##
|
15
|
+
|
16
|
+
|
17
|
+
class ManageTarget(abc.ABC): # noqa
|
18
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
19
|
+
super().__init_subclass__(**kwargs)
|
20
|
+
|
21
|
+
check.state(cls.__name__.endswith('ManageTarget'))
|
22
|
+
|
23
|
+
|
24
|
+
#
|
25
|
+
|
26
|
+
|
27
|
+
@dc.dataclass(frozen=True)
|
28
|
+
class PythonRemoteManageTarget:
|
29
|
+
DEFAULT_PYTHON: ta.ClassVar[str] = 'python3'
|
30
|
+
python: str = DEFAULT_PYTHON
|
31
|
+
|
32
|
+
|
33
|
+
#
|
34
|
+
|
35
|
+
|
36
|
+
class RemoteManageTarget(ManageTarget, abc.ABC):
|
37
|
+
pass
|
38
|
+
|
39
|
+
|
40
|
+
class PhysicallyRemoteManageTarget(RemoteManageTarget, abc.ABC):
|
41
|
+
pass
|
42
|
+
|
43
|
+
|
44
|
+
class LocalManageTarget(ManageTarget, abc.ABC):
|
45
|
+
pass
|
46
|
+
|
47
|
+
|
48
|
+
##
|
49
|
+
|
50
|
+
|
51
|
+
@dc.dataclass(frozen=True)
|
52
|
+
class SshManageTarget(PhysicallyRemoteManageTarget, PythonRemoteManageTarget):
|
53
|
+
host: ta.Optional[str] = None
|
54
|
+
username: ta.Optional[str] = None
|
55
|
+
key_file: ta.Optional[str] = None
|
56
|
+
|
57
|
+
def __post_init__(self) -> None:
|
58
|
+
check.non_empty_str(self.host)
|
59
|
+
|
60
|
+
|
61
|
+
##
|
62
|
+
|
63
|
+
|
64
|
+
@dc.dataclass(frozen=True)
|
65
|
+
class DockerManageTarget(RemoteManageTarget, PythonRemoteManageTarget): # noqa
|
66
|
+
image: ta.Optional[str] = None
|
67
|
+
container_id: ta.Optional[str] = None
|
68
|
+
|
69
|
+
def __post_init__(self) -> None:
|
70
|
+
check.arg(bool(self.image) ^ bool(self.container_id))
|
71
|
+
|
72
|
+
|
73
|
+
##
|
74
|
+
|
75
|
+
|
76
|
+
@dc.dataclass(frozen=True)
|
77
|
+
class InProcessManageTarget(LocalManageTarget):
|
78
|
+
class Mode(enum.Enum):
|
79
|
+
DIRECT = enum.auto()
|
80
|
+
FAKE_REMOTE = enum.auto()
|
81
|
+
|
82
|
+
mode: Mode = Mode.DIRECT
|
83
|
+
|
84
|
+
|
85
|
+
@dc.dataclass(frozen=True)
|
86
|
+
class SubprocessManageTarget(LocalManageTarget, PythonRemoteManageTarget):
|
87
|
+
pass
|
ominfra/scripts/journald2aws.py
CHANGED
@@ -961,8 +961,6 @@ def async_cached_nullary(fn): # ta.Callable[..., T]) -> ta.Callable[..., T]:
|
|
961
961
|
"""
|
962
962
|
TODO:
|
963
963
|
- def maybe(v: lang.Maybe[T])
|
964
|
-
- patch / override lite.check ?
|
965
|
-
- checker interface?
|
966
964
|
"""
|
967
965
|
|
968
966
|
|
@@ -2270,6 +2268,20 @@ def attr_setting(obj, attr, val, *, default=None): # noqa
|
|
2270
2268
|
setattr(obj, attr, orig)
|
2271
2269
|
|
2272
2270
|
|
2271
|
+
##
|
2272
|
+
|
2273
|
+
|
2274
|
+
class aclosing(contextlib.AbstractAsyncContextManager): # noqa
|
2275
|
+
def __init__(self, thing):
|
2276
|
+
self.thing = thing
|
2277
|
+
|
2278
|
+
async def __aenter__(self):
|
2279
|
+
return self.thing
|
2280
|
+
|
2281
|
+
async def __aexit__(self, *exc_info):
|
2282
|
+
await self.thing.aclose()
|
2283
|
+
|
2284
|
+
|
2273
2285
|
########################################
|
2274
2286
|
# ../../../../../omlish/lite/logs.py
|
2275
2287
|
"""
|
@@ -2545,7 +2557,8 @@ def configure_standard_logging(
|
|
2545
2557
|
"""
|
2546
2558
|
TODO:
|
2547
2559
|
- pickle stdlib objs? have to pin to 3.8 pickle protocol, will be cross-version
|
2548
|
-
-
|
2560
|
+
- namedtuple
|
2561
|
+
- literals
|
2549
2562
|
"""
|
2550
2563
|
|
2551
2564
|
|
@@ -2835,14 +2848,18 @@ class ObjMarshalerManager:
|
|
2835
2848
|
) -> ObjMarshaler:
|
2836
2849
|
if isinstance(ty, type):
|
2837
2850
|
if abc.ABC in ty.__bases__:
|
2838
|
-
|
2851
|
+
impls = [ity for ity in deep_subclasses(ty) if abc.ABC not in ity.__bases__] # type: ignore
|
2852
|
+
if all(ity.__qualname__.endswith(ty.__name__) for ity in impls):
|
2853
|
+
ins = {ity: snake_case(ity.__qualname__[:-len(ty.__name__)]) for ity in impls}
|
2854
|
+
else:
|
2855
|
+
ins = {ity: ity.__qualname__ for ity in impls}
|
2856
|
+
return PolymorphicObjMarshaler.of([
|
2839
2857
|
PolymorphicObjMarshaler.Impl(
|
2840
2858
|
ity,
|
2841
|
-
|
2859
|
+
itn,
|
2842
2860
|
rec(ity),
|
2843
2861
|
)
|
2844
|
-
for ity in
|
2845
|
-
if abc.ABC not in ity.__bases__
|
2862
|
+
for ity, itn in ins.items()
|
2846
2863
|
])
|
2847
2864
|
|
2848
2865
|
if issubclass(ty, enum.Enum):
|
@@ -3576,168 +3593,222 @@ SUBPROCESS_CHANNEL_OPTION_VALUES: ta.Mapping[SubprocessChannelOption, int] = {
|
|
3576
3593
|
_SUBPROCESS_SHELL_WRAP_EXECS = False
|
3577
3594
|
|
3578
3595
|
|
3579
|
-
def subprocess_shell_wrap_exec(*
|
3580
|
-
return ('sh', '-c', ' '.join(map(shlex.quote,
|
3596
|
+
def subprocess_shell_wrap_exec(*cmd: str) -> ta.Tuple[str, ...]:
|
3597
|
+
return ('sh', '-c', ' '.join(map(shlex.quote, cmd)))
|
3581
3598
|
|
3582
3599
|
|
3583
|
-
def subprocess_maybe_shell_wrap_exec(*
|
3600
|
+
def subprocess_maybe_shell_wrap_exec(*cmd: str) -> ta.Tuple[str, ...]:
|
3584
3601
|
if _SUBPROCESS_SHELL_WRAP_EXECS or is_debugger_attached():
|
3585
|
-
return subprocess_shell_wrap_exec(*
|
3602
|
+
return subprocess_shell_wrap_exec(*cmd)
|
3586
3603
|
else:
|
3587
|
-
return
|
3588
|
-
|
3589
|
-
|
3590
|
-
def prepare_subprocess_invocation(
|
3591
|
-
*args: str,
|
3592
|
-
env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
|
3593
|
-
extra_env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
|
3594
|
-
quiet: bool = False,
|
3595
|
-
shell: bool = False,
|
3596
|
-
**kwargs: ta.Any,
|
3597
|
-
) -> ta.Tuple[ta.Tuple[ta.Any, ...], ta.Dict[str, ta.Any]]:
|
3598
|
-
log.debug('prepare_subprocess_invocation: args=%r', args)
|
3599
|
-
if extra_env:
|
3600
|
-
log.debug('prepare_subprocess_invocation: extra_env=%r', extra_env)
|
3601
|
-
|
3602
|
-
if extra_env:
|
3603
|
-
env = {**(env if env is not None else os.environ), **extra_env}
|
3604
|
-
|
3605
|
-
if quiet and 'stderr' not in kwargs:
|
3606
|
-
if not log.isEnabledFor(logging.DEBUG):
|
3607
|
-
kwargs['stderr'] = subprocess.DEVNULL
|
3608
|
-
|
3609
|
-
if not shell:
|
3610
|
-
args = subprocess_maybe_shell_wrap_exec(*args)
|
3611
|
-
|
3612
|
-
return args, dict(
|
3613
|
-
env=env,
|
3614
|
-
shell=shell,
|
3615
|
-
**kwargs,
|
3616
|
-
)
|
3604
|
+
return cmd
|
3617
3605
|
|
3618
3606
|
|
3619
3607
|
##
|
3620
3608
|
|
3621
3609
|
|
3622
|
-
|
3623
|
-
|
3624
|
-
|
3625
|
-
|
3626
|
-
|
3627
|
-
|
3628
|
-
|
3629
|
-
|
3630
|
-
|
3631
|
-
|
3610
|
+
def subprocess_close(
|
3611
|
+
proc: subprocess.Popen,
|
3612
|
+
timeout: ta.Optional[float] = None,
|
3613
|
+
) -> None:
|
3614
|
+
# TODO: terminate, sleep, kill
|
3615
|
+
if proc.stdout:
|
3616
|
+
proc.stdout.close()
|
3617
|
+
if proc.stderr:
|
3618
|
+
proc.stderr.close()
|
3619
|
+
if proc.stdin:
|
3620
|
+
proc.stdin.close()
|
3632
3621
|
|
3633
|
-
|
3634
|
-
end_time = time.time()
|
3635
|
-
elapsed_s = end_time - start_time
|
3636
|
-
log.debug('subprocess_common_context.finally: elapsed_s=%f args=%r', elapsed_s, args)
|
3622
|
+
proc.wait(timeout)
|
3637
3623
|
|
3638
3624
|
|
3639
3625
|
##
|
3640
3626
|
|
3641
3627
|
|
3642
|
-
|
3643
|
-
|
3644
|
-
stdout: ta.Any = sys.stderr,
|
3645
|
-
**kwargs: ta.Any,
|
3646
|
-
) -> None:
|
3647
|
-
args, kwargs = prepare_subprocess_invocation(*args, stdout=stdout, **kwargs)
|
3648
|
-
with subprocess_common_context(*args, **kwargs):
|
3649
|
-
return subprocess.check_call(args, **kwargs) # type: ignore
|
3628
|
+
class AbstractSubprocesses(abc.ABC): # noqa
|
3629
|
+
DEFAULT_LOGGER: ta.ClassVar[ta.Optional[logging.Logger]] = log
|
3650
3630
|
|
3631
|
+
def __init__(
|
3632
|
+
self,
|
3633
|
+
*,
|
3634
|
+
log: ta.Optional[logging.Logger] = None,
|
3635
|
+
try_exceptions: ta.Optional[ta.Tuple[ta.Type[Exception], ...]] = None,
|
3636
|
+
) -> None:
|
3637
|
+
super().__init__()
|
3651
3638
|
|
3652
|
-
|
3653
|
-
|
3654
|
-
**kwargs: ta.Any,
|
3655
|
-
) -> bytes:
|
3656
|
-
args, kwargs = prepare_subprocess_invocation(*args, **kwargs)
|
3657
|
-
with subprocess_common_context(*args, **kwargs):
|
3658
|
-
return subprocess.check_output(args, **kwargs)
|
3639
|
+
self._log = log if log is not None else self.DEFAULT_LOGGER
|
3640
|
+
self._try_exceptions = try_exceptions if try_exceptions is not None else self.DEFAULT_TRY_EXCEPTIONS
|
3659
3641
|
|
3642
|
+
#
|
3660
3643
|
|
3661
|
-
def
|
3662
|
-
|
3644
|
+
def prepare_args(
|
3645
|
+
self,
|
3646
|
+
*cmd: str,
|
3647
|
+
env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
|
3648
|
+
extra_env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
|
3649
|
+
quiet: bool = False,
|
3650
|
+
shell: bool = False,
|
3651
|
+
**kwargs: ta.Any,
|
3652
|
+
) -> ta.Tuple[ta.Tuple[ta.Any, ...], ta.Dict[str, ta.Any]]:
|
3653
|
+
if self._log:
|
3654
|
+
self._log.debug('Subprocesses.prepare_args: cmd=%r', cmd)
|
3655
|
+
if extra_env:
|
3656
|
+
self._log.debug('Subprocesses.prepare_args: extra_env=%r', extra_env)
|
3663
3657
|
|
3658
|
+
if extra_env:
|
3659
|
+
env = {**(env if env is not None else os.environ), **extra_env}
|
3664
3660
|
|
3665
|
-
|
3661
|
+
if quiet and 'stderr' not in kwargs:
|
3662
|
+
if self._log and not self._log.isEnabledFor(logging.DEBUG):
|
3663
|
+
kwargs['stderr'] = subprocess.DEVNULL
|
3666
3664
|
|
3665
|
+
if not shell:
|
3666
|
+
cmd = subprocess_maybe_shell_wrap_exec(*cmd)
|
3667
3667
|
|
3668
|
-
|
3669
|
-
|
3670
|
-
|
3671
|
-
|
3668
|
+
return cmd, dict(
|
3669
|
+
env=env,
|
3670
|
+
shell=shell,
|
3671
|
+
**kwargs,
|
3672
|
+
)
|
3672
3673
|
|
3674
|
+
@contextlib.contextmanager
|
3675
|
+
def wrap_call(self, *cmd: ta.Any, **kwargs: ta.Any) -> ta.Iterator[None]:
|
3676
|
+
start_time = time.time()
|
3677
|
+
try:
|
3678
|
+
if self._log:
|
3679
|
+
self._log.debug('Subprocesses.wrap_call.try: cmd=%r', cmd)
|
3680
|
+
yield
|
3673
3681
|
|
3674
|
-
|
3675
|
-
|
3676
|
-
|
3677
|
-
|
3678
|
-
**kwargs: ta.Any,
|
3679
|
-
) -> ta.Union[T, Exception]:
|
3680
|
-
try:
|
3681
|
-
return fn(*args, **kwargs)
|
3682
|
-
except try_exceptions as e: # noqa
|
3683
|
-
if log.isEnabledFor(logging.DEBUG):
|
3684
|
-
log.exception('command failed')
|
3685
|
-
return e
|
3686
|
-
|
3687
|
-
|
3688
|
-
def subprocess_try_call(
|
3689
|
-
*args: str,
|
3690
|
-
try_exceptions: ta.Tuple[ta.Type[Exception], ...] = DEFAULT_SUBPROCESS_TRY_EXCEPTIONS,
|
3691
|
-
**kwargs: ta.Any,
|
3692
|
-
) -> bool:
|
3693
|
-
if isinstance(_subprocess_try_run(
|
3694
|
-
subprocess_check_call,
|
3695
|
-
*args,
|
3696
|
-
try_exceptions=try_exceptions,
|
3697
|
-
**kwargs,
|
3698
|
-
), Exception):
|
3699
|
-
return False
|
3700
|
-
else:
|
3701
|
-
return True
|
3682
|
+
except Exception as exc: # noqa
|
3683
|
+
if self._log:
|
3684
|
+
self._log.debug('Subprocesses.wrap_call.except: exc=%r', exc)
|
3685
|
+
raise
|
3702
3686
|
|
3687
|
+
finally:
|
3688
|
+
end_time = time.time()
|
3689
|
+
elapsed_s = end_time - start_time
|
3690
|
+
if self._log:
|
3691
|
+
self._log.debug('sSubprocesses.wrap_call.finally: elapsed_s=%f cmd=%r', elapsed_s, cmd)
|
3703
3692
|
|
3704
|
-
|
3705
|
-
|
3706
|
-
|
3707
|
-
|
3708
|
-
|
3709
|
-
|
3710
|
-
|
3711
|
-
|
3712
|
-
|
3713
|
-
|
3714
|
-
|
3715
|
-
|
3716
|
-
else:
|
3717
|
-
return ret
|
3693
|
+
@contextlib.contextmanager
|
3694
|
+
def prepare_and_wrap(
|
3695
|
+
self,
|
3696
|
+
*cmd: ta.Any,
|
3697
|
+
**kwargs: ta.Any,
|
3698
|
+
) -> ta.Iterator[ta.Tuple[
|
3699
|
+
ta.Tuple[ta.Any, ...],
|
3700
|
+
ta.Dict[str, ta.Any],
|
3701
|
+
]]:
|
3702
|
+
cmd, kwargs = self.prepare_args(*cmd, **kwargs)
|
3703
|
+
with self.wrap_call(*cmd, **kwargs):
|
3704
|
+
yield cmd, kwargs
|
3718
3705
|
|
3706
|
+
#
|
3719
3707
|
|
3720
|
-
|
3721
|
-
|
3722
|
-
|
3708
|
+
DEFAULT_TRY_EXCEPTIONS: ta.Tuple[ta.Type[Exception], ...] = (
|
3709
|
+
FileNotFoundError,
|
3710
|
+
subprocess.CalledProcessError,
|
3711
|
+
)
|
3712
|
+
|
3713
|
+
def try_fn(
|
3714
|
+
self,
|
3715
|
+
fn: ta.Callable[..., T],
|
3716
|
+
*cmd: str,
|
3717
|
+
try_exceptions: ta.Optional[ta.Tuple[ta.Type[Exception], ...]] = None,
|
3718
|
+
**kwargs: ta.Any,
|
3719
|
+
) -> ta.Union[T, Exception]:
|
3720
|
+
if try_exceptions is None:
|
3721
|
+
try_exceptions = self._try_exceptions
|
3722
|
+
|
3723
|
+
try:
|
3724
|
+
return fn(*cmd, **kwargs)
|
3725
|
+
|
3726
|
+
except try_exceptions as e: # noqa
|
3727
|
+
if self._log and self._log.isEnabledFor(logging.DEBUG):
|
3728
|
+
self._log.exception('command failed')
|
3729
|
+
return e
|
3730
|
+
|
3731
|
+
async def async_try_fn(
|
3732
|
+
self,
|
3733
|
+
fn: ta.Callable[..., ta.Awaitable[T]],
|
3734
|
+
*cmd: ta.Any,
|
3735
|
+
try_exceptions: ta.Optional[ta.Tuple[ta.Type[Exception], ...]] = None,
|
3736
|
+
**kwargs: ta.Any,
|
3737
|
+
) -> ta.Union[T, Exception]:
|
3738
|
+
if try_exceptions is None:
|
3739
|
+
try_exceptions = self._try_exceptions
|
3740
|
+
|
3741
|
+
try:
|
3742
|
+
return await fn(*cmd, **kwargs)
|
3743
|
+
|
3744
|
+
except try_exceptions as e: # noqa
|
3745
|
+
if self._log and self._log.isEnabledFor(logging.DEBUG):
|
3746
|
+
self._log.exception('command failed')
|
3747
|
+
return e
|
3723
3748
|
|
3724
3749
|
|
3725
3750
|
##
|
3726
3751
|
|
3727
3752
|
|
3728
|
-
|
3729
|
-
|
3730
|
-
|
3731
|
-
|
3732
|
-
|
3733
|
-
|
3734
|
-
|
3735
|
-
|
3736
|
-
|
3737
|
-
if proc.stdin:
|
3738
|
-
proc.stdin.close()
|
3753
|
+
class Subprocesses(AbstractSubprocesses):
|
3754
|
+
def check_call(
|
3755
|
+
self,
|
3756
|
+
*cmd: str,
|
3757
|
+
stdout: ta.Any = sys.stderr,
|
3758
|
+
**kwargs: ta.Any,
|
3759
|
+
) -> None:
|
3760
|
+
with self.prepare_and_wrap(*cmd, stdout=stdout, **kwargs) as (cmd, kwargs): # noqa
|
3761
|
+
subprocess.check_call(cmd, **kwargs)
|
3739
3762
|
|
3740
|
-
|
3763
|
+
def check_output(
|
3764
|
+
self,
|
3765
|
+
*cmd: str,
|
3766
|
+
**kwargs: ta.Any,
|
3767
|
+
) -> bytes:
|
3768
|
+
with self.prepare_and_wrap(*cmd, **kwargs) as (cmd, kwargs): # noqa
|
3769
|
+
return subprocess.check_output(cmd, **kwargs)
|
3770
|
+
|
3771
|
+
def check_output_str(
|
3772
|
+
self,
|
3773
|
+
*cmd: str,
|
3774
|
+
**kwargs: ta.Any,
|
3775
|
+
) -> str:
|
3776
|
+
return self.check_output(*cmd, **kwargs).decode().strip()
|
3777
|
+
|
3778
|
+
#
|
3779
|
+
|
3780
|
+
def try_call(
|
3781
|
+
self,
|
3782
|
+
*cmd: str,
|
3783
|
+
**kwargs: ta.Any,
|
3784
|
+
) -> bool:
|
3785
|
+
if isinstance(self.try_fn(self.check_call, *cmd, **kwargs), Exception):
|
3786
|
+
return False
|
3787
|
+
else:
|
3788
|
+
return True
|
3789
|
+
|
3790
|
+
def try_output(
|
3791
|
+
self,
|
3792
|
+
*cmd: str,
|
3793
|
+
**kwargs: ta.Any,
|
3794
|
+
) -> ta.Optional[bytes]:
|
3795
|
+
if isinstance(ret := self.try_fn(self.check_output, *cmd, **kwargs), Exception):
|
3796
|
+
return None
|
3797
|
+
else:
|
3798
|
+
return ret
|
3799
|
+
|
3800
|
+
def try_output_str(
|
3801
|
+
self,
|
3802
|
+
*cmd: str,
|
3803
|
+
**kwargs: ta.Any,
|
3804
|
+
) -> ta.Optional[str]:
|
3805
|
+
if (ret := self.try_output(*cmd, **kwargs)) is None:
|
3806
|
+
return None
|
3807
|
+
else:
|
3808
|
+
return ret.decode().strip()
|
3809
|
+
|
3810
|
+
|
3811
|
+
subprocesses = Subprocesses()
|
3741
3812
|
|
3742
3813
|
|
3743
3814
|
########################################
|