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.
@@ -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 asyncio_subprocess_communicate
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 asyncio_subprocess_popen(
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 asyncio_subprocess_communicate(
67
+ stdout, stderr = await asyncio_subprocesses.communicate(
69
68
  proc,
70
69
  input=cmd.input,
71
70
  timeout=cmd.timeout,
@@ -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 asyncio_subprocess_check_call
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 asyncio_subprocess_check_call(
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(asyncio_subprocess_check_call, cwd=dst_dir)
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)
@@ -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 asyncio_subprocess_check_call
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 asyncio_subprocess_check_call(sys_exe, '-m', 'venv', venv_dir)
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 asyncio_subprocess_check_call(venv_exe, '-m', 'pip', 'install', 'uv')
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 asyncio_subprocess_check_call(venv_exe, *pip_cmd,'install', '-r', reqs_txt)
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
- ts = self.args.target
84
- if not ts.startswith('{'):
85
- ts = json.dumps({ts: {}})
86
- tgt: ManageTarget = msh.unmarshal_obj(json.loads(ts), ManageTarget)
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 asyncio_subprocess_popen
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
- cmd = pc.cmd
86
- if not debug:
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 asyncio_subprocess_check_call
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 asyncio_subprocess_check_call('brew', 'update')
45
+ await asyncio_subprocesses.check_call('brew', 'update')
48
46
 
49
47
  async def upgrade(self) -> None:
50
- await asyncio_subprocess_check_call('brew', 'upgrade')
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 asyncio_subprocess_check_call('brew', 'install', *es)
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 asyncio_subprocess_check_output('brew', 'info', '--json', *pns)
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 asyncio_subprocess_check_call('sudo', 'apt', 'update', env={**os.environ, **self._APT_ENV})
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 asyncio_subprocess_check_call('sudo', 'apt', 'upgrade', '-y', env={**os.environ, **self._APT_ENV})
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 asyncio_subprocess_check_call('sudo', 'apt', 'install', '-y', *pns, env={**os.environ, **self._APT_ENV})
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 asyncio_subprocess_run(
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 asyncio_subprocess_check_call('sudo', 'yum', 'check-update')
108
+ await asyncio_subprocesses.check_call('sudo', 'yum', 'check-update')
111
109
 
112
110
  async def upgrade(self) -> None:
113
- await asyncio_subprocess_check_call('sudo', 'yum', 'update')
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 asyncio_subprocess_check_call('sudo', 'yum', 'install', *pns)
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 asyncio_subprocess_run(
121
+ out = await asyncio_subprocesses.run(
124
122
  'rpm', '-q', pn,
125
123
  capture_output=True,
126
124
  )
@@ -932,8 +932,8 @@ class _CachedNullary(_AbstractCachedNullary):
932
932
  return self._value
933
933
 
934
934
 
935
- def cached_nullary(fn): # ta.Callable[..., T]) -> ta.Callable[..., T]:
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(*args: str) -> ta.Tuple[str, ...]:
3597
- return ('sh', '-c', ' '.join(map(shlex.quote, args)))
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(*args: str) -> ta.Tuple[str, ...]:
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(*args)
3603
+ return subprocess_shell_wrap_exec(*cmd)
3603
3604
  else:
3604
- return args
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
- @contextlib.contextmanager
3640
- def subprocess_common_context(*args: ta.Any, **kwargs: ta.Any) -> ta.Iterator[None]:
3641
- start_time = time.time()
3642
- try:
3643
- log.debug('subprocess_common_context.try: args=%r', args)
3644
- yield
3645
-
3646
- except Exception as exc: # noqa
3647
- log.debug('subprocess_common_context.except: exc=%r', exc)
3648
- raise
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
- finally:
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
- def subprocess_check_call(
3660
- *args: str,
3661
- stdout: ta.Any = sys.stderr,
3662
- **kwargs: ta.Any,
3663
- ) -> None:
3664
- args, kwargs = prepare_subprocess_invocation(*args, stdout=stdout, **kwargs)
3665
- with subprocess_common_context(*args, **kwargs):
3666
- return subprocess.check_call(args, **kwargs) # type: ignore
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
- def subprocess_check_output(
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
- def subprocess_check_output_str(*args: str, **kwargs: ta.Any) -> str:
3679
- return subprocess_check_output(*args, **kwargs).decode().strip()
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
- DEFAULT_SUBPROCESS_TRY_EXCEPTIONS: ta.Tuple[ta.Type[Exception], ...] = (
3686
- FileNotFoundError,
3687
- subprocess.CalledProcessError,
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
- def _subprocess_try_run(
3692
- fn: ta.Callable[..., T],
3693
- *args: ta.Any,
3694
- try_exceptions: ta.Tuple[ta.Type[Exception], ...] = DEFAULT_SUBPROCESS_TRY_EXCEPTIONS,
3695
- **kwargs: ta.Any,
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
- def subprocess_try_output(
3722
- *args: str,
3723
- try_exceptions: ta.Tuple[ta.Type[Exception], ...] = DEFAULT_SUBPROCESS_TRY_EXCEPTIONS,
3724
- **kwargs: ta.Any,
3725
- ) -> ta.Optional[bytes]:
3726
- if isinstance(ret := _subprocess_try_run(
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
- def subprocess_try_output_str(*args: str, **kwargs: ta.Any) -> ta.Optional[str]:
3738
- out = subprocess_try_output(*args, **kwargs)
3739
- return out.decode().strip() if out is not None else None
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
- def subprocess_close(
3746
- proc: subprocess.Popen,
3747
- timeout: ta.Optional[float] = None,
3748
- ) -> None:
3749
- # TODO: terminate, sleep, kill
3750
- if proc.stdout:
3751
- proc.stdout.close()
3752
- if proc.stderr:
3753
- proc.stderr.close()
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
- proc.wait(timeout)
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
  ########################################