omlish 0.0.0.dev5__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/asyncs/__init__.py +9 -0
- omlish/asyncs/anyio.py +83 -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/collections/__init__.py +1 -0
- omlish/collections/identity.py +7 -0
- omlish/configs/strings.py +94 -0
- omlish/dataclasses/__init__.py +9 -0
- omlish/dataclasses/impl/copy.py +30 -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/utils.py +44 -0
- omlish/diag/__init__.py +4 -0
- omlish/diag/procfs.py +2 -2
- omlish/{testing → diag}/pydevd.py +35 -0
- omlish/dispatch/_dispatch2.py +65 -0
- omlish/dispatch/_dispatch3.py +104 -0
- omlish/docker.py +1 -1
- omlish/fnpairs.py +11 -0
- omlish/http/asgi.py +2 -1
- omlish/http/collections.py +15 -0
- omlish/http/consts.py +16 -1
- omlish/http/sessions.py +10 -3
- omlish/inject/__init__.py +45 -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 +1 -1
- omlish/inject/impl/elements.py +37 -12
- omlish/inject/impl/injector.py +19 -2
- omlish/inject/impl/inspect.py +33 -5
- omlish/inject/impl/origins.py +75 -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 +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 +13 -6
- omlish/inject/types.py +3 -1
- omlish/lang/__init__.py +8 -2
- omlish/lang/cached.py +2 -2
- omlish/lang/classes/restrict.py +2 -1
- omlish/lang/classes/simple.py +18 -8
- omlish/lang/contextmanagers.py +12 -3
- omlish/lang/descriptors.py +131 -0
- omlish/lang/functions.py +8 -28
- omlish/lang/iterables.py +20 -1
- omlish/lang/typing.py +5 -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/marshal/__init__.py +4 -0
- omlish/marshal/naming.py +4 -0
- omlish/marshal/objects.py +1 -0
- omlish/marshal/polymorphism.py +4 -4
- omlish/reflect.py +134 -19
- 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/{configs → serde}/dotenv.py +12 -24
- omlish/{json.py → serde/json.py} +2 -1
- 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/term.py +1 -1
- 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/text/glyphsplit.py +92 -0
- {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev6.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev6.dist-info}/RECORD +100 -72
- /omlish/{configs → serde}/props.py +0 -0
- {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev6.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev6.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev6.dist-info}/top_level.txt +0 -0
omlish/dataclasses/__init__.py
CHANGED
|
@@ -55,6 +55,7 @@ globals()['make_dataclass'] = xmake_dataclass
|
|
|
55
55
|
|
|
56
56
|
from .impl.exceptions import ( # noqa
|
|
57
57
|
CheckError,
|
|
58
|
+
FieldCheckError,
|
|
58
59
|
)
|
|
59
60
|
|
|
60
61
|
from .impl.metaclass import ( # noqa
|
|
@@ -81,3 +82,11 @@ from .impl.reflect import ( # noqa
|
|
|
81
82
|
ClassInfo,
|
|
82
83
|
reflect,
|
|
83
84
|
)
|
|
85
|
+
|
|
86
|
+
from .utils import ( # noqa
|
|
87
|
+
chain_metadata,
|
|
88
|
+
field_modifier,
|
|
89
|
+
maybe_post_init,
|
|
90
|
+
opt_repr,
|
|
91
|
+
update_field_metadata,
|
|
92
|
+
)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- __deepcopy__
|
|
4
|
+
"""
|
|
5
|
+
import dataclasses as dc
|
|
6
|
+
|
|
7
|
+
from .fields import field_type
|
|
8
|
+
from .internals import FIELDS_ATTR
|
|
9
|
+
from .internals import FieldType
|
|
10
|
+
from .processing import Processor
|
|
11
|
+
from .utils import set_new_attribute
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
MISSING = dc.MISSING
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _copy(obj):
|
|
18
|
+
kw = {}
|
|
19
|
+
|
|
20
|
+
for f in getattr(obj, FIELDS_ATTR).values():
|
|
21
|
+
if field_type(f) is FieldType.CLASS:
|
|
22
|
+
continue
|
|
23
|
+
kw[f.name] = getattr(obj, f.name)
|
|
24
|
+
|
|
25
|
+
return obj.__class__(**kw)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class CopyProcessor(Processor):
|
|
29
|
+
def _process(self) -> None:
|
|
30
|
+
set_new_attribute(self._cls, '__copy__', _copy)
|
|
@@ -98,51 +98,50 @@ def field_init(
|
|
|
98
98
|
|
|
99
99
|
lines = []
|
|
100
100
|
|
|
101
|
-
if fx.coerce is not None:
|
|
102
|
-
cn = f'__dataclass_coerce__{f.name}__'
|
|
103
|
-
locals[cn] = fx.coerce
|
|
104
|
-
lines.append(f'{f.name} = {cn}({f.name})')
|
|
105
|
-
|
|
106
|
-
if fx.check is not None:
|
|
107
|
-
cn = f'__dataclass_check__{f.name}__'
|
|
108
|
-
locals[cn] = fx.check
|
|
109
|
-
lines.append(f'if not {cn}({f.name}): raise __dataclass_CheckException__')
|
|
110
|
-
|
|
111
|
-
if fx.check_type:
|
|
112
|
-
cn = f'__dataclass_check_type__{f.name}__'
|
|
113
|
-
locals[cn] = f.type
|
|
114
|
-
lines.append(
|
|
115
|
-
f'if not __dataclass_builtins_isinstance__({f.name}, {cn}): '
|
|
116
|
-
f'raise __dataclass_builtins_TypeError__({f.name}, {cn})',
|
|
117
|
-
)
|
|
118
|
-
|
|
119
101
|
value: str | None = None
|
|
120
102
|
if f.default_factory is not MISSING:
|
|
121
103
|
if f.init:
|
|
122
104
|
locals[default_name] = f.default_factory
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
f'if {f.name} is __dataclass_HAS_DEFAULT_FACTORY__ '
|
|
126
|
-
f'else {f.name}'
|
|
127
|
-
)
|
|
105
|
+
lines.append(f'if {f.name} is __dataclass_HAS_DEFAULT_FACTORY__: {f.name} = {default_name}()')
|
|
106
|
+
value = f.name
|
|
128
107
|
else:
|
|
129
108
|
locals[default_name] = f.default_factory
|
|
130
|
-
|
|
109
|
+
lines.append(f'{f.name} = {default_name}()')
|
|
110
|
+
value = f.name
|
|
131
111
|
|
|
132
112
|
elif f.init:
|
|
133
113
|
if f.default is MISSING:
|
|
134
114
|
value = f.name
|
|
135
115
|
elif f.default is not MISSING:
|
|
136
|
-
locals[default_name] = f.default
|
|
116
|
+
locals[default_name] = f.default # Not referenced her, just useful / consistent to have in function scope
|
|
137
117
|
value = f.name
|
|
138
118
|
|
|
139
119
|
elif slots and f.default is not MISSING:
|
|
140
120
|
locals[default_name] = f.default
|
|
121
|
+
lines.append(f'{f.name} = {default_name}')
|
|
141
122
|
value = default_name
|
|
142
123
|
|
|
143
124
|
else:
|
|
144
125
|
pass
|
|
145
126
|
|
|
127
|
+
if fx.coerce is not None:
|
|
128
|
+
cn = f'__dataclass_coerce__{f.name}__'
|
|
129
|
+
locals[cn] = fx.coerce
|
|
130
|
+
lines.append(f'{value} = {cn}({value})')
|
|
131
|
+
|
|
132
|
+
if fx.check is not None:
|
|
133
|
+
cn = f'__dataclass_check__{f.name}__'
|
|
134
|
+
locals[cn] = fx.check
|
|
135
|
+
lines.append(f'if not {cn}({value}): raise __dataclass_FieldCheckError__({f.name})')
|
|
136
|
+
|
|
137
|
+
if fx.check_type:
|
|
138
|
+
cn = f'__dataclass_check_type__{f.name}__'
|
|
139
|
+
locals[cn] = f.type
|
|
140
|
+
lines.append(
|
|
141
|
+
f'if not __dataclass_builtins_isinstance__({value}, {cn}): '
|
|
142
|
+
f'raise __dataclass_builtins_TypeError__({value}, {cn})',
|
|
143
|
+
)
|
|
144
|
+
|
|
146
145
|
if value is not None and field_type(f) is not FieldType.INIT:
|
|
147
146
|
lines.append(field_assign(frozen, f.name, value, self_name, fx.override)) # noqa
|
|
148
147
|
|
omlish/dataclasses/impl/init.py
CHANGED
|
@@ -4,6 +4,7 @@ import typing as ta
|
|
|
4
4
|
|
|
5
5
|
from ... import lang
|
|
6
6
|
from .exceptions import CheckError
|
|
7
|
+
from .exceptions import FieldCheckError
|
|
7
8
|
from .fields import field_init
|
|
8
9
|
from .fields import field_type
|
|
9
10
|
from .fields import has_default
|
|
@@ -99,7 +100,8 @@ class InitBuilder:
|
|
|
99
100
|
'__dataclass_builtins_object__': object,
|
|
100
101
|
'__dataclass_builtins_isinstance__': isinstance,
|
|
101
102
|
'__dataclass_builtins_TypeError__': TypeError,
|
|
102
|
-
'
|
|
103
|
+
'__dataclass_CheckError__': CheckError,
|
|
104
|
+
'__dataclass_FieldCheckError__': FieldCheckError,
|
|
103
105
|
})
|
|
104
106
|
|
|
105
107
|
body_lines: list[str] = []
|
|
@@ -126,7 +128,7 @@ class InitBuilder:
|
|
|
126
128
|
locals[cn] = fn
|
|
127
129
|
csig = inspect.signature(fn)
|
|
128
130
|
cas = ', '.join(p.name for p in csig.parameters.values())
|
|
129
|
-
body_lines.append(f'if not {cn}({cas}): raise
|
|
131
|
+
body_lines.append(f'if not {cn}({cas}): raise __dataclass_CheckError__')
|
|
130
132
|
|
|
131
133
|
for i, fn in enumerate(self._info.merged_metadata.get(Init, [])):
|
|
132
134
|
cn = f'__dataclass_init_{i}__'
|
omlish/dataclasses/impl/main.py
CHANGED
|
@@ -4,6 +4,7 @@ import typing as ta
|
|
|
4
4
|
|
|
5
5
|
from ... import check
|
|
6
6
|
from ... import lang
|
|
7
|
+
from .copy import CopyProcessor
|
|
7
8
|
from .fields import preprocess_field
|
|
8
9
|
from .frozen import FrozenProcessor
|
|
9
10
|
from .hashing import HashProcessor
|
|
@@ -137,6 +138,7 @@ class MainProcessor:
|
|
|
137
138
|
DocProcessor,
|
|
138
139
|
MatchArgsProcessor,
|
|
139
140
|
ReplaceProcessor,
|
|
141
|
+
CopyProcessor,
|
|
140
142
|
]:
|
|
141
143
|
pcls(self._info).process()
|
|
142
144
|
|
|
@@ -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/diag/__init__.py
CHANGED
omlish/diag/procfs.py
CHANGED
|
@@ -12,9 +12,9 @@ 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
18
|
from .procstats import ProcStats
|
|
19
19
|
|
|
20
20
|
|
|
@@ -42,7 +42,7 @@ def parse_size(s: str) -> int:
|
|
|
42
42
|
return int(v) * us[u]
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
class ProcStat(lang.Namespace):
|
|
45
|
+
class ProcStat(lang.Namespace, lang.Final):
|
|
46
46
|
PID = 0
|
|
47
47
|
COMM = 1
|
|
48
48
|
STATE = 2
|
|
@@ -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)
|
|
@@ -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
omlish/fnpairs.py
CHANGED
|
@@ -29,6 +29,7 @@ if ta.TYPE_CHECKING:
|
|
|
29
29
|
import tomllib as _tomllib
|
|
30
30
|
|
|
31
31
|
import cloudpickle as _cloudpickle
|
|
32
|
+
import json5 as _json5
|
|
32
33
|
import lz4.frame as _lz4_frame
|
|
33
34
|
import snappy as _snappy
|
|
34
35
|
import yaml as _yaml
|
|
@@ -44,6 +45,7 @@ else:
|
|
|
44
45
|
_tomllib = lang.proxy_import('tomllib')
|
|
45
46
|
|
|
46
47
|
_cloudpickle = lang.proxy_import('cloudpickle')
|
|
48
|
+
_json5 = lang.proxy_import('json5')
|
|
47
49
|
_lz4_frame = lang.proxy_import('lz4.frame')
|
|
48
50
|
_snappy = lang.proxy_import('snappy')
|
|
49
51
|
_yaml = lang.proxy_import('yaml')
|
|
@@ -391,6 +393,15 @@ class Cloudpickle(ObjectBytes_):
|
|
|
391
393
|
return _cloudpickle.loads(t)
|
|
392
394
|
|
|
393
395
|
|
|
396
|
+
@_register_extension('json5')
|
|
397
|
+
class Json5(ObjectStr_):
|
|
398
|
+
def forward(self, f: ta.Any) -> str:
|
|
399
|
+
return _json5.dumps(f)
|
|
400
|
+
|
|
401
|
+
def backward(self, t: str) -> ta.Any:
|
|
402
|
+
return _json5.loads(t)
|
|
403
|
+
|
|
404
|
+
|
|
394
405
|
@_register_extension('yml', 'yaml')
|
|
395
406
|
class Yaml(ObjectStr_):
|
|
396
407
|
def forward(self, f: ta.Any) -> str:
|
omlish/http/asgi.py
CHANGED
|
@@ -18,9 +18,10 @@ AsgiMessage: ta.TypeAlias = ta.Mapping[str, ta.Any]
|
|
|
18
18
|
AsgiRecv: ta.TypeAlias = ta.Callable[[], ta.Awaitable[AsgiMessage]]
|
|
19
19
|
AsgiSend: ta.TypeAlias = ta.Callable[[AsgiMessage], ta.Awaitable[None]]
|
|
20
20
|
AsgiApp: ta.TypeAlias = ta.Callable[[AsgiScope, AsgiRecv, AsgiSend], ta.Awaitable[None]]
|
|
21
|
+
AsgiWrapper: ta.TypeAlias = ta.Callable[[AsgiApp, AsgiScope, AsgiRecv, AsgiSend], ta.Awaitable[None]]
|
|
21
22
|
|
|
22
23
|
|
|
23
|
-
class
|
|
24
|
+
class AsgiApp_(abc.ABC): # noqa
|
|
24
25
|
@abc.abstractmethod
|
|
25
26
|
async def __call__(self, scope: AsgiScope, recv: AsgiRecv, send: AsgiSend) -> None:
|
|
26
27
|
raise NotImplementedError
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import typing as ta
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
V = ta.TypeVar('V')
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class HttpMap(ta.Mapping[str, V]):
|
|
8
|
+
def __getitem__(self, k):
|
|
9
|
+
raise NotImplementedError
|
|
10
|
+
|
|
11
|
+
def __len__(self):
|
|
12
|
+
raise NotImplementedError
|
|
13
|
+
|
|
14
|
+
def __iter__(self):
|
|
15
|
+
raise NotImplementedError
|
omlish/http/consts.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import base64
|
|
2
|
+
import http
|
|
2
3
|
|
|
3
4
|
|
|
4
5
|
##
|
|
@@ -31,6 +32,8 @@ STATUS_GATEWAY_TIMEOUT = format_status(http.HTTPStatus.GATEWAY_TIMEOUT)
|
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
HEADER_CONTENT_TYPE = b'Content-Type'
|
|
35
|
+
HEADER_ACCEPT = b'Accept'
|
|
36
|
+
|
|
34
37
|
CONTENT_CHARSET_UTF8 = b'charset=utf-8'
|
|
35
38
|
|
|
36
39
|
CONTENT_TYPE_BYTES = b'application/octet-stream'
|
|
@@ -45,3 +48,15 @@ CONTENT_TYPE_JSON_UTF8 = b'; '.join([CONTENT_TYPE_JSON, CONTENT_CHARSET_UTF8])
|
|
|
45
48
|
|
|
46
49
|
CONTENT_TYPE_TEXT = b'text/plain'
|
|
47
50
|
CONTENT_TYPE_TEXT_UTF8 = b'; '.join([CONTENT_TYPE_TEXT, CONTENT_CHARSET_UTF8])
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
##
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
HEADER_AUTH = b'Authorization'
|
|
57
|
+
BEARER_AUTH_HEADER_PREFIX = b'Bearer '
|
|
58
|
+
BASIC_AUTH_HEADER_PREFIX = b'Basic '
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def format_basic_auth_header(username: str, password: str) -> bytes:
|
|
62
|
+
return BASIC_AUTH_HEADER_PREFIX + base64.b64encode(':'.join([username, password]).encode())
|
omlish/http/sessions.py
CHANGED
|
@@ -10,6 +10,7 @@ import zlib
|
|
|
10
10
|
|
|
11
11
|
from .. import fnpairs as fps
|
|
12
12
|
from .. import lang
|
|
13
|
+
from .. import secrets as sec
|
|
13
14
|
from .cookies import dump_cookie
|
|
14
15
|
from .cookies import parse_cookie
|
|
15
16
|
from .json import JSON_TAGGER
|
|
@@ -44,13 +45,19 @@ def bytes_to_int(bytestr: bytes) -> int:
|
|
|
44
45
|
class Signer:
|
|
45
46
|
@dc.dataclass(frozen=True)
|
|
46
47
|
class Config:
|
|
47
|
-
secret_key: str
|
|
48
|
+
secret_key: str | sec.Secret = dc.field()
|
|
48
49
|
salt: str = 'cookie-session'
|
|
49
50
|
|
|
50
|
-
def __init__(
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
config: Config,
|
|
54
|
+
*,
|
|
55
|
+
secrets: sec.Secrets = sec.EMPTY_SECRETS,
|
|
56
|
+
) -> None:
|
|
51
57
|
super().__init__()
|
|
52
58
|
|
|
53
59
|
self._config = config
|
|
60
|
+
self._secrets = secrets
|
|
54
61
|
|
|
55
62
|
@lang.cached_function
|
|
56
63
|
def digest(self) -> ta.Any:
|
|
@@ -58,7 +65,7 @@ class Signer:
|
|
|
58
65
|
|
|
59
66
|
@lang.cached_function
|
|
60
67
|
def derive_key(self) -> bytes:
|
|
61
|
-
mac = hmac.new(self._config.secret_key.encode(), digestmod=self.digest())
|
|
68
|
+
mac = hmac.new(self._secrets.fix(self._config.secret_key).encode(), digestmod=self.digest())
|
|
62
69
|
mac.update(self._config.salt.encode())
|
|
63
70
|
return mac.digest()
|
|
64
71
|
|