omlish 0.0.0.dev4__py3-none-any.whl → 0.0.0.dev6__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.
Potentially problematic release.
This version of omlish might be problematic. Click here for more details.
- omlish/__about__.py +1 -1
- omlish/__init__.py +1 -1
- omlish/asyncs/__init__.py +10 -4
- omlish/asyncs/anyio.py +142 -12
- omlish/asyncs/asyncio.py +23 -0
- omlish/asyncs/asyncs.py +9 -6
- omlish/asyncs/bridge.py +316 -0
- omlish/asyncs/flavors.py +27 -1
- omlish/asyncs/trio_asyncio.py +28 -18
- omlish/c3.py +1 -1
- omlish/cached.py +1 -2
- omlish/collections/__init__.py +5 -1
- omlish/collections/cache/impl.py +1 -1
- omlish/collections/identity.py +7 -0
- omlish/collections/indexed.py +1 -1
- omlish/collections/utils.py +38 -6
- omlish/configs/__init__.py +5 -0
- omlish/configs/classes.py +53 -0
- omlish/configs/strings.py +94 -0
- omlish/dataclasses/__init__.py +9 -0
- omlish/dataclasses/impl/api.py +1 -1
- omlish/dataclasses/impl/as_.py +1 -1
- omlish/dataclasses/impl/copy.py +30 -0
- omlish/dataclasses/impl/exceptions.py +6 -0
- omlish/dataclasses/impl/fields.py +25 -25
- omlish/dataclasses/impl/init.py +5 -3
- omlish/dataclasses/impl/main.py +3 -0
- omlish/dataclasses/impl/metaclass.py +6 -1
- omlish/dataclasses/impl/order.py +1 -1
- omlish/dataclasses/impl/reflect.py +15 -2
- omlish/dataclasses/utils.py +44 -0
- omlish/defs.py +1 -1
- omlish/diag/__init__.py +4 -0
- omlish/diag/procfs.py +31 -3
- omlish/diag/procstats.py +32 -0
- omlish/{testing → diag}/pydevd.py +35 -0
- omlish/diag/replserver/console.py +3 -3
- omlish/diag/replserver/server.py +6 -5
- omlish/diag/threads.py +86 -0
- omlish/dispatch/_dispatch2.py +65 -0
- omlish/dispatch/_dispatch3.py +104 -0
- omlish/docker.py +20 -1
- omlish/fnpairs.py +37 -18
- omlish/graphs/dags.py +113 -0
- omlish/graphs/domination.py +268 -0
- omlish/graphs/trees.py +2 -2
- omlish/http/__init__.py +25 -0
- omlish/http/asgi.py +132 -0
- omlish/http/collections.py +15 -0
- omlish/http/consts.py +47 -5
- omlish/http/cookies.py +194 -0
- omlish/http/dates.py +70 -0
- omlish/http/encodings.py +6 -0
- omlish/http/json.py +273 -0
- omlish/http/sessions.py +204 -0
- omlish/inject/__init__.py +51 -17
- omlish/inject/binder.py +185 -5
- omlish/inject/bindings.py +3 -36
- omlish/inject/eagers.py +2 -8
- omlish/inject/elements.py +30 -9
- omlish/inject/exceptions.py +3 -3
- omlish/inject/impl/elements.py +65 -31
- omlish/inject/impl/injector.py +20 -2
- omlish/inject/impl/inspect.py +33 -5
- omlish/inject/impl/multis.py +74 -0
- omlish/inject/impl/origins.py +75 -0
- omlish/inject/impl/{private.py → privates.py} +2 -2
- omlish/inject/impl/providers.py +19 -39
- omlish/inject/{proxy.py → impl/proxy.py} +2 -2
- omlish/inject/impl/scopes.py +7 -2
- omlish/inject/injector.py +9 -4
- omlish/inject/inspect.py +18 -0
- omlish/inject/keys.py +11 -23
- omlish/inject/listeners.py +26 -0
- omlish/inject/managed.py +76 -10
- omlish/inject/multis.py +120 -0
- omlish/inject/origins.py +27 -0
- omlish/inject/overrides.py +5 -4
- omlish/inject/{private.py → privates.py} +6 -10
- omlish/inject/providers.py +12 -85
- omlish/inject/scopes.py +20 -9
- omlish/inject/types.py +2 -8
- omlish/iterators.py +13 -0
- omlish/lang/__init__.py +12 -2
- omlish/lang/cached.py +2 -2
- omlish/lang/classes/restrict.py +3 -2
- omlish/lang/classes/simple.py +18 -8
- omlish/lang/classes/virtual.py +2 -2
- omlish/lang/contextmanagers.py +75 -2
- omlish/lang/datetimes.py +6 -5
- omlish/lang/descriptors.py +131 -0
- omlish/lang/functions.py +18 -28
- omlish/lang/imports.py +11 -2
- omlish/lang/iterables.py +20 -1
- omlish/lang/typing.py +6 -0
- omlish/lifecycles/__init__.py +34 -0
- omlish/lifecycles/abstract.py +43 -0
- omlish/lifecycles/base.py +51 -0
- omlish/lifecycles/contextmanagers.py +74 -0
- omlish/lifecycles/controller.py +116 -0
- omlish/lifecycles/manager.py +161 -0
- omlish/lifecycles/states.py +43 -0
- omlish/lifecycles/transitions.py +64 -0
- omlish/logs/formatters.py +1 -1
- omlish/logs/utils.py +1 -1
- omlish/marshal/__init__.py +4 -0
- omlish/marshal/datetimes.py +1 -1
- omlish/marshal/naming.py +4 -0
- omlish/marshal/objects.py +1 -0
- omlish/marshal/polymorphism.py +4 -4
- omlish/reflect.py +139 -18
- omlish/secrets/__init__.py +7 -0
- omlish/secrets/marshal.py +41 -0
- omlish/secrets/passwords.py +120 -0
- omlish/secrets/secrets.py +47 -0
- omlish/serde/__init__.py +0 -0
- omlish/serde/dotenv.py +574 -0
- omlish/{json.py → serde/json.py} +4 -2
- omlish/serde/props.py +604 -0
- omlish/serde/yaml.py +223 -0
- omlish/sql/dbs.py +1 -1
- omlish/sql/duckdb.py +136 -0
- omlish/sql/sqlean.py +17 -0
- omlish/sync.py +70 -0
- omlish/term.py +7 -2
- omlish/testing/pytest/__init__.py +8 -2
- omlish/testing/pytest/helpers.py +0 -24
- omlish/testing/pytest/inject/harness.py +4 -4
- omlish/testing/pytest/marks.py +45 -0
- omlish/testing/pytest/plugins/__init__.py +3 -0
- omlish/testing/pytest/plugins/asyncs.py +136 -0
- omlish/testing/pytest/plugins/managermarks.py +60 -0
- omlish/testing/pytest/plugins/pydevd.py +1 -1
- omlish/testing/testing.py +10 -0
- omlish/text/delimit.py +4 -0
- omlish/text/glyphsplit.py +92 -0
- {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev6.dist-info}/METADATA +1 -1
- omlish-0.0.0.dev6.dist-info/RECORD +240 -0
- {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev6.dist-info}/WHEEL +1 -1
- omlish/configs/props.py +0 -64
- omlish-0.0.0.dev4.dist-info/RECORD +0 -195
- {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev6.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev6.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import collections
|
|
2
|
+
import dataclasses as dc
|
|
3
|
+
import types
|
|
4
|
+
import typing as ta
|
|
5
|
+
|
|
6
|
+
from .. import check
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
T = ta.TypeVar('T')
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def maybe_post_init(sup: ta.Any) -> bool:
|
|
13
|
+
try:
|
|
14
|
+
fn = sup.__post_init__
|
|
15
|
+
except AttributeError:
|
|
16
|
+
return False
|
|
17
|
+
fn()
|
|
18
|
+
return True
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def opt_repr(o: ta.Any) -> str | None:
|
|
22
|
+
return repr(o) if o is not None else None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class field_modifier: # noqa
|
|
26
|
+
def __init__(self, fn: ta.Callable[[dc.Field], dc.Field]) -> None:
|
|
27
|
+
super().__init__()
|
|
28
|
+
self.fn = fn
|
|
29
|
+
|
|
30
|
+
def __ror__(self, other: T) -> T:
|
|
31
|
+
return self(other)
|
|
32
|
+
|
|
33
|
+
def __call__(self, f: T) -> T:
|
|
34
|
+
return check.isinstance(self.fn(check.isinstance(f, dc.Field)), dc.Field) # type: ignore
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def chain_metadata(*mds: ta.Mapping) -> types.MappingProxyType:
|
|
38
|
+
return types.MappingProxyType(collections.ChainMap(*mds)) # type: ignore # noqa
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def update_field_metadata(f: dc.Field, nmd: ta.Mapping) -> dc.Field:
|
|
42
|
+
check.isinstance(f, dc.Field)
|
|
43
|
+
f.metadata = chain_metadata(nmd, f.metadata)
|
|
44
|
+
return f
|
omlish/defs.py
CHANGED
|
@@ -141,7 +141,7 @@ def hash_eq(cls_dct, *attrs):
|
|
|
141
141
|
def __eq__(self, other): # noqa
|
|
142
142
|
if type(other) is not type(self):
|
|
143
143
|
return False
|
|
144
|
-
for attr in attrs:
|
|
144
|
+
for attr in attrs: # noqa
|
|
145
145
|
if getattr(self, attr) != getattr(other, attr):
|
|
146
146
|
return False
|
|
147
147
|
return True
|
omlish/diag/__init__.py
CHANGED
omlish/diag/procfs.py
CHANGED
|
@@ -12,9 +12,10 @@ import sys
|
|
|
12
12
|
import typing as ta
|
|
13
13
|
|
|
14
14
|
from .. import iterators as it
|
|
15
|
-
from .. import json
|
|
16
15
|
from .. import lang
|
|
17
16
|
from .. import os as oos
|
|
17
|
+
from ..serde import json
|
|
18
|
+
from .procstats import ProcStats
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
log = logging.getLogger(__name__)
|
|
@@ -23,6 +24,9 @@ log = logging.getLogger(__name__)
|
|
|
23
24
|
PidLike = int | str
|
|
24
25
|
|
|
25
26
|
|
|
27
|
+
##
|
|
28
|
+
|
|
29
|
+
|
|
26
30
|
RLIMIT_RESOURCES = {
|
|
27
31
|
getattr(resource, k): k
|
|
28
32
|
for k in dir(resource)
|
|
@@ -38,7 +42,7 @@ def parse_size(s: str) -> int:
|
|
|
38
42
|
return int(v) * us[u]
|
|
39
43
|
|
|
40
44
|
|
|
41
|
-
class ProcStat(lang.Namespace):
|
|
45
|
+
class ProcStat(lang.Namespace, lang.Final):
|
|
42
46
|
PID = 0
|
|
43
47
|
COMM = 1
|
|
44
48
|
STATE = 2
|
|
@@ -98,6 +102,9 @@ def _check_linux() -> None:
|
|
|
98
102
|
raise OSError
|
|
99
103
|
|
|
100
104
|
|
|
105
|
+
##
|
|
106
|
+
|
|
107
|
+
|
|
101
108
|
def get_process_stats(pid: PidLike = 'self') -> list[str]:
|
|
102
109
|
"""http://man7.org/linux/man-pages/man5/proc.5.html -> /proc/[pid]/stat"""
|
|
103
110
|
|
|
@@ -109,6 +116,18 @@ def get_process_stats(pid: PidLike = 'self') -> list[str]:
|
|
|
109
116
|
return [pid.strip(), comm, *r.strip().split(' ')]
|
|
110
117
|
|
|
111
118
|
|
|
119
|
+
def get_process_procstats(pid: int | None = None) -> ProcStats:
|
|
120
|
+
st = get_process_stats('self' if pid is None else pid)
|
|
121
|
+
return ProcStats(
|
|
122
|
+
pid=int(st[ProcStat.PID]),
|
|
123
|
+
|
|
124
|
+
rss=int(st[ProcStat.RSS]),
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
##
|
|
129
|
+
|
|
130
|
+
|
|
112
131
|
def get_process_chain(pid: PidLike = 'self') -> list[tuple[int, str]]:
|
|
113
132
|
_check_linux()
|
|
114
133
|
lst = []
|
|
@@ -148,6 +167,9 @@ def set_process_oom_score_adj(score: str, pid: PidLike = 'self') -> None:
|
|
|
148
167
|
f.write(str(score))
|
|
149
168
|
|
|
150
169
|
|
|
170
|
+
##
|
|
171
|
+
|
|
172
|
+
|
|
151
173
|
MAP_LINE_RX = re.compile(
|
|
152
174
|
r'^'
|
|
153
175
|
r'(?P<address>[A-Fa-f0-9]+)-(?P<end_address>[A-Fa-f0-9]+)\s+'
|
|
@@ -156,7 +178,7 @@ MAP_LINE_RX = re.compile(
|
|
|
156
178
|
r'(?P<device>\S+)\s+'
|
|
157
179
|
r'(?P<inode>\d+)\s+'
|
|
158
180
|
r'(?P<path>.*)'
|
|
159
|
-
r'$'
|
|
181
|
+
r'$',
|
|
160
182
|
)
|
|
161
183
|
|
|
162
184
|
|
|
@@ -198,6 +220,9 @@ def get_process_maps(pid: PidLike = 'self', sharing: bool = False) -> ta.Iterato
|
|
|
198
220
|
yield d
|
|
199
221
|
|
|
200
222
|
|
|
223
|
+
##
|
|
224
|
+
|
|
225
|
+
|
|
201
226
|
PAGEMAP_KEYS = (
|
|
202
227
|
'address',
|
|
203
228
|
'pfn',
|
|
@@ -243,6 +268,9 @@ def get_process_pagemaps(pid: PidLike = 'self') -> ta.Iterable[dict[str, int]]:
|
|
|
243
268
|
yield from get_process_range_pagemaps(m['address'], m['end_address'], pid)
|
|
244
269
|
|
|
245
270
|
|
|
271
|
+
##
|
|
272
|
+
|
|
273
|
+
|
|
246
274
|
def _dump_cmd(args: ta.Any) -> None:
|
|
247
275
|
total = 0
|
|
248
276
|
dirty_total = 0
|
omlish/diag/procstats.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import dataclasses as dc
|
|
2
|
+
import os
|
|
3
|
+
import typing as ta
|
|
4
|
+
|
|
5
|
+
from .. import lang
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
if ta.TYPE_CHECKING:
|
|
9
|
+
import psutil as _psutil
|
|
10
|
+
else:
|
|
11
|
+
_psutil = lang.proxy_import('psutil')
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dc.dataclass(frozen=True, kw_only=True)
|
|
15
|
+
class ProcStats:
|
|
16
|
+
pid: int
|
|
17
|
+
|
|
18
|
+
rss: int
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_psutil_procstats(pid: int | None = None) -> ProcStats:
|
|
22
|
+
if pid is None:
|
|
23
|
+
pid = os.getpid()
|
|
24
|
+
|
|
25
|
+
proc = _psutil.Process(pid)
|
|
26
|
+
mi = proc.memory_info()
|
|
27
|
+
|
|
28
|
+
return ProcStats(
|
|
29
|
+
pid=pid,
|
|
30
|
+
|
|
31
|
+
rss=mi.rss,
|
|
32
|
+
)
|
|
@@ -32,6 +32,7 @@ import os
|
|
|
32
32
|
import sys
|
|
33
33
|
import tempfile
|
|
34
34
|
import textwrap
|
|
35
|
+
import threading
|
|
35
36
|
import types
|
|
36
37
|
import typing as ta
|
|
37
38
|
|
|
@@ -243,3 +244,37 @@ def maybe_reexec(
|
|
|
243
244
|
args = [args[0], bootstrap_path]
|
|
244
245
|
|
|
245
246
|
os.execvp(sys.executable, args)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def debug_unhandled_exception(exc_info: ta.Any = None) -> None:
|
|
250
|
+
if exc_info is None:
|
|
251
|
+
exc_info = sys.exc_info()
|
|
252
|
+
|
|
253
|
+
try:
|
|
254
|
+
import pydevd
|
|
255
|
+
from pydevd import pydevd_tracing
|
|
256
|
+
|
|
257
|
+
except ImportError:
|
|
258
|
+
return
|
|
259
|
+
|
|
260
|
+
exctype, value, traceback = exc_info
|
|
261
|
+
frames = []
|
|
262
|
+
while traceback:
|
|
263
|
+
frames.append(traceback.tb_frame)
|
|
264
|
+
traceback = traceback.tb_next
|
|
265
|
+
|
|
266
|
+
thread = threading.current_thread()
|
|
267
|
+
frames_by_id = {id(frame): frame for frame in frames}
|
|
268
|
+
frame = frames[-1]
|
|
269
|
+
exception = (exctype, value, traceback)
|
|
270
|
+
|
|
271
|
+
if hasattr(thread, 'additional_info'):
|
|
272
|
+
thread.additional_info.pydev_message = 'server exception'
|
|
273
|
+
try:
|
|
274
|
+
debugger = pydevd.debugger # noqa
|
|
275
|
+
except AttributeError:
|
|
276
|
+
debugger = pydevd.get_global_debugger() # noqa
|
|
277
|
+
|
|
278
|
+
pydevd_tracing.SetTrace(None)
|
|
279
|
+
|
|
280
|
+
debugger.stop_on_unhandled_exception(thread, frame, frames_by_id, exception)
|
|
@@ -74,7 +74,7 @@ class InteractiveSocketConsole:
|
|
|
74
74
|
CPRT = 'Type "help", "copyright", "credits" or "license" for more information.'
|
|
75
75
|
|
|
76
76
|
def interact(self, banner: str | None = None, exitmsg: str | None = None) -> None:
|
|
77
|
-
log.info(
|
|
77
|
+
log.info('Console %x on thread %r interacting', id(self), threading.current_thread().ident)
|
|
78
78
|
|
|
79
79
|
try:
|
|
80
80
|
ps1 = getattr(sys, 'ps1', '>>> ')
|
|
@@ -115,7 +115,7 @@ class InteractiveSocketConsole:
|
|
|
115
115
|
pass
|
|
116
116
|
|
|
117
117
|
finally:
|
|
118
|
-
log.info(
|
|
118
|
+
log.info('Console %x on thread %r finished', id(self), threading.current_thread().ident)
|
|
119
119
|
|
|
120
120
|
def push_line(self, line: str) -> bool:
|
|
121
121
|
self._buffer.append(line)
|
|
@@ -208,7 +208,7 @@ class InteractiveSocketConsole:
|
|
|
208
208
|
exec(code, self._locals)
|
|
209
209
|
except SystemExit:
|
|
210
210
|
raise
|
|
211
|
-
except Exception:
|
|
211
|
+
except Exception: # noqa
|
|
212
212
|
self.show_traceback()
|
|
213
213
|
else:
|
|
214
214
|
if self._count == self._write_count:
|
omlish/diag/replserver/server.py
CHANGED
|
@@ -84,7 +84,7 @@ class ReplServer:
|
|
|
84
84
|
with contextlib.closing(self._socket):
|
|
85
85
|
self._socket.listen(1)
|
|
86
86
|
|
|
87
|
-
log.info(
|
|
87
|
+
log.info('Repl server listening on file %s', self._config.path)
|
|
88
88
|
|
|
89
89
|
self._is_running = True
|
|
90
90
|
try:
|
|
@@ -94,7 +94,7 @@ class ReplServer:
|
|
|
94
94
|
except TimeoutError:
|
|
95
95
|
continue
|
|
96
96
|
|
|
97
|
-
log.info(
|
|
97
|
+
log.info('Got repl server connection on file %s', self._config.path)
|
|
98
98
|
|
|
99
99
|
def run(conn):
|
|
100
100
|
with contextlib.closing(conn):
|
|
@@ -104,9 +104,10 @@ class ReplServer:
|
|
|
104
104
|
variables['__console__'] = console
|
|
105
105
|
|
|
106
106
|
log.info(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
'Starting console %x repl server connection on file %s on thread %r',
|
|
108
|
+
id(console),
|
|
109
|
+
self._config.path,
|
|
110
|
+
threading.current_thread().ident,
|
|
110
111
|
)
|
|
111
112
|
self._consoles_by_threads[threading.current_thread()] = console
|
|
112
113
|
console.interact()
|
omlish/diag/threads.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import itertools
|
|
3
|
+
import os
|
|
4
|
+
import signal
|
|
5
|
+
import sys
|
|
6
|
+
import threading
|
|
7
|
+
import time
|
|
8
|
+
import traceback
|
|
9
|
+
import typing as ta
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
_DEBUG_THREAD_COUNTER = itertools.count()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def create_thread_dump_thread(
|
|
16
|
+
*,
|
|
17
|
+
interval_s: float = 5.,
|
|
18
|
+
out: ta.TextIO = sys.stderr,
|
|
19
|
+
start: bool = False,
|
|
20
|
+
nodaemon: bool = False,
|
|
21
|
+
) -> threading.Thread:
|
|
22
|
+
def dump():
|
|
23
|
+
cthr = threading.current_thread()
|
|
24
|
+
thrs_by_tid = {t.ident: t for t in threading.enumerate()}
|
|
25
|
+
|
|
26
|
+
buf = io.StringIO()
|
|
27
|
+
for tid, fr in sys._current_frames().items(): # noqa
|
|
28
|
+
if tid == cthr.ident:
|
|
29
|
+
continue
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
thr = thrs_by_tid[tid]
|
|
33
|
+
except KeyError:
|
|
34
|
+
thr_rpr = repr(tid)
|
|
35
|
+
else:
|
|
36
|
+
thr_rpr = repr(thr)
|
|
37
|
+
|
|
38
|
+
tb = traceback.format_stack(fr)
|
|
39
|
+
|
|
40
|
+
buf.write(f'{thr_rpr}\n')
|
|
41
|
+
buf.write('\n'.join(l.strip() for l in tb))
|
|
42
|
+
buf.write('\n\n')
|
|
43
|
+
|
|
44
|
+
out.write(buf.getvalue())
|
|
45
|
+
|
|
46
|
+
def proc():
|
|
47
|
+
while True:
|
|
48
|
+
time.sleep(interval_s)
|
|
49
|
+
try:
|
|
50
|
+
dump()
|
|
51
|
+
except Exception as e: # noqa
|
|
52
|
+
out.write(repr(e) + '\n\n')
|
|
53
|
+
|
|
54
|
+
dthr = threading.Thread(
|
|
55
|
+
target=proc,
|
|
56
|
+
daemon=not nodaemon,
|
|
57
|
+
name=f'thread-dump-thread-{next(_DEBUG_THREAD_COUNTER)}',
|
|
58
|
+
)
|
|
59
|
+
if start:
|
|
60
|
+
dthr.start()
|
|
61
|
+
return dthr
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def create_suicide_thread(
|
|
65
|
+
*,
|
|
66
|
+
sig: int = signal.SIGKILL,
|
|
67
|
+
interval_s: float = 1.,
|
|
68
|
+
parent_thread: threading.Thread | None = None,
|
|
69
|
+
start: bool = False,
|
|
70
|
+
) -> threading.Thread:
|
|
71
|
+
if parent_thread is None:
|
|
72
|
+
parent_thread = threading.current_thread()
|
|
73
|
+
|
|
74
|
+
def proc():
|
|
75
|
+
while True:
|
|
76
|
+
parent_thread.join(interval_s)
|
|
77
|
+
if not parent_thread.is_alive():
|
|
78
|
+
os.kill(os.getpid(), sig)
|
|
79
|
+
|
|
80
|
+
dthr = threading.Thread(
|
|
81
|
+
target=proc,
|
|
82
|
+
name=f'suicide-thread-{next(_DEBUG_THREAD_COUNTER)}',
|
|
83
|
+
)
|
|
84
|
+
if start:
|
|
85
|
+
dthr.start()
|
|
86
|
+
return dthr
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import typing as ta
|
|
3
|
+
import weakref
|
|
4
|
+
|
|
5
|
+
from .dispatch import find_impl
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
T = ta.TypeVar('T')
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
##
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Dispatcher(ta.Generic[T]):
|
|
15
|
+
def __init__(self) -> None:
|
|
16
|
+
super().__init__()
|
|
17
|
+
|
|
18
|
+
self._impls_by_arg_cls: dict[type, T] = {}
|
|
19
|
+
self._dispatch_cache: dict[ta.Any, T | None] = {}
|
|
20
|
+
|
|
21
|
+
def cache_remove(k, self_ref=weakref.ref(self)):
|
|
22
|
+
if (ref_self := self_ref()) is not None:
|
|
23
|
+
cache = ref_self._dispatch_cache # noqa
|
|
24
|
+
try:
|
|
25
|
+
del cache[k]
|
|
26
|
+
except KeyError:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
self._cache_remove = cache_remove
|
|
30
|
+
|
|
31
|
+
self._cache_token: ta.Any = None
|
|
32
|
+
|
|
33
|
+
def cache_size(self) -> int:
|
|
34
|
+
return len(self._dispatch_cache)
|
|
35
|
+
|
|
36
|
+
def register(self, impl: T, cls_col: ta.Iterable[type]) -> T:
|
|
37
|
+
for cls in cls_col:
|
|
38
|
+
self._impls_by_arg_cls[cls] = impl
|
|
39
|
+
|
|
40
|
+
if self._cache_token is None and hasattr(cls, '__abstractmethods__'):
|
|
41
|
+
self._cache_token = abc.get_cache_token()
|
|
42
|
+
|
|
43
|
+
self._dispatch_cache.clear()
|
|
44
|
+
return impl
|
|
45
|
+
|
|
46
|
+
def dispatch(self, cls: type) -> T | None:
|
|
47
|
+
if self._cache_token is not None and (current_token := abc.get_cache_token()) != self._cache_token:
|
|
48
|
+
self._dispatch_cache.clear()
|
|
49
|
+
self._cache_token = current_token
|
|
50
|
+
|
|
51
|
+
cls_ref = weakref.ref(cls)
|
|
52
|
+
try:
|
|
53
|
+
return self._dispatch_cache[cls_ref]
|
|
54
|
+
except KeyError:
|
|
55
|
+
pass
|
|
56
|
+
del cls_ref
|
|
57
|
+
|
|
58
|
+
impl: T | None
|
|
59
|
+
try:
|
|
60
|
+
impl = self._impls_by_arg_cls[cls]
|
|
61
|
+
except KeyError:
|
|
62
|
+
impl = find_impl(cls, self._impls_by_arg_cls)
|
|
63
|
+
|
|
64
|
+
self._dispatch_cache[weakref.ref(cls, self._cache_remove)] = impl
|
|
65
|
+
return impl
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import typing as ta
|
|
3
|
+
import weakref
|
|
4
|
+
|
|
5
|
+
from .dispatch import find_impl
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
T = ta.TypeVar('T')
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
##
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DispatchCacheProtocol(ta.Protocol[T]):
|
|
15
|
+
def size(self) -> int: ...
|
|
16
|
+
def prepare(self, cls: type) -> None: ...
|
|
17
|
+
def clear(self) -> None: ...
|
|
18
|
+
def put(self, cls: type, impl: T) -> None: ...
|
|
19
|
+
def get(self, cls: type) -> T: ... # Raises[KeyError]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class DispatcherProtocol(ta.Protocol[T]):
|
|
23
|
+
def cache_size(self) -> int: ...
|
|
24
|
+
def register(self, impl: T, cls_col: ta.Iterable[type]) -> T: ...
|
|
25
|
+
def dispatch(self, cls: type) -> T | None: ...
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
##
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class DispatchCache(DispatchCacheProtocol[T]):
|
|
32
|
+
def __init__(self) -> None:
|
|
33
|
+
super().__init__()
|
|
34
|
+
|
|
35
|
+
self._dct: dict[ta.Any, T] = {}
|
|
36
|
+
|
|
37
|
+
def remove(k, self_ref=weakref.ref(self)):
|
|
38
|
+
if (ref_self := self_ref()) is not None:
|
|
39
|
+
dct = ref_self._dct # noqa
|
|
40
|
+
try:
|
|
41
|
+
del dct[k]
|
|
42
|
+
except KeyError:
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
self._remove = remove
|
|
46
|
+
|
|
47
|
+
self._token: ta.Any = None
|
|
48
|
+
|
|
49
|
+
def size(self) -> int:
|
|
50
|
+
return len(self._dct)
|
|
51
|
+
|
|
52
|
+
def prepare(self, cls: type) -> None:
|
|
53
|
+
if self._token is None and hasattr(cls, '__abstractmethods__'):
|
|
54
|
+
self._token = abc.get_cache_token()
|
|
55
|
+
|
|
56
|
+
self.clear()
|
|
57
|
+
|
|
58
|
+
def clear(self) -> None:
|
|
59
|
+
if self._dct:
|
|
60
|
+
self._dct.clear()
|
|
61
|
+
|
|
62
|
+
def put(self, cls: type, impl: T) -> None:
|
|
63
|
+
self._dct[weakref.ref(cls, self._remove)] = impl
|
|
64
|
+
|
|
65
|
+
def get(self, cls: type) -> T:
|
|
66
|
+
if self._token is not None and (current_token := abc.get_cache_token()) != self._token:
|
|
67
|
+
self._dct.clear()
|
|
68
|
+
self._token = current_token
|
|
69
|
+
|
|
70
|
+
cls_ref = weakref.ref(cls)
|
|
71
|
+
return self._dct[cls_ref]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class Dispatcher(DispatcherProtocol[T]):
|
|
75
|
+
def __init__(self) -> None:
|
|
76
|
+
super().__init__()
|
|
77
|
+
|
|
78
|
+
self._impls_by_arg_cls: dict[type, T] = {}
|
|
79
|
+
self._cache: DispatchCache[T | None] = DispatchCache()
|
|
80
|
+
|
|
81
|
+
def cache_size(self) -> int:
|
|
82
|
+
return self._cache.size()
|
|
83
|
+
|
|
84
|
+
def register(self, impl: T, cls_col: ta.Iterable[type]) -> T:
|
|
85
|
+
for cls in cls_col:
|
|
86
|
+
self._impls_by_arg_cls[cls] = impl
|
|
87
|
+
self._cache.prepare(cls)
|
|
88
|
+
|
|
89
|
+
return impl
|
|
90
|
+
|
|
91
|
+
def dispatch(self, cls: type) -> T | None:
|
|
92
|
+
try:
|
|
93
|
+
return self._cache.get(cls)
|
|
94
|
+
except KeyError:
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
impl: T | None
|
|
98
|
+
try:
|
|
99
|
+
impl = self._impls_by_arg_cls[cls]
|
|
100
|
+
except KeyError:
|
|
101
|
+
impl = find_impl(cls, self._impls_by_arg_cls)
|
|
102
|
+
|
|
103
|
+
self._cache.put(cls, impl)
|
|
104
|
+
return impl
|
omlish/docker.py
CHANGED
|
@@ -22,9 +22,10 @@ import typing as ta
|
|
|
22
22
|
|
|
23
23
|
from . import check
|
|
24
24
|
from . import dataclasses as dc
|
|
25
|
-
from . import json
|
|
26
25
|
from . import lang
|
|
27
26
|
from . import marshal as msh
|
|
27
|
+
from .serde import json
|
|
28
|
+
|
|
28
29
|
|
|
29
30
|
if ta.TYPE_CHECKING:
|
|
30
31
|
import yaml
|
|
@@ -32,6 +33,9 @@ else:
|
|
|
32
33
|
yaml = lang.proxy_import('yaml')
|
|
33
34
|
|
|
34
35
|
|
|
36
|
+
##
|
|
37
|
+
|
|
38
|
+
|
|
35
39
|
@dc.dataclass(frozen=True)
|
|
36
40
|
class PsItem(lang.Final):
|
|
37
41
|
dc.metadata(msh.ObjectMetadata(
|
|
@@ -113,6 +117,9 @@ def cli_inspect(ids: list[str]) -> list[Inspect]:
|
|
|
113
117
|
return msh.unmarshal(json.loads(o.decode()), list[Inspect])
|
|
114
118
|
|
|
115
119
|
|
|
120
|
+
##
|
|
121
|
+
|
|
122
|
+
|
|
116
123
|
class ComposeConfig:
|
|
117
124
|
def __init__(
|
|
118
125
|
self,
|
|
@@ -141,6 +148,18 @@ class ComposeConfig:
|
|
|
141
148
|
return ret
|
|
142
149
|
|
|
143
150
|
|
|
151
|
+
def get_compose_port(cfg: ta.Mapping[str, ta.Any], default: int) -> int:
|
|
152
|
+
return check.single(
|
|
153
|
+
int(l)
|
|
154
|
+
for p in cfg['ports']
|
|
155
|
+
for l, r in [p.split(':')]
|
|
156
|
+
if int(r) == default
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
##
|
|
161
|
+
|
|
162
|
+
|
|
144
163
|
def timebomb_payload(delay_s: float, name: str = 'omlish-docker-timebomb') -> str:
|
|
145
164
|
return (
|
|
146
165
|
'('
|