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
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