omlish 0.0.0.dev223__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 (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