omlish 0.0.0.dev224__py3-none-any.whl → 0.0.0.dev225__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 (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