omlish 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.
omlish/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev155'
2
- __revision__ = '7187ecad3907bd95f19a85608e9cbd7b9a103920'
1
+ __version__ = '0.0.0.dev157'
2
+ __revision__ = 'f327aa714545eba416378a8b99f407e8c5eeaac4'
3
3
 
4
4
 
5
5
  #
omlish/argparse/cli.py CHANGED
@@ -14,6 +14,8 @@ import sys
14
14
  import typing as ta
15
15
 
16
16
  from ..lite.check import check
17
+ from ..lite.reflect import get_optional_alias_arg
18
+ from ..lite.reflect import is_optional_alias
17
19
 
18
20
 
19
21
  T = ta.TypeVar('T')
@@ -120,6 +122,8 @@ def _get_argparse_arg_ann_kwargs(ann: ta.Any) -> ta.Mapping[str, ta.Any]:
120
122
  return {'action': 'store_true'}
121
123
  elif ann is list:
122
124
  return {'action': 'append'}
125
+ elif is_optional_alias(ann):
126
+ return _get_argparse_arg_ann_kwargs(get_optional_alias_arg(ann))
123
127
  else:
124
128
  raise TypeError(ann)
125
129
 
@@ -4,7 +4,6 @@ import asyncio.subprocess
4
4
  import contextlib
5
5
  import dataclasses as dc
6
6
  import functools
7
- import logging
8
7
  import subprocess
9
8
  import sys
10
9
  import typing as ta
@@ -12,9 +11,7 @@ import typing as ta
12
11
  from ...asyncs.asyncio.timeouts import asyncio_maybe_timeout
13
12
  from ..check import check
14
13
  from ..logs import log
15
- from ..subprocesses import DEFAULT_SUBPROCESS_TRY_EXCEPTIONS
16
- from ..subprocesses import prepare_subprocess_invocation
17
- from ..subprocesses import subprocess_common_context
14
+ from ..subprocesses import AbstractSubprocesses
18
15
 
19
16
 
20
17
  T = ta.TypeVar('T')
@@ -23,43 +20,6 @@ T = ta.TypeVar('T')
23
20
  ##
24
21
 
25
22
 
26
- @contextlib.asynccontextmanager
27
- async def asyncio_subprocess_popen(
28
- *cmd: str,
29
- shell: bool = False,
30
- timeout: ta.Optional[float] = None,
31
- **kwargs: ta.Any,
32
- ) -> ta.AsyncGenerator[asyncio.subprocess.Process, None]:
33
- fac: ta.Any
34
- if shell:
35
- fac = functools.partial(
36
- asyncio.create_subprocess_shell,
37
- check.single(cmd),
38
- )
39
- else:
40
- fac = functools.partial(
41
- asyncio.create_subprocess_exec,
42
- *cmd,
43
- )
44
-
45
- with subprocess_common_context(
46
- *cmd,
47
- shell=shell,
48
- timeout=timeout,
49
- **kwargs,
50
- ):
51
- proc: asyncio.subprocess.Process
52
- proc = await fac(**kwargs)
53
- try:
54
- yield proc
55
-
56
- finally:
57
- await asyncio_maybe_timeout(proc.wait(), timeout)
58
-
59
-
60
- ##
61
-
62
-
63
23
  class AsyncioProcessCommunicator:
64
24
  def __init__(
65
25
  self,
@@ -170,145 +130,144 @@ class AsyncioProcessCommunicator:
170
130
  return await asyncio_maybe_timeout(self._communicate(input), timeout)
171
131
 
172
132
 
173
- async def asyncio_subprocess_communicate(
174
- proc: asyncio.subprocess.Process,
175
- input: ta.Any = None, # noqa
176
- timeout: ta.Optional[float] = None,
177
- ) -> ta.Tuple[ta.Optional[bytes], ta.Optional[bytes]]:
178
- return await AsyncioProcessCommunicator(proc).communicate(input, timeout) # noqa
179
-
180
-
181
- @dc.dataclass(frozen=True)
182
- class AsyncioSubprocessOutput:
183
- proc: asyncio.subprocess.Process
184
- stdout: ta.Optional[bytes]
185
- stderr: ta.Optional[bytes]
186
-
187
-
188
- async def asyncio_subprocess_run(
189
- *args: str,
190
- input: ta.Any = None, # noqa
191
- timeout: ta.Optional[float] = None,
192
- check: bool = False, # noqa
193
- capture_output: ta.Optional[bool] = None,
194
- **kwargs: ta.Any,
195
- ) -> AsyncioSubprocessOutput:
196
- if capture_output:
197
- kwargs.setdefault('stdout', subprocess.PIPE)
198
- kwargs.setdefault('stderr', subprocess.PIPE)
199
-
200
- args, kwargs = prepare_subprocess_invocation(*args, **kwargs)
201
-
202
- proc: asyncio.subprocess.Process
203
- async with asyncio_subprocess_popen(*args, **kwargs) as proc:
204
- stdout, stderr = await asyncio_subprocess_communicate(proc, input, timeout)
205
-
206
- if check and proc.returncode:
207
- raise subprocess.CalledProcessError(
208
- proc.returncode,
209
- args,
210
- output=stdout,
211
- stderr=stderr,
212
- )
133
+ ##
213
134
 
214
- return AsyncioSubprocessOutput(
215
- proc,
216
- stdout,
217
- stderr,
218
- )
219
135
 
136
+ class AsyncioSubprocesses(AbstractSubprocesses):
137
+ async def communicate(
138
+ self,
139
+ proc: asyncio.subprocess.Process,
140
+ input: ta.Any = None, # noqa
141
+ timeout: ta.Optional[float] = None,
142
+ ) -> ta.Tuple[ta.Optional[bytes], ta.Optional[bytes]]:
143
+ return await AsyncioProcessCommunicator(proc).communicate(input, timeout) # noqa
220
144
 
221
- ##
145
+ #
222
146
 
147
+ @contextlib.asynccontextmanager
148
+ async def popen(
149
+ self,
150
+ *cmd: str,
151
+ shell: bool = False,
152
+ timeout: ta.Optional[float] = None,
153
+ **kwargs: ta.Any,
154
+ ) -> ta.AsyncGenerator[asyncio.subprocess.Process, None]:
155
+ fac: ta.Any
156
+ if shell:
157
+ fac = functools.partial(
158
+ asyncio.create_subprocess_shell,
159
+ check.single(cmd),
160
+ )
161
+ else:
162
+ fac = functools.partial(
163
+ asyncio.create_subprocess_exec,
164
+ *cmd,
165
+ )
223
166
 
224
- async def asyncio_subprocess_check_call(
225
- *args: str,
226
- stdout: ta.Any = sys.stderr,
227
- input: ta.Any = None, # noqa
228
- timeout: ta.Optional[float] = None,
229
- **kwargs: ta.Any,
230
- ) -> None:
231
- await asyncio_subprocess_run(
232
- *args,
233
- stdout=stdout,
234
- input=input,
235
- timeout=timeout,
236
- check=True,
237
- **kwargs,
238
- )
239
-
240
-
241
- async def asyncio_subprocess_check_output(
242
- *args: str,
243
- input: ta.Any = None, # noqa
244
- timeout: ta.Optional[float] = None,
245
- **kwargs: ta.Any,
246
- ) -> bytes:
247
- out = await asyncio_subprocess_run(
248
- *args,
249
- stdout=asyncio.subprocess.PIPE,
250
- input=input,
251
- timeout=timeout,
252
- check=True,
253
- **kwargs,
254
- )
255
-
256
- return check.not_none(out.stdout)
257
-
258
-
259
- async def asyncio_subprocess_check_output_str(*args: str, **kwargs: ta.Any) -> str:
260
- return (await asyncio_subprocess_check_output(*args, **kwargs)).decode().strip()
167
+ with self.prepare_and_wrap( *cmd, shell=shell, **kwargs) as (cmd, kwargs): # noqa
168
+ proc: asyncio.subprocess.Process = await fac(**kwargs)
169
+ try:
170
+ yield proc
261
171
 
172
+ finally:
173
+ await asyncio_maybe_timeout(proc.wait(), timeout)
262
174
 
263
- ##
175
+ #
264
176
 
177
+ @dc.dataclass(frozen=True)
178
+ class RunOutput:
179
+ proc: asyncio.subprocess.Process
180
+ stdout: ta.Optional[bytes]
181
+ stderr: ta.Optional[bytes]
265
182
 
266
- async def _asyncio_subprocess_try_run(
267
- fn: ta.Callable[..., ta.Awaitable[T]],
268
- *args: ta.Any,
269
- try_exceptions: ta.Tuple[ta.Type[Exception], ...] = DEFAULT_SUBPROCESS_TRY_EXCEPTIONS,
270
- **kwargs: ta.Any,
271
- ) -> ta.Union[T, Exception]:
272
- try:
273
- return await fn(*args, **kwargs)
274
- except try_exceptions as e: # noqa
275
- if log.isEnabledFor(logging.DEBUG):
276
- log.exception('command failed')
277
- return e
278
-
279
-
280
- async def asyncio_subprocess_try_call(
281
- *args: str,
282
- try_exceptions: ta.Tuple[ta.Type[Exception], ...] = DEFAULT_SUBPROCESS_TRY_EXCEPTIONS,
283
- **kwargs: ta.Any,
284
- ) -> bool:
285
- if isinstance(await _asyncio_subprocess_try_run(
286
- asyncio_subprocess_check_call,
287
- *args,
288
- try_exceptions=try_exceptions,
289
- **kwargs,
290
- ), Exception):
291
- return False
292
- else:
293
- return True
294
-
295
-
296
- async def asyncio_subprocess_try_output(
297
- *args: str,
298
- try_exceptions: ta.Tuple[ta.Type[Exception], ...] = DEFAULT_SUBPROCESS_TRY_EXCEPTIONS,
299
- **kwargs: ta.Any,
300
- ) -> ta.Optional[bytes]:
301
- if isinstance(ret := await _asyncio_subprocess_try_run(
302
- asyncio_subprocess_check_output,
303
- *args,
304
- try_exceptions=try_exceptions,
305
- **kwargs,
306
- ), Exception):
307
- return None
308
- else:
309
- return ret
183
+ async def run(
184
+ self,
185
+ *cmd: str,
186
+ input: ta.Any = None, # noqa
187
+ timeout: ta.Optional[float] = None,
188
+ check: bool = False, # noqa
189
+ capture_output: ta.Optional[bool] = None,
190
+ **kwargs: ta.Any,
191
+ ) -> RunOutput:
192
+ if capture_output:
193
+ kwargs.setdefault('stdout', subprocess.PIPE)
194
+ kwargs.setdefault('stderr', subprocess.PIPE)
195
+
196
+ proc: asyncio.subprocess.Process
197
+ async with self.popen(*cmd, **kwargs) as proc:
198
+ stdout, stderr = await self.communicate(proc, input, timeout)
199
+
200
+ if check and proc.returncode:
201
+ raise subprocess.CalledProcessError(
202
+ proc.returncode,
203
+ cmd,
204
+ output=stdout,
205
+ stderr=stderr,
206
+ )
207
+
208
+ return self.RunOutput(
209
+ proc,
210
+ stdout,
211
+ stderr,
212
+ )
213
+
214
+ #
215
+
216
+ async def check_call(
217
+ self,
218
+ *cmd: str,
219
+ stdout: ta.Any = sys.stderr,
220
+ **kwargs: ta.Any,
221
+ ) -> None:
222
+ with self.prepare_and_wrap(*cmd, stdout=stdout, check=True, **kwargs) as (cmd, kwargs): # noqa
223
+ await self.run(*cmd, **kwargs)
224
+
225
+ async def check_output(
226
+ self,
227
+ *cmd: str,
228
+ **kwargs: ta.Any,
229
+ ) -> bytes:
230
+ with self.prepare_and_wrap(*cmd, stdout=subprocess.PIPE, check=True, **kwargs) as (cmd, kwargs): # noqa
231
+ return check.not_none((await self.run(*cmd, **kwargs)).stdout)
232
+
233
+ async def check_output_str(
234
+ self,
235
+ *cmd: str,
236
+ **kwargs: ta.Any,
237
+ ) -> str:
238
+ return (await self.check_output(*cmd, **kwargs)).decode().strip()
239
+
240
+ #
241
+
242
+ async def try_call(
243
+ self,
244
+ *cmd: str,
245
+ **kwargs: ta.Any,
246
+ ) -> bool:
247
+ if isinstance(await self.async_try_fn(self.check_call, *cmd, **kwargs), Exception):
248
+ return False
249
+ else:
250
+ return True
251
+
252
+ async def try_output(
253
+ self,
254
+ *cmd: str,
255
+ **kwargs: ta.Any,
256
+ ) -> ta.Optional[bytes]:
257
+ if isinstance(ret := await self.async_try_fn(self.check_output, *cmd, **kwargs), Exception):
258
+ return None
259
+ else:
260
+ return ret
261
+
262
+ async def try_output_str(
263
+ self,
264
+ *cmd: str,
265
+ **kwargs: ta.Any,
266
+ ) -> ta.Optional[str]:
267
+ if (ret := await self.try_output(*cmd, **kwargs)) is None:
268
+ return None
269
+ else:
270
+ return ret.decode().strip()
310
271
 
311
272
 
312
- async def asyncio_subprocess_try_output_str(*args: str, **kwargs: ta.Any) -> ta.Optional[str]:
313
- out = await asyncio_subprocess_try_output(*args, **kwargs)
314
- return out.decode().strip() if out is not None else None
273
+ asyncio_subprocesses = AsyncioSubprocesses()
omlish/lite/cached.py CHANGED
@@ -35,8 +35,8 @@ class _CachedNullary(_AbstractCachedNullary):
35
35
  return self._value
36
36
 
37
37
 
38
- def cached_nullary(fn): # ta.Callable[..., T]) -> ta.Callable[..., T]:
39
- return _CachedNullary(fn)
38
+ def cached_nullary(fn: CallableT) -> CallableT:
39
+ return _CachedNullary(fn) # type: ignore
40
40
 
41
41
 
42
42
  def static_init(fn: CallableT) -> CallableT:
omlish/lite/marshal.py CHANGED
@@ -3,6 +3,7 @@ TODO:
3
3
  - pickle stdlib objs? have to pin to 3.8 pickle protocol, will be cross-version
4
4
  - namedtuple
5
5
  - literals
6
+ - newtypes?
6
7
  """
7
8
  # ruff: noqa: UP006 UP007
8
9
  import abc
@@ -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()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: omlish
3
- Version: 0.0.0.dev155
3
+ Version: 0.0.0.dev157
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -1,5 +1,5 @@
1
1
  omlish/.manifests.json,sha256=RX24SRc6DCEg77PUVnaXOKCWa5TF_c9RQJdGIf7gl9c,1135
2
- omlish/__about__.py,sha256=TTGSgQiaw8RlS3TEaNm9PX3osMhDFRCTCzGdQmtU5dQ,3409
2
+ omlish/__about__.py,sha256=Gp7-GXOxzTN9ee-T1I3svA-Tc_rF24tMP_-u8t9ApaI,3409
3
3
  omlish/__init__.py,sha256=SsyiITTuK0v74XpKV8dqNaCmjOlan1JZKrHQv5rWKPA,253
4
4
  omlish/c3.py,sha256=ubu7lHwss5V4UznbejAI0qXhXahrU01MysuHOZI9C4U,8116
5
5
  omlish/cached.py,sha256=UI-XTFBwA6YXWJJJeBn-WkwBkfzDjLBBaZf4nIJA9y0,510
@@ -75,7 +75,7 @@ omlish/antlr/_runtime/xpath/XPathLexer.py,sha256=xFtdr4ZXMZxb2dnB_ggWyhvlQiC7RXQ
75
75
  omlish/antlr/_runtime/xpath/__init__.py,sha256=lMd_BbXYdlDhZQN_q0TKN978XW5G0pq618F0NaLkpFE,71
76
76
  omlish/argparse/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
77
77
  omlish/argparse/all.py,sha256=EfUSf27vFWqa4Q93AycU5YRsrHt-Nx3pU3uNVapb-EE,1054
78
- omlish/argparse/cli.py,sha256=RrFql1bS1Lw3GA1ooLCJDQX4bK_Ci-dDqD5nEkVgHGI,8072
78
+ omlish/argparse/cli.py,sha256=Nd16RNNABOd9UjfLGtJrDOYkIs5uv1BnoqvJdQ2Ipc8,8273
79
79
  omlish/asyncs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
80
80
  omlish/asyncs/all.py,sha256=uUz9ziKh4_QrgmdhKFMgq6j7mFbiZd3LiogguDCQsGI,587
81
81
  omlish/asyncs/anyio.py,sha256=gfpx-D8QGmUfhnQxHEaHXcAP8zSMQjcGw4COFTGNnHI,8021
@@ -333,13 +333,13 @@ omlish/lifecycles/manager.py,sha256=Au66KaO-fI-SEJALaPUJsCHYW2GE20xextk1wKn2BEU,
333
333
  omlish/lifecycles/states.py,sha256=zqMOU2ZU-MDNnWuwauM3_anIAiXM8LoBDElDEraptFg,1292
334
334
  omlish/lifecycles/transitions.py,sha256=qQtFby-h4VzbvgaUqT2NnbNumlcOx9FVVADP9t83xj4,1939
335
335
  omlish/lite/__init__.py,sha256=Y3l4WY4JRi2uLG6kgbGp93fuGfkxkKwZDvhsa0Rwgtk,15
336
- omlish/lite/cached.py,sha256=hBW77-F7ZLtFqbLwVrlqaJ4-iFHMQleMWZXaZN1IubA,1308
336
+ omlish/lite/cached.py,sha256=O7ozcoDNFm1Hg2wtpHEqYSp_i_nCLNOP6Ueq_Uk-7mU,1300
337
337
  omlish/lite/check.py,sha256=KvcO86LqWlh2j4ORaZXRR4FM8fFb7kUkNqq3lTs0Ta0,12821
338
338
  omlish/lite/contextmanagers.py,sha256=m9JO--p7L7mSl4cycXysH-1AO27weDKjP3DZG61cwwM,1683
339
339
  omlish/lite/inject.py,sha256=729Qi0TLbQgBtkvx97q1EUMe73VFYA1hu4woXkOTcwM,23572
340
340
  omlish/lite/json.py,sha256=7-02Ny4fq-6YAu5ynvqoijhuYXWpLmfCI19GUeZnb1c,740
341
341
  omlish/lite/logs.py,sha256=1pcGu0ekhVCcLUckLSP16VccnAoprjtl5Vkdfm7y1Wg,6184
342
- omlish/lite/marshal.py,sha256=jbdKjTeumReSKUNNEn-oAyd5Bdy6NK_9_LsPSHpvqRU,13817
342
+ omlish/lite/marshal.py,sha256=u5n43bEbct1ps8oIR7wjFCSWeyOhfHAF7LxUjELh8Jk,13830
343
343
  omlish/lite/maybes.py,sha256=7OlHJ8Q2r4wQ-aRbZSlJY7x0e8gDvufFdlohGEIJ3P4,833
344
344
  omlish/lite/pycharm.py,sha256=pUOJevrPClSqTCEOkQBO11LKX2003tfDcp18a03QFrc,1163
345
345
  omlish/lite/reflect.py,sha256=ad_ya_zZJOQB8HoNjs9yc66R54zgflwJVPJqiBXMzqA,1681
@@ -349,10 +349,10 @@ omlish/lite/secrets.py,sha256=3Mz3V2jf__XU9qNHcH56sBSw95L3U2UPL24bjvobG0c,816
349
349
  omlish/lite/socket.py,sha256=7OYgkXTcQv0wq7TQuLnl9y6dJA1ZT6Vbc1JH59QlxgY,1792
350
350
  omlish/lite/socketserver.py,sha256=doTXIctu_6c8XneFtzPFVG_Wq6xVmA3p9ymut8IvBoU,1586
351
351
  omlish/lite/strings.py,sha256=QURcE4-1pKVW8eT_5VCJpXaHDWR2dW2pYOChTJnZDiQ,1504
352
- omlish/lite/subprocesses.py,sha256=iN-HX44g9uxkZ7HII2Upvkfjp7YK6qQuhPrBqM4Hnp0,4934
352
+ omlish/lite/subprocesses.py,sha256=fN8Hu1cDhu9PoYlfwYyIsMpeznOzisIU_3eKcYcnARs,6700
353
353
  omlish/lite/typing.py,sha256=U3-JaEnkDSYxK4tsu_MzUn3RP6qALBe5FXQXpD-licE,1090
354
354
  omlish/lite/asyncio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
355
- omlish/lite/asyncio/subprocesses.py,sha256=luHARTCKB5qoxwUln1LH_v-z8k7YTkf32b2rgV_qkMg,8634
355
+ omlish/lite/asyncio/subprocesses.py,sha256=pTcSbBDM6rpPKnjl8YqfdOhThDSalDcZHuvaCzYF07o,7729
356
356
  omlish/logs/__init__.py,sha256=FbOyAW-lGH8gyBlSVArwljdYAU6RnwZLI5LwAfuNnrk,438
357
357
  omlish/logs/abc.py,sha256=ho4ABKYMKX-V7g4sp1BByuOLzslYzLlQ0MESmjEpT-o,8005
358
358
  omlish/logs/configs.py,sha256=EE0jlNaXJbGnM7V-y4xS5VwyTBSTzFzc0BYaVjg0JmA,1283
@@ -520,9 +520,9 @@ omlish/text/glyphsplit.py,sha256=Ug-dPRO7x-OrNNr8g1y6DotSZ2KH0S-VcOmUobwa4B0,329
520
520
  omlish/text/indent.py,sha256=6Jj6TFY9unaPa4xPzrnZemJ-fHsV53IamP93XGjSUHs,1274
521
521
  omlish/text/parts.py,sha256=7vPF1aTZdvLVYJ4EwBZVzRSy8XB3YqPd7JwEnNGGAOo,6495
522
522
  omlish/text/random.py,sha256=jNWpqiaKjKyTdMXC-pWAsSC10AAP-cmRRPVhm59ZWLk,194
523
- omlish-0.0.0.dev155.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
524
- omlish-0.0.0.dev155.dist-info/METADATA,sha256=EZnz0auK1L6Br8TpcHXEE4T5zN79d2pJpORfJEZeFss,4264
525
- omlish-0.0.0.dev155.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
526
- omlish-0.0.0.dev155.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
527
- omlish-0.0.0.dev155.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
528
- omlish-0.0.0.dev155.dist-info/RECORD,,
523
+ omlish-0.0.0.dev157.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
524
+ omlish-0.0.0.dev157.dist-info/METADATA,sha256=7EjWfGRPNiPJlMhOs3pO4xT7Le7y2kO4WqbSIZFX-Gc,4264
525
+ omlish-0.0.0.dev157.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
526
+ omlish-0.0.0.dev157.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
527
+ omlish-0.0.0.dev157.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
528
+ omlish-0.0.0.dev157.dist-info/RECORD,,