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.
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
  ########################################