omlish 0.0.0.dev223__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 (39) 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/http/handlers.py +52 -1
  10. omlish/lang/__init__.py +1 -0
  11. omlish/lang/imports.py +22 -0
  12. omlish/libc.py +10 -0
  13. omlish/lite/timing.py +8 -0
  14. omlish/logs/timing.py +58 -0
  15. omlish/multiprocessing/__init__.py +0 -7
  16. omlish/os/pidfiles/__init__.py +0 -0
  17. omlish/os/pidfiles/manager.py +97 -0
  18. omlish/os/pidfiles/pidfile.py +142 -0
  19. omlish/secrets/crypto.py +1 -2
  20. omlish/secrets/openssl.py +1 -1
  21. omlish/secrets/tempssl.py +40 -21
  22. omlish/sockets/handlers.py +4 -0
  23. omlish/sockets/server/handlers.py +22 -0
  24. omlish/subprocesses/__init__.py +0 -0
  25. omlish/subprocesses/async_.py +96 -0
  26. omlish/subprocesses/base.py +215 -0
  27. omlish/subprocesses/run.py +98 -0
  28. omlish/subprocesses/sync.py +147 -0
  29. omlish/subprocesses/utils.py +22 -0
  30. omlish/subprocesses/wrap.py +23 -0
  31. {omlish-0.0.0.dev223.dist-info → omlish-0.0.0.dev225.dist-info}/METADATA +1 -1
  32. {omlish-0.0.0.dev223.dist-info → omlish-0.0.0.dev225.dist-info}/RECORD +37 -26
  33. omlish/os/pidfile.py +0 -69
  34. omlish/subprocesses.py +0 -491
  35. /omlish/{multiprocessing → os}/death.py +0 -0
  36. {omlish-0.0.0.dev223.dist-info → omlish-0.0.0.dev225.dist-info}/LICENSE +0 -0
  37. {omlish-0.0.0.dev223.dist-info → omlish-0.0.0.dev225.dist-info}/WHEEL +0 -0
  38. {omlish-0.0.0.dev223.dist-info → omlish-0.0.0.dev225.dist-info}/entry_points.txt +0 -0
  39. {omlish-0.0.0.dev223.dist-info → omlish-0.0.0.dev225.dist-info}/top_level.txt +0 -0
omlish/subprocesses.py DELETED
@@ -1,491 +0,0 @@
1
- # ruff: noqa: UP006 UP007
2
- # @omlish-lite
3
- import abc
4
- import contextlib
5
- import dataclasses as dc
6
- import logging
7
- import os
8
- import shlex
9
- import subprocess
10
- import sys
11
- import time
12
- import typing as ta
13
-
14
- from .lite.runtime import is_debugger_attached
15
-
16
-
17
- T = ta.TypeVar('T')
18
- SubprocessChannelOption = ta.Literal['pipe', 'stdout', 'devnull'] # ta.TypeAlias
19
-
20
-
21
- ##
22
-
23
-
24
- # Valid channel type kwarg values:
25
- # - A special flag negative int
26
- # - A positive fd int
27
- # - A file-like object
28
- # - None
29
-
30
- SUBPROCESS_CHANNEL_OPTION_VALUES: ta.Mapping[SubprocessChannelOption, int] = {
31
- 'pipe': subprocess.PIPE,
32
- 'stdout': subprocess.STDOUT,
33
- 'devnull': subprocess.DEVNULL,
34
- }
35
-
36
-
37
- ##
38
-
39
-
40
- _SUBPROCESS_SHELL_WRAP_EXECS = False
41
-
42
-
43
- def subprocess_shell_wrap_exec(*cmd: str) -> ta.Tuple[str, ...]:
44
- return ('sh', '-c', ' '.join(map(shlex.quote, cmd)))
45
-
46
-
47
- def subprocess_maybe_shell_wrap_exec(*cmd: str) -> ta.Tuple[str, ...]:
48
- if _SUBPROCESS_SHELL_WRAP_EXECS or is_debugger_attached():
49
- return subprocess_shell_wrap_exec(*cmd)
50
- else:
51
- return cmd
52
-
53
-
54
- ##
55
-
56
-
57
- def subprocess_close(
58
- proc: subprocess.Popen,
59
- timeout: ta.Optional[float] = None,
60
- ) -> None:
61
- # TODO: terminate, sleep, kill
62
- if proc.stdout:
63
- proc.stdout.close()
64
- if proc.stderr:
65
- proc.stderr.close()
66
- if proc.stdin:
67
- proc.stdin.close()
68
-
69
- proc.wait(timeout)
70
-
71
-
72
- ##
73
-
74
-
75
- class VerboseCalledProcessError(subprocess.CalledProcessError):
76
- @classmethod
77
- def from_std(cls, e: subprocess.CalledProcessError) -> 'VerboseCalledProcessError':
78
- return cls(
79
- e.returncode,
80
- e.cmd,
81
- output=e.output,
82
- stderr=e.stderr,
83
- )
84
-
85
- def __str__(self) -> str:
86
- msg = super().__str__()
87
- if self.output is not None:
88
- msg += f' Output: {self.output!r}'
89
- if self.stderr is not None:
90
- msg += f' Stderr: {self.stderr!r}'
91
- return msg
92
-
93
-
94
- class BaseSubprocesses(abc.ABC): # noqa
95
- DEFAULT_LOGGER: ta.ClassVar[ta.Optional[logging.Logger]] = None
96
-
97
- def __init__(
98
- self,
99
- *,
100
- log: ta.Optional[logging.Logger] = None,
101
- try_exceptions: ta.Optional[ta.Tuple[ta.Type[Exception], ...]] = None,
102
- ) -> None:
103
- super().__init__()
104
-
105
- self._log = log if log is not None else self.DEFAULT_LOGGER
106
- self._try_exceptions = try_exceptions if try_exceptions is not None else self.DEFAULT_TRY_EXCEPTIONS
107
-
108
- def set_logger(self, log: ta.Optional[logging.Logger]) -> None:
109
- self._log = log
110
-
111
- #
112
-
113
- def prepare_args(
114
- self,
115
- *cmd: str,
116
- env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
117
- extra_env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
118
- quiet: bool = False,
119
- shell: bool = False,
120
- **kwargs: ta.Any,
121
- ) -> ta.Tuple[ta.Tuple[ta.Any, ...], ta.Dict[str, ta.Any]]:
122
- if self._log:
123
- self._log.debug('Subprocesses.prepare_args: cmd=%r', cmd)
124
- if extra_env:
125
- self._log.debug('Subprocesses.prepare_args: extra_env=%r', extra_env)
126
-
127
- #
128
-
129
- if extra_env:
130
- env = {**(env if env is not None else os.environ), **extra_env}
131
-
132
- #
133
-
134
- if quiet and 'stderr' not in kwargs:
135
- if self._log and not self._log.isEnabledFor(logging.DEBUG):
136
- kwargs['stderr'] = subprocess.DEVNULL
137
-
138
- for chk in ('stdout', 'stderr'):
139
- try:
140
- chv = kwargs[chk]
141
- except KeyError:
142
- continue
143
- kwargs[chk] = SUBPROCESS_CHANNEL_OPTION_VALUES.get(chv, chv)
144
-
145
- #
146
-
147
- if not shell:
148
- cmd = subprocess_maybe_shell_wrap_exec(*cmd)
149
-
150
- #
151
-
152
- return cmd, dict(
153
- env=env,
154
- shell=shell,
155
- **kwargs,
156
- )
157
-
158
- @contextlib.contextmanager
159
- def wrap_call(
160
- self,
161
- *cmd: ta.Any,
162
- raise_verbose: bool = False,
163
- **kwargs: ta.Any,
164
- ) -> ta.Iterator[None]:
165
- start_time = time.time()
166
- try:
167
- if self._log:
168
- self._log.debug('Subprocesses.wrap_call.try: cmd=%r', cmd)
169
-
170
- yield
171
-
172
- except Exception as exc: # noqa
173
- if self._log:
174
- self._log.debug('Subprocesses.wrap_call.except: exc=%r', exc)
175
-
176
- if (
177
- raise_verbose and
178
- isinstance(exc, subprocess.CalledProcessError) and
179
- not isinstance(exc, VerboseCalledProcessError) and
180
- (exc.output is not None or exc.stderr is not None)
181
- ):
182
- raise VerboseCalledProcessError.from_std(exc) from exc
183
-
184
- raise
185
-
186
- finally:
187
- end_time = time.time()
188
- elapsed_s = end_time - start_time
189
-
190
- if self._log:
191
- self._log.debug('Subprocesses.wrap_call.finally: elapsed_s=%f cmd=%r', elapsed_s, cmd)
192
-
193
- @contextlib.contextmanager
194
- def prepare_and_wrap(
195
- self,
196
- *cmd: ta.Any,
197
- raise_verbose: bool = False,
198
- **kwargs: ta.Any,
199
- ) -> ta.Iterator[ta.Tuple[
200
- ta.Tuple[ta.Any, ...],
201
- ta.Dict[str, ta.Any],
202
- ]]:
203
- cmd, kwargs = self.prepare_args(*cmd, **kwargs)
204
-
205
- with self.wrap_call(
206
- *cmd,
207
- raise_verbose=raise_verbose,
208
- **kwargs,
209
- ):
210
- yield cmd, kwargs
211
-
212
- #
213
-
214
- DEFAULT_TRY_EXCEPTIONS: ta.Tuple[ta.Type[Exception], ...] = (
215
- FileNotFoundError,
216
- subprocess.CalledProcessError,
217
- )
218
-
219
- def try_fn(
220
- self,
221
- fn: ta.Callable[..., T],
222
- *cmd: str,
223
- try_exceptions: ta.Optional[ta.Tuple[ta.Type[Exception], ...]] = None,
224
- **kwargs: ta.Any,
225
- ) -> ta.Union[T, Exception]:
226
- if try_exceptions is None:
227
- try_exceptions = self._try_exceptions
228
-
229
- try:
230
- return fn(*cmd, **kwargs)
231
-
232
- except try_exceptions as e: # noqa
233
- if self._log and self._log.isEnabledFor(logging.DEBUG):
234
- self._log.exception('command failed')
235
- return e
236
-
237
- async def async_try_fn(
238
- self,
239
- fn: ta.Callable[..., ta.Awaitable[T]],
240
- *cmd: ta.Any,
241
- try_exceptions: ta.Optional[ta.Tuple[ta.Type[Exception], ...]] = None,
242
- **kwargs: ta.Any,
243
- ) -> ta.Union[T, Exception]:
244
- if try_exceptions is None:
245
- try_exceptions = self._try_exceptions
246
-
247
- try:
248
- return await fn(*cmd, **kwargs)
249
-
250
- except try_exceptions as e: # noqa
251
- if self._log and self._log.isEnabledFor(logging.DEBUG):
252
- self._log.exception('command failed')
253
- return e
254
-
255
-
256
- ##
257
-
258
-
259
- @dc.dataclass(frozen=True)
260
- class SubprocessRun:
261
- cmd: ta.Sequence[str]
262
- input: ta.Any = None
263
- timeout: ta.Optional[float] = None
264
- check: bool = False
265
- capture_output: ta.Optional[bool] = None
266
- kwargs: ta.Optional[ta.Mapping[str, ta.Any]] = None
267
-
268
-
269
- @dc.dataclass(frozen=True)
270
- class SubprocessRunOutput(ta.Generic[T]):
271
- proc: T
272
-
273
- returncode: int # noqa
274
-
275
- stdout: ta.Optional[bytes] = None
276
- stderr: ta.Optional[bytes] = None
277
-
278
-
279
- class AbstractSubprocesses(BaseSubprocesses, abc.ABC):
280
- @abc.abstractmethod
281
- def run_(self, run: SubprocessRun) -> SubprocessRunOutput:
282
- raise NotImplementedError
283
-
284
- def run(
285
- self,
286
- *cmd: str,
287
- input: ta.Any = None, # noqa
288
- timeout: ta.Optional[float] = None,
289
- check: bool = False,
290
- capture_output: ta.Optional[bool] = None,
291
- **kwargs: ta.Any,
292
- ) -> SubprocessRunOutput:
293
- return self.run_(SubprocessRun(
294
- cmd=cmd,
295
- input=input,
296
- timeout=timeout,
297
- check=check,
298
- capture_output=capture_output,
299
- kwargs=kwargs,
300
- ))
301
-
302
- #
303
-
304
- @abc.abstractmethod
305
- def check_call(
306
- self,
307
- *cmd: str,
308
- stdout: ta.Any = sys.stderr,
309
- **kwargs: ta.Any,
310
- ) -> None:
311
- raise NotImplementedError
312
-
313
- @abc.abstractmethod
314
- def check_output(
315
- self,
316
- *cmd: str,
317
- **kwargs: ta.Any,
318
- ) -> bytes:
319
- raise NotImplementedError
320
-
321
- #
322
-
323
- def check_output_str(
324
- self,
325
- *cmd: str,
326
- **kwargs: ta.Any,
327
- ) -> str:
328
- return self.check_output(*cmd, **kwargs).decode().strip()
329
-
330
- #
331
-
332
- def try_call(
333
- self,
334
- *cmd: str,
335
- **kwargs: ta.Any,
336
- ) -> bool:
337
- if isinstance(self.try_fn(self.check_call, *cmd, **kwargs), Exception):
338
- return False
339
- else:
340
- return True
341
-
342
- def try_output(
343
- self,
344
- *cmd: str,
345
- **kwargs: ta.Any,
346
- ) -> ta.Optional[bytes]:
347
- if isinstance(ret := self.try_fn(self.check_output, *cmd, **kwargs), Exception):
348
- return None
349
- else:
350
- return ret
351
-
352
- def try_output_str(
353
- self,
354
- *cmd: str,
355
- **kwargs: ta.Any,
356
- ) -> ta.Optional[str]:
357
- if (ret := self.try_output(*cmd, **kwargs)) is None:
358
- return None
359
- else:
360
- return ret.decode().strip()
361
-
362
-
363
- ##
364
-
365
-
366
- class Subprocesses(AbstractSubprocesses):
367
- def run_(self, run: SubprocessRun) -> SubprocessRunOutput[subprocess.CompletedProcess]:
368
- proc = subprocess.run(
369
- run.cmd,
370
- input=run.input,
371
- timeout=run.timeout,
372
- check=run.check,
373
- capture_output=run.capture_output or False,
374
- **(run.kwargs or {}),
375
- )
376
-
377
- return SubprocessRunOutput(
378
- proc=proc,
379
-
380
- returncode=proc.returncode,
381
-
382
- stdout=proc.stdout, # noqa
383
- stderr=proc.stderr, # noqa
384
- )
385
-
386
- def check_call(
387
- self,
388
- *cmd: str,
389
- stdout: ta.Any = sys.stderr,
390
- **kwargs: ta.Any,
391
- ) -> None:
392
- with self.prepare_and_wrap(*cmd, stdout=stdout, **kwargs) as (cmd, kwargs): # noqa
393
- subprocess.check_call(cmd, **kwargs)
394
-
395
- def check_output(
396
- self,
397
- *cmd: str,
398
- **kwargs: ta.Any,
399
- ) -> bytes:
400
- with self.prepare_and_wrap(*cmd, **kwargs) as (cmd, kwargs): # noqa
401
- return subprocess.check_output(cmd, **kwargs)
402
-
403
-
404
- subprocesses = Subprocesses()
405
-
406
-
407
- ##
408
-
409
-
410
- class AbstractAsyncSubprocesses(BaseSubprocesses):
411
- @abc.abstractmethod
412
- async def run_(self, run: SubprocessRun) -> SubprocessRunOutput:
413
- raise NotImplementedError
414
-
415
- def run(
416
- self,
417
- *cmd: str,
418
- input: ta.Any = None, # noqa
419
- timeout: ta.Optional[float] = None,
420
- check: bool = False,
421
- capture_output: ta.Optional[bool] = None,
422
- **kwargs: ta.Any,
423
- ) -> ta.Awaitable[SubprocessRunOutput]:
424
- return self.run_(SubprocessRun(
425
- cmd=cmd,
426
- input=input,
427
- timeout=timeout,
428
- check=check,
429
- capture_output=capture_output,
430
- kwargs=kwargs,
431
- ))
432
-
433
- #
434
-
435
- @abc.abstractmethod
436
- async def check_call(
437
- self,
438
- *cmd: str,
439
- stdout: ta.Any = sys.stderr,
440
- **kwargs: ta.Any,
441
- ) -> None:
442
- raise NotImplementedError
443
-
444
- @abc.abstractmethod
445
- async def check_output(
446
- self,
447
- *cmd: str,
448
- **kwargs: ta.Any,
449
- ) -> bytes:
450
- raise NotImplementedError
451
-
452
- #
453
-
454
- async def check_output_str(
455
- self,
456
- *cmd: str,
457
- **kwargs: ta.Any,
458
- ) -> str:
459
- return (await self.check_output(*cmd, **kwargs)).decode().strip()
460
-
461
- #
462
-
463
- async def try_call(
464
- self,
465
- *cmd: str,
466
- **kwargs: ta.Any,
467
- ) -> bool:
468
- if isinstance(await self.async_try_fn(self.check_call, *cmd, **kwargs), Exception):
469
- return False
470
- else:
471
- return True
472
-
473
- async def try_output(
474
- self,
475
- *cmd: str,
476
- **kwargs: ta.Any,
477
- ) -> ta.Optional[bytes]:
478
- if isinstance(ret := await self.async_try_fn(self.check_output, *cmd, **kwargs), Exception):
479
- return None
480
- else:
481
- return ret
482
-
483
- async def try_output_str(
484
- self,
485
- *cmd: str,
486
- **kwargs: ta.Any,
487
- ) -> ta.Optional[str]:
488
- if (ret := await self.try_output(*cmd, **kwargs)) is None:
489
- return None
490
- else:
491
- return ret.decode().strip()
File without changes