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.

Files changed (100) hide show
  1. omlish/__about__.py +1 -1
  2. omlish/asyncs/__init__.py +9 -0
  3. omlish/asyncs/anyio.py +83 -19
  4. omlish/asyncs/asyncio.py +23 -0
  5. omlish/asyncs/asyncs.py +9 -6
  6. omlish/asyncs/bridge.py +316 -0
  7. omlish/asyncs/trio_asyncio.py +7 -3
  8. omlish/collections/__init__.py +1 -0
  9. omlish/collections/identity.py +7 -0
  10. omlish/configs/strings.py +94 -0
  11. omlish/dataclasses/__init__.py +9 -0
  12. omlish/dataclasses/impl/copy.py +30 -0
  13. omlish/dataclasses/impl/exceptions.py +6 -0
  14. omlish/dataclasses/impl/fields.py +24 -25
  15. omlish/dataclasses/impl/init.py +4 -2
  16. omlish/dataclasses/impl/main.py +2 -0
  17. omlish/dataclasses/utils.py +44 -0
  18. omlish/diag/__init__.py +4 -0
  19. omlish/diag/procfs.py +2 -2
  20. omlish/{testing → diag}/pydevd.py +35 -0
  21. omlish/dispatch/_dispatch2.py +65 -0
  22. omlish/dispatch/_dispatch3.py +104 -0
  23. omlish/docker.py +1 -1
  24. omlish/fnpairs.py +11 -0
  25. omlish/http/asgi.py +2 -1
  26. omlish/http/collections.py +15 -0
  27. omlish/http/consts.py +16 -1
  28. omlish/http/sessions.py +10 -3
  29. omlish/inject/__init__.py +45 -17
  30. omlish/inject/binder.py +185 -5
  31. omlish/inject/bindings.py +3 -36
  32. omlish/inject/eagers.py +2 -8
  33. omlish/inject/elements.py +30 -9
  34. omlish/inject/exceptions.py +1 -1
  35. omlish/inject/impl/elements.py +37 -12
  36. omlish/inject/impl/injector.py +19 -2
  37. omlish/inject/impl/inspect.py +33 -5
  38. omlish/inject/impl/origins.py +75 -0
  39. omlish/inject/impl/{private.py → privates.py} +2 -2
  40. omlish/inject/impl/scopes.py +6 -2
  41. omlish/inject/injector.py +8 -4
  42. omlish/inject/inspect.py +18 -0
  43. omlish/inject/keys.py +8 -14
  44. omlish/inject/listeners.py +26 -0
  45. omlish/inject/managed.py +76 -10
  46. omlish/inject/multis.py +68 -18
  47. omlish/inject/origins.py +27 -0
  48. omlish/inject/overrides.py +5 -4
  49. omlish/inject/{private.py → privates.py} +6 -10
  50. omlish/inject/providers.py +12 -85
  51. omlish/inject/scopes.py +13 -6
  52. omlish/inject/types.py +3 -1
  53. omlish/lang/__init__.py +8 -2
  54. omlish/lang/cached.py +2 -2
  55. omlish/lang/classes/restrict.py +2 -1
  56. omlish/lang/classes/simple.py +18 -8
  57. omlish/lang/contextmanagers.py +12 -3
  58. omlish/lang/descriptors.py +131 -0
  59. omlish/lang/functions.py +8 -28
  60. omlish/lang/iterables.py +20 -1
  61. omlish/lang/typing.py +5 -0
  62. omlish/lifecycles/__init__.py +34 -0
  63. omlish/lifecycles/abstract.py +43 -0
  64. omlish/lifecycles/base.py +51 -0
  65. omlish/lifecycles/contextmanagers.py +74 -0
  66. omlish/lifecycles/controller.py +116 -0
  67. omlish/lifecycles/manager.py +161 -0
  68. omlish/lifecycles/states.py +43 -0
  69. omlish/lifecycles/transitions.py +64 -0
  70. omlish/logs/formatters.py +1 -1
  71. omlish/marshal/__init__.py +4 -0
  72. omlish/marshal/naming.py +4 -0
  73. omlish/marshal/objects.py +1 -0
  74. omlish/marshal/polymorphism.py +4 -4
  75. omlish/reflect.py +134 -19
  76. omlish/secrets/__init__.py +7 -0
  77. omlish/secrets/marshal.py +41 -0
  78. omlish/secrets/passwords.py +120 -0
  79. omlish/secrets/secrets.py +47 -0
  80. omlish/serde/__init__.py +0 -0
  81. omlish/{configs → serde}/dotenv.py +12 -24
  82. omlish/{json.py → serde/json.py} +2 -1
  83. omlish/serde/yaml.py +223 -0
  84. omlish/sql/dbs.py +1 -1
  85. omlish/sql/duckdb.py +136 -0
  86. omlish/sql/sqlean.py +17 -0
  87. omlish/term.py +1 -1
  88. omlish/testing/pytest/__init__.py +3 -2
  89. omlish/testing/pytest/inject/harness.py +3 -3
  90. omlish/testing/pytest/marks.py +4 -7
  91. omlish/testing/pytest/plugins/__init__.py +1 -0
  92. omlish/testing/pytest/plugins/asyncs.py +136 -0
  93. omlish/testing/pytest/plugins/pydevd.py +1 -1
  94. omlish/text/glyphsplit.py +92 -0
  95. {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev6.dist-info}/METADATA +1 -1
  96. {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev6.dist-info}/RECORD +100 -72
  97. /omlish/{configs → serde}/props.py +0 -0
  98. {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev6.dist-info}/LICENSE +0 -0
  99. {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev6.dist-info}/WHEEL +0 -0
  100. {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev6.dist-info}/top_level.txt +0 -0
@@ -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)
@@ -1,2 +1,8 @@
1
1
  class CheckError(Exception):
2
2
  pass
3
+
4
+
5
+ class FieldCheckError(CheckError):
6
+ def __init__(self, field: str) -> None:
7
+ super().__init__(field)
8
+ self.field = field
@@ -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
- value = (
124
- f'{default_name}() '
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
- value = f'{default_name}()'
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
 
@@ -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
- '__dataclass_CheckException__': CheckError,
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 __dataclass_CheckException__')
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}__'
@@ -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
@@ -0,0 +1,4 @@
1
+ """
2
+ TODO:
3
+ - https://github.com/aristocratos/btop/blob/00c90884c328eb3e44a0ab094e833161092aae9c/src/linux/btop_collect.cpp#L443
4
+ """
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
@@ -22,9 +22,9 @@ 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
30
  if ta.TYPE_CHECKING:
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 AbstractAsgiApp(abc.ABC):
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 http # noqa
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__(self, config: Config) -> None:
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