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