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/reflect.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""
|
|
2
2
|
TODO:
|
|
3
|
-
-
|
|
3
|
+
- visitor / transformer
|
|
4
4
|
- uniform collection isinstance - items() for mappings, iter() for other
|
|
5
5
|
- also check instance type in isinstance not just items lol
|
|
6
6
|
- ta.Generic in mro causing trouble - omit? no longer 1:1
|
|
@@ -8,6 +8,7 @@ TODO:
|
|
|
8
8
|
- cache __hash__ in Generic/Union
|
|
9
9
|
"""
|
|
10
10
|
import collections.abc
|
|
11
|
+
import dataclasses as dc
|
|
11
12
|
import types
|
|
12
13
|
import typing as ta
|
|
13
14
|
|
|
@@ -27,9 +28,10 @@ _NONE_TYPE_FROZENSET: frozenset['Type'] = frozenset([_NoneType])
|
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
_GenericAlias = ta._GenericAlias # type: ignore # noqa
|
|
30
|
-
|
|
31
|
+
_CallableGenericAlias = ta._CallableGenericAlias # type: ignore # noqa
|
|
31
32
|
_SpecialGenericAlias = ta._SpecialGenericAlias # type: ignore # noqa
|
|
32
33
|
_UnionGenericAlias = ta._UnionGenericAlias # type: ignore # noqa
|
|
34
|
+
_AnnotatedAlias = ta._AnnotatedAlias # type: ignore # noqa
|
|
33
35
|
|
|
34
36
|
|
|
35
37
|
##
|
|
@@ -57,9 +59,11 @@ _KNOWN_SPECIALS_BY_NAME = {s.name: s for s in _KNOWN_SPECIALS}
|
|
|
57
59
|
_KNOWN_SPECIALS_BY_ALIAS = {s.alias: s for s in _KNOWN_SPECIALS}
|
|
58
60
|
_KNOWN_SPECIALS_BY_ORIGIN = {s.origin: s for s in _KNOWN_SPECIALS}
|
|
59
61
|
|
|
62
|
+
_MAX_KNOWN_SPECIAL_TYPE_VARS = 16
|
|
63
|
+
|
|
60
64
|
_KNOWN_SPECIAL_TYPE_VARS = tuple(
|
|
61
65
|
ta.TypeVar(f'_{i}') # noqa
|
|
62
|
-
for i in range(
|
|
66
|
+
for i in range(_MAX_KNOWN_SPECIAL_TYPE_VARS)
|
|
63
67
|
)
|
|
64
68
|
|
|
65
69
|
|
|
@@ -76,28 +80,40 @@ def get_params(obj: ta.Any) -> tuple[ta.TypeVar, ...]:
|
|
|
76
80
|
|
|
77
81
|
oty = type(obj)
|
|
78
82
|
|
|
79
|
-
if
|
|
83
|
+
if (
|
|
84
|
+
oty is _GenericAlias or
|
|
85
|
+
oty is ta.GenericAlias # type: ignore # noqa
|
|
86
|
+
):
|
|
80
87
|
return obj.__dict__.get('__parameters__', ()) # noqa
|
|
81
88
|
|
|
89
|
+
if oty is _CallableGenericAlias:
|
|
90
|
+
raise NotImplementedError('get_params not yet implemented for typing.Callable')
|
|
91
|
+
|
|
82
92
|
raise TypeError(obj)
|
|
83
93
|
|
|
84
94
|
|
|
85
95
|
def is_union_type(cls: ta.Any) -> bool:
|
|
86
96
|
if hasattr(ta, 'UnionType'):
|
|
87
97
|
return ta.get_origin(cls) in {ta.Union, getattr(ta, 'UnionType')}
|
|
98
|
+
|
|
88
99
|
else:
|
|
89
100
|
return ta.get_origin(cls) in {ta.Union}
|
|
90
101
|
|
|
91
102
|
|
|
103
|
+
def get_orig_class(obj: ta.Any) -> ta.Any:
|
|
104
|
+
return obj.__orig_class__ # noqa
|
|
105
|
+
|
|
106
|
+
|
|
92
107
|
##
|
|
93
108
|
|
|
94
109
|
|
|
95
110
|
Type = ta.Union[
|
|
96
111
|
type,
|
|
97
112
|
ta.TypeVar,
|
|
98
|
-
'NewType',
|
|
99
113
|
'Union',
|
|
100
114
|
'Generic',
|
|
115
|
+
'NewType',
|
|
116
|
+
'Annotated',
|
|
101
117
|
]
|
|
102
118
|
|
|
103
119
|
|
|
@@ -117,19 +133,26 @@ class Union(ta.NamedTuple):
|
|
|
117
133
|
return Union(rem)
|
|
118
134
|
|
|
119
135
|
|
|
120
|
-
|
|
136
|
+
@dc.dataclass(frozen=True)
|
|
137
|
+
class Generic:
|
|
121
138
|
cls: type
|
|
122
|
-
args: tuple[Type, ...]
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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)
|
|
126
145
|
|
|
127
|
-
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:
|
|
128
151
|
return (
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
152
|
+
self.cls == other.cls and
|
|
153
|
+
self.args == other.args and
|
|
154
|
+
self.params == other.params and
|
|
155
|
+
self.obj == other.obj
|
|
133
156
|
)
|
|
134
157
|
|
|
135
158
|
|
|
@@ -137,15 +160,50 @@ class NewType(ta.NamedTuple):
|
|
|
137
160
|
obj: ta.Any
|
|
138
161
|
|
|
139
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
|
+
|
|
140
171
|
TYPES: tuple[type, ...] = (
|
|
141
172
|
type,
|
|
142
173
|
ta.TypeVar,
|
|
143
174
|
Union,
|
|
144
175
|
Generic,
|
|
145
176
|
NewType,
|
|
177
|
+
Annotated,
|
|
146
178
|
)
|
|
147
179
|
|
|
148
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
|
+
|
|
149
207
|
def type_(obj: ta.Any) -> Type:
|
|
150
208
|
if isinstance(obj, (Union, Generic, ta.TypeVar, NewType)): # noqa
|
|
151
209
|
return obj
|
|
@@ -156,15 +214,22 @@ def type_(obj: ta.Any) -> Type:
|
|
|
156
214
|
return Union(frozenset(type_(a) for a in ta.get_args(obj)))
|
|
157
215
|
|
|
158
216
|
if isinstance(obj, ta.NewType): # noqa
|
|
159
|
-
return NewType(
|
|
217
|
+
return NewType(obj)
|
|
160
218
|
|
|
161
219
|
if (
|
|
162
220
|
oty is _GenericAlias or
|
|
163
|
-
oty is ta.GenericAlias # type: ignore # noqa
|
|
221
|
+
oty is ta.GenericAlias or # type: ignore # noqa
|
|
222
|
+
oty is _CallableGenericAlias
|
|
164
223
|
):
|
|
165
224
|
origin = ta.get_origin(obj)
|
|
166
225
|
args = ta.get_args(obj)
|
|
167
|
-
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:
|
|
168
233
|
params = args
|
|
169
234
|
else:
|
|
170
235
|
params = get_params(origin)
|
|
@@ -198,15 +263,52 @@ def type_(obj: ta.Any) -> Type:
|
|
|
198
263
|
obj,
|
|
199
264
|
)
|
|
200
265
|
|
|
266
|
+
if isinstance(obj, _AnnotatedAlias):
|
|
267
|
+
o = ta.get_args(obj)[0]
|
|
268
|
+
return Annotated(type_(o), md=obj.__metadata__, obj=obj)
|
|
269
|
+
|
|
201
270
|
raise TypeError(obj)
|
|
202
271
|
|
|
203
272
|
|
|
204
273
|
##
|
|
205
274
|
|
|
206
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
|
+
|
|
207
308
|
def types_equivalent(l: Type, r: Type) -> bool:
|
|
208
309
|
if isinstance(l, Generic) and isinstance(r, Generic):
|
|
209
310
|
return l.cls == r.cls and l.args == r.args
|
|
311
|
+
|
|
210
312
|
return l == r
|
|
211
313
|
|
|
212
314
|
|
|
@@ -217,28 +319,36 @@ def get_underlying(nt: NewType) -> Type:
|
|
|
217
319
|
def get_concrete_type(ty: Type) -> type | None:
|
|
218
320
|
if isinstance(ty, type):
|
|
219
321
|
return ty
|
|
322
|
+
|
|
220
323
|
if isinstance(ty, Generic):
|
|
221
324
|
return ty.cls
|
|
325
|
+
|
|
222
326
|
if isinstance(ty, NewType):
|
|
223
327
|
return get_concrete_type(get_underlying(ty))
|
|
328
|
+
|
|
224
329
|
if isinstance(ty, (Union, ta.TypeVar)):
|
|
225
330
|
return None
|
|
331
|
+
|
|
226
332
|
raise TypeError(ty)
|
|
227
333
|
|
|
228
334
|
|
|
229
335
|
def get_type_var_replacements(ty: Type) -> ta.Mapping[ta.TypeVar, Type]:
|
|
230
336
|
if isinstance(ty, Generic):
|
|
231
337
|
return dict(zip(ty.params, ty.args))
|
|
338
|
+
|
|
232
339
|
return {}
|
|
233
340
|
|
|
234
341
|
|
|
235
342
|
def to_annotation(ty: Type) -> ta.Any:
|
|
236
343
|
if isinstance(ty, Generic):
|
|
237
344
|
return ty.obj if ty.obj is not None else ty.cls
|
|
345
|
+
|
|
238
346
|
if isinstance(ty, Union):
|
|
239
347
|
return ta.Union[*tuple(to_annotation(e) for e in ty.args)]
|
|
348
|
+
|
|
240
349
|
if isinstance(ty, (type, ta.TypeVar, NewType)):
|
|
241
350
|
return ty
|
|
351
|
+
|
|
242
352
|
raise TypeError(ty)
|
|
243
353
|
|
|
244
354
|
|
|
@@ -251,8 +361,10 @@ def replace_type_vars(
|
|
|
251
361
|
def rec(cur):
|
|
252
362
|
if isinstance(cur, type):
|
|
253
363
|
return cur
|
|
364
|
+
|
|
254
365
|
if isinstance(cur, NewType):
|
|
255
366
|
return cur
|
|
367
|
+
|
|
256
368
|
if isinstance(cur, Generic):
|
|
257
369
|
args = tuple(rec(a) for a in cur.args)
|
|
258
370
|
if update_aliases:
|
|
@@ -266,11 +378,14 @@ def replace_type_vars(
|
|
|
266
378
|
obj = cur.obj[*nargs]
|
|
267
379
|
else:
|
|
268
380
|
obj = None
|
|
269
|
-
return
|
|
381
|
+
return dc.replace(cur, args=args, obj=obj)
|
|
382
|
+
|
|
270
383
|
if isinstance(cur, Union):
|
|
271
384
|
return Union(frozenset(rec(e) for e in cur.args))
|
|
385
|
+
|
|
272
386
|
if isinstance(cur, ta.TypeVar):
|
|
273
387
|
return rpl[cur]
|
|
388
|
+
|
|
274
389
|
raise TypeError(cur)
|
|
275
390
|
|
|
276
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
|
|
@@ -332,6 +332,7 @@ class DotEnv:
|
|
|
332
332
|
encoding: str | None = None,
|
|
333
333
|
interpolate: bool = True,
|
|
334
334
|
override: bool = True,
|
|
335
|
+
env: ta.Mapping[str, str] | None = None,
|
|
335
336
|
) -> None:
|
|
336
337
|
super().__init__()
|
|
337
338
|
self.path: StrPath | None = path
|
|
@@ -341,6 +342,7 @@ class DotEnv:
|
|
|
341
342
|
self.encoding: str | None = encoding
|
|
342
343
|
self.interpolate: bool = interpolate
|
|
343
344
|
self.override: bool = override
|
|
345
|
+
self.env = env or {}
|
|
344
346
|
|
|
345
347
|
@contextlib.contextmanager
|
|
346
348
|
def _get_stream(self) -> ta.Iterator[ta.IO[str]]:
|
|
@@ -364,7 +366,7 @@ class DotEnv:
|
|
|
364
366
|
raw_values = self.parse()
|
|
365
367
|
|
|
366
368
|
if self.interpolate:
|
|
367
|
-
self._dict = resolve_variables(raw_values, override=self.override)
|
|
369
|
+
self._dict = resolve_variables(raw_values, override=self.override, env=self.env)
|
|
368
370
|
else:
|
|
369
371
|
self._dict = dict(raw_values)
|
|
370
372
|
|
|
@@ -376,22 +378,7 @@ class DotEnv:
|
|
|
376
378
|
if mapping.key is not None:
|
|
377
379
|
yield mapping.key, mapping.value
|
|
378
380
|
|
|
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
381
|
def get(self, key: str) -> str | None:
|
|
393
|
-
"""
|
|
394
|
-
"""
|
|
395
382
|
data = self.dict()
|
|
396
383
|
|
|
397
384
|
if key in data:
|
|
@@ -525,8 +512,9 @@ def unset_key(
|
|
|
525
512
|
|
|
526
513
|
|
|
527
514
|
def resolve_variables(
|
|
528
|
-
|
|
529
|
-
|
|
515
|
+
values: ta.Iterable[tuple[str, str | None]],
|
|
516
|
+
override: bool,
|
|
517
|
+
env: ta.Mapping[str, str],
|
|
530
518
|
) -> dict[str, str | None]:
|
|
531
519
|
new_values: dict[str, str | None] = {}
|
|
532
520
|
|
|
@@ -535,14 +523,14 @@ def resolve_variables(
|
|
|
535
523
|
result = None
|
|
536
524
|
else:
|
|
537
525
|
atoms = parse_variables(value)
|
|
538
|
-
|
|
526
|
+
aenv: dict[str, str | None] = {}
|
|
539
527
|
if override:
|
|
540
|
-
|
|
541
|
-
|
|
528
|
+
aenv.update(env)
|
|
529
|
+
aenv.update(new_values)
|
|
542
530
|
else:
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
result = ''.join(atom.resolve(
|
|
531
|
+
aenv.update(new_values)
|
|
532
|
+
aenv.update(env)
|
|
533
|
+
result = ''.join(atom.resolve(aenv) for atom in atoms)
|
|
546
534
|
|
|
547
535
|
new_values[name] = result
|
|
548
536
|
|
omlish/{json.py → serde/json.py}
RENAMED
|
@@ -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
|
|