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/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev223'
2
- __revision__ = '383167fabe843d406905492267a5c570dae7cba3'
1
+ __version__ = '0.0.0.dev225'
2
+ __revision__ = '99eb1740a6c8647c20c80278b6d0bedff0f7c103'
3
3
 
4
4
 
5
5
  #
@@ -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
@@ -55,7 +55,6 @@ async def async_list(fn: ta.Callable[..., ta.AsyncIterator[T]], *args, **kwargs)
55
55
 
56
56
 
57
57
  class SyncableIterable(ta.Generic[T]):
58
-
59
58
  def __init__(self, obj) -> None:
60
59
  super().__init__()
61
60
  self._obj = obj
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
@@ -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 EOFError:
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 EOFError # noqa
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
@@ -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 .server import CoroHttpServerSocketHandler
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.INFO
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
@@ -138,6 +138,7 @@ from .generators import ( # noqa
138
138
  from .imports import ( # noqa
139
139
  can_import,
140
140
  import_all,
141
+ import_attr,
141
142
  import_module,
142
143
  import_module_attr,
143
144
  lazy_import,
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
@@ -0,0 +1,8 @@
1
+ from ..logs.timing import LogTimingContext
2
+ from ..logs.timing import log_timing_context
3
+ from .logs import log
4
+
5
+
6
+ LogTimingContext.DEFAULT_LOG = log
7
+
8
+ log_timing_context = log_timing_context # noqa
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
@@ -4,13 +4,6 @@ import typing as _ta
4
4
  from .. import lang as _lang
5
5
 
6
6
 
7
- from .death import ( # noqa
8
- BaseDeathpact,
9
- Deathpact,
10
- NopDeathpact,
11
- PipeDeathpact,
12
- )
13
-
14
7
  from .proxies import ( # noqa
15
8
  DummyValueProxy,
16
9
  ValueProxy,
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