ominfra 0.0.0.dev154__py3-none-any.whl → 0.0.0.dev156__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.
- 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
|
########################################
|