omlish 0.0.0.dev242__py3-none-any.whl → 0.0.0.dev244__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/sockets.py +45 -0
- omlish/daemons/daemon.py +4 -2
- omlish/daemons/launching.py +10 -6
- omlish/daemons/reparent.py +14 -1
- omlish/daemons/services.py +43 -15
- omlish/daemons/spawning.py +56 -16
- omlish/http/coro/simple.py +2 -0
- omlish/lang/__init__.py +6 -3
- omlish/lang/contextmanagers.py +0 -97
- omlish/lite/contextmanagers.py +91 -12
- omlish/logs/protocol.py +14 -14
- omlish/sockets/server/server.py +71 -38
- omlish/sockets/wait.py +61 -0
- {omlish-0.0.0.dev242.dist-info → omlish-0.0.0.dev244.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev242.dist-info → omlish-0.0.0.dev244.dist-info}/RECORD +20 -18
- {omlish-0.0.0.dev242.dist-info → omlish-0.0.0.dev244.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev242.dist-info → omlish-0.0.0.dev244.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev242.dist-info → omlish-0.0.0.dev244.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev242.dist-info → omlish-0.0.0.dev244.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
@@ -0,0 +1,45 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
import asyncio
|
4
|
+
import typing as ta
|
5
|
+
|
6
|
+
from ...lite.timeouts import Timeout
|
7
|
+
from ...lite.timeouts import TimeoutLike
|
8
|
+
|
9
|
+
|
10
|
+
async def asyncio_wait_until_can_connect(
|
11
|
+
host: ta.Any = None,
|
12
|
+
port: ta.Any = None,
|
13
|
+
*,
|
14
|
+
timeout: ta.Optional[TimeoutLike] = None,
|
15
|
+
on_fail: ta.Optional[ta.Callable[[BaseException], None]] = None,
|
16
|
+
sleep_s: float = .1,
|
17
|
+
exception: ta.Union[ta.Type[BaseException], ta.Tuple[ta.Type[BaseException], ...]] = (Exception,),
|
18
|
+
) -> None:
|
19
|
+
timeout = Timeout.of(timeout)
|
20
|
+
|
21
|
+
async def inner():
|
22
|
+
while True:
|
23
|
+
timeout()
|
24
|
+
|
25
|
+
try:
|
26
|
+
reader, writer = await asyncio.open_connection(host, port)
|
27
|
+
|
28
|
+
except asyncio.CancelledError:
|
29
|
+
raise
|
30
|
+
|
31
|
+
except exception as e: # noqa
|
32
|
+
if on_fail is not None:
|
33
|
+
on_fail(e)
|
34
|
+
|
35
|
+
else:
|
36
|
+
writer.close()
|
37
|
+
await asyncio.wait_for(writer.wait_closed(), timeout=timeout.or_(None))
|
38
|
+
break
|
39
|
+
|
40
|
+
await asyncio.sleep(min(sleep_s, timeout.remaining()))
|
41
|
+
|
42
|
+
if timeout() != float('inf'):
|
43
|
+
await asyncio.wait_for(inner(), timeout=timeout())
|
44
|
+
else:
|
45
|
+
await inner()
|
omlish/daemons/daemon.py
CHANGED
@@ -48,7 +48,9 @@ class Daemon:
|
|
48
48
|
|
49
49
|
#
|
50
50
|
|
51
|
+
# TODO: None, defaults, figure out from spawn method
|
51
52
|
reparent_process: bool = False
|
53
|
+
|
52
54
|
launched_timeout_s: float = 5.
|
53
55
|
|
54
56
|
#
|
@@ -126,7 +128,7 @@ class Daemon:
|
|
126
128
|
|
127
129
|
#
|
128
130
|
|
129
|
-
def launch_no_wait(self) ->
|
131
|
+
def launch_no_wait(self) -> bool:
|
130
132
|
launcher = Launcher(
|
131
133
|
target=self._target,
|
132
134
|
spawning=check.not_none(self._config.spawning),
|
@@ -136,7 +138,7 @@ class Daemon:
|
|
136
138
|
launched_timeout_s=self._config.launched_timeout_s,
|
137
139
|
)
|
138
140
|
|
139
|
-
launcher.launch()
|
141
|
+
return launcher.launch()
|
140
142
|
|
141
143
|
def launch(self, timeout: lang.TimeoutLike = lang.Timeout.Default) -> None:
|
142
144
|
self.launch_no_wait()
|
omlish/daemons/launching.py
CHANGED
@@ -53,8 +53,10 @@ class Launcher:
|
|
53
53
|
self,
|
54
54
|
*,
|
55
55
|
pidfile_manager: ta.ContextManager | None,
|
56
|
-
|
56
|
+
callback: ta.Callable[[], None] | None = None,
|
57
57
|
) -> None:
|
58
|
+
callback_called = False
|
59
|
+
|
58
60
|
try:
|
59
61
|
if self._reparent_process:
|
60
62
|
log.info('Reparenting')
|
@@ -66,15 +68,16 @@ class Launcher:
|
|
66
68
|
pidfile = check.isinstance(es.enter_context(pidfile_manager), Pidfile)
|
67
69
|
pidfile.write()
|
68
70
|
|
69
|
-
if
|
70
|
-
|
71
|
+
if callback is not None:
|
72
|
+
callback_called = True
|
73
|
+
callback()
|
71
74
|
|
72
75
|
runner = target_runner_for(self._target)
|
73
76
|
runner.run()
|
74
77
|
|
75
78
|
finally:
|
76
|
-
if
|
77
|
-
|
79
|
+
if callback is not None and not callback_called:
|
80
|
+
callback()
|
78
81
|
|
79
82
|
def launch(self) -> bool:
|
80
83
|
with contextlib.ExitStack() as es:
|
@@ -110,8 +113,9 @@ class Launcher:
|
|
110
113
|
functools.partial(
|
111
114
|
self._inner_launch,
|
112
115
|
pidfile_manager=pidfile_manager,
|
113
|
-
|
116
|
+
callback=launched_event.set if launched_event is not None else None,
|
114
117
|
),
|
118
|
+
target=self._target,
|
115
119
|
inherit_fds=inherit_fds,
|
116
120
|
))
|
117
121
|
|
omlish/daemons/reparent.py
CHANGED
@@ -2,7 +2,10 @@ import os
|
|
2
2
|
import sys
|
3
3
|
|
4
4
|
|
5
|
-
def reparent_process(
|
5
|
+
def reparent_process(
|
6
|
+
*,
|
7
|
+
no_close_stdio: bool = False,
|
8
|
+
) -> None:
|
6
9
|
if (pid := os.fork()): # noqa
|
7
10
|
sys.exit(0)
|
8
11
|
raise RuntimeError('Unreachable') # noqa
|
@@ -12,5 +15,15 @@ def reparent_process() -> None:
|
|
12
15
|
if (pid := os.fork()): # noqa
|
13
16
|
sys.exit(0)
|
14
17
|
|
18
|
+
if not no_close_stdio:
|
19
|
+
rn_fd = os.open('/dev/null', os.O_RDONLY)
|
20
|
+
os.dup2(rn_fd, 0)
|
21
|
+
os.close(rn_fd)
|
22
|
+
|
23
|
+
wn_fd = os.open('/dev/null', os.O_WRONLY)
|
24
|
+
os.dup2(wn_fd, 1)
|
25
|
+
os.dup2(wn_fd, 2)
|
26
|
+
os.close(wn_fd)
|
27
|
+
|
15
28
|
sys.stdout.flush()
|
16
29
|
sys.stderr.flush()
|
omlish/daemons/services.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import abc
|
2
|
+
import threading
|
2
3
|
import typing as ta
|
3
4
|
|
4
5
|
from .. import cached
|
@@ -12,6 +13,7 @@ from .targets import TargetRunner
|
|
12
13
|
from .targets import target_runner_for
|
13
14
|
|
14
15
|
|
16
|
+
ServiceT = ta.TypeVar('ServiceT', bound='Service')
|
15
17
|
ServiceConfigT = ta.TypeVar('ServiceConfigT', bound='Service.Config')
|
16
18
|
|
17
19
|
|
@@ -82,27 +84,53 @@ def _(target: ServiceConfigTarget) -> ServiceConfigTargetRunner:
|
|
82
84
|
|
83
85
|
|
84
86
|
@dc.dataclass(frozen=True)
|
85
|
-
class ServiceDaemon(lang.Final):
|
86
|
-
service:
|
87
|
+
class ServiceDaemon(lang.Final, ta.Generic[ServiceT, ServiceConfigT]):
|
88
|
+
service: ServiceT | ServiceConfigT
|
87
89
|
|
88
90
|
@cached.function
|
89
|
-
def
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
91
|
+
def service_config(self) -> ServiceConfigT:
|
92
|
+
with self._lock:
|
93
|
+
if isinstance(self.service, Service):
|
94
|
+
return self.service.config
|
95
|
+
elif isinstance(self.service, Service.Config):
|
96
|
+
return self.service
|
97
|
+
else:
|
98
|
+
raise TypeError(self.service)
|
99
|
+
|
100
|
+
@cached.function
|
101
|
+
def service_(self) -> ServiceT:
|
102
|
+
with self._lock:
|
103
|
+
if isinstance(self.service, Service):
|
104
|
+
return self.service # type: ignore[return-value]
|
105
|
+
elif isinstance(self.service, Service.Config):
|
106
|
+
return Service.from_config(self.service) # type: ignore[return-value]
|
107
|
+
else:
|
108
|
+
raise TypeError(self.service)
|
96
109
|
|
97
110
|
#
|
98
111
|
|
99
112
|
daemon: Daemon | Daemon.Config = Daemon.Config()
|
100
113
|
|
114
|
+
@cached.function
|
115
|
+
def daemon_config(self) -> Daemon.Config:
|
116
|
+
with self._lock:
|
117
|
+
if isinstance(self.daemon, Daemon):
|
118
|
+
return self.daemon.config
|
119
|
+
elif isinstance(self.daemon, Daemon.Config):
|
120
|
+
return self.daemon
|
121
|
+
else:
|
122
|
+
raise TypeError(self.daemon)
|
123
|
+
|
101
124
|
@cached.function
|
102
125
|
def daemon_(self) -> Daemon:
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
126
|
+
with self._lock:
|
127
|
+
if isinstance(self.daemon, Daemon):
|
128
|
+
return self.daemon
|
129
|
+
elif isinstance(self.daemon, Daemon.Config):
|
130
|
+
return Daemon(Target.of(self.service_()), self.daemon)
|
131
|
+
else:
|
132
|
+
raise TypeError(self.daemon)
|
133
|
+
|
134
|
+
#
|
135
|
+
|
136
|
+
_lock: threading.RLock = dc.field(default_factory=lambda: threading.RLock(), init=False)
|
omlish/daemons/spawning.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import abc
|
2
|
+
import enum
|
2
3
|
import functools
|
3
4
|
import os
|
4
5
|
import sys
|
@@ -9,6 +10,7 @@ from .. import check
|
|
9
10
|
from .. import dataclasses as dc
|
10
11
|
from .. import lang
|
11
12
|
from ..diag import pydevd
|
13
|
+
from .targets import Target
|
12
14
|
|
13
15
|
|
14
16
|
if ta.TYPE_CHECKING:
|
@@ -37,6 +39,8 @@ class Spawn(dc.Frozen, final=True):
|
|
37
39
|
|
38
40
|
_: dc.KW_ONLY
|
39
41
|
|
42
|
+
target: Target | None = None
|
43
|
+
|
40
44
|
inherit_fds: ta.Collection[int] | None = None
|
41
45
|
|
42
46
|
|
@@ -59,10 +63,29 @@ def spawner_for(spawning: Spawning) -> Spawner:
|
|
59
63
|
|
60
64
|
|
61
65
|
class MultiprocessingSpawning(Spawning, kw_only=True):
|
66
|
+
class StartMethod(enum.Enum):
|
67
|
+
SPAWN = enum.auto()
|
68
|
+
FORK = enum.auto()
|
69
|
+
# TODO: FORK_SERVER
|
70
|
+
|
62
71
|
# Defaults to 'fork' if under pydevd, else 'spawn'
|
63
|
-
start_method:
|
72
|
+
start_method: StartMethod | None = None
|
73
|
+
|
74
|
+
#
|
75
|
+
|
76
|
+
# Note: Per multiprocessing docs, `no_linger=True` processes (corresponding to `Process(daemon=True)`) cannot spawn
|
77
|
+
# subprocesses, and thus will fail if `Daemon.Config.reparent_process` is set.
|
78
|
+
no_linger: bool = False
|
64
79
|
|
65
|
-
|
80
|
+
#
|
81
|
+
|
82
|
+
@dc.dataclass(frozen=True, kw_only=True)
|
83
|
+
class EntrypointArgs:
|
84
|
+
spawning: 'MultiprocessingSpawning'
|
85
|
+
spawn: Spawn
|
86
|
+
start_method: 'MultiprocessingSpawning.StartMethod'
|
87
|
+
|
88
|
+
entrypoint: ta.Callable[[EntrypointArgs], None] | None = None
|
66
89
|
|
67
90
|
|
68
91
|
class MultiprocessingSpawner(Spawner):
|
@@ -72,20 +95,26 @@ class MultiprocessingSpawner(Spawner):
|
|
72
95
|
self._spawning = spawning
|
73
96
|
self._process: ta.Optional['mp.process.BaseProcess'] = None # noqa
|
74
97
|
|
98
|
+
@lang.cached_function
|
99
|
+
def _determine_start_method(self) -> 'MultiprocessingSpawning.StartMethod':
|
100
|
+
if (start_method := self._spawning.start_method) is not None:
|
101
|
+
return start_method
|
102
|
+
|
103
|
+
# Unfortunately, pydevd forces the use of the 'fork' start_method, which cannot be mixed with 'spawn':
|
104
|
+
# https://github.com/python/cpython/blob/a7427f2db937adb4c787754deb4c337f1894fe86/Lib/multiprocessing/spawn.py#L102 # noqa
|
105
|
+
if pydevd.is_running():
|
106
|
+
return MultiprocessingSpawning.StartMethod.FORK
|
107
|
+
|
108
|
+
return MultiprocessingSpawning.StartMethod.SPAWN
|
109
|
+
|
75
110
|
def _process_cls(self, spawn: Spawn) -> type['mp.process.BaseProcess']:
|
76
|
-
|
77
|
-
# Unfortunately, pydevd forces the use of the 'fork' start_method, which cannot be mixed with 'spawn':
|
78
|
-
# https://github.com/python/cpython/blob/a7427f2db937adb4c787754deb4c337f1894fe86/Lib/multiprocessing/spawn.py#L102 # noqa
|
79
|
-
if pydevd.is_running():
|
80
|
-
start_method = 'fork'
|
81
|
-
else:
|
82
|
-
start_method = 'spawn'
|
111
|
+
start_method = self._determine_start_method()
|
83
112
|
|
84
113
|
ctx: 'mp.context.BaseContext' # noqa
|
85
|
-
if start_method ==
|
86
|
-
ctx = mp.get_context(check.non_empty_str(
|
114
|
+
if start_method == MultiprocessingSpawning.StartMethod.FORK:
|
115
|
+
ctx = mp.get_context(check.non_empty_str('fork'))
|
87
116
|
|
88
|
-
elif start_method ==
|
117
|
+
elif start_method == MultiprocessingSpawning.StartMethod.SPAWN:
|
89
118
|
ctx = omp_spawn.ExtrasSpawnContext(omp_spawn.SpawnExtras(
|
90
119
|
pass_fds=frozenset(spawn.inherit_fds) if spawn.inherit_fds is not None else None,
|
91
120
|
))
|
@@ -97,9 +126,20 @@ class MultiprocessingSpawner(Spawner):
|
|
97
126
|
|
98
127
|
def spawn(self, spawn: Spawn) -> None:
|
99
128
|
check.none(self._process)
|
129
|
+
|
130
|
+
target: ta.Callable[[], None]
|
131
|
+
if (ep := self._spawning.entrypoint) is not None:
|
132
|
+
target = functools.partial(ep, MultiprocessingSpawning.EntrypointArgs(
|
133
|
+
spawning=self._spawning,
|
134
|
+
spawn=spawn,
|
135
|
+
start_method=self._determine_start_method(),
|
136
|
+
))
|
137
|
+
else:
|
138
|
+
target = spawn.fn
|
139
|
+
|
100
140
|
self._process = self._process_cls(spawn)(
|
101
|
-
target=
|
102
|
-
daemon=
|
141
|
+
target=target,
|
142
|
+
daemon=self._spawning.no_linger,
|
103
143
|
)
|
104
144
|
self._process.start()
|
105
145
|
|
@@ -142,7 +182,7 @@ def _(spawning: ForkSpawning) -> ForkSpawner:
|
|
142
182
|
|
143
183
|
|
144
184
|
class ThreadSpawning(Spawning, kw_only=True):
|
145
|
-
|
185
|
+
linger: bool = False
|
146
186
|
|
147
187
|
|
148
188
|
class ThreadSpawner(InProcessSpawner):
|
@@ -156,7 +196,7 @@ class ThreadSpawner(InProcessSpawner):
|
|
156
196
|
check.none(self._thread)
|
157
197
|
self._thread = threading.Thread(
|
158
198
|
target=spawn.fn,
|
159
|
-
daemon=not self._spawning.
|
199
|
+
daemon=not self._spawning.linger,
|
160
200
|
)
|
161
201
|
self._thread.start()
|
162
202
|
|
omlish/http/coro/simple.py
CHANGED
@@ -43,6 +43,7 @@ def make_simple_http_server(
|
|
43
43
|
ignore_ssl_errors: bool = False,
|
44
44
|
executor: ta.Optional[cf.Executor] = None,
|
45
45
|
use_threads: bool = False,
|
46
|
+
**kwargs: ta.Any,
|
46
47
|
) -> ta.Iterator[SocketServer]:
|
47
48
|
check.arg(not (executor is not None and use_threads))
|
48
49
|
|
@@ -107,6 +108,7 @@ def make_simple_http_server(
|
|
107
108
|
server = es.enter_context(SocketServer(
|
108
109
|
SocketBinder.of(bind),
|
109
110
|
server_handler,
|
111
|
+
**kwargs,
|
110
112
|
))
|
111
113
|
|
112
114
|
yield server
|
omlish/lang/__init__.py
CHANGED
@@ -54,18 +54,15 @@ from .cmp import ( # noqa
|
|
54
54
|
|
55
55
|
from .contextmanagers import ( # noqa
|
56
56
|
AsyncContextManager,
|
57
|
-
AsyncExitStacked,
|
58
57
|
ContextManaged,
|
59
58
|
ContextManager,
|
60
59
|
ContextWrapped,
|
61
60
|
DefaultLockable,
|
62
|
-
ExitStacked,
|
63
61
|
Lockable,
|
64
62
|
NOP_CONTEXT_MANAGER,
|
65
63
|
NopContextManager,
|
66
64
|
Timer,
|
67
65
|
a_defer,
|
68
|
-
attr_setting,
|
69
66
|
breakpoint_on_exception,
|
70
67
|
context_var_setting,
|
71
68
|
context_wrapped,
|
@@ -232,6 +229,12 @@ from .typing import ( # noqa
|
|
232
229
|
|
233
230
|
##
|
234
231
|
|
232
|
+
from ..lite.contextmanagers import ( # noqa
|
233
|
+
attr_setting,
|
234
|
+
AsyncExitStacked,
|
235
|
+
ExitStacked,
|
236
|
+
)
|
237
|
+
|
235
238
|
from ..lite.imports import ( # noqa
|
236
239
|
import_attr,
|
237
240
|
import_module,
|
omlish/lang/contextmanagers.py
CHANGED
@@ -170,103 +170,6 @@ def context_var_setting(var: contextvars.ContextVar[T], val: T) -> ta.Iterator[T
|
|
170
170
|
var.reset(token)
|
171
171
|
|
172
172
|
|
173
|
-
@contextlib.contextmanager
|
174
|
-
def attr_setting(obj, attr, val, *, default=None): # noqa
|
175
|
-
not_set = object()
|
176
|
-
orig = getattr(obj, attr, not_set)
|
177
|
-
try:
|
178
|
-
setattr(obj, attr, val)
|
179
|
-
if orig is not not_set:
|
180
|
-
yield orig
|
181
|
-
else:
|
182
|
-
yield default
|
183
|
-
finally:
|
184
|
-
if orig is not_set:
|
185
|
-
delattr(obj, attr)
|
186
|
-
else:
|
187
|
-
setattr(obj, attr, orig)
|
188
|
-
|
189
|
-
|
190
|
-
##
|
191
|
-
|
192
|
-
|
193
|
-
class ExitStacked:
|
194
|
-
@property
|
195
|
-
def _exit_stack(self) -> contextlib.ExitStack:
|
196
|
-
try:
|
197
|
-
return self.__exit_stack # type: ignore
|
198
|
-
except AttributeError:
|
199
|
-
es = self.__exit_stack = contextlib.ExitStack()
|
200
|
-
return es
|
201
|
-
|
202
|
-
def _enter_context(self, context_manager: ta.ContextManager[T]) -> T:
|
203
|
-
return self._exit_stack.enter_context(ta.cast(ta.ContextManager, context_manager))
|
204
|
-
|
205
|
-
def __enter__(self) -> ta.Self:
|
206
|
-
try:
|
207
|
-
superfn = super().__enter__ # type: ignore
|
208
|
-
except AttributeError:
|
209
|
-
ret = self
|
210
|
-
else:
|
211
|
-
ret = superfn()
|
212
|
-
self._exit_stack.__enter__()
|
213
|
-
return ret
|
214
|
-
|
215
|
-
def __exit__(
|
216
|
-
self,
|
217
|
-
exc_type: type[BaseException] | None,
|
218
|
-
exc_val: BaseException | None,
|
219
|
-
exc_tb: types.TracebackType | None,
|
220
|
-
) -> bool | None:
|
221
|
-
self._exit_stack.__exit__(exc_type, exc_val, exc_tb)
|
222
|
-
try:
|
223
|
-
superfn = super().__exit__ # type: ignore
|
224
|
-
except AttributeError:
|
225
|
-
return None
|
226
|
-
else:
|
227
|
-
return superfn(exc_type, exc_val, exc_tb)
|
228
|
-
|
229
|
-
|
230
|
-
class AsyncExitStacked:
|
231
|
-
@property
|
232
|
-
def _exit_stack(self) -> contextlib.AsyncExitStack:
|
233
|
-
try:
|
234
|
-
return self.__exit_stack # type: ignore
|
235
|
-
except AttributeError:
|
236
|
-
es = self.__exit_stack = contextlib.AsyncExitStack()
|
237
|
-
return es
|
238
|
-
|
239
|
-
async def _enter_async_context(self, context_manager: ta.AsyncContextManager[T]) -> T:
|
240
|
-
return await self._exit_stack.enter_async_context(ta.cast(ta.AsyncContextManager, context_manager))
|
241
|
-
|
242
|
-
def _enter_context(self, context_manager: ta.ContextManager[T]) -> T:
|
243
|
-
return self._exit_stack.enter_context(ta.cast(ta.ContextManager, context_manager))
|
244
|
-
|
245
|
-
async def __aenter__(self) -> ta.Self:
|
246
|
-
try:
|
247
|
-
superfn = super().__aenter__ # type: ignore
|
248
|
-
except AttributeError:
|
249
|
-
ret = self
|
250
|
-
else:
|
251
|
-
ret = await superfn()
|
252
|
-
await self._exit_stack.__aenter__()
|
253
|
-
return ret
|
254
|
-
|
255
|
-
async def __aexit__(
|
256
|
-
self,
|
257
|
-
exc_type: type[BaseException] | None,
|
258
|
-
exc_val: BaseException | None,
|
259
|
-
exc_tb: types.TracebackType | None,
|
260
|
-
) -> bool | None:
|
261
|
-
await self._exit_stack.__aexit__(exc_type, exc_val, exc_tb)
|
262
|
-
try:
|
263
|
-
superfn = super().__aexit__ # type: ignore
|
264
|
-
except AttributeError:
|
265
|
-
return None
|
266
|
-
else:
|
267
|
-
return await superfn(exc_type, exc_val, exc_tb)
|
268
|
-
|
269
|
-
|
270
173
|
##
|
271
174
|
|
272
175
|
|
omlish/lite/contextmanagers.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# ruff: noqa: UP007
|
2
2
|
import contextlib
|
3
|
+
import sys
|
3
4
|
import typing as ta
|
4
5
|
|
5
6
|
from .check import check
|
@@ -14,20 +15,64 @@ AsyncExitStackedT = ta.TypeVar('AsyncExitStackedT', bound='AsyncExitStacked')
|
|
14
15
|
|
15
16
|
|
16
17
|
class ExitStacked:
|
18
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
19
|
+
super().__init_subclass__(**kwargs)
|
20
|
+
|
21
|
+
for a in ('__enter__', '__exit__'):
|
22
|
+
for b in cls.__bases__:
|
23
|
+
if b is ExitStacked:
|
24
|
+
continue
|
25
|
+
try:
|
26
|
+
fn = getattr(b, a)
|
27
|
+
except AttributeError:
|
28
|
+
pass
|
29
|
+
else:
|
30
|
+
if fn is not getattr(ExitStacked, a):
|
31
|
+
raise TypeError(f'ExitStacked subclass {cls} must not not override {a} via {b}')
|
32
|
+
|
17
33
|
_exit_stack: ta.Optional[contextlib.ExitStack] = None
|
18
34
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
35
|
+
@contextlib.contextmanager
|
36
|
+
def _exit_stacked_init_wrapper(self) -> ta.Iterator[None]:
|
37
|
+
"""
|
38
|
+
Overridable wrapper around __enter__ which deliberately does not have access to an _exit_stack yet. Intended for
|
39
|
+
things like wrapping __enter__ in a lock.
|
40
|
+
"""
|
41
|
+
|
42
|
+
yield
|
24
43
|
|
44
|
+
@ta.final
|
45
|
+
def __enter__(self: ExitStackedT) -> ExitStackedT:
|
46
|
+
"""
|
47
|
+
Final because any contexts entered during this init must be exited if any exception is thrown, and user
|
48
|
+
overriding would likely interfere with that. Override `_enter_contexts` for such init.
|
49
|
+
"""
|
50
|
+
|
51
|
+
with self._exit_stacked_init_wrapper():
|
52
|
+
check.state(self._exit_stack is None)
|
53
|
+
es = self._exit_stack = contextlib.ExitStack()
|
54
|
+
es.__enter__()
|
55
|
+
try:
|
56
|
+
self._enter_contexts()
|
57
|
+
except Exception: # noqa
|
58
|
+
es.__exit__(*sys.exc_info())
|
59
|
+
raise
|
60
|
+
return self
|
61
|
+
|
62
|
+
@ta.final
|
25
63
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
26
64
|
if (es := self._exit_stack) is None:
|
27
65
|
return None
|
28
|
-
|
66
|
+
try:
|
67
|
+
self._exit_contexts()
|
68
|
+
except Exception: # noqa
|
69
|
+
es.__exit__(*sys.exc_info())
|
70
|
+
raise
|
29
71
|
return es.__exit__(exc_type, exc_val, exc_tb)
|
30
72
|
|
73
|
+
def _enter_contexts(self) -> None:
|
74
|
+
pass
|
75
|
+
|
31
76
|
def _exit_contexts(self) -> None:
|
32
77
|
pass
|
33
78
|
|
@@ -37,20 +82,54 @@ class ExitStacked:
|
|
37
82
|
|
38
83
|
|
39
84
|
class AsyncExitStacked:
|
85
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
86
|
+
super().__init_subclass__(**kwargs)
|
87
|
+
|
88
|
+
for a in ('__aenter__', '__aexit__'):
|
89
|
+
for b in cls.__bases__:
|
90
|
+
if b is AsyncExitStacked:
|
91
|
+
continue
|
92
|
+
try:
|
93
|
+
fn = getattr(b, a)
|
94
|
+
except AttributeError:
|
95
|
+
pass
|
96
|
+
else:
|
97
|
+
if fn is not getattr(AsyncExitStacked, a):
|
98
|
+
raise TypeError(f'AsyncExitStacked subclass {cls} must not not override {a} via {b}')
|
99
|
+
|
40
100
|
_exit_stack: ta.Optional[contextlib.AsyncExitStack] = None
|
41
101
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
await es.__aenter__()
|
46
|
-
return self
|
102
|
+
@contextlib.asynccontextmanager
|
103
|
+
async def _async_exit_stacked_init_wrapper(self) -> ta.AsyncGenerator[None, None]:
|
104
|
+
yield
|
47
105
|
|
106
|
+
@ta.final
|
107
|
+
async def __aenter__(self: AsyncExitStackedT) -> AsyncExitStackedT:
|
108
|
+
async with self._async_exit_stacked_init_wrapper():
|
109
|
+
check.state(self._exit_stack is None)
|
110
|
+
es = self._exit_stack = contextlib.AsyncExitStack()
|
111
|
+
await es.__aenter__()
|
112
|
+
try:
|
113
|
+
await self._async_enter_contexts()
|
114
|
+
except Exception: # noqa
|
115
|
+
await es.__aexit__(*sys.exc_info())
|
116
|
+
raise
|
117
|
+
return self
|
118
|
+
|
119
|
+
@ta.final
|
48
120
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
49
121
|
if (es := self._exit_stack) is None:
|
50
122
|
return None
|
51
|
-
|
123
|
+
try:
|
124
|
+
await self._async_exit_contexts()
|
125
|
+
except Exception: # noqa
|
126
|
+
await es.__aexit__(*sys.exc_info())
|
127
|
+
raise
|
52
128
|
return await es.__aexit__(exc_type, exc_val, exc_tb)
|
53
129
|
|
130
|
+
async def _async_enter_contexts(self) -> None:
|
131
|
+
pass
|
132
|
+
|
54
133
|
async def _async_exit_contexts(self) -> None:
|
55
134
|
pass
|
56
135
|
|
omlish/logs/protocol.py
CHANGED
@@ -23,25 +23,25 @@ class Logging(ta.Protocol):
|
|
23
23
|
|
24
24
|
#
|
25
25
|
|
26
|
-
def debug(self, msg: str, *args, **kwargs) -> None:
|
26
|
+
def debug(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None:
|
27
27
|
...
|
28
28
|
|
29
|
-
def info(self, msg: str, *args, **kwargs) -> None:
|
29
|
+
def info(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None:
|
30
30
|
...
|
31
31
|
|
32
|
-
def warning(self, msg: str, *args, **kwargs) -> None:
|
32
|
+
def warning(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None:
|
33
33
|
...
|
34
34
|
|
35
|
-
def error(self, msg: str, *args, **kwargs) -> None:
|
35
|
+
def error(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None:
|
36
36
|
...
|
37
37
|
|
38
|
-
def exception(self, msg: str, *args, exc_info=True, **kwargs) -> None:
|
38
|
+
def exception(self, msg: str, *args: ta.Any, exc_info: bool = True, **kwargs) -> None:
|
39
39
|
...
|
40
40
|
|
41
|
-
def critical(self, msg: str, *args, **kwargs) -> None:
|
41
|
+
def critical(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None:
|
42
42
|
...
|
43
43
|
|
44
|
-
def log(self, level: LogLevel, msg: str, *args, **kwargs) -> None:
|
44
|
+
def log(self, level: LogLevel, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None:
|
45
45
|
...
|
46
46
|
|
47
47
|
|
@@ -66,30 +66,30 @@ class AbstractLogging(abc.ABC):
|
|
66
66
|
|
67
67
|
#
|
68
68
|
|
69
|
-
def debug(self, msg: str, *args, **kwargs) -> None:
|
69
|
+
def debug(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None:
|
70
70
|
if self.is_enabled_for(logging.DEBUG):
|
71
71
|
self.log(logging.DEBUG, msg, args, **kwargs)
|
72
72
|
|
73
|
-
def info(self, msg: str, *args, **kwargs) -> None:
|
73
|
+
def info(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None:
|
74
74
|
if self.is_enabled_for(logging.INFO):
|
75
75
|
self.log(logging.INFO, msg, args, **kwargs)
|
76
76
|
|
77
|
-
def warning(self, msg: str, *args, **kwargs) -> None:
|
77
|
+
def warning(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None:
|
78
78
|
if self.is_enabled_for(logging.WARNING):
|
79
79
|
self.log(logging.WARNING, msg, args, **kwargs)
|
80
80
|
|
81
|
-
def error(self, msg: str, *args, **kwargs) -> None:
|
81
|
+
def error(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None:
|
82
82
|
if self.is_enabled_for(logging.ERROR):
|
83
83
|
self.log(logging.ERROR, msg, args, **kwargs)
|
84
84
|
|
85
|
-
def exception(self, msg: str, *args, exc_info=True, **kwargs) -> None:
|
85
|
+
def exception(self, msg: str, *args: ta.Any, exc_info: bool = True, **kwargs: ta.Any) -> None:
|
86
86
|
self.error(msg, *args, exc_info=exc_info, **kwargs)
|
87
87
|
|
88
|
-
def critical(self, msg: str, *args, **kwargs) -> None:
|
88
|
+
def critical(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None:
|
89
89
|
if self.is_enabled_for(logging.CRITICAL):
|
90
90
|
self.log(logging.CRITICAL, msg, args, **kwargs)
|
91
91
|
|
92
|
-
def log(self, level: LogLevel, msg: str, *args, **kwargs) -> None:
|
92
|
+
def log(self, level: LogLevel, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None:
|
93
93
|
if not isinstance(level, int):
|
94
94
|
raise TypeError('Level must be an integer.')
|
95
95
|
if self.is_enabled_for(level):
|
omlish/sockets/server/server.py
CHANGED
@@ -2,11 +2,14 @@
|
|
2
2
|
# ruff: noqa: UP006 UP007
|
3
3
|
import abc
|
4
4
|
import contextlib
|
5
|
+
import enum
|
5
6
|
import logging
|
6
7
|
import selectors
|
7
8
|
import threading
|
8
9
|
import typing as ta
|
9
10
|
|
11
|
+
from ...lite.contextmanagers import ExitStacked
|
12
|
+
from ...lite.contextmanagers import defer
|
10
13
|
from ..addresses import SocketAndAddress
|
11
14
|
from ..bind import SocketBinder
|
12
15
|
from ..io import close_socket_immediately
|
@@ -76,58 +79,88 @@ class SocketServer(abc.ABC):
|
|
76
79
|
|
77
80
|
#
|
78
81
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
82
|
+
class PollResult(enum.Enum):
|
83
|
+
TIMEOUT = enum.auto()
|
84
|
+
CONNECTION = enum.auto()
|
85
|
+
ERROR = enum.auto()
|
86
|
+
SHUTDOWN = enum.auto()
|
84
87
|
|
85
|
-
|
88
|
+
class PollContext(ExitStacked, abc.ABC):
|
89
|
+
@abc.abstractmethod
|
90
|
+
def poll(self, timeout: ta.Optional[float] = None) -> 'SocketServer.PollResult':
|
91
|
+
raise NotImplementedError
|
86
92
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
# polling. Polling reduces our responsiveness to a shutdown request and wastes cpu at all other times.
|
91
|
-
with self.Selector() as selector:
|
92
|
-
selector.register(self._binder.fileno(), selectors.EVENT_READ)
|
93
|
+
class _PollContext(PollContext):
|
94
|
+
def __init__(self, server: 'SocketServer') -> None:
|
95
|
+
super().__init__()
|
93
96
|
|
94
|
-
|
97
|
+
self._server = server
|
95
98
|
|
96
|
-
|
97
|
-
self._is_shutdown.set()
|
99
|
+
_selector: ta.Any = None
|
98
100
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
poll_interval = self._poll_interval
|
101
|
+
def _enter_contexts(self) -> None:
|
102
|
+
self._enter_context(self._server._lock) # noqa: SLF001
|
103
|
+
self._enter_context(self._server._binder) # noqa: SLF001
|
103
104
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
105
|
+
self._server._binder.listen() # noqa: SLF001
|
106
|
+
|
107
|
+
self._server._is_shutdown.clear() # noqa: SLF001
|
108
|
+
self._enter_context(defer(self._server._is_shutdown.set)) # noqa
|
109
|
+
|
110
|
+
# XXX: Consider using another file descriptor or connecting to the socket to wake this up instead of
|
111
|
+
# polling. Polling reduces our responsiveness to a shutdown request and wastes cpu at all other times.
|
112
|
+
self._selector = self._enter_context(self._server.Selector())
|
113
|
+
self._selector.register(self._server._binder.fileno(), selectors.EVENT_READ) # noqa: SLF001
|
108
114
|
|
109
|
-
|
110
|
-
|
111
|
-
|
115
|
+
def poll(self, timeout: ta.Optional[float] = None) -> 'SocketServer.PollResult':
|
116
|
+
if self._server._should_shutdown: # noqa: SLF001
|
117
|
+
return SocketServer.PollResult.SHUTDOWN
|
112
118
|
|
113
|
-
|
114
|
-
try:
|
115
|
-
conn = self._binder.accept()
|
119
|
+
ready = self._selector.select(timeout)
|
116
120
|
|
117
|
-
|
118
|
-
|
121
|
+
# bpo-35017: shutdown() called during select(), exit immediately.
|
122
|
+
if self._server._should_shutdown: # noqa: SLF001
|
123
|
+
return SocketServer.PollResult.SHUTDOWN # type: ignore[unreachable]
|
119
124
|
|
120
|
-
|
125
|
+
if not ready:
|
126
|
+
return SocketServer.PollResult.TIMEOUT
|
121
127
|
|
122
|
-
|
123
|
-
|
128
|
+
try:
|
129
|
+
conn = self._server._binder.accept() # noqa: SLF001
|
130
|
+
|
131
|
+
except OSError as exc:
|
132
|
+
self._server._handle_error(exc) # noqa: SLF001
|
124
133
|
|
125
|
-
|
126
|
-
self._handle_error(exc, conn)
|
134
|
+
return SocketServer.PollResult.ERROR
|
127
135
|
|
128
|
-
|
136
|
+
try:
|
137
|
+
self._server._handler(conn) # noqa: SLF001
|
129
138
|
|
130
|
-
|
139
|
+
except Exception as exc: # noqa
|
140
|
+
self._server._handle_error(exc, conn) # noqa: SLF001
|
141
|
+
|
142
|
+
close_socket_immediately(conn.socket)
|
143
|
+
|
144
|
+
return SocketServer.PollResult.CONNECTION
|
145
|
+
|
146
|
+
def poll_context(self) -> PollContext:
|
147
|
+
return self._PollContext(self)
|
148
|
+
|
149
|
+
#
|
150
|
+
|
151
|
+
@contextlib.contextmanager
|
152
|
+
def loop_context(self, poll_interval: ta.Optional[float] = None) -> ta.Iterator[ta.Iterator[bool]]:
|
153
|
+
if poll_interval is None:
|
154
|
+
poll_interval = self._poll_interval
|
155
|
+
|
156
|
+
with self.poll_context() as pc:
|
157
|
+
def loop():
|
158
|
+
while True:
|
159
|
+
res = pc.poll(poll_interval)
|
160
|
+
if res in (SocketServer.PollResult.ERROR, SocketServer.PollResult.SHUTDOWN):
|
161
|
+
return
|
162
|
+
else:
|
163
|
+
yield res == SocketServer.PollResult.CONNECTION
|
131
164
|
|
132
165
|
yield loop()
|
133
166
|
|
omlish/sockets/wait.py
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
import socket
|
4
|
+
import threading
|
5
|
+
import typing as ta
|
6
|
+
|
7
|
+
from ..lite.timeouts import Timeout
|
8
|
+
from ..lite.timeouts import TimeoutLike
|
9
|
+
|
10
|
+
|
11
|
+
##
|
12
|
+
|
13
|
+
|
14
|
+
def socket_can_connect(
|
15
|
+
address: ta.Any,
|
16
|
+
*,
|
17
|
+
timeout: ta.Optional[TimeoutLike] = None,
|
18
|
+
on_fail: ta.Optional[ta.Callable[[BaseException], None]] = None,
|
19
|
+
exception: ta.Union[ta.Type[BaseException], ta.Tuple[ta.Type[BaseException], ...]] = (ConnectionRefusedError,),
|
20
|
+
) -> bool:
|
21
|
+
timeout = Timeout.of(timeout)
|
22
|
+
|
23
|
+
try:
|
24
|
+
conn = socket.create_connection(address, timeout=timeout.or_(None))
|
25
|
+
|
26
|
+
except exception as e: # noqa
|
27
|
+
if on_fail is not None:
|
28
|
+
on_fail(e)
|
29
|
+
return False
|
30
|
+
|
31
|
+
else:
|
32
|
+
conn.close()
|
33
|
+
return True
|
34
|
+
|
35
|
+
|
36
|
+
def socket_wait_until_can_connect(
|
37
|
+
address: ta.Any,
|
38
|
+
*,
|
39
|
+
timeout: ta.Optional[TimeoutLike] = None,
|
40
|
+
on_fail: ta.Optional[ta.Callable[[BaseException], None]] = None,
|
41
|
+
sleep_s: float = .1,
|
42
|
+
exception: ta.Union[ta.Type[BaseException], ta.Tuple[ta.Type[BaseException], ...]] = (ConnectionRefusedError,),
|
43
|
+
cancel_event: ta.Optional[threading.Event] = None,
|
44
|
+
) -> None:
|
45
|
+
timeout = Timeout.of(timeout)
|
46
|
+
|
47
|
+
if cancel_event is None:
|
48
|
+
cancel_event = threading.Event()
|
49
|
+
|
50
|
+
while not cancel_event.is_set():
|
51
|
+
timeout()
|
52
|
+
|
53
|
+
if socket_can_connect(
|
54
|
+
address,
|
55
|
+
timeout=timeout,
|
56
|
+
on_fail=on_fail,
|
57
|
+
exception=exception,
|
58
|
+
):
|
59
|
+
break
|
60
|
+
|
61
|
+
cancel_event.wait(min(sleep_s, timeout.remaining()))
|
@@ -1,5 +1,5 @@
|
|
1
1
|
omlish/.manifests.json,sha256=vQTAIvR8OblSq-uP2GUfnbei0RnmAnM5j0T1-OToh9E,8253
|
2
|
-
omlish/__about__.py,sha256=
|
2
|
+
omlish/__about__.py,sha256=I6B5uZQiJsqsRd0Vxt8abjKC5YXkiwOJFtBfeWlVYAM,3380
|
3
3
|
omlish/__init__.py,sha256=SsyiITTuK0v74XpKV8dqNaCmjOlan1JZKrHQv5rWKPA,253
|
4
4
|
omlish/c3.py,sha256=ubu7lHwss5V4UznbejAI0qXhXahrU01MysuHOZI9C4U,8116
|
5
5
|
omlish/cached.py,sha256=UI-XTFBwA6YXWJJJeBn-WkwBkfzDjLBBaZf4nIJA9y0,510
|
@@ -101,6 +101,7 @@ omlish/asyncs/asyncio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
|
|
101
101
|
omlish/asyncs/asyncio/all.py,sha256=EksCHjRQKobiGrxuDW72IaH53WJMs7rdj_ZDBI3iKcg,315
|
102
102
|
omlish/asyncs/asyncio/asyncio.py,sha256=mDjYNm1cylUhQ8slWXwdPoXasuWfafjzu78GHt2Mdig,2437
|
103
103
|
omlish/asyncs/asyncio/channels.py,sha256=ZbmsEmdK1fV96liHdcVpRqA2dAMkXJt4Q3rFAg3YOIw,1074
|
104
|
+
omlish/asyncs/asyncio/sockets.py,sha256=Ni5O80fNAccSMAGrlSkZ923Nawxi3FHoXrKj4nr0xlU,1266
|
104
105
|
omlish/asyncs/asyncio/streams.py,sha256=Uc9PCWSfBqrK2kdVtfjjQU1eaYTWYmZm8QISDj2xiuw,1004
|
105
106
|
omlish/asyncs/asyncio/subprocesses.py,sha256=f30-wi-3n9R5dftm4CMrzp23EEa4GX283bORixm1_UU,6931
|
106
107
|
omlish/asyncs/asyncio/timeouts.py,sha256=hokhi7jZSAtBv0ME3qL1cO5eshNA9ViEH7BLafNCBpQ,454
|
@@ -179,11 +180,11 @@ omlish/configs/processing/names.py,sha256=weHmaTclzgM9lUn3aBtw-kwZ3mc2N-CZlFg3Kd
|
|
179
180
|
omlish/configs/processing/rewriting.py,sha256=v7PfHtuTn5v_5Y6Au7oMN2Z0nxAMy1iYyO5CXnTvZhs,4226
|
180
181
|
omlish/configs/processing/strings.py,sha256=qFS2oh6z02IaM_q4lTKLdufzkJqAJ6J-Qjrz5S-QJoM,826
|
181
182
|
omlish/daemons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
182
|
-
omlish/daemons/daemon.py,sha256=
|
183
|
-
omlish/daemons/launching.py,sha256=
|
184
|
-
omlish/daemons/reparent.py,sha256=
|
185
|
-
omlish/daemons/services.py,sha256=
|
186
|
-
omlish/daemons/spawning.py,sha256=
|
183
|
+
omlish/daemons/daemon.py,sha256=3Wkvu8M_EaCKSpKI5UN5OayRXV0oVdF62tBss9_hlr0,3479
|
184
|
+
omlish/daemons/launching.py,sha256=sNOYW939IGI4ZlLQ0bKxzXj6EyeOiwV7Upqhd5XfoHc,3747
|
185
|
+
omlish/daemons/reparent.py,sha256=7uJ9oPGt9Ud7uA8bDl_SHcuqjcsmXa3kkjp9jf29wOw,585
|
186
|
+
omlish/daemons/services.py,sha256=jIlGWhiWoqQlm_OFeffkSs9jjr-icDF1-I-SNMEgg9Y,3406
|
187
|
+
omlish/daemons/spawning.py,sha256=psR73zOYjMKTqNpx1bMib8uU9wAZz62tw5TaWHrTdyY,5337
|
187
188
|
omlish/daemons/targets.py,sha256=00KmtlknMhQ5PyyVAhWl3rpeTMPym0GxvHHq6mYPZ7c,3051
|
188
189
|
omlish/daemons/waiting.py,sha256=RfgD1L33QQVbD2431dkKZGE4w6DUcGvYeRXXi8puAP4,1676
|
189
190
|
omlish/dataclasses/__init__.py,sha256=b7EZCIfHnEHCHWwgD3YXxkdsU-uYd9iD4hM36RgpI1g,1598
|
@@ -332,7 +333,7 @@ omlish/http/wsgi.py,sha256=czZsVUX-l2YTlMrUjKN49wRoP4rVpS0qpeBn4O5BoMY,948
|
|
332
333
|
omlish/http/coro/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
333
334
|
omlish/http/coro/fdio.py,sha256=bd9K4EYVWbXV3e3npDPXI9DuDAruJiyDmrgFpgNcjzY,4035
|
334
335
|
omlish/http/coro/server.py,sha256=30FTcJG8kuFeThf0HJYpTzMZN-giLTBP7wr5Wl3b9X0,18285
|
335
|
-
omlish/http/coro/simple.py,sha256=
|
336
|
+
omlish/http/coro/simple.py,sha256=inWA_ss6Nz5Rqmy4dL9_SGah4anYoDecDTRQqVIGYeY,3200
|
336
337
|
omlish/http/coro/sockets.py,sha256=rtpZZ-XCOfC5tXr4Fmo1HSn-8f5nxfIOlJaPUkQeDyU,1654
|
337
338
|
omlish/inject/__init__.py,sha256=n0RC9UDGsBQQ39cST39-XJqJPq2M0tnnh9yJubW9azo,1891
|
338
339
|
omlish/inject/binder.py,sha256=DAbc8TZi5w8Mna0TUtq0mT4jeDVA7i7SlBtOFrh2swc,4185
|
@@ -398,11 +399,11 @@ omlish/iterators/iterators.py,sha256=ghI4dO6WPyyFOLTIIMaHQ_IOy2xXaFpGPqveZ5YGIBU
|
|
398
399
|
omlish/iterators/recipes.py,sha256=53mkexitMhkwXQZbL6DrhpT0WePQ_56uXd5Jaw3DfzI,467
|
399
400
|
omlish/iterators/tools.py,sha256=Pi4ybXytUXVZ3xwK89xpPImQfYYId9p1vIFQvVqVLqA,2551
|
400
401
|
omlish/iterators/unique.py,sha256=0jAX3kwzVfRNhe0Tmh7kVP_Q2WBIn8POo_O-rgFV0rQ,1390
|
401
|
-
omlish/lang/__init__.py,sha256=
|
402
|
+
omlish/lang/__init__.py,sha256=T_hx_ygi-IqmeDPQ-uto2U4ZuAUDs-agOSAsF6WWLVI,4193
|
402
403
|
omlish/lang/cached.py,sha256=tQaqMu1LID0q4NSTk5vPXsgxIBWSFAmjs5AhQoEHoCQ,7833
|
403
404
|
omlish/lang/clsdct.py,sha256=sJYadm-fwzti-gsi98knR5qQUxriBmOqQE_qz3RopNk,1743
|
404
405
|
omlish/lang/cmp.py,sha256=5vbzWWbqdzDmNKAGL19z6ZfUKe5Ci49e-Oegf9f4BsE,1346
|
405
|
-
omlish/lang/contextmanagers.py,sha256=
|
406
|
+
omlish/lang/contextmanagers.py,sha256=UPH6daYwSP9cH5AfSVsJyEHk1UURMGhVPM5ZRhp_Hvw,7576
|
406
407
|
omlish/lang/datetimes.py,sha256=ehI_DhQRM-bDxAavnp470XcekbbXc4Gdw9y1KpHDJT0,223
|
407
408
|
omlish/lang/descriptors.py,sha256=mZ2h9zJ__MMpw8hByjRbAiONcwfVb6GD0btNnVi8C5w,6573
|
408
409
|
omlish/lang/exceptions.py,sha256=qJBo3NU1mOWWm-NhQUHCY5feYXR3arZVyEHinLsmRH4,47
|
@@ -434,7 +435,7 @@ omlish/lite/__init__.py,sha256=ISLhM4q0LR1XXTCaHdZOZxBRyIsoZqYm4u0bf1BPcVk,148
|
|
434
435
|
omlish/lite/cached.py,sha256=O7ozcoDNFm1Hg2wtpHEqYSp_i_nCLNOP6Ueq_Uk-7mU,1300
|
435
436
|
omlish/lite/check.py,sha256=OLwtE2x6nlbGx4vS3Rda7zMHpgqzDSLJminTAX2lqLA,13529
|
436
437
|
omlish/lite/configs.py,sha256=Ev_19sbII67pTWzInYjYqa9VyTiZBvyjhZqyG8TtufE,908
|
437
|
-
omlish/lite/contextmanagers.py,sha256=
|
438
|
+
omlish/lite/contextmanagers.py,sha256=XSCwr9GpPBJxXR9Vr07M4A_BH3uLpZettyoSE5KqJu8,5566
|
438
439
|
omlish/lite/dataclasses.py,sha256=t1G5-xOuvE6o6w9RyqHzLT9wHD0HkqBh5P8HUZWxGzs,1912
|
439
440
|
omlish/lite/imports.py,sha256=o9WWrNrWg0hKeMvaj91giaovED_9VFanN2MyEHBGekY,1346
|
440
441
|
omlish/lite/inject.py,sha256=-tTsOqqef-Ix5Tgl2DP_JAsNWJQDFUptERl3lk14Uzs,29007
|
@@ -462,7 +463,7 @@ omlish/logs/filters.py,sha256=2noFRyBez3y519fpfsDSt1vo8wX-85b8sMXZi5o_xyE,208
|
|
462
463
|
omlish/logs/handlers.py,sha256=zgSnKQA5q9Fu7T0Nkd7twog9H1Wg9-bDCzz4_F1TOBo,319
|
463
464
|
omlish/logs/json.py,sha256=zyqMWpZY3lk4WRk4wgmataBomGX9S3iDsydiM1sS-lI,1366
|
464
465
|
omlish/logs/noisy.py,sha256=Ubc-eTH6ZbGYsLfUUi69JAotwuUwzb-SJBeGo_0dIZI,348
|
465
|
-
omlish/logs/protocol.py,sha256=
|
466
|
+
omlish/logs/protocol.py,sha256=dfAR0_5kLEAkx0nhuWBhWMTVjWjhEl2uL-MxejrW1lk,4732
|
466
467
|
omlish/logs/proxy.py,sha256=A-ROPUUAlF397qTbEqhel6YhQMstNuXL3Xmts7w9dAo,2347
|
467
468
|
omlish/logs/standard.py,sha256=FbKdF2Z4Na5i2TNwKn0avLJXyICe2JKsPufjvKCHGn0,3162
|
468
469
|
omlish/logs/timing.py,sha256=XrFUHIPT4EHDujLKbGs9fGFMmoM3NEP8xPRaESJr7bQ,1513
|
@@ -565,9 +566,10 @@ omlish/sockets/bind.py,sha256=J1SfFFFnVf3H5nqESDX2NGEY8DmjyIMUXZciZM33zQY,8003
|
|
565
566
|
omlish/sockets/handlers.py,sha256=Gj6xZoo4vommge8XvkehYw3B7O4aql2P4qzZIIa0p24,462
|
566
567
|
omlish/sockets/io.py,sha256=lfhTkB7NnAIx9kuQhAkwgsEUXY78Mp1_WtYrIQNS_k8,1408
|
567
568
|
omlish/sockets/ports.py,sha256=Wm4mRFFz5MdD8KbdaEfT1c4PbJnsuK_iyJlZJE_-8jo,1402
|
569
|
+
omlish/sockets/wait.py,sha256=aznyOzGa9oNBc31xnyk1S7TylO8hGx7vFyOVsdY3zFE,1585
|
568
570
|
omlish/sockets/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
569
571
|
omlish/sockets/server/handlers.py,sha256=PPsb1X5oU9dN8jfztaMGsRiqWTyEANT-1aSLbS6bUVg,3867
|
570
|
-
omlish/sockets/server/server.py,sha256=
|
572
|
+
omlish/sockets/server/server.py,sha256=FkaishIxJuU4it9tTI7wzlGqJYzFGXzDrd_HgV0jAmU,6253
|
571
573
|
omlish/sockets/server/ssl.py,sha256=VE0GpdA-gYsN2m9_uvfDwWmXtIbRQqJomVdpGJO8o2M,1061
|
572
574
|
omlish/sockets/server/threading.py,sha256=YmW3Ym_p5j_F4SIH9BgRHIObywjq1HS39j9CGWIcMAY,2856
|
573
575
|
omlish/specs/__init__.py,sha256=zZwF8yXTEkSstYtORkDhVLK-_hWU8WOJCuBpognb_NY,118
|
@@ -727,9 +729,9 @@ omlish/text/mangle.py,sha256=kfzFLfvepH-chl1P89_mdc5vC4FSqyPA2aVtgzuB8IY,1133
|
|
727
729
|
omlish/text/minja.py,sha256=jZC-fp3Xuhx48ppqsf2Sf1pHbC0t8XBB7UpUUoOk2Qw,5751
|
728
730
|
omlish/text/parts.py,sha256=JkNZpyR2tv2CNcTaWJJhpQ9E4F0yPR8P_YfDbZfMtwQ,6182
|
729
731
|
omlish/text/random.py,sha256=jNWpqiaKjKyTdMXC-pWAsSC10AAP-cmRRPVhm59ZWLk,194
|
730
|
-
omlish-0.0.0.
|
731
|
-
omlish-0.0.0.
|
732
|
-
omlish-0.0.0.
|
733
|
-
omlish-0.0.0.
|
734
|
-
omlish-0.0.0.
|
735
|
-
omlish-0.0.0.
|
732
|
+
omlish-0.0.0.dev244.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
|
733
|
+
omlish-0.0.0.dev244.dist-info/METADATA,sha256=7T41OvUEW2aOLkXNGyFicYfwqbooZHctPFTfjF0XpnQ,4176
|
734
|
+
omlish-0.0.0.dev244.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
|
735
|
+
omlish-0.0.0.dev244.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
|
736
|
+
omlish-0.0.0.dev244.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
|
737
|
+
omlish-0.0.0.dev244.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|