omlish 0.0.0.dev224__py3-none-any.whl → 0.0.0.dev225__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. omlish/__about__.py +2 -2
  2. omlish/asyncs/asyncio/subprocesses.py +15 -15
  3. omlish/asyncs/asyncs.py +0 -1
  4. omlish/bootstrap/sys.py +2 -2
  5. omlish/dataclasses/impl/metaclass.py +5 -0
  6. omlish/http/coro/server.py +5 -54
  7. omlish/http/coro/simple.py +1 -1
  8. omlish/http/coro/sockets.py +59 -0
  9. omlish/lang/__init__.py +1 -0
  10. omlish/lang/imports.py +22 -0
  11. omlish/libc.py +10 -0
  12. omlish/multiprocessing/__init__.py +0 -7
  13. omlish/os/pidfiles/__init__.py +0 -0
  14. omlish/os/pidfiles/manager.py +97 -0
  15. omlish/os/pidfiles/pidfile.py +142 -0
  16. omlish/secrets/crypto.py +1 -2
  17. omlish/secrets/openssl.py +1 -1
  18. omlish/secrets/tempssl.py +4 -7
  19. omlish/sockets/handlers.py +4 -0
  20. omlish/sockets/server/handlers.py +22 -0
  21. omlish/subprocesses/__init__.py +0 -0
  22. omlish/subprocesses/async_.py +96 -0
  23. omlish/subprocesses/base.py +215 -0
  24. omlish/subprocesses/run.py +98 -0
  25. omlish/subprocesses/sync.py +147 -0
  26. omlish/subprocesses/utils.py +22 -0
  27. omlish/subprocesses/wrap.py +23 -0
  28. {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev225.dist-info}/METADATA +1 -1
  29. {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev225.dist-info}/RECORD +34 -25
  30. omlish/os/pidfile.py +0 -69
  31. omlish/subprocesses.py +0 -510
  32. /omlish/{multiprocessing → os}/death.py +0 -0
  33. {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev225.dist-info}/LICENSE +0 -0
  34. {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev225.dist-info}/WHEEL +0 -0
  35. {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev225.dist-info}/entry_points.txt +0 -0
  36. {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev225.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,96 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ import abc
4
+ import sys
5
+ import typing as ta
6
+
7
+ from .base import BaseSubprocesses
8
+ from .run import SubprocessRun
9
+ from .run import SubprocessRunOutput
10
+
11
+
12
+ ##
13
+
14
+
15
+ class AbstractAsyncSubprocesses(BaseSubprocesses):
16
+ @abc.abstractmethod
17
+ async def run_(self, run: SubprocessRun) -> SubprocessRunOutput:
18
+ raise NotImplementedError
19
+
20
+ def run(
21
+ self,
22
+ *cmd: str,
23
+ input: ta.Any = None, # noqa
24
+ timeout: ta.Optional[float] = None,
25
+ check: bool = False,
26
+ capture_output: ta.Optional[bool] = None,
27
+ **kwargs: ta.Any,
28
+ ) -> ta.Awaitable[SubprocessRunOutput]:
29
+ return self.run_(SubprocessRun(
30
+ cmd=cmd,
31
+ input=input,
32
+ timeout=timeout,
33
+ check=check,
34
+ capture_output=capture_output,
35
+ kwargs=kwargs,
36
+ ))
37
+
38
+ #
39
+
40
+ @abc.abstractmethod
41
+ async def check_call(
42
+ self,
43
+ *cmd: str,
44
+ stdout: ta.Any = sys.stderr,
45
+ **kwargs: ta.Any,
46
+ ) -> None:
47
+ raise NotImplementedError
48
+
49
+ @abc.abstractmethod
50
+ async def check_output(
51
+ self,
52
+ *cmd: str,
53
+ **kwargs: ta.Any,
54
+ ) -> bytes:
55
+ raise NotImplementedError
56
+
57
+ #
58
+
59
+ async def check_output_str(
60
+ self,
61
+ *cmd: str,
62
+ **kwargs: ta.Any,
63
+ ) -> str:
64
+ return (await self.check_output(*cmd, **kwargs)).decode().strip()
65
+
66
+ #
67
+
68
+ async def try_call(
69
+ self,
70
+ *cmd: str,
71
+ **kwargs: ta.Any,
72
+ ) -> bool:
73
+ if isinstance(await self.async_try_fn(self.check_call, *cmd, **kwargs), Exception):
74
+ return False
75
+ else:
76
+ return True
77
+
78
+ async def try_output(
79
+ self,
80
+ *cmd: str,
81
+ **kwargs: ta.Any,
82
+ ) -> ta.Optional[bytes]:
83
+ if isinstance(ret := await self.async_try_fn(self.check_output, *cmd, **kwargs), Exception):
84
+ return None
85
+ else:
86
+ return ret
87
+
88
+ async def try_output_str(
89
+ self,
90
+ *cmd: str,
91
+ **kwargs: ta.Any,
92
+ ) -> ta.Optional[str]:
93
+ if (ret := await self.try_output(*cmd, **kwargs)) is None:
94
+ return None
95
+ else:
96
+ return ret.decode().strip()
@@ -0,0 +1,215 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ import abc
4
+ import contextlib
5
+ import logging
6
+ import os
7
+ import subprocess
8
+ import time
9
+ import typing as ta
10
+
11
+ from .wrap import subprocess_maybe_shell_wrap_exec
12
+
13
+
14
+ T = ta.TypeVar('T')
15
+ SubprocessChannelOption = ta.Literal['pipe', 'stdout', 'devnull'] # ta.TypeAlias
16
+
17
+
18
+ ##
19
+
20
+
21
+ # Valid channel type kwarg values:
22
+ # - A special flag negative int
23
+ # - A positive fd int
24
+ # - A file-like object
25
+ # - None
26
+
27
+ SUBPROCESS_CHANNEL_OPTION_VALUES: ta.Mapping[SubprocessChannelOption, int] = {
28
+ 'pipe': subprocess.PIPE,
29
+ 'stdout': subprocess.STDOUT,
30
+ 'devnull': subprocess.DEVNULL,
31
+ }
32
+
33
+
34
+ ##
35
+
36
+
37
+ class VerboseCalledProcessError(subprocess.CalledProcessError):
38
+ @classmethod
39
+ def from_std(cls, e: subprocess.CalledProcessError) -> 'VerboseCalledProcessError':
40
+ return cls(
41
+ e.returncode,
42
+ e.cmd,
43
+ output=e.output,
44
+ stderr=e.stderr,
45
+ )
46
+
47
+ def __str__(self) -> str:
48
+ msg = super().__str__()
49
+ if self.output is not None:
50
+ msg += f' Output: {self.output!r}'
51
+ if self.stderr is not None:
52
+ msg += f' Stderr: {self.stderr!r}'
53
+ return msg
54
+
55
+
56
+ class BaseSubprocesses(abc.ABC): # noqa
57
+ DEFAULT_LOGGER: ta.ClassVar[ta.Optional[logging.Logger]] = None
58
+
59
+ def __init__(
60
+ self,
61
+ *,
62
+ log: ta.Optional[logging.Logger] = None,
63
+ try_exceptions: ta.Optional[ta.Tuple[ta.Type[Exception], ...]] = None,
64
+ ) -> None:
65
+ super().__init__()
66
+
67
+ self._log = log if log is not None else self.DEFAULT_LOGGER
68
+ self._try_exceptions = try_exceptions if try_exceptions is not None else self.DEFAULT_TRY_EXCEPTIONS
69
+
70
+ def set_logger(self, log: ta.Optional[logging.Logger]) -> None:
71
+ self._log = log
72
+
73
+ #
74
+
75
+ def prepare_args(
76
+ self,
77
+ *cmd: str,
78
+ env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
79
+ extra_env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
80
+ quiet: bool = False,
81
+ shell: bool = False,
82
+ **kwargs: ta.Any,
83
+ ) -> ta.Tuple[ta.Tuple[ta.Any, ...], ta.Dict[str, ta.Any]]:
84
+ if self._log:
85
+ self._log.debug('Subprocesses.prepare_args: cmd=%r', cmd)
86
+ if extra_env:
87
+ self._log.debug('Subprocesses.prepare_args: extra_env=%r', extra_env)
88
+
89
+ #
90
+
91
+ if extra_env:
92
+ env = {**(env if env is not None else os.environ), **extra_env}
93
+
94
+ #
95
+
96
+ if quiet and 'stderr' not in kwargs:
97
+ if self._log and not self._log.isEnabledFor(logging.DEBUG):
98
+ kwargs['stderr'] = subprocess.DEVNULL
99
+
100
+ for chk in ('stdout', 'stderr'):
101
+ try:
102
+ chv = kwargs[chk]
103
+ except KeyError:
104
+ continue
105
+ kwargs[chk] = SUBPROCESS_CHANNEL_OPTION_VALUES.get(chv, chv)
106
+
107
+ #
108
+
109
+ if not shell:
110
+ cmd = subprocess_maybe_shell_wrap_exec(*cmd)
111
+
112
+ #
113
+
114
+ return cmd, dict(
115
+ env=env,
116
+ shell=shell,
117
+ **kwargs,
118
+ )
119
+
120
+ @contextlib.contextmanager
121
+ def wrap_call(
122
+ self,
123
+ *cmd: ta.Any,
124
+ raise_verbose: bool = False,
125
+ **kwargs: ta.Any,
126
+ ) -> ta.Iterator[None]:
127
+ start_time = time.time()
128
+ try:
129
+ if self._log:
130
+ self._log.debug('Subprocesses.wrap_call.try: cmd=%r', cmd)
131
+
132
+ yield
133
+
134
+ except Exception as exc: # noqa
135
+ if self._log:
136
+ self._log.debug('Subprocesses.wrap_call.except: exc=%r', exc)
137
+
138
+ if (
139
+ raise_verbose and
140
+ isinstance(exc, subprocess.CalledProcessError) and
141
+ not isinstance(exc, VerboseCalledProcessError) and
142
+ (exc.output is not None or exc.stderr is not None)
143
+ ):
144
+ raise VerboseCalledProcessError.from_std(exc) from exc
145
+
146
+ raise
147
+
148
+ finally:
149
+ end_time = time.time()
150
+ elapsed_s = end_time - start_time
151
+
152
+ if self._log:
153
+ self._log.debug('Subprocesses.wrap_call.finally: elapsed_s=%f cmd=%r', elapsed_s, cmd)
154
+
155
+ @contextlib.contextmanager
156
+ def prepare_and_wrap(
157
+ self,
158
+ *cmd: ta.Any,
159
+ raise_verbose: bool = False,
160
+ **kwargs: ta.Any,
161
+ ) -> ta.Iterator[ta.Tuple[
162
+ ta.Tuple[ta.Any, ...],
163
+ ta.Dict[str, ta.Any],
164
+ ]]:
165
+ cmd, kwargs = self.prepare_args(*cmd, **kwargs)
166
+
167
+ with self.wrap_call(
168
+ *cmd,
169
+ raise_verbose=raise_verbose,
170
+ **kwargs,
171
+ ):
172
+ yield cmd, kwargs
173
+
174
+ #
175
+
176
+ DEFAULT_TRY_EXCEPTIONS: ta.Tuple[ta.Type[Exception], ...] = (
177
+ FileNotFoundError,
178
+ subprocess.CalledProcessError,
179
+ )
180
+
181
+ def try_fn(
182
+ self,
183
+ fn: ta.Callable[..., T],
184
+ *cmd: str,
185
+ try_exceptions: ta.Optional[ta.Tuple[ta.Type[Exception], ...]] = None,
186
+ **kwargs: ta.Any,
187
+ ) -> ta.Union[T, Exception]:
188
+ if try_exceptions is None:
189
+ try_exceptions = self._try_exceptions
190
+
191
+ try:
192
+ return fn(*cmd, **kwargs)
193
+
194
+ except try_exceptions as e: # noqa
195
+ if self._log and self._log.isEnabledFor(logging.DEBUG):
196
+ self._log.exception('command failed')
197
+ return e
198
+
199
+ async def async_try_fn(
200
+ self,
201
+ fn: ta.Callable[..., ta.Awaitable[T]],
202
+ *cmd: ta.Any,
203
+ try_exceptions: ta.Optional[ta.Tuple[ta.Type[Exception], ...]] = None,
204
+ **kwargs: ta.Any,
205
+ ) -> ta.Union[T, Exception]:
206
+ if try_exceptions is None:
207
+ try_exceptions = self._try_exceptions
208
+
209
+ try:
210
+ return await fn(*cmd, **kwargs)
211
+
212
+ except try_exceptions as e: # noqa
213
+ if self._log and self._log.isEnabledFor(logging.DEBUG):
214
+ self._log.exception('command failed')
215
+ return e
@@ -0,0 +1,98 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ import abc
4
+ import dataclasses as dc
5
+ import typing as ta
6
+
7
+ from ..lite.check import check
8
+
9
+
10
+ T = ta.TypeVar('T')
11
+
12
+
13
+ ##
14
+
15
+
16
+ @dc.dataclass(frozen=True)
17
+ class SubprocessRunOutput(ta.Generic[T]):
18
+ proc: T
19
+
20
+ returncode: int # noqa
21
+
22
+ stdout: ta.Optional[bytes] = None
23
+ stderr: ta.Optional[bytes] = None
24
+
25
+
26
+ ##
27
+
28
+
29
+ @dc.dataclass(frozen=True)
30
+ class SubprocessRun:
31
+ cmd: ta.Sequence[str]
32
+ input: ta.Any = None
33
+ timeout: ta.Optional[float] = None
34
+ check: bool = False
35
+ capture_output: ta.Optional[bool] = None
36
+ kwargs: ta.Optional[ta.Mapping[str, ta.Any]] = None
37
+
38
+ @classmethod
39
+ def of(
40
+ cls,
41
+ *cmd: str,
42
+ input: ta.Any = None, # noqa
43
+ timeout: ta.Optional[float] = None,
44
+ check: bool = False, # noqa
45
+ capture_output: ta.Optional[bool] = None,
46
+ **kwargs: ta.Any,
47
+ ) -> 'SubprocessRun':
48
+ return cls(
49
+ cmd=cmd,
50
+ input=input,
51
+ timeout=timeout,
52
+ check=check,
53
+ capture_output=capture_output,
54
+ kwargs=kwargs,
55
+ )
56
+
57
+ #
58
+
59
+ _DEFAULT_SUBPROCESSES: ta.ClassVar[ta.Optional[ta.Any]] = None # AbstractSubprocesses
60
+
61
+ def run(
62
+ self,
63
+ subprocesses: ta.Optional[ta.Any] = None, # AbstractSubprocesses
64
+ ) -> SubprocessRunOutput:
65
+ if subprocesses is None:
66
+ subprocesses = self._DEFAULT_SUBPROCESSES
67
+ return check.not_none(subprocesses).run_(self) # type: ignore[attr-defined]
68
+
69
+ _DEFAULT_ASYNC_SUBPROCESSES: ta.ClassVar[ta.Optional[ta.Any]] = None # AbstractAsyncSubprocesses
70
+
71
+ async def async_run(
72
+ self,
73
+ async_subprocesses: ta.Optional[ta.Any] = None, # AbstractAsyncSubprocesses
74
+ ) -> SubprocessRunOutput:
75
+ if async_subprocesses is None:
76
+ async_subprocesses = self._DEFAULT_ASYNC_SUBPROCESSES
77
+ return await check.not_none(async_subprocesses).run_(self) # type: ignore[attr-defined]
78
+
79
+
80
+ ##
81
+
82
+
83
+ class SubprocessRunnable(abc.ABC, ta.Generic[T]):
84
+ @abc.abstractmethod
85
+ def make_run(self) -> SubprocessRun:
86
+ raise NotImplementedError
87
+
88
+ @abc.abstractmethod
89
+ def handle_run_output(self, output: SubprocessRunOutput) -> T:
90
+ raise NotImplementedError
91
+
92
+ #
93
+
94
+ def run(self, subprocesses: ta.Optional[ta.Any] = None) -> T: # AbstractSubprocesses
95
+ return self.handle_run_output(self.make_run().run(subprocesses))
96
+
97
+ async def async_run(self, async_subprocesses: ta.Optional[ta.Any] = None) -> T: # AbstractAsyncSubprocesses
98
+ return self.handle_run_output(await self.make_run().async_run(async_subprocesses))
@@ -0,0 +1,147 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ import abc
4
+ import subprocess
5
+ import sys
6
+ import typing as ta
7
+
8
+ from .base import BaseSubprocesses
9
+ from .run import SubprocessRun
10
+ from .run import SubprocessRunOutput
11
+
12
+
13
+ ##
14
+
15
+
16
+ class AbstractSubprocesses(BaseSubprocesses, abc.ABC):
17
+ @abc.abstractmethod
18
+ def run_(self, run: SubprocessRun) -> SubprocessRunOutput:
19
+ raise NotImplementedError
20
+
21
+ def run(
22
+ self,
23
+ *cmd: str,
24
+ input: ta.Any = None, # noqa
25
+ timeout: ta.Optional[float] = None,
26
+ check: bool = False,
27
+ capture_output: ta.Optional[bool] = None,
28
+ **kwargs: ta.Any,
29
+ ) -> SubprocessRunOutput:
30
+ return self.run_(SubprocessRun(
31
+ cmd=cmd,
32
+ input=input,
33
+ timeout=timeout,
34
+ check=check,
35
+ capture_output=capture_output,
36
+ kwargs=kwargs,
37
+ ))
38
+
39
+ #
40
+
41
+ @abc.abstractmethod
42
+ def check_call(
43
+ self,
44
+ *cmd: str,
45
+ stdout: ta.Any = sys.stderr,
46
+ **kwargs: ta.Any,
47
+ ) -> None:
48
+ raise NotImplementedError
49
+
50
+ @abc.abstractmethod
51
+ def check_output(
52
+ self,
53
+ *cmd: str,
54
+ **kwargs: ta.Any,
55
+ ) -> bytes:
56
+ raise NotImplementedError
57
+
58
+ #
59
+
60
+ def check_output_str(
61
+ self,
62
+ *cmd: str,
63
+ **kwargs: ta.Any,
64
+ ) -> str:
65
+ return self.check_output(*cmd, **kwargs).decode().strip()
66
+
67
+ #
68
+
69
+ def try_call(
70
+ self,
71
+ *cmd: str,
72
+ **kwargs: ta.Any,
73
+ ) -> bool:
74
+ if isinstance(self.try_fn(self.check_call, *cmd, **kwargs), Exception):
75
+ return False
76
+ else:
77
+ return True
78
+
79
+ def try_output(
80
+ self,
81
+ *cmd: str,
82
+ **kwargs: ta.Any,
83
+ ) -> ta.Optional[bytes]:
84
+ if isinstance(ret := self.try_fn(self.check_output, *cmd, **kwargs), Exception):
85
+ return None
86
+ else:
87
+ return ret
88
+
89
+ def try_output_str(
90
+ self,
91
+ *cmd: str,
92
+ **kwargs: ta.Any,
93
+ ) -> ta.Optional[str]:
94
+ if (ret := self.try_output(*cmd, **kwargs)) is None:
95
+ return None
96
+ else:
97
+ return ret.decode().strip()
98
+
99
+
100
+ ##
101
+
102
+
103
+ class Subprocesses(AbstractSubprocesses):
104
+ def run_(self, run: SubprocessRun) -> SubprocessRunOutput[subprocess.CompletedProcess]:
105
+ with self.prepare_and_wrap(
106
+ *run.cmd,
107
+ input=run.input,
108
+ timeout=run.timeout,
109
+ check=run.check,
110
+ capture_output=run.capture_output or False,
111
+ **(run.kwargs or {}),
112
+ ) as (cmd, kwargs):
113
+ proc = subprocess.run(cmd, **kwargs) # noqa
114
+
115
+ return SubprocessRunOutput(
116
+ proc=proc,
117
+
118
+ returncode=proc.returncode,
119
+
120
+ stdout=proc.stdout, # noqa
121
+ stderr=proc.stderr, # noqa
122
+ )
123
+
124
+ def check_call(
125
+ self,
126
+ *cmd: str,
127
+ stdout: ta.Any = sys.stderr,
128
+ **kwargs: ta.Any,
129
+ ) -> None:
130
+ with self.prepare_and_wrap(*cmd, stdout=stdout, **kwargs) as (cmd, kwargs): # noqa
131
+ subprocess.check_call(cmd, **kwargs)
132
+
133
+ def check_output(
134
+ self,
135
+ *cmd: str,
136
+ **kwargs: ta.Any,
137
+ ) -> bytes:
138
+ with self.prepare_and_wrap(*cmd, **kwargs) as (cmd, kwargs): # noqa
139
+ return subprocess.check_output(cmd, **kwargs)
140
+
141
+
142
+ ##
143
+
144
+
145
+ subprocesses = Subprocesses()
146
+
147
+ SubprocessRun._DEFAULT_SUBPROCESSES = subprocesses # noqa
@@ -0,0 +1,22 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ import subprocess
4
+ import typing as ta
5
+
6
+
7
+ ##
8
+
9
+
10
+ def subprocess_close(
11
+ proc: subprocess.Popen,
12
+ timeout: ta.Optional[float] = None,
13
+ ) -> None:
14
+ # TODO: terminate, sleep, kill
15
+ if proc.stdout:
16
+ proc.stdout.close()
17
+ if proc.stderr:
18
+ proc.stderr.close()
19
+ if proc.stdin:
20
+ proc.stdin.close()
21
+
22
+ proc.wait(timeout)
@@ -0,0 +1,23 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ import shlex
4
+ import typing as ta
5
+
6
+ from ..lite.runtime import is_debugger_attached
7
+
8
+
9
+ ##
10
+
11
+
12
+ _SUBPROCESS_SHELL_WRAP_EXECS = False
13
+
14
+
15
+ def subprocess_shell_wrap_exec(*cmd: str) -> ta.Tuple[str, ...]:
16
+ return ('sh', '-c', ' '.join(map(shlex.quote, cmd)))
17
+
18
+
19
+ def subprocess_maybe_shell_wrap_exec(*cmd: str) -> ta.Tuple[str, ...]:
20
+ if _SUBPROCESS_SHELL_WRAP_EXECS or is_debugger_attached():
21
+ return subprocess_shell_wrap_exec(*cmd)
22
+ else:
23
+ return cmd
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: omlish
3
- Version: 0.0.0.dev224
3
+ Version: 0.0.0.dev225
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause