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
omlish/subprocesses.py DELETED
@@ -1,510 +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
- @classmethod
269
- def of(
270
- cls,
271
- *cmd: str,
272
- input: ta.Any = None, # noqa
273
- timeout: ta.Optional[float] = None,
274
- check: bool = False,
275
- capture_output: ta.Optional[bool] = None,
276
- **kwargs: ta.Any,
277
- ) -> 'SubprocessRun':
278
- return cls(
279
- cmd=cmd,
280
- input=input,
281
- timeout=timeout,
282
- check=check,
283
- capture_output=capture_output,
284
- kwargs=kwargs,
285
- )
286
-
287
-
288
- @dc.dataclass(frozen=True)
289
- class SubprocessRunOutput(ta.Generic[T]):
290
- proc: T
291
-
292
- returncode: int # noqa
293
-
294
- stdout: ta.Optional[bytes] = None
295
- stderr: ta.Optional[bytes] = None
296
-
297
-
298
- class AbstractSubprocesses(BaseSubprocesses, abc.ABC):
299
- @abc.abstractmethod
300
- def run_(self, run: SubprocessRun) -> SubprocessRunOutput:
301
- raise NotImplementedError
302
-
303
- def run(
304
- self,
305
- *cmd: str,
306
- input: ta.Any = None, # noqa
307
- timeout: ta.Optional[float] = None,
308
- check: bool = False,
309
- capture_output: ta.Optional[bool] = None,
310
- **kwargs: ta.Any,
311
- ) -> SubprocessRunOutput:
312
- return self.run_(SubprocessRun(
313
- cmd=cmd,
314
- input=input,
315
- timeout=timeout,
316
- check=check,
317
- capture_output=capture_output,
318
- kwargs=kwargs,
319
- ))
320
-
321
- #
322
-
323
- @abc.abstractmethod
324
- def check_call(
325
- self,
326
- *cmd: str,
327
- stdout: ta.Any = sys.stderr,
328
- **kwargs: ta.Any,
329
- ) -> None:
330
- raise NotImplementedError
331
-
332
- @abc.abstractmethod
333
- def check_output(
334
- self,
335
- *cmd: str,
336
- **kwargs: ta.Any,
337
- ) -> bytes:
338
- raise NotImplementedError
339
-
340
- #
341
-
342
- def check_output_str(
343
- self,
344
- *cmd: str,
345
- **kwargs: ta.Any,
346
- ) -> str:
347
- return self.check_output(*cmd, **kwargs).decode().strip()
348
-
349
- #
350
-
351
- def try_call(
352
- self,
353
- *cmd: str,
354
- **kwargs: ta.Any,
355
- ) -> bool:
356
- if isinstance(self.try_fn(self.check_call, *cmd, **kwargs), Exception):
357
- return False
358
- else:
359
- return True
360
-
361
- def try_output(
362
- self,
363
- *cmd: str,
364
- **kwargs: ta.Any,
365
- ) -> ta.Optional[bytes]:
366
- if isinstance(ret := self.try_fn(self.check_output, *cmd, **kwargs), Exception):
367
- return None
368
- else:
369
- return ret
370
-
371
- def try_output_str(
372
- self,
373
- *cmd: str,
374
- **kwargs: ta.Any,
375
- ) -> ta.Optional[str]:
376
- if (ret := self.try_output(*cmd, **kwargs)) is None:
377
- return None
378
- else:
379
- return ret.decode().strip()
380
-
381
-
382
- ##
383
-
384
-
385
- class Subprocesses(AbstractSubprocesses):
386
- def run_(self, run: SubprocessRun) -> SubprocessRunOutput[subprocess.CompletedProcess]:
387
- proc = subprocess.run(
388
- run.cmd,
389
- input=run.input,
390
- timeout=run.timeout,
391
- check=run.check,
392
- capture_output=run.capture_output or False,
393
- **(run.kwargs or {}),
394
- )
395
-
396
- return SubprocessRunOutput(
397
- proc=proc,
398
-
399
- returncode=proc.returncode,
400
-
401
- stdout=proc.stdout, # noqa
402
- stderr=proc.stderr, # noqa
403
- )
404
-
405
- def check_call(
406
- self,
407
- *cmd: str,
408
- stdout: ta.Any = sys.stderr,
409
- **kwargs: ta.Any,
410
- ) -> None:
411
- with self.prepare_and_wrap(*cmd, stdout=stdout, **kwargs) as (cmd, kwargs): # noqa
412
- subprocess.check_call(cmd, **kwargs)
413
-
414
- def check_output(
415
- self,
416
- *cmd: str,
417
- **kwargs: ta.Any,
418
- ) -> bytes:
419
- with self.prepare_and_wrap(*cmd, **kwargs) as (cmd, kwargs): # noqa
420
- return subprocess.check_output(cmd, **kwargs)
421
-
422
-
423
- subprocesses = Subprocesses()
424
-
425
-
426
- ##
427
-
428
-
429
- class AbstractAsyncSubprocesses(BaseSubprocesses):
430
- @abc.abstractmethod
431
- async def run_(self, run: SubprocessRun) -> SubprocessRunOutput:
432
- raise NotImplementedError
433
-
434
- def run(
435
- self,
436
- *cmd: str,
437
- input: ta.Any = None, # noqa
438
- timeout: ta.Optional[float] = None,
439
- check: bool = False,
440
- capture_output: ta.Optional[bool] = None,
441
- **kwargs: ta.Any,
442
- ) -> ta.Awaitable[SubprocessRunOutput]:
443
- return self.run_(SubprocessRun(
444
- cmd=cmd,
445
- input=input,
446
- timeout=timeout,
447
- check=check,
448
- capture_output=capture_output,
449
- kwargs=kwargs,
450
- ))
451
-
452
- #
453
-
454
- @abc.abstractmethod
455
- async def check_call(
456
- self,
457
- *cmd: str,
458
- stdout: ta.Any = sys.stderr,
459
- **kwargs: ta.Any,
460
- ) -> None:
461
- raise NotImplementedError
462
-
463
- @abc.abstractmethod
464
- async def check_output(
465
- self,
466
- *cmd: str,
467
- **kwargs: ta.Any,
468
- ) -> bytes:
469
- raise NotImplementedError
470
-
471
- #
472
-
473
- async def check_output_str(
474
- self,
475
- *cmd: str,
476
- **kwargs: ta.Any,
477
- ) -> str:
478
- return (await self.check_output(*cmd, **kwargs)).decode().strip()
479
-
480
- #
481
-
482
- async def try_call(
483
- self,
484
- *cmd: str,
485
- **kwargs: ta.Any,
486
- ) -> bool:
487
- if isinstance(await self.async_try_fn(self.check_call, *cmd, **kwargs), Exception):
488
- return False
489
- else:
490
- return True
491
-
492
- async def try_output(
493
- self,
494
- *cmd: str,
495
- **kwargs: ta.Any,
496
- ) -> ta.Optional[bytes]:
497
- if isinstance(ret := await self.async_try_fn(self.check_output, *cmd, **kwargs), Exception):
498
- return None
499
- else:
500
- return ret
501
-
502
- async def try_output_str(
503
- self,
504
- *cmd: str,
505
- **kwargs: ta.Any,
506
- ) -> ta.Optional[str]:
507
- if (ret := await self.try_output(*cmd, **kwargs)) is None:
508
- return None
509
- else:
510
- return ret.decode().strip()
File without changes