omlish 0.0.0.dev5__py3-none-any.whl → 0.0.0.dev7__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 +109 -5
- omlish/__init__.py +0 -8
- omlish/asyncs/__init__.py +9 -9
- omlish/asyncs/anyio.py +123 -19
- omlish/asyncs/asyncio.py +23 -0
- omlish/asyncs/asyncs.py +9 -6
- omlish/asyncs/bridge.py +316 -0
- omlish/asyncs/trio_asyncio.py +7 -3
- omlish/bootstrap.py +737 -0
- omlish/check.py +1 -1
- omlish/collections/__init__.py +5 -0
- omlish/collections/exceptions.py +2 -0
- omlish/collections/identity.py +7 -0
- omlish/collections/utils.py +38 -9
- omlish/configs/strings.py +96 -0
- omlish/dataclasses/__init__.py +16 -0
- omlish/dataclasses/impl/copy.py +30 -0
- omlish/dataclasses/impl/descriptors.py +95 -0
- omlish/dataclasses/impl/exceptions.py +6 -0
- omlish/dataclasses/impl/fields.py +24 -25
- omlish/dataclasses/impl/init.py +4 -2
- omlish/dataclasses/impl/main.py +2 -0
- omlish/dataclasses/impl/reflect.py +1 -1
- omlish/dataclasses/utils.py +67 -0
- omlish/{lang/datetimes.py → datetimes.py} +8 -4
- omlish/diag/__init__.py +4 -0
- omlish/diag/procfs.py +2 -2
- omlish/{testing → diag}/pydevd.py +35 -0
- omlish/diag/threads.py +131 -48
- omlish/dispatch/_dispatch2.py +65 -0
- omlish/dispatch/_dispatch3.py +104 -0
- omlish/docker.py +16 -1
- omlish/fnpairs.py +11 -4
- omlish/formats/__init__.py +0 -0
- omlish/{configs → formats}/dotenv.py +15 -24
- omlish/{json.py → formats/json.py} +2 -1
- omlish/formats/yaml.py +223 -0
- omlish/graphs/trees.py +1 -1
- omlish/http/asgi.py +2 -1
- omlish/http/collections.py +15 -0
- omlish/http/consts.py +22 -1
- omlish/http/sessions.py +10 -3
- omlish/inject/__init__.py +49 -17
- omlish/inject/binder.py +185 -5
- omlish/inject/bindings.py +3 -36
- omlish/inject/eagers.py +2 -8
- omlish/inject/elements.py +31 -10
- omlish/inject/exceptions.py +1 -1
- omlish/inject/impl/elements.py +37 -12
- omlish/inject/impl/injector.py +72 -25
- omlish/inject/impl/inspect.py +33 -5
- omlish/inject/impl/origins.py +77 -0
- omlish/inject/impl/{private.py → privates.py} +2 -2
- omlish/inject/impl/scopes.py +6 -2
- omlish/inject/injector.py +8 -4
- omlish/inject/inspect.py +18 -0
- omlish/inject/keys.py +8 -14
- omlish/inject/listeners.py +26 -0
- omlish/inject/managed.py +76 -10
- omlish/inject/multis.py +68 -18
- omlish/inject/origins.py +30 -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 +13 -6
- omlish/inject/types.py +3 -1
- omlish/inject/utils.py +18 -0
- omlish/iterators.py +69 -2
- omlish/lang/__init__.py +24 -9
- omlish/lang/cached.py +2 -2
- omlish/lang/classes/restrict.py +12 -1
- omlish/lang/classes/simple.py +18 -8
- omlish/lang/contextmanagers.py +13 -4
- omlish/lang/descriptors.py +132 -1
- omlish/lang/functions.py +8 -28
- omlish/lang/imports.py +67 -0
- omlish/lang/iterables.py +60 -1
- omlish/lang/maybes.py +3 -0
- omlish/lang/objects.py +38 -0
- omlish/lang/strings.py +25 -0
- omlish/lang/sys.py +9 -0
- omlish/lang/typing.py +42 -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/lite/__init__.py +1 -0
- omlish/lite/cached.py +18 -0
- omlish/lite/check.py +29 -0
- omlish/lite/contextmanagers.py +18 -0
- omlish/lite/json.py +30 -0
- omlish/lite/logs.py +52 -0
- omlish/lite/marshal.py +316 -0
- omlish/lite/reflect.py +49 -0
- omlish/lite/runtime.py +18 -0
- omlish/lite/secrets.py +19 -0
- omlish/lite/strings.py +25 -0
- omlish/lite/subprocesses.py +112 -0
- omlish/logs/configs.py +15 -2
- omlish/logs/formatters.py +7 -2
- omlish/marshal/__init__.py +32 -0
- omlish/marshal/any.py +5 -5
- omlish/marshal/base.py +27 -11
- omlish/marshal/base64.py +24 -9
- omlish/marshal/dataclasses.py +34 -28
- omlish/marshal/datetimes.py +74 -18
- omlish/marshal/enums.py +14 -8
- omlish/marshal/exceptions.py +11 -1
- omlish/marshal/factories.py +59 -74
- omlish/marshal/forbidden.py +35 -0
- omlish/marshal/global_.py +11 -4
- omlish/marshal/iterables.py +21 -24
- omlish/marshal/mappings.py +23 -26
- omlish/marshal/naming.py +4 -0
- omlish/marshal/numbers.py +51 -0
- omlish/marshal/objects.py +1 -0
- omlish/marshal/optionals.py +11 -12
- omlish/marshal/polymorphism.py +86 -21
- omlish/marshal/primitives.py +4 -5
- omlish/marshal/standard.py +13 -8
- omlish/marshal/uuids.py +4 -5
- omlish/matchfns.py +218 -0
- omlish/os.py +64 -0
- omlish/reflect/__init__.py +39 -0
- omlish/reflect/isinstance.py +38 -0
- omlish/reflect/ops.py +84 -0
- omlish/reflect/subst.py +110 -0
- omlish/reflect/types.py +275 -0
- omlish/secrets/__init__.py +23 -0
- omlish/secrets/crypto.py +132 -0
- omlish/secrets/marshal.py +70 -0
- omlish/secrets/openssl.py +207 -0
- omlish/secrets/passwords.py +120 -0
- omlish/secrets/secrets.py +299 -0
- omlish/secrets/subprocesses.py +42 -0
- omlish/sql/dbs.py +7 -6
- omlish/sql/duckdb.py +136 -0
- omlish/sql/exprs.py +12 -0
- omlish/sql/secrets.py +10 -0
- omlish/sql/sqlean.py +17 -0
- omlish/term.py +2 -2
- omlish/testing/pytest/__init__.py +3 -2
- omlish/testing/pytest/inject/harness.py +3 -3
- omlish/testing/pytest/marks.py +4 -7
- omlish/testing/pytest/plugins/__init__.py +1 -0
- omlish/testing/pytest/plugins/asyncs.py +136 -0
- omlish/testing/pytest/plugins/pydevd.py +1 -1
- omlish/testing/pytest/plugins/switches.py +54 -19
- omlish/text/glyphsplit.py +97 -0
- omlish-0.0.0.dev7.dist-info/METADATA +50 -0
- omlish-0.0.0.dev7.dist-info/RECORD +268 -0
- {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev7.dist-info}/WHEEL +1 -1
- omlish/reflect.py +0 -355
- omlish-0.0.0.dev5.dist-info/METADATA +0 -34
- omlish-0.0.0.dev5.dist-info/RECORD +0 -212
- /omlish/{asyncs/futures.py → concurrent.py} +0 -0
- /omlish/{configs → formats}/props.py +0 -0
- {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev7.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev7.dist-info}/top_level.txt +0 -0
|
@@ -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)
|
omlish/diag/threads.py
CHANGED
|
@@ -9,6 +9,90 @@ import traceback
|
|
|
9
9
|
import typing as ta
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
##
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def dump_threads(out: ta.IO) -> None:
|
|
16
|
+
out.write('\n\n')
|
|
17
|
+
|
|
18
|
+
thrs_by_tid = {t.ident: t for t in threading.enumerate()}
|
|
19
|
+
|
|
20
|
+
for tid, fr in sys._current_frames().items(): # noqa
|
|
21
|
+
try:
|
|
22
|
+
thr = thrs_by_tid[tid]
|
|
23
|
+
except KeyError:
|
|
24
|
+
thr_rpr = repr(tid)
|
|
25
|
+
else:
|
|
26
|
+
thr_rpr = repr(thr)
|
|
27
|
+
|
|
28
|
+
tb = traceback.format_stack(fr)
|
|
29
|
+
|
|
30
|
+
out.write(f'{thr_rpr}\n')
|
|
31
|
+
out.write('\n'.join(l.strip() for l in tb))
|
|
32
|
+
out.write('\n\n')
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def dump_threads_str() -> str:
|
|
36
|
+
out = io.StringIO()
|
|
37
|
+
dump_threads(out)
|
|
38
|
+
return out.getvalue()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
##
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class StoppableThread:
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
fn: ta.Callable[[], None],
|
|
48
|
+
interval_s: float,
|
|
49
|
+
*,
|
|
50
|
+
tick_immediately: bool = False,
|
|
51
|
+
start: bool = False,
|
|
52
|
+
**kwargs: ta.Any,
|
|
53
|
+
) -> None:
|
|
54
|
+
super().__init__()
|
|
55
|
+
self._fn = fn
|
|
56
|
+
self._interval_s = interval_s
|
|
57
|
+
self._tick_immediately = tick_immediately
|
|
58
|
+
self._thread = threading.Thread(target=self._loop, **kwargs)
|
|
59
|
+
self._stop_event = threading.Event()
|
|
60
|
+
if start:
|
|
61
|
+
self.start()
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def thread(self) -> threading.Thread:
|
|
65
|
+
return self._thread
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def ident(self) -> int | None:
|
|
69
|
+
return self._thread.ident
|
|
70
|
+
|
|
71
|
+
def start(self) -> None:
|
|
72
|
+
return self._thread.start()
|
|
73
|
+
|
|
74
|
+
def stop_nowait(self) -> None:
|
|
75
|
+
self._stop_event.set()
|
|
76
|
+
|
|
77
|
+
def stop_wait(self, timeout: float | None = None) -> None:
|
|
78
|
+
self.stop_nowait()
|
|
79
|
+
self._thread.join(timeout)
|
|
80
|
+
|
|
81
|
+
def _loop(self) -> None:
|
|
82
|
+
if self._tick_immediately:
|
|
83
|
+
self._fn()
|
|
84
|
+
|
|
85
|
+
while True:
|
|
86
|
+
self._stop_event.wait(self._interval_s)
|
|
87
|
+
if self._stop_event.is_set():
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
self._fn()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
##
|
|
94
|
+
|
|
95
|
+
|
|
12
96
|
_DEBUG_THREAD_COUNTER = itertools.count()
|
|
13
97
|
|
|
14
98
|
|
|
@@ -18,47 +102,23 @@ def create_thread_dump_thread(
|
|
|
18
102
|
out: ta.TextIO = sys.stderr,
|
|
19
103
|
start: bool = False,
|
|
20
104
|
nodaemon: bool = False,
|
|
21
|
-
) ->
|
|
22
|
-
def
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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')
|
|
105
|
+
) -> StoppableThread:
|
|
106
|
+
def proc() -> None:
|
|
107
|
+
try:
|
|
108
|
+
out.write(dump_threads_str())
|
|
109
|
+
except Exception as e: # noqa
|
|
110
|
+
out.write(repr(e) + '\n\n')
|
|
43
111
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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,
|
|
112
|
+
return StoppableThread(
|
|
113
|
+
proc,
|
|
114
|
+
interval_s,
|
|
56
115
|
daemon=not nodaemon,
|
|
57
116
|
name=f'thread-dump-thread-{next(_DEBUG_THREAD_COUNTER)}',
|
|
117
|
+
start=start,
|
|
58
118
|
)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
##
|
|
62
122
|
|
|
63
123
|
|
|
64
124
|
def create_suicide_thread(
|
|
@@ -67,20 +127,43 @@ def create_suicide_thread(
|
|
|
67
127
|
interval_s: float = 1.,
|
|
68
128
|
parent_thread: threading.Thread | None = None,
|
|
69
129
|
start: bool = False,
|
|
70
|
-
) ->
|
|
130
|
+
) -> StoppableThread:
|
|
131
|
+
"""Kills process when parent_thread dies."""
|
|
132
|
+
|
|
71
133
|
if parent_thread is None:
|
|
72
134
|
parent_thread = threading.current_thread()
|
|
73
135
|
|
|
74
|
-
def proc():
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if not parent_thread.is_alive():
|
|
78
|
-
os.kill(os.getpid(), sig)
|
|
136
|
+
def proc() -> None:
|
|
137
|
+
if not parent_thread.is_alive():
|
|
138
|
+
os.kill(os.getpid(), sig)
|
|
79
139
|
|
|
80
|
-
|
|
81
|
-
|
|
140
|
+
return StoppableThread(
|
|
141
|
+
proc,
|
|
142
|
+
interval_s,
|
|
82
143
|
name=f'suicide-thread-{next(_DEBUG_THREAD_COUNTER)}',
|
|
144
|
+
start=start,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
##
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def create_timebomb_thread(
|
|
152
|
+
delay_s: float,
|
|
153
|
+
*,
|
|
154
|
+
sig: int = signal.SIGKILL,
|
|
155
|
+
interval_s: float = 1.,
|
|
156
|
+
start: bool = False,
|
|
157
|
+
) -> StoppableThread:
|
|
158
|
+
def proc() -> None:
|
|
159
|
+
if time.time() >= deadline:
|
|
160
|
+
os.kill(os.getpid(), sig)
|
|
161
|
+
|
|
162
|
+
deadline = time.time() + delay_s
|
|
163
|
+
|
|
164
|
+
return StoppableThread(
|
|
165
|
+
proc,
|
|
166
|
+
interval_s,
|
|
167
|
+
name=f'timebomb-thread-{next(_DEBUG_THREAD_COUNTER)}',
|
|
168
|
+
start=start,
|
|
83
169
|
)
|
|
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
|
@@ -18,13 +18,14 @@ import datetime
|
|
|
18
18
|
import re
|
|
19
19
|
import shlex
|
|
20
20
|
import subprocess
|
|
21
|
+
import sys
|
|
21
22
|
import typing as ta
|
|
22
23
|
|
|
23
24
|
from . import check
|
|
24
25
|
from . import dataclasses as dc
|
|
25
|
-
from . import json
|
|
26
26
|
from . import lang
|
|
27
27
|
from . import marshal as msh
|
|
28
|
+
from .formats import json
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
if ta.TYPE_CHECKING:
|
|
@@ -168,3 +169,17 @@ def timebomb_payload(delay_s: float, name: str = 'omlish-docker-timebomb') -> st
|
|
|
168
169
|
'sh -c \'killall5 -9 -o $PPID -o $$ ; kill 1\''
|
|
169
170
|
') &'
|
|
170
171
|
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
##
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
_LIKELY_IN_DOCKER_PATTERN = re.compile(r'^overlay / .*/docker/')
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def is_likely_in_docker() -> bool:
|
|
181
|
+
if sys.platform != 'linux':
|
|
182
|
+
return False
|
|
183
|
+
with open('/proc/mounts') as f: # type: ignore
|
|
184
|
+
ls = f.readlines()
|
|
185
|
+
return any(_LIKELY_IN_DOCKER_PATTERN.match(l) for l in ls)
|
omlish/fnpairs.py
CHANGED
|
@@ -4,11 +4,7 @@ TODO:
|
|
|
4
4
|
- csv
|
|
5
5
|
- csvloader
|
|
6
6
|
- cbor
|
|
7
|
-
- cloudpickle
|
|
8
7
|
- alt json backends
|
|
9
|
-
- compression
|
|
10
|
-
- snappy
|
|
11
|
-
- lz4
|
|
12
8
|
- wrapped (wait for usecase)
|
|
13
9
|
"""
|
|
14
10
|
import abc
|
|
@@ -29,6 +25,7 @@ if ta.TYPE_CHECKING:
|
|
|
29
25
|
import tomllib as _tomllib
|
|
30
26
|
|
|
31
27
|
import cloudpickle as _cloudpickle
|
|
28
|
+
import json5 as _json5
|
|
32
29
|
import lz4.frame as _lz4_frame
|
|
33
30
|
import snappy as _snappy
|
|
34
31
|
import yaml as _yaml
|
|
@@ -44,6 +41,7 @@ else:
|
|
|
44
41
|
_tomllib = lang.proxy_import('tomllib')
|
|
45
42
|
|
|
46
43
|
_cloudpickle = lang.proxy_import('cloudpickle')
|
|
44
|
+
_json5 = lang.proxy_import('json5')
|
|
47
45
|
_lz4_frame = lang.proxy_import('lz4.frame')
|
|
48
46
|
_snappy = lang.proxy_import('snappy')
|
|
49
47
|
_yaml = lang.proxy_import('yaml')
|
|
@@ -391,6 +389,15 @@ class Cloudpickle(ObjectBytes_):
|
|
|
391
389
|
return _cloudpickle.loads(t)
|
|
392
390
|
|
|
393
391
|
|
|
392
|
+
@_register_extension('json5')
|
|
393
|
+
class Json5(ObjectStr_):
|
|
394
|
+
def forward(self, f: ta.Any) -> str:
|
|
395
|
+
return _json5.dumps(f)
|
|
396
|
+
|
|
397
|
+
def backward(self, t: str) -> ta.Any:
|
|
398
|
+
return _json5.loads(t)
|
|
399
|
+
|
|
400
|
+
|
|
394
401
|
@_register_extension('yml', 'yaml')
|
|
395
402
|
class Yaml(ObjectStr_):
|
|
396
403
|
def forward(self, f: ta.Any) -> str:
|
|
File without changes
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
21
21
|
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
22
22
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
23
|
+
# https://github.com/theskumar/python-dotenv/tree/4d505f2c9bc3569791e64bca0f2e4300f43df0e0/src/dotenv
|
|
23
24
|
import abc
|
|
24
25
|
import codecs
|
|
25
26
|
import contextlib
|
|
@@ -332,6 +333,7 @@ class DotEnv:
|
|
|
332
333
|
encoding: str | None = None,
|
|
333
334
|
interpolate: bool = True,
|
|
334
335
|
override: bool = True,
|
|
336
|
+
env: ta.Mapping[str, str] | None = None,
|
|
335
337
|
) -> None:
|
|
336
338
|
super().__init__()
|
|
337
339
|
self.path: StrPath | None = path
|
|
@@ -341,6 +343,7 @@ class DotEnv:
|
|
|
341
343
|
self.encoding: str | None = encoding
|
|
342
344
|
self.interpolate: bool = interpolate
|
|
343
345
|
self.override: bool = override
|
|
346
|
+
self.env = env or {}
|
|
344
347
|
|
|
345
348
|
@contextlib.contextmanager
|
|
346
349
|
def _get_stream(self) -> ta.Iterator[ta.IO[str]]:
|
|
@@ -364,7 +367,7 @@ class DotEnv:
|
|
|
364
367
|
raw_values = self.parse()
|
|
365
368
|
|
|
366
369
|
if self.interpolate:
|
|
367
|
-
self._dict = resolve_variables(raw_values, override=self.override)
|
|
370
|
+
self._dict = resolve_variables(raw_values, override=self.override, env=self.env)
|
|
368
371
|
else:
|
|
369
372
|
self._dict = dict(raw_values)
|
|
370
373
|
|
|
@@ -376,22 +379,7 @@ class DotEnv:
|
|
|
376
379
|
if mapping.key is not None:
|
|
377
380
|
yield mapping.key, mapping.value
|
|
378
381
|
|
|
379
|
-
def set_as_environment_variables(self) -> bool:
|
|
380
|
-
"""Load the current dotenv as system environment variable."""
|
|
381
|
-
if not self.dict():
|
|
382
|
-
return False
|
|
383
|
-
|
|
384
|
-
for k, v in self.dict().items():
|
|
385
|
-
if k in os.environ and not self.override:
|
|
386
|
-
continue
|
|
387
|
-
if v is not None:
|
|
388
|
-
os.environ[k] = v
|
|
389
|
-
|
|
390
|
-
return True
|
|
391
|
-
|
|
392
382
|
def get(self, key: str) -> str | None:
|
|
393
|
-
"""
|
|
394
|
-
"""
|
|
395
383
|
data = self.dict()
|
|
396
384
|
|
|
397
385
|
if key in data:
|
|
@@ -525,8 +513,9 @@ def unset_key(
|
|
|
525
513
|
|
|
526
514
|
|
|
527
515
|
def resolve_variables(
|
|
528
|
-
|
|
529
|
-
|
|
516
|
+
values: ta.Iterable[tuple[str, str | None]],
|
|
517
|
+
override: bool,
|
|
518
|
+
env: ta.Mapping[str, str],
|
|
530
519
|
) -> dict[str, str | None]:
|
|
531
520
|
new_values: dict[str, str | None] = {}
|
|
532
521
|
|
|
@@ -535,14 +524,14 @@ def resolve_variables(
|
|
|
535
524
|
result = None
|
|
536
525
|
else:
|
|
537
526
|
atoms = parse_variables(value)
|
|
538
|
-
|
|
527
|
+
aenv: dict[str, str | None] = {}
|
|
539
528
|
if override:
|
|
540
|
-
|
|
541
|
-
|
|
529
|
+
aenv.update(env)
|
|
530
|
+
aenv.update(new_values)
|
|
542
531
|
else:
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
result = ''.join(atom.resolve(
|
|
532
|
+
aenv.update(new_values)
|
|
533
|
+
aenv.update(env)
|
|
534
|
+
result = ''.join(atom.resolve(aenv) for atom in atoms)
|
|
546
535
|
|
|
547
536
|
new_values[name] = result
|
|
548
537
|
|
|
@@ -556,6 +545,7 @@ def dotenv_values(
|
|
|
556
545
|
verbose: bool = False,
|
|
557
546
|
interpolate: bool = True,
|
|
558
547
|
encoding: str | None = 'utf-8',
|
|
548
|
+
env: ta.Mapping[str, str] | None = None,
|
|
559
549
|
) -> dict[str, str | None]:
|
|
560
550
|
"""
|
|
561
551
|
Parse a .env file and return its content as a dict.
|
|
@@ -583,4 +573,5 @@ def dotenv_values(
|
|
|
583
573
|
interpolate=interpolate,
|
|
584
574
|
override=True,
|
|
585
575
|
encoding=encoding,
|
|
576
|
+
env=env,
|
|
586
577
|
).dict()
|
|
@@ -125,7 +125,7 @@ import functools
|
|
|
125
125
|
import json as _json
|
|
126
126
|
import typing as ta
|
|
127
127
|
|
|
128
|
-
from
|
|
128
|
+
from .. import lang
|
|
129
129
|
|
|
130
130
|
|
|
131
131
|
if ta.TYPE_CHECKING:
|
|
@@ -160,6 +160,7 @@ PRETTY_KWARGS: ta.Mapping[str, ta.Any] = dict(
|
|
|
160
160
|
dump_pretty: ta.Callable[..., bytes] = functools.partial(dump, **PRETTY_KWARGS) # type: ignore
|
|
161
161
|
dumps_pretty: ta.Callable[..., str] = functools.partial(dumps, **PRETTY_KWARGS)
|
|
162
162
|
|
|
163
|
+
|
|
163
164
|
##
|
|
164
165
|
|
|
165
166
|
|