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.
Files changed (43) hide show
  1. ominfra/manage/bootstrap.py +4 -0
  2. ominfra/manage/bootstrap_.py +5 -0
  3. ominfra/manage/commands/inject.py +8 -11
  4. ominfra/manage/commands/{execution.py → local.py} +1 -5
  5. ominfra/manage/commands/ping.py +23 -0
  6. ominfra/manage/commands/subprocess.py +3 -4
  7. ominfra/manage/commands/types.py +8 -0
  8. ominfra/manage/deploy/apps.py +72 -0
  9. ominfra/manage/deploy/config.py +8 -0
  10. ominfra/manage/deploy/git.py +136 -0
  11. ominfra/manage/deploy/inject.py +21 -0
  12. ominfra/manage/deploy/paths.py +81 -28
  13. ominfra/manage/deploy/types.py +13 -0
  14. ominfra/manage/deploy/venvs.py +66 -0
  15. ominfra/manage/inject.py +20 -4
  16. ominfra/manage/main.py +15 -27
  17. ominfra/manage/remote/_main.py +1 -1
  18. ominfra/manage/remote/config.py +0 -2
  19. ominfra/manage/remote/connection.py +7 -24
  20. ominfra/manage/remote/execution.py +1 -1
  21. ominfra/manage/remote/inject.py +3 -14
  22. ominfra/manage/remote/spawning.py +2 -2
  23. ominfra/manage/system/commands.py +22 -2
  24. ominfra/manage/system/config.py +3 -1
  25. ominfra/manage/system/inject.py +16 -6
  26. ominfra/manage/system/packages.py +38 -14
  27. ominfra/manage/system/platforms.py +72 -0
  28. ominfra/manage/targets/__init__.py +0 -0
  29. ominfra/manage/targets/connection.py +150 -0
  30. ominfra/manage/targets/inject.py +42 -0
  31. ominfra/manage/targets/targets.py +87 -0
  32. ominfra/scripts/journald2aws.py +205 -134
  33. ominfra/scripts/manage.py +2192 -734
  34. ominfra/scripts/supervisor.py +187 -25
  35. ominfra/supervisor/configs.py +163 -18
  36. {ominfra-0.0.0.dev154.dist-info → ominfra-0.0.0.dev156.dist-info}/METADATA +3 -3
  37. {ominfra-0.0.0.dev154.dist-info → ominfra-0.0.0.dev156.dist-info}/RECORD +42 -31
  38. ominfra/manage/system/types.py +0 -5
  39. /ominfra/manage/{commands → deploy}/interp.py +0 -0
  40. {ominfra-0.0.0.dev154.dist-info → ominfra-0.0.0.dev156.dist-info}/LICENSE +0 -0
  41. {ominfra-0.0.0.dev154.dist-info → ominfra-0.0.0.dev156.dist-info}/WHEEL +0 -0
  42. {ominfra-0.0.0.dev154.dist-info → ominfra-0.0.0.dev156.dist-info}/entry_points.txt +0 -0
  43. {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
@@ -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
- - nonstrict toggle
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
- return PolymorphicObjMarshaler.of([ # type: ignore
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
- ity.__qualname__,
2859
+ itn,
2842
2860
  rec(ity),
2843
2861
  )
2844
- for ity in deep_subclasses(ty)
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(*args: str) -> ta.Tuple[str, ...]:
3580
- return ('sh', '-c', ' '.join(map(shlex.quote, args)))
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(*args: str) -> ta.Tuple[str, ...]:
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(*args)
3602
+ return subprocess_shell_wrap_exec(*cmd)
3586
3603
  else:
3587
- return args
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
- @contextlib.contextmanager
3623
- def subprocess_common_context(*args: ta.Any, **kwargs: ta.Any) -> ta.Iterator[None]:
3624
- start_time = time.time()
3625
- try:
3626
- log.debug('subprocess_common_context.try: args=%r', args)
3627
- yield
3628
-
3629
- except Exception as exc: # noqa
3630
- log.debug('subprocess_common_context.except: exc=%r', exc)
3631
- raise
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
- finally:
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
- def subprocess_check_call(
3643
- *args: str,
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
- def subprocess_check_output(
3653
- *args: str,
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 subprocess_check_output_str(*args: str, **kwargs: ta.Any) -> str:
3662
- return subprocess_check_output(*args, **kwargs).decode().strip()
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
- DEFAULT_SUBPROCESS_TRY_EXCEPTIONS: ta.Tuple[ta.Type[Exception], ...] = (
3669
- FileNotFoundError,
3670
- subprocess.CalledProcessError,
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
- def _subprocess_try_run(
3675
- fn: ta.Callable[..., T],
3676
- *args: ta.Any,
3677
- try_exceptions: ta.Tuple[ta.Type[Exception], ...] = DEFAULT_SUBPROCESS_TRY_EXCEPTIONS,
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
- def subprocess_try_output(
3705
- *args: str,
3706
- try_exceptions: ta.Tuple[ta.Type[Exception], ...] = DEFAULT_SUBPROCESS_TRY_EXCEPTIONS,
3707
- **kwargs: ta.Any,
3708
- ) -> ta.Optional[bytes]:
3709
- if isinstance(ret := _subprocess_try_run(
3710
- subprocess_check_output,
3711
- *args,
3712
- try_exceptions=try_exceptions,
3713
- **kwargs,
3714
- ), Exception):
3715
- return None
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
- def subprocess_try_output_str(*args: str, **kwargs: ta.Any) -> ta.Optional[str]:
3721
- out = subprocess_try_output(*args, **kwargs)
3722
- return out.decode().strip() if out is not None else None
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
- def subprocess_close(
3729
- proc: subprocess.Popen,
3730
- timeout: ta.Optional[float] = None,
3731
- ) -> None:
3732
- # TODO: terminate, sleep, kill
3733
- if proc.stdout:
3734
- proc.stdout.close()
3735
- if proc.stderr:
3736
- proc.stderr.close()
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
- proc.wait(timeout)
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
  ########################################