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.
- omlish/__about__.py +2 -2
- omlish/asyncs/asyncio/subprocesses.py +15 -15
- omlish/asyncs/asyncs.py +0 -1
- omlish/bootstrap/sys.py +2 -2
- omlish/dataclasses/impl/metaclass.py +5 -0
- omlish/http/coro/server.py +5 -54
- omlish/http/coro/simple.py +1 -1
- omlish/http/coro/sockets.py +59 -0
- omlish/http/handlers.py +52 -1
- omlish/lang/__init__.py +1 -0
- omlish/lang/imports.py +22 -0
- omlish/libc.py +10 -0
- omlish/lite/timing.py +8 -0
- omlish/logs/timing.py +58 -0
- omlish/multiprocessing/__init__.py +0 -7
- omlish/os/pidfiles/__init__.py +0 -0
- omlish/os/pidfiles/manager.py +97 -0
- omlish/os/pidfiles/pidfile.py +142 -0
- omlish/secrets/crypto.py +1 -2
- omlish/secrets/openssl.py +1 -1
- omlish/secrets/tempssl.py +40 -21
- omlish/sockets/handlers.py +4 -0
- omlish/sockets/server/handlers.py +22 -0
- omlish/subprocesses/__init__.py +0 -0
- omlish/subprocesses/async_.py +96 -0
- omlish/subprocesses/base.py +215 -0
- omlish/subprocesses/run.py +98 -0
- omlish/subprocesses/sync.py +147 -0
- omlish/subprocesses/utils.py +22 -0
- omlish/subprocesses/wrap.py +23 -0
- {omlish-0.0.0.dev223.dist-info → omlish-0.0.0.dev225.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev223.dist-info → omlish-0.0.0.dev225.dist-info}/RECORD +37 -26
- omlish/os/pidfile.py +0 -69
- omlish/subprocesses.py +0 -491
- /omlish/{multiprocessing → os}/death.py +0 -0
- {omlish-0.0.0.dev223.dist-info → omlish-0.0.0.dev225.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev223.dist-info → omlish-0.0.0.dev225.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev223.dist-info → omlish-0.0.0.dev225.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev223.dist-info → omlish-0.0.0.dev225.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
@@ -10,9 +10,9 @@ import sys
|
|
10
10
|
import typing as ta
|
11
11
|
|
12
12
|
from ...lite.check import check
|
13
|
-
from ...subprocesses import AbstractAsyncSubprocesses
|
14
|
-
from ...subprocesses import SubprocessRun
|
15
|
-
from ...subprocesses import SubprocessRunOutput
|
13
|
+
from ...subprocesses.async_ import AbstractAsyncSubprocesses
|
14
|
+
from ...subprocesses.run import SubprocessRun
|
15
|
+
from ...subprocesses.run import SubprocessRunOutput
|
16
16
|
from .timeouts import asyncio_maybe_timeout
|
17
17
|
|
18
18
|
|
@@ -157,19 +157,19 @@ class AsyncioSubprocesses(AbstractAsyncSubprocesses):
|
|
157
157
|
timeout: ta.Optional[float] = None,
|
158
158
|
**kwargs: ta.Any,
|
159
159
|
) -> ta.AsyncGenerator[asyncio.subprocess.Process, None]:
|
160
|
-
fac: ta.Any
|
161
|
-
if shell:
|
162
|
-
fac = functools.partial(
|
163
|
-
asyncio.create_subprocess_shell,
|
164
|
-
check.single(cmd),
|
165
|
-
)
|
166
|
-
else:
|
167
|
-
fac = functools.partial(
|
168
|
-
asyncio.create_subprocess_exec,
|
169
|
-
*cmd,
|
170
|
-
)
|
171
|
-
|
172
160
|
with self.prepare_and_wrap( *cmd, shell=shell, **kwargs) as (cmd, kwargs): # noqa
|
161
|
+
fac: ta.Any
|
162
|
+
if shell:
|
163
|
+
fac = functools.partial(
|
164
|
+
asyncio.create_subprocess_shell,
|
165
|
+
check.single(cmd),
|
166
|
+
)
|
167
|
+
else:
|
168
|
+
fac = functools.partial(
|
169
|
+
asyncio.create_subprocess_exec,
|
170
|
+
*cmd,
|
171
|
+
)
|
172
|
+
|
173
173
|
proc: asyncio.subprocess.Process = await fac(**kwargs)
|
174
174
|
try:
|
175
175
|
yield proc
|
omlish/asyncs/asyncs.py
CHANGED
omlish/bootstrap/sys.py
CHANGED
@@ -23,13 +23,13 @@ if ta.TYPE_CHECKING:
|
|
23
23
|
from .. import libc
|
24
24
|
from ..formats import dotenv
|
25
25
|
from ..logs import all as logs
|
26
|
-
from ..os import pidfile
|
26
|
+
from ..os.pidfiles import pidfile
|
27
27
|
|
28
28
|
else:
|
29
29
|
libc = lang.proxy_import('..libc', __package__)
|
30
30
|
logs = lang.proxy_import('..logs', __package__)
|
31
31
|
dotenv = lang.proxy_import('..formats.dotenv', __package__)
|
32
|
-
pidfile = lang.proxy_import('..os.pidfile', __package__)
|
32
|
+
pidfile = lang.proxy_import('..os.pidfiles.pidfile', __package__)
|
33
33
|
|
34
34
|
|
35
35
|
##
|
@@ -188,8 +188,13 @@ class Data(
|
|
188
188
|
metaclass=DataMeta,
|
189
189
|
):
|
190
190
|
def __init__(self, *args, **kwargs):
|
191
|
+
# Typechecking barrier
|
191
192
|
super().__init__(*args, **kwargs)
|
192
193
|
|
194
|
+
def __init_subclass__(cls, **kwargs):
|
195
|
+
# Typechecking barrier
|
196
|
+
super().__init_subclass__(**kwargs)
|
197
|
+
|
193
198
|
def __post_init__(self, *args, **kwargs) -> None:
|
194
199
|
try:
|
195
200
|
spi = super().__post_init__ # type: ignore # noqa
|
omlish/http/coro/server.py
CHANGED
@@ -65,8 +65,6 @@ import typing as ta
|
|
65
65
|
|
66
66
|
from ...lite.check import check
|
67
67
|
from ...sockets.addresses import SocketAddress
|
68
|
-
from ...sockets.handlers import SocketHandler_
|
69
|
-
from ...sockets.io import SocketIoPair
|
70
68
|
from ..handlers import HttpHandler
|
71
69
|
from ..handlers import HttpHandlerRequest
|
72
70
|
from ..handlers import HttpHandlerResponseData
|
@@ -423,6 +421,9 @@ class CoroHttpServer:
|
|
423
421
|
def coro_handle(self) -> ta.Generator[Io, ta.Optional[bytes], None]:
|
424
422
|
return self._coro_run_handler(self._coro_handle_one())
|
425
423
|
|
424
|
+
class Close(Exception): # noqa
|
425
|
+
pass
|
426
|
+
|
426
427
|
def _coro_run_handler(
|
427
428
|
self,
|
428
429
|
gen: ta.Generator[
|
@@ -462,7 +463,7 @@ class CoroHttpServer:
|
|
462
463
|
|
463
464
|
try:
|
464
465
|
o = gen.send(i)
|
465
|
-
except
|
466
|
+
except self.Close:
|
466
467
|
return
|
467
468
|
except StopIteration:
|
468
469
|
break
|
@@ -491,7 +492,7 @@ class CoroHttpServer:
|
|
491
492
|
break
|
492
493
|
|
493
494
|
if isinstance(parsed, EmptyParsedHttpResult):
|
494
|
-
raise
|
495
|
+
raise self.Close
|
495
496
|
|
496
497
|
if isinstance(parsed, ParseHttpRequestError):
|
497
498
|
err = self._build_error(
|
@@ -581,53 +582,3 @@ class CoroHttpServer:
|
|
581
582
|
handler_response.close()
|
582
583
|
|
583
584
|
raise
|
584
|
-
|
585
|
-
|
586
|
-
##
|
587
|
-
|
588
|
-
|
589
|
-
class CoroHttpServerSocketHandler(SocketHandler_):
|
590
|
-
def __init__(
|
591
|
-
self,
|
592
|
-
server_factory: CoroHttpServerFactory,
|
593
|
-
*,
|
594
|
-
log_handler: ta.Optional[ta.Callable[[CoroHttpServer, CoroHttpServer.AnyLogIo], None]] = None,
|
595
|
-
) -> None:
|
596
|
-
super().__init__()
|
597
|
-
|
598
|
-
self._server_factory = server_factory
|
599
|
-
self._log_handler = log_handler
|
600
|
-
|
601
|
-
def __call__(self, client_address: SocketAddress, fp: SocketIoPair) -> None:
|
602
|
-
server = self._server_factory(client_address)
|
603
|
-
|
604
|
-
gen = server.coro_handle()
|
605
|
-
|
606
|
-
o = next(gen)
|
607
|
-
while True:
|
608
|
-
if isinstance(o, CoroHttpServer.AnyLogIo):
|
609
|
-
i = None
|
610
|
-
if self._log_handler is not None:
|
611
|
-
self._log_handler(server, o)
|
612
|
-
|
613
|
-
elif isinstance(o, CoroHttpServer.ReadIo):
|
614
|
-
i = fp.r.read(o.sz)
|
615
|
-
|
616
|
-
elif isinstance(o, CoroHttpServer.ReadLineIo):
|
617
|
-
i = fp.r.readline(o.sz)
|
618
|
-
|
619
|
-
elif isinstance(o, CoroHttpServer.WriteIo):
|
620
|
-
i = None
|
621
|
-
fp.w.write(o.data)
|
622
|
-
fp.w.flush()
|
623
|
-
|
624
|
-
else:
|
625
|
-
raise TypeError(o)
|
626
|
-
|
627
|
-
try:
|
628
|
-
if i is not None:
|
629
|
-
o = gen.send(i)
|
630
|
-
else:
|
631
|
-
o = next(gen)
|
632
|
-
except StopIteration:
|
633
|
-
break
|
omlish/http/coro/simple.py
CHANGED
@@ -21,7 +21,7 @@ from ..parsing import HttpRequestParser
|
|
21
21
|
from ..versions import HttpProtocolVersion
|
22
22
|
from ..versions import HttpProtocolVersions
|
23
23
|
from .server import CoroHttpServer
|
24
|
-
from .
|
24
|
+
from .sockets import CoroHttpServerSocketHandler
|
25
25
|
|
26
26
|
|
27
27
|
if ta.TYPE_CHECKING:
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
import typing as ta
|
4
|
+
|
5
|
+
from ...sockets.addresses import SocketAddress
|
6
|
+
from ...sockets.handlers import SocketHandler_
|
7
|
+
from ...sockets.io import SocketIoPair
|
8
|
+
from .server import CoroHttpServer
|
9
|
+
from .server import CoroHttpServerFactory
|
10
|
+
|
11
|
+
|
12
|
+
##
|
13
|
+
|
14
|
+
|
15
|
+
class CoroHttpServerSocketHandler(SocketHandler_):
|
16
|
+
def __init__(
|
17
|
+
self,
|
18
|
+
server_factory: CoroHttpServerFactory,
|
19
|
+
*,
|
20
|
+
log_handler: ta.Optional[ta.Callable[[CoroHttpServer, CoroHttpServer.AnyLogIo], None]] = None,
|
21
|
+
) -> None:
|
22
|
+
super().__init__()
|
23
|
+
|
24
|
+
self._server_factory = server_factory
|
25
|
+
self._log_handler = log_handler
|
26
|
+
|
27
|
+
def __call__(self, client_address: SocketAddress, fp: SocketIoPair) -> None:
|
28
|
+
server = self._server_factory(client_address)
|
29
|
+
|
30
|
+
gen = server.coro_handle()
|
31
|
+
|
32
|
+
o = next(gen)
|
33
|
+
while True:
|
34
|
+
if isinstance(o, CoroHttpServer.AnyLogIo):
|
35
|
+
i = None
|
36
|
+
if self._log_handler is not None:
|
37
|
+
self._log_handler(server, o)
|
38
|
+
|
39
|
+
elif isinstance(o, CoroHttpServer.ReadIo):
|
40
|
+
i = fp.r.read(o.sz)
|
41
|
+
|
42
|
+
elif isinstance(o, CoroHttpServer.ReadLineIo):
|
43
|
+
i = fp.r.readline(o.sz)
|
44
|
+
|
45
|
+
elif isinstance(o, CoroHttpServer.WriteIo):
|
46
|
+
i = None
|
47
|
+
fp.w.write(o.data)
|
48
|
+
fp.w.flush()
|
49
|
+
|
50
|
+
else:
|
51
|
+
raise TypeError(o)
|
52
|
+
|
53
|
+
try:
|
54
|
+
if i is not None:
|
55
|
+
o = gen.send(i)
|
56
|
+
else:
|
57
|
+
o = next(gen)
|
58
|
+
except StopIteration:
|
59
|
+
break
|
omlish/http/handlers.py
CHANGED
@@ -63,14 +63,65 @@ class HttpHandler_(abc.ABC): # noqa
|
|
63
63
|
raise NotImplementedError
|
64
64
|
|
65
65
|
|
66
|
+
##
|
67
|
+
|
68
|
+
|
66
69
|
@dc.dataclass(frozen=True)
|
67
70
|
class LoggingHttpHandler(HttpHandler_):
|
68
71
|
handler: HttpHandler
|
69
72
|
log: logging.Logger
|
70
|
-
level: int = logging.
|
73
|
+
level: int = logging.DEBUG
|
71
74
|
|
72
75
|
def __call__(self, req: HttpHandlerRequest) -> HttpHandlerResponse:
|
73
76
|
self.log.log(self.level, '%r', req)
|
74
77
|
resp = self.handler(req)
|
75
78
|
self.log.log(self.level, '%r', resp)
|
76
79
|
return resp
|
80
|
+
|
81
|
+
|
82
|
+
##
|
83
|
+
|
84
|
+
|
85
|
+
@dc.dataclass(frozen=True)
|
86
|
+
class BytesResponseHttpHandler(HttpHandler_):
|
87
|
+
data: bytes
|
88
|
+
|
89
|
+
status: ta.Union[http.HTTPStatus, int] = 200
|
90
|
+
content_type: ta.Optional[str] = 'application/octet-stream'
|
91
|
+
headers: ta.Optional[ta.Mapping[str, str]] = None
|
92
|
+
close_connection: bool = True
|
93
|
+
|
94
|
+
def __call__(self, req: HttpHandlerRequest) -> HttpHandlerResponse:
|
95
|
+
return HttpHandlerResponse(
|
96
|
+
status=self.status,
|
97
|
+
headers={
|
98
|
+
**({'Content-Type': self.content_type} if self.content_type else {}),
|
99
|
+
'Content-Length': str(len(self.data)),
|
100
|
+
**(self.headers or {}),
|
101
|
+
},
|
102
|
+
data=self.data,
|
103
|
+
close_connection=self.close_connection,
|
104
|
+
)
|
105
|
+
|
106
|
+
|
107
|
+
@dc.dataclass(frozen=True)
|
108
|
+
class StringResponseHttpHandler(HttpHandler_):
|
109
|
+
data: str
|
110
|
+
|
111
|
+
status: ta.Union[http.HTTPStatus, int] = 200
|
112
|
+
content_type: ta.Optional[str] = 'text/plain; charset=utf-8'
|
113
|
+
headers: ta.Optional[ta.Mapping[str, str]] = None
|
114
|
+
close_connection: bool = True
|
115
|
+
|
116
|
+
def __call__(self, req: HttpHandlerRequest) -> HttpHandlerResponse:
|
117
|
+
data = self.data.encode('utf-8')
|
118
|
+
return HttpHandlerResponse(
|
119
|
+
status=self.status,
|
120
|
+
headers={
|
121
|
+
**({'Content-Type': self.content_type} if self.content_type else {}),
|
122
|
+
'Content-Length': str(len(data)),
|
123
|
+
**(self.headers or {}),
|
124
|
+
},
|
125
|
+
data=data,
|
126
|
+
close_connection=self.close_connection,
|
127
|
+
)
|
omlish/lang/__init__.py
CHANGED
omlish/lang/imports.py
CHANGED
@@ -108,6 +108,28 @@ def import_module_attr(dotted_path: str) -> ta.Any:
|
|
108
108
|
raise AttributeError(f'Module {module_name!r} has no attr {class_name!r}') from None
|
109
109
|
|
110
110
|
|
111
|
+
def import_attr(dotted_path: str) -> ta.Any:
|
112
|
+
parts = dotted_path.split('.')
|
113
|
+
mod: ta.Any = None
|
114
|
+
mod_pos = 0
|
115
|
+
while mod_pos < len(parts):
|
116
|
+
mod_name = '.'.join(parts[:mod_pos + 1])
|
117
|
+
try:
|
118
|
+
mod = importlib.import_module(mod_name)
|
119
|
+
except ImportError:
|
120
|
+
break
|
121
|
+
mod_pos += 1
|
122
|
+
if mod is None:
|
123
|
+
raise ImportError(dotted_path)
|
124
|
+
obj = mod
|
125
|
+
for att_pos in range(mod_pos, len(parts)):
|
126
|
+
obj = getattr(obj, parts[att_pos])
|
127
|
+
return obj
|
128
|
+
|
129
|
+
|
130
|
+
##
|
131
|
+
|
132
|
+
|
111
133
|
SPECIAL_IMPORTABLE: ta.AbstractSet[str] = frozenset([
|
112
134
|
'__init__.py',
|
113
135
|
'__main__.py',
|
omlish/libc.py
CHANGED
@@ -392,6 +392,14 @@ if LINUX or DARWIN:
|
|
392
392
|
return CMSG_ALIGN(ct.sizeof(cmsghdr)) + sz
|
393
393
|
|
394
394
|
def sendfd(sock, fd, data='.'):
|
395
|
+
"""
|
396
|
+
Note: stdlib as of 3.7:
|
397
|
+
|
398
|
+
https://github.com/python/cpython/blob/84ed9a68bd9a13252b376b21a9167dabae254325/Lib/multiprocessing/reduction.py#L141
|
399
|
+
|
400
|
+
But still kept around due to other use of cmsg machinery.
|
401
|
+
""" # noqa
|
402
|
+
|
395
403
|
if not data:
|
396
404
|
raise ValueError(data)
|
397
405
|
|
@@ -424,6 +432,8 @@ if LINUX or DARWIN:
|
|
424
432
|
return libc.sendmsg(sock, msgh, 0)
|
425
433
|
|
426
434
|
def recvfd(sock, buf_len=4096):
|
435
|
+
"""See sendfd."""
|
436
|
+
|
427
437
|
if buf_len < 1:
|
428
438
|
raise ValueError(buf_len)
|
429
439
|
|
omlish/lite/timing.py
ADDED
omlish/logs/timing.py
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
import logging
|
4
|
+
import time
|
5
|
+
import typing as ta
|
6
|
+
|
7
|
+
|
8
|
+
##
|
9
|
+
|
10
|
+
|
11
|
+
class LogTimingContext:
|
12
|
+
DEFAULT_LOG: ta.ClassVar[ta.Optional[logging.Logger]] = None
|
13
|
+
|
14
|
+
class _NOT_SPECIFIED: # noqa
|
15
|
+
def __new__(cls, *args, **kwargs): # noqa
|
16
|
+
raise TypeError
|
17
|
+
|
18
|
+
def __init__(
|
19
|
+
self,
|
20
|
+
description: str,
|
21
|
+
*,
|
22
|
+
log: ta.Union[logging.Logger, ta.Type[_NOT_SPECIFIED], None] = _NOT_SPECIFIED, # noqa
|
23
|
+
level: int = logging.DEBUG,
|
24
|
+
) -> None:
|
25
|
+
super().__init__()
|
26
|
+
|
27
|
+
self._description = description
|
28
|
+
if log is self._NOT_SPECIFIED:
|
29
|
+
log = self.DEFAULT_LOG # noqa
|
30
|
+
self._log: ta.Optional[logging.Logger] = log # type: ignore
|
31
|
+
self._level = level
|
32
|
+
|
33
|
+
def set_description(self, description: str) -> 'LogTimingContext':
|
34
|
+
self._description = description
|
35
|
+
return self
|
36
|
+
|
37
|
+
_begin_time: float
|
38
|
+
_end_time: float
|
39
|
+
|
40
|
+
def __enter__(self) -> 'LogTimingContext':
|
41
|
+
self._begin_time = time.time()
|
42
|
+
|
43
|
+
if self._log is not None:
|
44
|
+
self._log.log(self._level, f'Begin : {self._description}') # noqa
|
45
|
+
|
46
|
+
return self
|
47
|
+
|
48
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
49
|
+
self._end_time = time.time()
|
50
|
+
|
51
|
+
if self._log is not None:
|
52
|
+
self._log.log(
|
53
|
+
self._level,
|
54
|
+
f'End : {self._description} - {self._end_time - self._begin_time:0.2f} s elapsed',
|
55
|
+
)
|
56
|
+
|
57
|
+
|
58
|
+
log_timing_context = LogTimingContext
|
File without changes
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# ruff: noqa: UP007
|
2
|
+
# @omlish-lite
|
3
|
+
import contextlib
|
4
|
+
import os
|
5
|
+
import threading
|
6
|
+
import typing as ta
|
7
|
+
import weakref
|
8
|
+
|
9
|
+
from ...lite.check import check
|
10
|
+
from .pidfile import Pidfile
|
11
|
+
|
12
|
+
|
13
|
+
##
|
14
|
+
|
15
|
+
|
16
|
+
class _PidfileManager:
|
17
|
+
def __new__(cls, *args, **kwargs): # noqa
|
18
|
+
raise TypeError
|
19
|
+
|
20
|
+
_lock: ta.ClassVar[threading.Lock] = threading.Lock()
|
21
|
+
_installed: ta.ClassVar[bool] = False
|
22
|
+
_pidfile_threads: ta.ClassVar[ta.MutableMapping[Pidfile, threading.Thread]] = weakref.WeakKeyDictionary()
|
23
|
+
|
24
|
+
@classmethod
|
25
|
+
def _before_fork(cls) -> None:
|
26
|
+
cls._lock.acquire()
|
27
|
+
|
28
|
+
@classmethod
|
29
|
+
def _after_fork_in_parent(cls) -> None:
|
30
|
+
cls._lock.release()
|
31
|
+
|
32
|
+
@classmethod
|
33
|
+
def _after_fork_in_child(cls) -> None:
|
34
|
+
th = threading.current_thread()
|
35
|
+
for pf, pf_th in list(cls._pidfile_threads.items()):
|
36
|
+
if pf_th is not th:
|
37
|
+
pf.close()
|
38
|
+
del cls._pidfile_threads[pf]
|
39
|
+
|
40
|
+
cls._lock.release()
|
41
|
+
|
42
|
+
#
|
43
|
+
|
44
|
+
@classmethod
|
45
|
+
def _install(cls) -> None:
|
46
|
+
check.state(not cls._installed)
|
47
|
+
|
48
|
+
os.register_at_fork(
|
49
|
+
before=cls._before_fork,
|
50
|
+
after_in_parent=cls._after_fork_in_parent,
|
51
|
+
after_in_child=cls._after_fork_in_child,
|
52
|
+
)
|
53
|
+
|
54
|
+
cls._installed = True
|
55
|
+
|
56
|
+
@classmethod
|
57
|
+
def install(cls) -> bool:
|
58
|
+
with cls._lock:
|
59
|
+
if cls._installed:
|
60
|
+
return False
|
61
|
+
|
62
|
+
cls._install()
|
63
|
+
return True
|
64
|
+
|
65
|
+
@classmethod
|
66
|
+
@contextlib.contextmanager
|
67
|
+
def inheritable_pidfile_context(
|
68
|
+
cls,
|
69
|
+
path: str,
|
70
|
+
*,
|
71
|
+
inheritable: bool = True,
|
72
|
+
**kwargs: ta.Any,
|
73
|
+
) -> ta.Iterator[Pidfile]:
|
74
|
+
check.arg(inheritable)
|
75
|
+
|
76
|
+
cls.install()
|
77
|
+
|
78
|
+
pf = Pidfile(
|
79
|
+
path,
|
80
|
+
inheritable=False,
|
81
|
+
**kwargs,
|
82
|
+
)
|
83
|
+
|
84
|
+
with cls._lock:
|
85
|
+
cls._pidfile_threads[pf] = threading.current_thread()
|
86
|
+
try:
|
87
|
+
with pf:
|
88
|
+
os.set_inheritable(check.not_none(pf.fileno()), True)
|
89
|
+
yield pf
|
90
|
+
|
91
|
+
finally:
|
92
|
+
with cls._lock:
|
93
|
+
del cls._pidfile_threads[pf]
|
94
|
+
|
95
|
+
|
96
|
+
def open_inheritable_pidfile(path: str, **kwargs: ta.Any) -> ta.ContextManager[Pidfile]:
|
97
|
+
return _PidfileManager.inheritable_pidfile_context(path, **kwargs) # noqa
|