ominfra 0.0.0.dev155__py3-none-any.whl → 0.0.0.dev157__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/commands/subprocess.py +3 -4
- ominfra/manage/deploy/git.py +3 -3
- ominfra/manage/deploy/venvs.py +4 -4
- ominfra/manage/main.py +33 -4
- ominfra/manage/remote/spawning.py +3 -8
- ominfra/manage/system/packages.py +13 -15
- ominfra/scripts/journald2aws.py +184 -129
- ominfra/scripts/manage.py +1276 -325
- ominfra/scripts/supervisor.py +44 -31
- ominfra/supervisor/configs.py +2 -0
- ominfra/supervisor/supervisor.py +2 -33
- ominfra/supervisor/utils/os.py +41 -0
- {ominfra-0.0.0.dev155.dist-info → ominfra-0.0.0.dev157.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev155.dist-info → ominfra-0.0.0.dev157.dist-info}/RECORD +18 -18
- {ominfra-0.0.0.dev155.dist-info → ominfra-0.0.0.dev157.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev155.dist-info → ominfra-0.0.0.dev157.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev155.dist-info → ominfra-0.0.0.dev157.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev155.dist-info → ominfra-0.0.0.dev157.dist-info}/top_level.txt +0 -0
@@ -6,8 +6,7 @@ import subprocess
|
|
6
6
|
import time
|
7
7
|
import typing as ta
|
8
8
|
|
9
|
-
from omlish.lite.asyncio.subprocesses import
|
10
|
-
from omlish.lite.asyncio.subprocesses import asyncio_subprocess_popen
|
9
|
+
from omlish.lite.asyncio.subprocesses import asyncio_subprocesses
|
11
10
|
from omlish.lite.check import check
|
12
11
|
from omlish.lite.subprocesses import SUBPROCESS_CHANNEL_OPTION_VALUES
|
13
12
|
from omlish.lite.subprocesses import SubprocessChannelOption
|
@@ -51,7 +50,7 @@ class SubprocessCommand(Command['SubprocessCommand.Output']):
|
|
51
50
|
class SubprocessCommandExecutor(CommandExecutor[SubprocessCommand, SubprocessCommand.Output]):
|
52
51
|
async def execute(self, cmd: SubprocessCommand) -> SubprocessCommand.Output:
|
53
52
|
proc: asyncio.subprocess.Process
|
54
|
-
async with
|
53
|
+
async with asyncio_subprocesses.popen(
|
55
54
|
*subprocess_maybe_shell_wrap_exec(*cmd.cmd),
|
56
55
|
|
57
56
|
shell=cmd.shell,
|
@@ -65,7 +64,7 @@ class SubprocessCommandExecutor(CommandExecutor[SubprocessCommand, SubprocessCom
|
|
65
64
|
timeout=cmd.timeout,
|
66
65
|
) as proc:
|
67
66
|
start_time = time.time()
|
68
|
-
stdout, stderr = await
|
67
|
+
stdout, stderr = await asyncio_subprocesses.communicate(
|
69
68
|
proc,
|
70
69
|
input=cmd.input,
|
71
70
|
timeout=cmd.timeout,
|
ominfra/manage/deploy/git.py
CHANGED
@@ -13,7 +13,7 @@ import functools
|
|
13
13
|
import os.path
|
14
14
|
import typing as ta
|
15
15
|
|
16
|
-
from omlish.lite.asyncio.subprocesses import
|
16
|
+
from omlish.lite.asyncio.subprocesses import asyncio_subprocesses
|
17
17
|
from omlish.lite.cached import async_cached_nullary
|
18
18
|
from omlish.lite.check import check
|
19
19
|
|
@@ -92,7 +92,7 @@ class DeployGitManager(DeployPathOwner):
|
|
92
92
|
return f'https://{self._repo.host}/{self._repo.path}'
|
93
93
|
|
94
94
|
async def _call(self, *cmd: str) -> None:
|
95
|
-
await
|
95
|
+
await asyncio_subprocesses.check_call(
|
96
96
|
*cmd,
|
97
97
|
cwd=self._dir,
|
98
98
|
)
|
@@ -118,7 +118,7 @@ class DeployGitManager(DeployPathOwner):
|
|
118
118
|
# FIXME: temp dir swap
|
119
119
|
os.makedirs(dst_dir)
|
120
120
|
|
121
|
-
dst_call = functools.partial(
|
121
|
+
dst_call = functools.partial(asyncio_subprocesses.check_call, cwd=dst_dir)
|
122
122
|
await dst_call('git', 'init')
|
123
123
|
|
124
124
|
await dst_call('git', 'remote', 'add', 'local', self._dir)
|
ominfra/manage/deploy/venvs.py
CHANGED
@@ -7,7 +7,7 @@ TODO:
|
|
7
7
|
import os.path
|
8
8
|
import typing as ta
|
9
9
|
|
10
|
-
from omlish.lite.asyncio.subprocesses import
|
10
|
+
from omlish.lite.asyncio.subprocesses import asyncio_subprocesses
|
11
11
|
|
12
12
|
from .paths import DeployPath
|
13
13
|
from .paths import DeployPathOwner
|
@@ -40,7 +40,7 @@ class DeployVenvManager(DeployPathOwner):
|
|
40
40
|
) -> None:
|
41
41
|
sys_exe = 'python3'
|
42
42
|
|
43
|
-
await
|
43
|
+
await asyncio_subprocesses.check_call(sys_exe, '-m', 'venv', venv_dir)
|
44
44
|
|
45
45
|
#
|
46
46
|
|
@@ -52,12 +52,12 @@ class DeployVenvManager(DeployPathOwner):
|
|
52
52
|
|
53
53
|
if os.path.isfile(reqs_txt):
|
54
54
|
if use_uv:
|
55
|
-
await
|
55
|
+
await asyncio_subprocesses.check_call(venv_exe, '-m', 'pip', 'install', 'uv')
|
56
56
|
pip_cmd = ['-m', 'uv', 'pip']
|
57
57
|
else:
|
58
58
|
pip_cmd = ['-m', 'pip']
|
59
59
|
|
60
|
-
await
|
60
|
+
await asyncio_subprocesses.check_call(venv_exe, *pip_cmd,'install', '-r', reqs_txt)
|
61
61
|
|
62
62
|
async def setup_app_venv(self, app_tag: DeployAppTag) -> None:
|
63
63
|
await self.setup_venv(
|
ominfra/manage/main.py
CHANGED
@@ -6,18 +6,23 @@ manage.py -s 'docker run -i python:3.12'
|
|
6
6
|
manage.py -s 'ssh -i /foo/bar.pem foo@bar.baz' -q --python=python3.8
|
7
7
|
"""
|
8
8
|
import asyncio
|
9
|
+
import dataclasses as dc
|
9
10
|
import json
|
11
|
+
import os.path
|
10
12
|
import sys
|
11
13
|
import typing as ta
|
12
14
|
|
13
15
|
from omlish.argparse.cli import ArgparseCli
|
14
16
|
from omlish.argparse.cli import argparse_arg
|
15
17
|
from omlish.argparse.cli import argparse_command
|
18
|
+
from omlish.lite.cached import cached_nullary
|
19
|
+
from omlish.lite.check import check
|
16
20
|
from omlish.lite.logs import log # noqa
|
17
21
|
from omlish.lite.marshal import ObjMarshalerManager
|
18
22
|
from omlish.lite.marshal import ObjMarshalOptions
|
19
23
|
from omlish.lite.pycharm import PycharmRemoteDebug
|
20
24
|
|
25
|
+
from ..configs import read_config_file
|
21
26
|
from .bootstrap import MainBootstrap
|
22
27
|
from .bootstrap_ import main_bootstrap
|
23
28
|
from .commands.base import Command
|
@@ -28,7 +33,28 @@ from .targets.connection import ManageTargetConnector
|
|
28
33
|
from .targets.targets import ManageTarget
|
29
34
|
|
30
35
|
|
36
|
+
@dc.dataclass(frozen=True)
|
37
|
+
class ManageConfig:
|
38
|
+
targets: ta.Optional[ta.Mapping[str, ManageTarget]] = None
|
39
|
+
|
40
|
+
|
31
41
|
class MainCli(ArgparseCli):
|
42
|
+
config_file: ta.Optional[str] = argparse_arg('--config-file', help='Config file path') # type: ignore
|
43
|
+
|
44
|
+
@cached_nullary
|
45
|
+
def config(self) -> ManageConfig:
|
46
|
+
if (cf := self.config_file) is None:
|
47
|
+
cf = os.path.expanduser('~/.omlish/manage.yml')
|
48
|
+
if not os.path.isfile(cf):
|
49
|
+
cf = None
|
50
|
+
|
51
|
+
if cf is None:
|
52
|
+
return ManageConfig()
|
53
|
+
else:
|
54
|
+
return read_config_file(cf, ManageConfig)
|
55
|
+
|
56
|
+
#
|
57
|
+
|
32
58
|
@argparse_command(
|
33
59
|
argparse_arg('--_payload-file'),
|
34
60
|
|
@@ -80,10 +106,13 @@ class MainCli(ArgparseCli):
|
|
80
106
|
|
81
107
|
msh = injector[ObjMarshalerManager]
|
82
108
|
|
83
|
-
|
84
|
-
if not ts.startswith('{'):
|
85
|
-
|
86
|
-
|
109
|
+
tgt: ManageTarget
|
110
|
+
if not (ts := self.args.target).startswith('{'):
|
111
|
+
tgt = check.not_none(self.config().targets)[ts]
|
112
|
+
else:
|
113
|
+
tgt = msh.unmarshal_obj(json.loads(ts), ManageTarget)
|
114
|
+
|
115
|
+
#
|
87
116
|
|
88
117
|
cmds: ta.List[Command] = []
|
89
118
|
cmd: Command
|
@@ -7,11 +7,10 @@ import shlex
|
|
7
7
|
import subprocess
|
8
8
|
import typing as ta
|
9
9
|
|
10
|
-
from omlish.lite.asyncio.subprocesses import
|
10
|
+
from omlish.lite.asyncio.subprocesses import asyncio_subprocesses
|
11
11
|
from omlish.lite.check import check
|
12
12
|
from omlish.lite.subprocesses import SUBPROCESS_CHANNEL_OPTION_VALUES
|
13
13
|
from omlish.lite.subprocesses import SubprocessChannelOption
|
14
|
-
from omlish.lite.subprocesses import subprocess_maybe_shell_wrap_exec
|
15
14
|
|
16
15
|
|
17
16
|
##
|
@@ -82,12 +81,8 @@ class SubprocessRemoteSpawning(RemoteSpawning):
|
|
82
81
|
) -> ta.AsyncGenerator[RemoteSpawning.Spawned, None]:
|
83
82
|
pc = self._prepare_cmd(tgt, src)
|
84
83
|
|
85
|
-
|
86
|
-
|
87
|
-
cmd = subprocess_maybe_shell_wrap_exec(*cmd)
|
88
|
-
|
89
|
-
async with asyncio_subprocess_popen(
|
90
|
-
*cmd,
|
84
|
+
async with asyncio_subprocesses.popen(
|
85
|
+
*pc.cmd,
|
91
86
|
shell=pc.shell,
|
92
87
|
stdin=subprocess.PIPE,
|
93
88
|
stdout=subprocess.PIPE,
|
@@ -9,9 +9,7 @@ import json
|
|
9
9
|
import os
|
10
10
|
import typing as ta
|
11
11
|
|
12
|
-
from omlish.lite.asyncio.subprocesses import
|
13
|
-
from omlish.lite.asyncio.subprocesses import asyncio_subprocess_check_output
|
14
|
-
from omlish.lite.asyncio.subprocesses import asyncio_subprocess_run
|
12
|
+
from omlish.lite.asyncio.subprocesses import asyncio_subprocesses
|
15
13
|
from omlish.lite.check import check
|
16
14
|
|
17
15
|
|
@@ -44,10 +42,10 @@ class SystemPackageManager(abc.ABC):
|
|
44
42
|
|
45
43
|
class BrewSystemPackageManager(SystemPackageManager):
|
46
44
|
async def update(self) -> None:
|
47
|
-
await
|
45
|
+
await asyncio_subprocesses.check_call('brew', 'update')
|
48
46
|
|
49
47
|
async def upgrade(self) -> None:
|
50
|
-
await
|
48
|
+
await asyncio_subprocesses.check_call('brew', 'upgrade')
|
51
49
|
|
52
50
|
async def install(self, *packages: SystemPackageOrStr) -> None:
|
53
51
|
es: ta.List[str] = []
|
@@ -56,11 +54,11 @@ class BrewSystemPackageManager(SystemPackageManager):
|
|
56
54
|
es.append(p.name + (f'@{p.version}' if p.version is not None else ''))
|
57
55
|
else:
|
58
56
|
es.append(p)
|
59
|
-
await
|
57
|
+
await asyncio_subprocesses.check_call('brew', 'install', *es)
|
60
58
|
|
61
59
|
async def query(self, *packages: SystemPackageOrStr) -> ta.Mapping[str, SystemPackage]:
|
62
60
|
pns = [p.name if isinstance(p, SystemPackage) else p for p in packages]
|
63
|
-
o = await
|
61
|
+
o = await asyncio_subprocesses.check_output('brew', 'info', '--json', *pns)
|
64
62
|
j = json.loads(o.decode())
|
65
63
|
d: ta.Dict[str, SystemPackage] = {}
|
66
64
|
for e in j:
|
@@ -79,18 +77,18 @@ class AptSystemPackageManager(SystemPackageManager):
|
|
79
77
|
}
|
80
78
|
|
81
79
|
async def update(self) -> None:
|
82
|
-
await
|
80
|
+
await asyncio_subprocesses.check_call('sudo', 'apt', 'update', env={**os.environ, **self._APT_ENV})
|
83
81
|
|
84
82
|
async def upgrade(self) -> None:
|
85
|
-
await
|
83
|
+
await asyncio_subprocesses.check_call('sudo', 'apt', 'upgrade', '-y', env={**os.environ, **self._APT_ENV})
|
86
84
|
|
87
85
|
async def install(self, *packages: SystemPackageOrStr) -> None:
|
88
86
|
pns = [p.name if isinstance(p, SystemPackage) else p for p in packages] # FIXME: versions
|
89
|
-
await
|
87
|
+
await asyncio_subprocesses.check_call('sudo', 'apt', 'install', '-y', *pns, env={**os.environ, **self._APT_ENV})
|
90
88
|
|
91
89
|
async def query(self, *packages: SystemPackageOrStr) -> ta.Mapping[str, SystemPackage]:
|
92
90
|
pns = [p.name if isinstance(p, SystemPackage) else p for p in packages]
|
93
|
-
out = await
|
91
|
+
out = await asyncio_subprocesses.run(
|
94
92
|
'dpkg-query', '-W', '-f=${Package}=${Version}\n', *pns,
|
95
93
|
capture_output=True,
|
96
94
|
check=False,
|
@@ -107,20 +105,20 @@ class AptSystemPackageManager(SystemPackageManager):
|
|
107
105
|
|
108
106
|
class YumSystemPackageManager(SystemPackageManager):
|
109
107
|
async def update(self) -> None:
|
110
|
-
await
|
108
|
+
await asyncio_subprocesses.check_call('sudo', 'yum', 'check-update')
|
111
109
|
|
112
110
|
async def upgrade(self) -> None:
|
113
|
-
await
|
111
|
+
await asyncio_subprocesses.check_call('sudo', 'yum', 'update')
|
114
112
|
|
115
113
|
async def install(self, *packages: SystemPackageOrStr) -> None:
|
116
114
|
pns = [p.name if isinstance(p, SystemPackage) else p for p in packages] # FIXME: versions
|
117
|
-
await
|
115
|
+
await asyncio_subprocesses.check_call('sudo', 'yum', 'install', *pns)
|
118
116
|
|
119
117
|
async def query(self, *packages: SystemPackageOrStr) -> ta.Mapping[str, SystemPackage]:
|
120
118
|
pns = [p.name if isinstance(p, SystemPackage) else p for p in packages]
|
121
119
|
d: ta.Dict[str, SystemPackage] = {}
|
122
120
|
for pn in pns:
|
123
|
-
out = await
|
121
|
+
out = await asyncio_subprocesses.run(
|
124
122
|
'rpm', '-q', pn,
|
125
123
|
capture_output=True,
|
126
124
|
)
|
ominfra/scripts/journald2aws.py
CHANGED
@@ -932,8 +932,8 @@ class _CachedNullary(_AbstractCachedNullary):
|
|
932
932
|
return self._value
|
933
933
|
|
934
934
|
|
935
|
-
def cached_nullary(fn
|
936
|
-
return _CachedNullary(fn)
|
935
|
+
def cached_nullary(fn: CallableT) -> CallableT:
|
936
|
+
return _CachedNullary(fn) # type: ignore
|
937
937
|
|
938
938
|
|
939
939
|
def static_init(fn: CallableT) -> CallableT:
|
@@ -2559,6 +2559,7 @@ TODO:
|
|
2559
2559
|
- pickle stdlib objs? have to pin to 3.8 pickle protocol, will be cross-version
|
2560
2560
|
- namedtuple
|
2561
2561
|
- literals
|
2562
|
+
- newtypes?
|
2562
2563
|
"""
|
2563
2564
|
|
2564
2565
|
|
@@ -3593,168 +3594,222 @@ SUBPROCESS_CHANNEL_OPTION_VALUES: ta.Mapping[SubprocessChannelOption, int] = {
|
|
3593
3594
|
_SUBPROCESS_SHELL_WRAP_EXECS = False
|
3594
3595
|
|
3595
3596
|
|
3596
|
-
def subprocess_shell_wrap_exec(*
|
3597
|
-
return ('sh', '-c', ' '.join(map(shlex.quote,
|
3597
|
+
def subprocess_shell_wrap_exec(*cmd: str) -> ta.Tuple[str, ...]:
|
3598
|
+
return ('sh', '-c', ' '.join(map(shlex.quote, cmd)))
|
3598
3599
|
|
3599
3600
|
|
3600
|
-
def subprocess_maybe_shell_wrap_exec(*
|
3601
|
+
def subprocess_maybe_shell_wrap_exec(*cmd: str) -> ta.Tuple[str, ...]:
|
3601
3602
|
if _SUBPROCESS_SHELL_WRAP_EXECS or is_debugger_attached():
|
3602
|
-
return subprocess_shell_wrap_exec(*
|
3603
|
+
return subprocess_shell_wrap_exec(*cmd)
|
3603
3604
|
else:
|
3604
|
-
return
|
3605
|
-
|
3606
|
-
|
3607
|
-
def prepare_subprocess_invocation(
|
3608
|
-
*args: str,
|
3609
|
-
env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
|
3610
|
-
extra_env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
|
3611
|
-
quiet: bool = False,
|
3612
|
-
shell: bool = False,
|
3613
|
-
**kwargs: ta.Any,
|
3614
|
-
) -> ta.Tuple[ta.Tuple[ta.Any, ...], ta.Dict[str, ta.Any]]:
|
3615
|
-
log.debug('prepare_subprocess_invocation: args=%r', args)
|
3616
|
-
if extra_env:
|
3617
|
-
log.debug('prepare_subprocess_invocation: extra_env=%r', extra_env)
|
3618
|
-
|
3619
|
-
if extra_env:
|
3620
|
-
env = {**(env if env is not None else os.environ), **extra_env}
|
3621
|
-
|
3622
|
-
if quiet and 'stderr' not in kwargs:
|
3623
|
-
if not log.isEnabledFor(logging.DEBUG):
|
3624
|
-
kwargs['stderr'] = subprocess.DEVNULL
|
3625
|
-
|
3626
|
-
if not shell:
|
3627
|
-
args = subprocess_maybe_shell_wrap_exec(*args)
|
3628
|
-
|
3629
|
-
return args, dict(
|
3630
|
-
env=env,
|
3631
|
-
shell=shell,
|
3632
|
-
**kwargs,
|
3633
|
-
)
|
3605
|
+
return cmd
|
3634
3606
|
|
3635
3607
|
|
3636
3608
|
##
|
3637
3609
|
|
3638
3610
|
|
3639
|
-
|
3640
|
-
|
3641
|
-
|
3642
|
-
|
3643
|
-
|
3644
|
-
|
3645
|
-
|
3646
|
-
|
3647
|
-
|
3648
|
-
|
3611
|
+
def subprocess_close(
|
3612
|
+
proc: subprocess.Popen,
|
3613
|
+
timeout: ta.Optional[float] = None,
|
3614
|
+
) -> None:
|
3615
|
+
# TODO: terminate, sleep, kill
|
3616
|
+
if proc.stdout:
|
3617
|
+
proc.stdout.close()
|
3618
|
+
if proc.stderr:
|
3619
|
+
proc.stderr.close()
|
3620
|
+
if proc.stdin:
|
3621
|
+
proc.stdin.close()
|
3649
3622
|
|
3650
|
-
|
3651
|
-
end_time = time.time()
|
3652
|
-
elapsed_s = end_time - start_time
|
3653
|
-
log.debug('subprocess_common_context.finally: elapsed_s=%f args=%r', elapsed_s, args)
|
3623
|
+
proc.wait(timeout)
|
3654
3624
|
|
3655
3625
|
|
3656
3626
|
##
|
3657
3627
|
|
3658
3628
|
|
3659
|
-
|
3660
|
-
|
3661
|
-
|
3662
|
-
|
3663
|
-
|
3664
|
-
|
3665
|
-
|
3666
|
-
|
3629
|
+
class AbstractSubprocesses(abc.ABC): # noqa
|
3630
|
+
DEFAULT_LOGGER: ta.ClassVar[ta.Optional[logging.Logger]] = log
|
3631
|
+
|
3632
|
+
def __init__(
|
3633
|
+
self,
|
3634
|
+
*,
|
3635
|
+
log: ta.Optional[logging.Logger] = None,
|
3636
|
+
try_exceptions: ta.Optional[ta.Tuple[ta.Type[Exception], ...]] = None,
|
3637
|
+
) -> None:
|
3638
|
+
super().__init__()
|
3667
3639
|
|
3640
|
+
self._log = log if log is not None else self.DEFAULT_LOGGER
|
3641
|
+
self._try_exceptions = try_exceptions if try_exceptions is not None else self.DEFAULT_TRY_EXCEPTIONS
|
3668
3642
|
|
3669
|
-
|
3670
|
-
*args: str,
|
3671
|
-
**kwargs: ta.Any,
|
3672
|
-
) -> bytes:
|
3673
|
-
args, kwargs = prepare_subprocess_invocation(*args, **kwargs)
|
3674
|
-
with subprocess_common_context(*args, **kwargs):
|
3675
|
-
return subprocess.check_output(args, **kwargs)
|
3643
|
+
#
|
3676
3644
|
|
3645
|
+
def prepare_args(
|
3646
|
+
self,
|
3647
|
+
*cmd: str,
|
3648
|
+
env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
|
3649
|
+
extra_env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
|
3650
|
+
quiet: bool = False,
|
3651
|
+
shell: bool = False,
|
3652
|
+
**kwargs: ta.Any,
|
3653
|
+
) -> ta.Tuple[ta.Tuple[ta.Any, ...], ta.Dict[str, ta.Any]]:
|
3654
|
+
if self._log:
|
3655
|
+
self._log.debug('Subprocesses.prepare_args: cmd=%r', cmd)
|
3656
|
+
if extra_env:
|
3657
|
+
self._log.debug('Subprocesses.prepare_args: extra_env=%r', extra_env)
|
3677
3658
|
|
3678
|
-
|
3679
|
-
|
3659
|
+
if extra_env:
|
3660
|
+
env = {**(env if env is not None else os.environ), **extra_env}
|
3680
3661
|
|
3662
|
+
if quiet and 'stderr' not in kwargs:
|
3663
|
+
if self._log and not self._log.isEnabledFor(logging.DEBUG):
|
3664
|
+
kwargs['stderr'] = subprocess.DEVNULL
|
3681
3665
|
|
3682
|
-
|
3666
|
+
if not shell:
|
3667
|
+
cmd = subprocess_maybe_shell_wrap_exec(*cmd)
|
3683
3668
|
|
3669
|
+
return cmd, dict(
|
3670
|
+
env=env,
|
3671
|
+
shell=shell,
|
3672
|
+
**kwargs,
|
3673
|
+
)
|
3684
3674
|
|
3685
|
-
|
3686
|
-
|
3687
|
-
|
3688
|
-
|
3675
|
+
@contextlib.contextmanager
|
3676
|
+
def wrap_call(self, *cmd: ta.Any, **kwargs: ta.Any) -> ta.Iterator[None]:
|
3677
|
+
start_time = time.time()
|
3678
|
+
try:
|
3679
|
+
if self._log:
|
3680
|
+
self._log.debug('Subprocesses.wrap_call.try: cmd=%r', cmd)
|
3681
|
+
yield
|
3689
3682
|
|
3683
|
+
except Exception as exc: # noqa
|
3684
|
+
if self._log:
|
3685
|
+
self._log.debug('Subprocesses.wrap_call.except: exc=%r', exc)
|
3686
|
+
raise
|
3690
3687
|
|
3691
|
-
|
3692
|
-
|
3693
|
-
|
3694
|
-
|
3695
|
-
|
3696
|
-
) -> ta.Union[T, Exception]:
|
3697
|
-
try:
|
3698
|
-
return fn(*args, **kwargs)
|
3699
|
-
except try_exceptions as e: # noqa
|
3700
|
-
if log.isEnabledFor(logging.DEBUG):
|
3701
|
-
log.exception('command failed')
|
3702
|
-
return e
|
3703
|
-
|
3704
|
-
|
3705
|
-
def subprocess_try_call(
|
3706
|
-
*args: str,
|
3707
|
-
try_exceptions: ta.Tuple[ta.Type[Exception], ...] = DEFAULT_SUBPROCESS_TRY_EXCEPTIONS,
|
3708
|
-
**kwargs: ta.Any,
|
3709
|
-
) -> bool:
|
3710
|
-
if isinstance(_subprocess_try_run(
|
3711
|
-
subprocess_check_call,
|
3712
|
-
*args,
|
3713
|
-
try_exceptions=try_exceptions,
|
3714
|
-
**kwargs,
|
3715
|
-
), Exception):
|
3716
|
-
return False
|
3717
|
-
else:
|
3718
|
-
return True
|
3688
|
+
finally:
|
3689
|
+
end_time = time.time()
|
3690
|
+
elapsed_s = end_time - start_time
|
3691
|
+
if self._log:
|
3692
|
+
self._log.debug('sSubprocesses.wrap_call.finally: elapsed_s=%f cmd=%r', elapsed_s, cmd)
|
3719
3693
|
|
3694
|
+
@contextlib.contextmanager
|
3695
|
+
def prepare_and_wrap(
|
3696
|
+
self,
|
3697
|
+
*cmd: ta.Any,
|
3698
|
+
**kwargs: ta.Any,
|
3699
|
+
) -> ta.Iterator[ta.Tuple[
|
3700
|
+
ta.Tuple[ta.Any, ...],
|
3701
|
+
ta.Dict[str, ta.Any],
|
3702
|
+
]]:
|
3703
|
+
cmd, kwargs = self.prepare_args(*cmd, **kwargs)
|
3704
|
+
with self.wrap_call(*cmd, **kwargs):
|
3705
|
+
yield cmd, kwargs
|
3720
3706
|
|
3721
|
-
|
3722
|
-
|
3723
|
-
|
3724
|
-
|
3725
|
-
|
3726
|
-
|
3727
|
-
subprocess_check_output,
|
3728
|
-
*args,
|
3729
|
-
try_exceptions=try_exceptions,
|
3730
|
-
**kwargs,
|
3731
|
-
), Exception):
|
3732
|
-
return None
|
3733
|
-
else:
|
3734
|
-
return ret
|
3707
|
+
#
|
3708
|
+
|
3709
|
+
DEFAULT_TRY_EXCEPTIONS: ta.Tuple[ta.Type[Exception], ...] = (
|
3710
|
+
FileNotFoundError,
|
3711
|
+
subprocess.CalledProcessError,
|
3712
|
+
)
|
3735
3713
|
|
3714
|
+
def try_fn(
|
3715
|
+
self,
|
3716
|
+
fn: ta.Callable[..., T],
|
3717
|
+
*cmd: str,
|
3718
|
+
try_exceptions: ta.Optional[ta.Tuple[ta.Type[Exception], ...]] = None,
|
3719
|
+
**kwargs: ta.Any,
|
3720
|
+
) -> ta.Union[T, Exception]:
|
3721
|
+
if try_exceptions is None:
|
3722
|
+
try_exceptions = self._try_exceptions
|
3736
3723
|
|
3737
|
-
|
3738
|
-
|
3739
|
-
|
3724
|
+
try:
|
3725
|
+
return fn(*cmd, **kwargs)
|
3726
|
+
|
3727
|
+
except try_exceptions as e: # noqa
|
3728
|
+
if self._log and self._log.isEnabledFor(logging.DEBUG):
|
3729
|
+
self._log.exception('command failed')
|
3730
|
+
return e
|
3731
|
+
|
3732
|
+
async def async_try_fn(
|
3733
|
+
self,
|
3734
|
+
fn: ta.Callable[..., ta.Awaitable[T]],
|
3735
|
+
*cmd: ta.Any,
|
3736
|
+
try_exceptions: ta.Optional[ta.Tuple[ta.Type[Exception], ...]] = None,
|
3737
|
+
**kwargs: ta.Any,
|
3738
|
+
) -> ta.Union[T, Exception]:
|
3739
|
+
if try_exceptions is None:
|
3740
|
+
try_exceptions = self._try_exceptions
|
3741
|
+
|
3742
|
+
try:
|
3743
|
+
return await fn(*cmd, **kwargs)
|
3744
|
+
|
3745
|
+
except try_exceptions as e: # noqa
|
3746
|
+
if self._log and self._log.isEnabledFor(logging.DEBUG):
|
3747
|
+
self._log.exception('command failed')
|
3748
|
+
return e
|
3740
3749
|
|
3741
3750
|
|
3742
3751
|
##
|
3743
3752
|
|
3744
3753
|
|
3745
|
-
|
3746
|
-
|
3747
|
-
|
3748
|
-
|
3749
|
-
|
3750
|
-
|
3751
|
-
|
3752
|
-
|
3753
|
-
|
3754
|
-
if proc.stdin:
|
3755
|
-
proc.stdin.close()
|
3754
|
+
class Subprocesses(AbstractSubprocesses):
|
3755
|
+
def check_call(
|
3756
|
+
self,
|
3757
|
+
*cmd: str,
|
3758
|
+
stdout: ta.Any = sys.stderr,
|
3759
|
+
**kwargs: ta.Any,
|
3760
|
+
) -> None:
|
3761
|
+
with self.prepare_and_wrap(*cmd, stdout=stdout, **kwargs) as (cmd, kwargs): # noqa
|
3762
|
+
subprocess.check_call(cmd, **kwargs)
|
3756
3763
|
|
3757
|
-
|
3764
|
+
def check_output(
|
3765
|
+
self,
|
3766
|
+
*cmd: str,
|
3767
|
+
**kwargs: ta.Any,
|
3768
|
+
) -> bytes:
|
3769
|
+
with self.prepare_and_wrap(*cmd, **kwargs) as (cmd, kwargs): # noqa
|
3770
|
+
return subprocess.check_output(cmd, **kwargs)
|
3771
|
+
|
3772
|
+
def check_output_str(
|
3773
|
+
self,
|
3774
|
+
*cmd: str,
|
3775
|
+
**kwargs: ta.Any,
|
3776
|
+
) -> str:
|
3777
|
+
return self.check_output(*cmd, **kwargs).decode().strip()
|
3778
|
+
|
3779
|
+
#
|
3780
|
+
|
3781
|
+
def try_call(
|
3782
|
+
self,
|
3783
|
+
*cmd: str,
|
3784
|
+
**kwargs: ta.Any,
|
3785
|
+
) -> bool:
|
3786
|
+
if isinstance(self.try_fn(self.check_call, *cmd, **kwargs), Exception):
|
3787
|
+
return False
|
3788
|
+
else:
|
3789
|
+
return True
|
3790
|
+
|
3791
|
+
def try_output(
|
3792
|
+
self,
|
3793
|
+
*cmd: str,
|
3794
|
+
**kwargs: ta.Any,
|
3795
|
+
) -> ta.Optional[bytes]:
|
3796
|
+
if isinstance(ret := self.try_fn(self.check_output, *cmd, **kwargs), Exception):
|
3797
|
+
return None
|
3798
|
+
else:
|
3799
|
+
return ret
|
3800
|
+
|
3801
|
+
def try_output_str(
|
3802
|
+
self,
|
3803
|
+
*cmd: str,
|
3804
|
+
**kwargs: ta.Any,
|
3805
|
+
) -> ta.Optional[str]:
|
3806
|
+
if (ret := self.try_output(*cmd, **kwargs)) is None:
|
3807
|
+
return None
|
3808
|
+
else:
|
3809
|
+
return ret.decode().strip()
|
3810
|
+
|
3811
|
+
|
3812
|
+
subprocesses = Subprocesses()
|
3758
3813
|
|
3759
3814
|
|
3760
3815
|
########################################
|