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
omlish/reflect.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""
|
|
2
2
|
TODO:
|
|
3
|
+
- visitor / transformer
|
|
3
4
|
- uniform collection isinstance - items() for mappings, iter() for other
|
|
4
5
|
- also check instance type in isinstance not just items lol
|
|
5
6
|
- ta.Generic in mro causing trouble - omit? no longer 1:1
|
|
@@ -7,12 +8,14 @@ TODO:
|
|
|
7
8
|
- cache __hash__ in Generic/Union
|
|
8
9
|
"""
|
|
9
10
|
import collections.abc
|
|
10
|
-
import
|
|
11
|
+
import dataclasses as dc
|
|
11
12
|
import types
|
|
13
|
+
import typing as ta
|
|
12
14
|
|
|
13
15
|
from . import c3
|
|
14
16
|
from . import lang
|
|
15
17
|
|
|
18
|
+
|
|
16
19
|
if ta.TYPE_CHECKING:
|
|
17
20
|
from .collections import cache
|
|
18
21
|
else:
|
|
@@ -25,8 +28,10 @@ _NONE_TYPE_FROZENSET: frozenset['Type'] = frozenset([_NoneType])
|
|
|
25
28
|
|
|
26
29
|
|
|
27
30
|
_GenericAlias = ta._GenericAlias # type: ignore # noqa
|
|
31
|
+
_CallableGenericAlias = ta._CallableGenericAlias # type: ignore # noqa
|
|
28
32
|
_SpecialGenericAlias = ta._SpecialGenericAlias # type: ignore # noqa
|
|
29
33
|
_UnionGenericAlias = ta._UnionGenericAlias # type: ignore # noqa
|
|
34
|
+
_AnnotatedAlias = ta._AnnotatedAlias # type: ignore # noqa
|
|
30
35
|
|
|
31
36
|
|
|
32
37
|
##
|
|
@@ -54,9 +59,11 @@ _KNOWN_SPECIALS_BY_NAME = {s.name: s for s in _KNOWN_SPECIALS}
|
|
|
54
59
|
_KNOWN_SPECIALS_BY_ALIAS = {s.alias: s for s in _KNOWN_SPECIALS}
|
|
55
60
|
_KNOWN_SPECIALS_BY_ORIGIN = {s.origin: s for s in _KNOWN_SPECIALS}
|
|
56
61
|
|
|
62
|
+
_MAX_KNOWN_SPECIAL_TYPE_VARS = 16
|
|
63
|
+
|
|
57
64
|
_KNOWN_SPECIAL_TYPE_VARS = tuple(
|
|
58
65
|
ta.TypeVar(f'_{i}') # noqa
|
|
59
|
-
for i in range(
|
|
66
|
+
for i in range(_MAX_KNOWN_SPECIAL_TYPE_VARS)
|
|
60
67
|
)
|
|
61
68
|
|
|
62
69
|
|
|
@@ -73,28 +80,40 @@ def get_params(obj: ta.Any) -> tuple[ta.TypeVar, ...]:
|
|
|
73
80
|
|
|
74
81
|
oty = type(obj)
|
|
75
82
|
|
|
76
|
-
if
|
|
83
|
+
if (
|
|
84
|
+
oty is _GenericAlias or
|
|
85
|
+
oty is ta.GenericAlias # type: ignore # noqa
|
|
86
|
+
):
|
|
77
87
|
return obj.__dict__.get('__parameters__', ()) # noqa
|
|
78
88
|
|
|
89
|
+
if oty is _CallableGenericAlias:
|
|
90
|
+
raise NotImplementedError('get_params not yet implemented for typing.Callable')
|
|
91
|
+
|
|
79
92
|
raise TypeError(obj)
|
|
80
93
|
|
|
81
94
|
|
|
82
95
|
def is_union_type(cls: ta.Any) -> bool:
|
|
83
96
|
if hasattr(ta, 'UnionType'):
|
|
84
97
|
return ta.get_origin(cls) in {ta.Union, getattr(ta, 'UnionType')}
|
|
98
|
+
|
|
85
99
|
else:
|
|
86
100
|
return ta.get_origin(cls) in {ta.Union}
|
|
87
101
|
|
|
88
102
|
|
|
103
|
+
def get_orig_class(obj: ta.Any) -> ta.Any:
|
|
104
|
+
return obj.__orig_class__ # noqa
|
|
105
|
+
|
|
106
|
+
|
|
89
107
|
##
|
|
90
108
|
|
|
91
109
|
|
|
92
110
|
Type = ta.Union[
|
|
93
111
|
type,
|
|
94
112
|
ta.TypeVar,
|
|
95
|
-
'NewType',
|
|
96
113
|
'Union',
|
|
97
114
|
'Generic',
|
|
115
|
+
'NewType',
|
|
116
|
+
'Annotated',
|
|
98
117
|
]
|
|
99
118
|
|
|
100
119
|
|
|
@@ -114,19 +133,26 @@ class Union(ta.NamedTuple):
|
|
|
114
133
|
return Union(rem)
|
|
115
134
|
|
|
116
135
|
|
|
117
|
-
|
|
136
|
+
@dc.dataclass(frozen=True)
|
|
137
|
+
class Generic:
|
|
118
138
|
cls: type
|
|
119
|
-
args: tuple[Type, ...]
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
139
|
+
args: tuple[Type, ...] # map[int, V] = (int, V) | map[T, T] = (T, T)
|
|
140
|
+
|
|
141
|
+
params: tuple[ta.TypeVar, ...] = dc.field(compare=False, repr=False) # map[int, V] = (_0, _1) | map[T, T] = (_0, _1) # noqa
|
|
142
|
+
# params2: tuple[ta.TypeVar, ...] # map[int, V] = (V,) | map[T, T] = (T,)
|
|
143
|
+
|
|
144
|
+
obj: ta.Any = dc.field(compare=False, repr=False)
|
|
123
145
|
|
|
124
|
-
def
|
|
146
|
+
# def __post_init__(self) -> None:
|
|
147
|
+
# if not isinstance(self.cls, type):
|
|
148
|
+
# raise TypeError(self.cls)
|
|
149
|
+
|
|
150
|
+
def full_eq(self, other: 'Generic') -> bool:
|
|
125
151
|
return (
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
152
|
+
self.cls == other.cls and
|
|
153
|
+
self.args == other.args and
|
|
154
|
+
self.params == other.params and
|
|
155
|
+
self.obj == other.obj
|
|
130
156
|
)
|
|
131
157
|
|
|
132
158
|
|
|
@@ -134,15 +160,50 @@ class NewType(ta.NamedTuple):
|
|
|
134
160
|
obj: ta.Any
|
|
135
161
|
|
|
136
162
|
|
|
163
|
+
@dc.dataclass(frozen=True)
|
|
164
|
+
class Annotated:
|
|
165
|
+
ty: Type
|
|
166
|
+
md: ta.Sequence[ta.Any]
|
|
167
|
+
|
|
168
|
+
obj: ta.Any = dc.field(compare=False, repr=False)
|
|
169
|
+
|
|
170
|
+
|
|
137
171
|
TYPES: tuple[type, ...] = (
|
|
138
172
|
type,
|
|
139
173
|
ta.TypeVar,
|
|
140
174
|
Union,
|
|
141
175
|
Generic,
|
|
142
176
|
NewType,
|
|
177
|
+
Annotated,
|
|
143
178
|
)
|
|
144
179
|
|
|
145
180
|
|
|
181
|
+
##
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def is_type(obj: ta.Any) -> bool:
|
|
185
|
+
if isinstance(obj, (Union, Generic, ta.TypeVar, NewType)): # noqa
|
|
186
|
+
return True
|
|
187
|
+
|
|
188
|
+
oty = type(obj)
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
oty is _UnionGenericAlias or oty is types.UnionType or # noqa
|
|
192
|
+
|
|
193
|
+
isinstance(obj, ta.NewType) or # noqa
|
|
194
|
+
|
|
195
|
+
(
|
|
196
|
+
oty is _GenericAlias or
|
|
197
|
+
oty is ta.GenericAlias or # type: ignore # noqa
|
|
198
|
+
oty is _CallableGenericAlias
|
|
199
|
+
) or
|
|
200
|
+
|
|
201
|
+
isinstance(obj, type) or
|
|
202
|
+
|
|
203
|
+
isinstance(obj, _SpecialGenericAlias)
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
|
|
146
207
|
def type_(obj: ta.Any) -> Type:
|
|
147
208
|
if isinstance(obj, (Union, Generic, ta.TypeVar, NewType)): # noqa
|
|
148
209
|
return obj
|
|
@@ -153,12 +214,22 @@ def type_(obj: ta.Any) -> Type:
|
|
|
153
214
|
return Union(frozenset(type_(a) for a in ta.get_args(obj)))
|
|
154
215
|
|
|
155
216
|
if isinstance(obj, ta.NewType): # noqa
|
|
156
|
-
return NewType(
|
|
217
|
+
return NewType(obj)
|
|
157
218
|
|
|
158
|
-
if
|
|
219
|
+
if (
|
|
220
|
+
oty is _GenericAlias or
|
|
221
|
+
oty is ta.GenericAlias or # type: ignore # noqa
|
|
222
|
+
oty is _CallableGenericAlias
|
|
223
|
+
):
|
|
159
224
|
origin = ta.get_origin(obj)
|
|
160
225
|
args = ta.get_args(obj)
|
|
161
|
-
if
|
|
226
|
+
if oty is _CallableGenericAlias:
|
|
227
|
+
p, r = args
|
|
228
|
+
if p is Ellipsis or isinstance(p, ta.ParamSpec):
|
|
229
|
+
raise TypeError(f'Callable argument not yet supported for {obj=}')
|
|
230
|
+
args = (*p, r)
|
|
231
|
+
params = _KNOWN_SPECIAL_TYPE_VARS[:len(args)]
|
|
232
|
+
elif origin is ta.Generic:
|
|
162
233
|
params = args
|
|
163
234
|
else:
|
|
164
235
|
params = get_params(origin)
|
|
@@ -192,15 +263,52 @@ def type_(obj: ta.Any) -> Type:
|
|
|
192
263
|
obj,
|
|
193
264
|
)
|
|
194
265
|
|
|
266
|
+
if isinstance(obj, _AnnotatedAlias):
|
|
267
|
+
o = ta.get_args(obj)[0]
|
|
268
|
+
return Annotated(type_(o), md=obj.__metadata__, obj=obj)
|
|
269
|
+
|
|
195
270
|
raise TypeError(obj)
|
|
196
271
|
|
|
197
272
|
|
|
198
273
|
##
|
|
199
274
|
|
|
200
275
|
|
|
276
|
+
def strip_objs(ty: Type) -> Type:
|
|
277
|
+
if isinstance(ty, (type, ta.TypeVar, NewType)):
|
|
278
|
+
return ty
|
|
279
|
+
|
|
280
|
+
if isinstance(ty, Union):
|
|
281
|
+
return Union(frozenset(map(strip_objs, ty.args)))
|
|
282
|
+
|
|
283
|
+
if isinstance(ty, Generic):
|
|
284
|
+
return Generic(ty.cls, tuple(map(strip_objs, ty.args)), ty.params, None)
|
|
285
|
+
|
|
286
|
+
if isinstance(ty, Annotated):
|
|
287
|
+
return Annotated(strip_objs(ty.ty), ty.md, None)
|
|
288
|
+
|
|
289
|
+
raise TypeError(ty)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def strip_annotations(ty: Type) -> Type:
|
|
293
|
+
if isinstance(ty, (type, ta.TypeVar, NewType)):
|
|
294
|
+
return ty
|
|
295
|
+
|
|
296
|
+
if isinstance(ty, Union):
|
|
297
|
+
return Union(frozenset(map(strip_annotations, ty.args)))
|
|
298
|
+
|
|
299
|
+
if isinstance(ty, Generic):
|
|
300
|
+
return Generic(ty.cls, tuple(map(strip_annotations, ty.args)), ty.params, ty.obj)
|
|
301
|
+
|
|
302
|
+
if isinstance(ty, Annotated):
|
|
303
|
+
return strip_annotations(ty.ty)
|
|
304
|
+
|
|
305
|
+
raise TypeError(ty)
|
|
306
|
+
|
|
307
|
+
|
|
201
308
|
def types_equivalent(l: Type, r: Type) -> bool:
|
|
202
309
|
if isinstance(l, Generic) and isinstance(r, Generic):
|
|
203
310
|
return l.cls == r.cls and l.args == r.args
|
|
311
|
+
|
|
204
312
|
return l == r
|
|
205
313
|
|
|
206
314
|
|
|
@@ -211,28 +319,36 @@ def get_underlying(nt: NewType) -> Type:
|
|
|
211
319
|
def get_concrete_type(ty: Type) -> type | None:
|
|
212
320
|
if isinstance(ty, type):
|
|
213
321
|
return ty
|
|
322
|
+
|
|
214
323
|
if isinstance(ty, Generic):
|
|
215
324
|
return ty.cls
|
|
325
|
+
|
|
216
326
|
if isinstance(ty, NewType):
|
|
217
327
|
return get_concrete_type(get_underlying(ty))
|
|
328
|
+
|
|
218
329
|
if isinstance(ty, (Union, ta.TypeVar)):
|
|
219
330
|
return None
|
|
331
|
+
|
|
220
332
|
raise TypeError(ty)
|
|
221
333
|
|
|
222
334
|
|
|
223
335
|
def get_type_var_replacements(ty: Type) -> ta.Mapping[ta.TypeVar, Type]:
|
|
224
336
|
if isinstance(ty, Generic):
|
|
225
337
|
return dict(zip(ty.params, ty.args))
|
|
338
|
+
|
|
226
339
|
return {}
|
|
227
340
|
|
|
228
341
|
|
|
229
342
|
def to_annotation(ty: Type) -> ta.Any:
|
|
230
343
|
if isinstance(ty, Generic):
|
|
231
344
|
return ty.obj if ty.obj is not None else ty.cls
|
|
345
|
+
|
|
232
346
|
if isinstance(ty, Union):
|
|
233
347
|
return ta.Union[*tuple(to_annotation(e) for e in ty.args)]
|
|
348
|
+
|
|
234
349
|
if isinstance(ty, (type, ta.TypeVar, NewType)):
|
|
235
350
|
return ty
|
|
351
|
+
|
|
236
352
|
raise TypeError(ty)
|
|
237
353
|
|
|
238
354
|
|
|
@@ -245,8 +361,10 @@ def replace_type_vars(
|
|
|
245
361
|
def rec(cur):
|
|
246
362
|
if isinstance(cur, type):
|
|
247
363
|
return cur
|
|
364
|
+
|
|
248
365
|
if isinstance(cur, NewType):
|
|
249
366
|
return cur
|
|
367
|
+
|
|
250
368
|
if isinstance(cur, Generic):
|
|
251
369
|
args = tuple(rec(a) for a in cur.args)
|
|
252
370
|
if update_aliases:
|
|
@@ -260,11 +378,14 @@ def replace_type_vars(
|
|
|
260
378
|
obj = cur.obj[*nargs]
|
|
261
379
|
else:
|
|
262
380
|
obj = None
|
|
263
|
-
return
|
|
381
|
+
return dc.replace(cur, args=args, obj=obj)
|
|
382
|
+
|
|
264
383
|
if isinstance(cur, Union):
|
|
265
384
|
return Union(frozenset(rec(e) for e in cur.args))
|
|
385
|
+
|
|
266
386
|
if isinstance(cur, ta.TypeVar):
|
|
267
387
|
return rpl[cur]
|
|
388
|
+
|
|
268
389
|
raise TypeError(cur)
|
|
269
390
|
|
|
270
391
|
return rec(ty)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import collections.abc
|
|
2
|
+
import typing as ta
|
|
3
|
+
|
|
4
|
+
from .. import check
|
|
5
|
+
from .. import dataclasses as dc
|
|
6
|
+
from .. import marshal as msh
|
|
7
|
+
from .secrets import Secret
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class StrOrSecretMarshaler(msh.Marshaler):
|
|
11
|
+
def marshal(self, ctx: msh.MarshalContext, o: ta.Any) -> msh.Value:
|
|
12
|
+
if isinstance(o, str):
|
|
13
|
+
return o
|
|
14
|
+
elif isinstance(o, Secret):
|
|
15
|
+
return {'secret': o.key}
|
|
16
|
+
else:
|
|
17
|
+
raise TypeError(o)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class StrOrSecretUnmarshaler(msh.Unmarshaler):
|
|
21
|
+
def unmarshal(self, ctx: msh.UnmarshalContext, v: msh.Value) -> ta.Any:
|
|
22
|
+
if isinstance(v, str):
|
|
23
|
+
return v
|
|
24
|
+
elif isinstance(v, collections.abc.Mapping):
|
|
25
|
+
[(mk, mv)] = v.items()
|
|
26
|
+
if mk != 'secret':
|
|
27
|
+
raise TypeError(v)
|
|
28
|
+
return Secret(check.isinstance(mv, str))
|
|
29
|
+
else:
|
|
30
|
+
raise TypeError(v)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dc.field_modifier
|
|
34
|
+
def marshal_secret_field(f: dc.Field) -> dc.Field:
|
|
35
|
+
return dc.update_field_metadata(f, {
|
|
36
|
+
msh.FieldMetadata: dc.replace(
|
|
37
|
+
f.metadata.get(msh.FieldMetadata, msh.FieldMetadata()),
|
|
38
|
+
marshaler=StrOrSecretMarshaler(),
|
|
39
|
+
unmarshaler=StrOrSecretUnmarshaler(),
|
|
40
|
+
),
|
|
41
|
+
})
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""
|
|
2
|
+
~> https://github.com/pallets/werkzeug/blob/7a76170c473c26685bdfa2774d083ba2386fc60f/src/werkzeug/security.py
|
|
3
|
+
"""
|
|
4
|
+
# Copyright 2007 Pallets
|
|
5
|
+
#
|
|
6
|
+
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
|
|
7
|
+
# following conditions are met:
|
|
8
|
+
#
|
|
9
|
+
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
|
|
10
|
+
# disclaimer.
|
|
11
|
+
#
|
|
12
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
|
|
13
|
+
# following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
14
|
+
#
|
|
15
|
+
# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
|
|
16
|
+
# products derived from this software without specific prior written permission.
|
|
17
|
+
#
|
|
18
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
|
19
|
+
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
20
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
21
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
22
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
23
|
+
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
24
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
25
|
+
import hashlib
|
|
26
|
+
import hmac
|
|
27
|
+
import secrets
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
SALT_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
|
31
|
+
DEFAULT_PBKDF2_ITERATIONS = 600000
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def gen_salt(length: int) -> str:
|
|
35
|
+
if length <= 0:
|
|
36
|
+
raise ValueError('Salt length must be at least 1.')
|
|
37
|
+
|
|
38
|
+
return ''.join(secrets.choice(SALT_CHARS) for _ in range(length))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _hash_internal(
|
|
42
|
+
method: str,
|
|
43
|
+
salt: str,
|
|
44
|
+
password: str,
|
|
45
|
+
) -> tuple[str, str]:
|
|
46
|
+
method, *args = method.split(':')
|
|
47
|
+
salt_bytes = salt.encode()
|
|
48
|
+
password_bytes = password.encode()
|
|
49
|
+
|
|
50
|
+
if method == 'scrypt':
|
|
51
|
+
if not args:
|
|
52
|
+
n = 2 ** 15
|
|
53
|
+
r = 8
|
|
54
|
+
p = 1
|
|
55
|
+
else:
|
|
56
|
+
try:
|
|
57
|
+
n, r, p = map(int, args)
|
|
58
|
+
except ValueError:
|
|
59
|
+
raise ValueError("'scrypt' takes 3 arguments.") from None
|
|
60
|
+
|
|
61
|
+
maxmem = 132 * n * r * p # ideally 128, but some extra seems needed
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
hashlib.scrypt(
|
|
65
|
+
password_bytes,
|
|
66
|
+
salt=salt_bytes,
|
|
67
|
+
n=n,
|
|
68
|
+
r=r,
|
|
69
|
+
p=p,
|
|
70
|
+
maxmem=maxmem,
|
|
71
|
+
).hex(),
|
|
72
|
+
f'scrypt:{n}:{r}:{p}',
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
elif method == 'pbkdf2':
|
|
76
|
+
len_args = len(args)
|
|
77
|
+
|
|
78
|
+
if len_args == 0:
|
|
79
|
+
hash_name = 'sha256'
|
|
80
|
+
iterations = DEFAULT_PBKDF2_ITERATIONS
|
|
81
|
+
elif len_args == 1:
|
|
82
|
+
hash_name = args[0]
|
|
83
|
+
iterations = DEFAULT_PBKDF2_ITERATIONS
|
|
84
|
+
elif len_args == 2:
|
|
85
|
+
hash_name = args[0]
|
|
86
|
+
iterations = int(args[1])
|
|
87
|
+
else:
|
|
88
|
+
raise ValueError("'pbkdf2' takes 2 arguments.")
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
hashlib.pbkdf2_hmac(
|
|
92
|
+
hash_name,
|
|
93
|
+
password_bytes,
|
|
94
|
+
salt_bytes,
|
|
95
|
+
iterations,
|
|
96
|
+
).hex(),
|
|
97
|
+
f'pbkdf2:{hash_name}:{iterations}',
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
else:
|
|
101
|
+
raise ValueError(f"Invalid hash method '{method}'.")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def generate_password_hash(
|
|
105
|
+
password: str,
|
|
106
|
+
method: str = 'scrypt',
|
|
107
|
+
salt_length: int = 16,
|
|
108
|
+
) -> str:
|
|
109
|
+
salt = gen_salt(salt_length)
|
|
110
|
+
h, actual_method = _hash_internal(method, salt, password)
|
|
111
|
+
return f'{actual_method}${salt}${h}'
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def check_password_hash(pwhash: str, password: str) -> bool:
|
|
115
|
+
try:
|
|
116
|
+
method, salt, hashval = pwhash.split('$', 2)
|
|
117
|
+
except ValueError:
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
return hmac.compare_digest(_hash_internal(method, salt, password)[0], hashval)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import typing as ta
|
|
3
|
+
|
|
4
|
+
from .. import dataclasses as dc
|
|
5
|
+
from .. import lang
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
##
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dc.dataclass(frozen=True)
|
|
12
|
+
class Secret:
|
|
13
|
+
key: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
##
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Secrets(lang.Abstract):
|
|
20
|
+
def fix(self, obj: str | Secret) -> str:
|
|
21
|
+
if isinstance(obj, str):
|
|
22
|
+
return obj
|
|
23
|
+
elif isinstance(obj, Secret):
|
|
24
|
+
return self.get(obj.key)
|
|
25
|
+
else:
|
|
26
|
+
raise TypeError(obj)
|
|
27
|
+
|
|
28
|
+
@abc.abstractmethod
|
|
29
|
+
def get(self, key: str) -> str:
|
|
30
|
+
raise NotImplementedError
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class EmptySecrets(Secrets):
|
|
34
|
+
def get(self, key: str) -> str:
|
|
35
|
+
raise KeyError(key)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
EMPTY_SECRETS = EmptySecrets()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class SimpleSecrets(Secrets):
|
|
42
|
+
def __init__(self, dct: ta.Mapping[str, str]) -> None:
|
|
43
|
+
super().__init__()
|
|
44
|
+
self._dct = dct
|
|
45
|
+
|
|
46
|
+
def get(self, key: str) -> str:
|
|
47
|
+
return self._dct[key]
|
omlish/serde/__init__.py
ADDED
|
File without changes
|