omlish 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.
@@ -1,4 +1,5 @@
1
1
  # ruff: noqa: UP006 UP007
2
+ import abc
2
3
  import contextlib
3
4
  import logging
4
5
  import os
@@ -8,8 +9,8 @@ import sys
8
9
  import time
9
10
  import typing as ta
10
11
 
11
- from .logs import log
12
- from .runtime import is_debugger_attached
12
+ from omlish.lite.logs import log
13
+ from omlish.lite.runtime import is_debugger_attached
13
14
 
14
15
 
15
16
  T = ta.TypeVar('T')
@@ -32,165 +33,219 @@ SUBPROCESS_CHANNEL_OPTION_VALUES: ta.Mapping[SubprocessChannelOption, int] = {
32
33
  _SUBPROCESS_SHELL_WRAP_EXECS = False
33
34
 
34
35
 
35
- def subprocess_shell_wrap_exec(*args: str) -> ta.Tuple[str, ...]:
36
- return ('sh', '-c', ' '.join(map(shlex.quote, args)))
36
+ def subprocess_shell_wrap_exec(*cmd: str) -> ta.Tuple[str, ...]:
37
+ return ('sh', '-c', ' '.join(map(shlex.quote, cmd)))
37
38
 
38
39
 
39
- def subprocess_maybe_shell_wrap_exec(*args: str) -> ta.Tuple[str, ...]:
40
+ def subprocess_maybe_shell_wrap_exec(*cmd: str) -> ta.Tuple[str, ...]:
40
41
  if _SUBPROCESS_SHELL_WRAP_EXECS or is_debugger_attached():
41
- return subprocess_shell_wrap_exec(*args)
42
+ return subprocess_shell_wrap_exec(*cmd)
42
43
  else:
43
- return args
44
-
45
-
46
- def prepare_subprocess_invocation(
47
- *args: str,
48
- env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
49
- extra_env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
50
- quiet: bool = False,
51
- shell: bool = False,
52
- **kwargs: ta.Any,
53
- ) -> ta.Tuple[ta.Tuple[ta.Any, ...], ta.Dict[str, ta.Any]]:
54
- log.debug('prepare_subprocess_invocation: args=%r', args)
55
- if extra_env:
56
- log.debug('prepare_subprocess_invocation: extra_env=%r', extra_env)
57
-
58
- if extra_env:
59
- env = {**(env if env is not None else os.environ), **extra_env}
60
-
61
- if quiet and 'stderr' not in kwargs:
62
- if not log.isEnabledFor(logging.DEBUG):
63
- kwargs['stderr'] = subprocess.DEVNULL
64
-
65
- if not shell:
66
- args = subprocess_maybe_shell_wrap_exec(*args)
67
-
68
- return args, dict(
69
- env=env,
70
- shell=shell,
71
- **kwargs,
72
- )
73
-
74
-
75
- ##
76
-
77
-
78
- @contextlib.contextmanager
79
- def subprocess_common_context(*args: ta.Any, **kwargs: ta.Any) -> ta.Iterator[None]:
80
- start_time = time.time()
81
- try:
82
- log.debug('subprocess_common_context.try: args=%r', args)
83
- yield
84
-
85
- except Exception as exc: # noqa
86
- log.debug('subprocess_common_context.except: exc=%r', exc)
87
- raise
88
-
89
- finally:
90
- end_time = time.time()
91
- elapsed_s = end_time - start_time
92
- log.debug('subprocess_common_context.finally: elapsed_s=%f args=%r', elapsed_s, args)
44
+ return cmd
93
45
 
94
46
 
95
47
  ##
96
48
 
97
49
 
98
- def subprocess_check_call(
99
- *args: str,
100
- stdout: ta.Any = sys.stderr,
101
- **kwargs: ta.Any,
50
+ def subprocess_close(
51
+ proc: subprocess.Popen,
52
+ timeout: ta.Optional[float] = None,
102
53
  ) -> None:
103
- args, kwargs = prepare_subprocess_invocation(*args, stdout=stdout, **kwargs)
104
- with subprocess_common_context(*args, **kwargs):
105
- return subprocess.check_call(args, **kwargs) # type: ignore
106
-
107
-
108
- def subprocess_check_output(
109
- *args: str,
110
- **kwargs: ta.Any,
111
- ) -> bytes:
112
- args, kwargs = prepare_subprocess_invocation(*args, **kwargs)
113
- with subprocess_common_context(*args, **kwargs):
114
- return subprocess.check_output(args, **kwargs)
115
-
54
+ # TODO: terminate, sleep, kill
55
+ if proc.stdout:
56
+ proc.stdout.close()
57
+ if proc.stderr:
58
+ proc.stderr.close()
59
+ if proc.stdin:
60
+ proc.stdin.close()
116
61
 
117
- def subprocess_check_output_str(*args: str, **kwargs: ta.Any) -> str:
118
- return subprocess_check_output(*args, **kwargs).decode().strip()
62
+ proc.wait(timeout)
119
63
 
120
64
 
121
65
  ##
122
66
 
123
67
 
124
- DEFAULT_SUBPROCESS_TRY_EXCEPTIONS: ta.Tuple[ta.Type[Exception], ...] = (
125
- FileNotFoundError,
126
- subprocess.CalledProcessError,
127
- )
128
-
129
-
130
- def _subprocess_try_run(
131
- fn: ta.Callable[..., T],
132
- *args: ta.Any,
133
- try_exceptions: ta.Tuple[ta.Type[Exception], ...] = DEFAULT_SUBPROCESS_TRY_EXCEPTIONS,
134
- **kwargs: ta.Any,
135
- ) -> ta.Union[T, Exception]:
136
- try:
137
- return fn(*args, **kwargs)
138
- except try_exceptions as e: # noqa
139
- if log.isEnabledFor(logging.DEBUG):
140
- log.exception('command failed')
141
- return e
142
-
143
-
144
- def subprocess_try_call(
145
- *args: str,
146
- try_exceptions: ta.Tuple[ta.Type[Exception], ...] = DEFAULT_SUBPROCESS_TRY_EXCEPTIONS,
147
- **kwargs: ta.Any,
148
- ) -> bool:
149
- if isinstance(_subprocess_try_run(
150
- subprocess_check_call,
151
- *args,
152
- try_exceptions=try_exceptions,
68
+ class AbstractSubprocesses(abc.ABC): # noqa
69
+ DEFAULT_LOGGER: ta.ClassVar[ta.Optional[logging.Logger]] = log
70
+
71
+ def __init__(
72
+ self,
73
+ *,
74
+ log: ta.Optional[logging.Logger] = None,
75
+ try_exceptions: ta.Optional[ta.Tuple[ta.Type[Exception], ...]] = None,
76
+ ) -> None:
77
+ super().__init__()
78
+
79
+ self._log = log if log is not None else self.DEFAULT_LOGGER
80
+ self._try_exceptions = try_exceptions if try_exceptions is not None else self.DEFAULT_TRY_EXCEPTIONS
81
+
82
+ #
83
+
84
+ def prepare_args(
85
+ self,
86
+ *cmd: str,
87
+ env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
88
+ extra_env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
89
+ quiet: bool = False,
90
+ shell: bool = False,
91
+ **kwargs: ta.Any,
92
+ ) -> ta.Tuple[ta.Tuple[ta.Any, ...], ta.Dict[str, ta.Any]]:
93
+ if self._log:
94
+ self._log.debug('Subprocesses.prepare_args: cmd=%r', cmd)
95
+ if extra_env:
96
+ self._log.debug('Subprocesses.prepare_args: extra_env=%r', extra_env)
97
+
98
+ if extra_env:
99
+ env = {**(env if env is not None else os.environ), **extra_env}
100
+
101
+ if quiet and 'stderr' not in kwargs:
102
+ if self._log and not self._log.isEnabledFor(logging.DEBUG):
103
+ kwargs['stderr'] = subprocess.DEVNULL
104
+
105
+ if not shell:
106
+ cmd = subprocess_maybe_shell_wrap_exec(*cmd)
107
+
108
+ return cmd, dict(
109
+ env=env,
110
+ shell=shell,
153
111
  **kwargs,
154
- ), Exception):
155
- return False
156
- else:
157
- return True
158
-
159
-
160
- def subprocess_try_output(
161
- *args: str,
162
- try_exceptions: ta.Tuple[ta.Type[Exception], ...] = DEFAULT_SUBPROCESS_TRY_EXCEPTIONS,
163
- **kwargs: ta.Any,
164
- ) -> ta.Optional[bytes]:
165
- if isinstance(ret := _subprocess_try_run(
166
- subprocess_check_output,
167
- *args,
168
- try_exceptions=try_exceptions,
169
- **kwargs,
170
- ), Exception):
171
- return None
172
- else:
173
- return ret
174
-
112
+ )
113
+
114
+ @contextlib.contextmanager
115
+ def wrap_call(self, *cmd: ta.Any, **kwargs: ta.Any) -> ta.Iterator[None]:
116
+ start_time = time.time()
117
+ try:
118
+ if self._log:
119
+ self._log.debug('Subprocesses.wrap_call.try: cmd=%r', cmd)
120
+ yield
121
+
122
+ except Exception as exc: # noqa
123
+ if self._log:
124
+ self._log.debug('Subprocesses.wrap_call.except: exc=%r', exc)
125
+ raise
126
+
127
+ finally:
128
+ end_time = time.time()
129
+ elapsed_s = end_time - start_time
130
+ if self._log:
131
+ self._log.debug('sSubprocesses.wrap_call.finally: elapsed_s=%f cmd=%r', elapsed_s, cmd)
132
+
133
+ @contextlib.contextmanager
134
+ def prepare_and_wrap(
135
+ self,
136
+ *cmd: ta.Any,
137
+ **kwargs: ta.Any,
138
+ ) -> ta.Iterator[ta.Tuple[
139
+ ta.Tuple[ta.Any, ...],
140
+ ta.Dict[str, ta.Any],
141
+ ]]:
142
+ cmd, kwargs = self.prepare_args(*cmd, **kwargs)
143
+ with self.wrap_call(*cmd, **kwargs):
144
+ yield cmd, kwargs
145
+
146
+ #
147
+
148
+ DEFAULT_TRY_EXCEPTIONS: ta.Tuple[ta.Type[Exception], ...] = (
149
+ FileNotFoundError,
150
+ subprocess.CalledProcessError,
151
+ )
175
152
 
176
- def subprocess_try_output_str(*args: str, **kwargs: ta.Any) -> ta.Optional[str]:
177
- out = subprocess_try_output(*args, **kwargs)
178
- return out.decode().strip() if out is not None else None
153
+ def try_fn(
154
+ self,
155
+ fn: ta.Callable[..., T],
156
+ *cmd: str,
157
+ try_exceptions: ta.Optional[ta.Tuple[ta.Type[Exception], ...]] = None,
158
+ **kwargs: ta.Any,
159
+ ) -> ta.Union[T, Exception]:
160
+ if try_exceptions is None:
161
+ try_exceptions = self._try_exceptions
162
+
163
+ try:
164
+ return fn(*cmd, **kwargs)
165
+
166
+ except try_exceptions as e: # noqa
167
+ if self._log and self._log.isEnabledFor(logging.DEBUG):
168
+ self._log.exception('command failed')
169
+ return e
170
+
171
+ async def async_try_fn(
172
+ self,
173
+ fn: ta.Callable[..., ta.Awaitable[T]],
174
+ *cmd: ta.Any,
175
+ try_exceptions: ta.Optional[ta.Tuple[ta.Type[Exception], ...]] = None,
176
+ **kwargs: ta.Any,
177
+ ) -> ta.Union[T, Exception]:
178
+ if try_exceptions is None:
179
+ try_exceptions = self._try_exceptions
180
+
181
+ try:
182
+ return await fn(*cmd, **kwargs)
183
+
184
+ except try_exceptions as e: # noqa
185
+ if self._log and self._log.isEnabledFor(logging.DEBUG):
186
+ self._log.exception('command failed')
187
+ return e
179
188
 
180
189
 
181
190
  ##
182
191
 
183
192
 
184
- def subprocess_close(
185
- proc: subprocess.Popen,
186
- timeout: ta.Optional[float] = None,
187
- ) -> None:
188
- # TODO: terminate, sleep, kill
189
- if proc.stdout:
190
- proc.stdout.close()
191
- if proc.stderr:
192
- proc.stderr.close()
193
- if proc.stdin:
194
- proc.stdin.close()
195
-
196
- proc.wait(timeout)
193
+ class Subprocesses(AbstractSubprocesses):
194
+ def check_call(
195
+ self,
196
+ *cmd: str,
197
+ stdout: ta.Any = sys.stderr,
198
+ **kwargs: ta.Any,
199
+ ) -> None:
200
+ with self.prepare_and_wrap(*cmd, stdout=stdout, **kwargs) as (cmd, kwargs): # noqa
201
+ subprocess.check_call(cmd, **kwargs)
202
+
203
+ def check_output(
204
+ self,
205
+ *cmd: str,
206
+ **kwargs: ta.Any,
207
+ ) -> bytes:
208
+ with self.prepare_and_wrap(*cmd, **kwargs) as (cmd, kwargs): # noqa
209
+ return subprocess.check_output(cmd, **kwargs)
210
+
211
+ def check_output_str(
212
+ self,
213
+ *cmd: str,
214
+ **kwargs: ta.Any,
215
+ ) -> str:
216
+ return self.check_output(*cmd, **kwargs).decode().strip()
217
+
218
+ #
219
+
220
+ def try_call(
221
+ self,
222
+ *cmd: str,
223
+ **kwargs: ta.Any,
224
+ ) -> bool:
225
+ if isinstance(self.try_fn(self.check_call, *cmd, **kwargs), Exception):
226
+ return False
227
+ else:
228
+ return True
229
+
230
+ def try_output(
231
+ self,
232
+ *cmd: str,
233
+ **kwargs: ta.Any,
234
+ ) -> ta.Optional[bytes]:
235
+ if isinstance(ret := self.try_fn(self.check_output, *cmd, **kwargs), Exception):
236
+ return None
237
+ else:
238
+ return ret
239
+
240
+ def try_output_str(
241
+ self,
242
+ *cmd: str,
243
+ **kwargs: ta.Any,
244
+ ) -> ta.Optional[str]:
245
+ if (ret := self.try_output(*cmd, **kwargs)) is None:
246
+ return None
247
+ else:
248
+ return ret.decode().strip()
249
+
250
+
251
+ subprocesses = Subprocesses()