omlish 0.0.0.dev284__py3-none-any.whl → 0.0.0.dev286__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.
- omlish/__about__.py +6 -2
- omlish/dataclasses/__init__.py +58 -60
- omlish/dataclasses/api/__init__.py +25 -0
- omlish/dataclasses/api/classes/__init__.py +0 -0
- omlish/dataclasses/api/classes/conversion.py +30 -0
- omlish/dataclasses/api/classes/decorator.py +145 -0
- omlish/dataclasses/api/classes/make.py +109 -0
- omlish/dataclasses/api/classes/metadata.py +133 -0
- omlish/dataclasses/api/classes/params.py +78 -0
- omlish/dataclasses/api/fields/__init__.py +0 -0
- omlish/dataclasses/api/fields/building.py +120 -0
- omlish/dataclasses/api/fields/constructor.py +56 -0
- omlish/dataclasses/api/fields/conversion.py +191 -0
- omlish/dataclasses/api/fields/metadata.py +94 -0
- omlish/dataclasses/concerns/__init__.py +17 -0
- omlish/dataclasses/concerns/abc.py +15 -0
- omlish/dataclasses/concerns/copy.py +63 -0
- omlish/dataclasses/concerns/doc.py +53 -0
- omlish/dataclasses/concerns/eq.py +60 -0
- omlish/dataclasses/concerns/fields.py +119 -0
- omlish/dataclasses/concerns/frozen.py +133 -0
- omlish/dataclasses/concerns/hash.py +165 -0
- omlish/dataclasses/concerns/init.py +453 -0
- omlish/dataclasses/concerns/matchargs.py +27 -0
- omlish/dataclasses/concerns/mro.py +16 -0
- omlish/dataclasses/concerns/order.py +87 -0
- omlish/dataclasses/concerns/override.py +98 -0
- omlish/dataclasses/concerns/params.py +14 -0
- omlish/dataclasses/concerns/replace.py +48 -0
- omlish/dataclasses/concerns/repr.py +95 -0
- omlish/dataclasses/{impl → concerns}/slots.py +25 -1
- omlish/dataclasses/debug.py +2 -0
- omlish/dataclasses/errors.py +115 -0
- omlish/dataclasses/generation/__init__.py +0 -0
- omlish/dataclasses/generation/base.py +38 -0
- omlish/dataclasses/generation/compilation.py +258 -0
- omlish/dataclasses/generation/execution.py +195 -0
- omlish/dataclasses/generation/globals.py +83 -0
- omlish/dataclasses/generation/idents.py +6 -0
- omlish/dataclasses/generation/mangling.py +18 -0
- omlish/dataclasses/generation/manifests.py +20 -0
- omlish/dataclasses/generation/ops.py +97 -0
- omlish/dataclasses/generation/plans.py +35 -0
- omlish/dataclasses/generation/processor.py +179 -0
- omlish/dataclasses/generation/registry.py +42 -0
- omlish/dataclasses/generation/utils.py +83 -0
- omlish/dataclasses/{impl/reflect.py → inspect.py} +53 -90
- omlish/dataclasses/{impl/internals.py → internals.py} +26 -32
- omlish/dataclasses/metaclass/__init__.py +0 -0
- omlish/dataclasses/metaclass/bases.py +69 -0
- omlish/dataclasses/metaclass/confer.py +65 -0
- omlish/dataclasses/metaclass/meta.py +115 -0
- omlish/dataclasses/metaclass/specs.py +38 -0
- omlish/dataclasses/processing/__init__.py +0 -0
- omlish/dataclasses/processing/base.py +83 -0
- omlish/dataclasses/processing/driving.py +49 -0
- omlish/dataclasses/processing/priority.py +13 -0
- omlish/dataclasses/processing/registry.py +81 -0
- omlish/dataclasses/reflection.py +81 -0
- omlish/dataclasses/specs.py +224 -0
- omlish/dataclasses/tools/__init__.py +0 -0
- omlish/dataclasses/{impl → tools}/as_.py +23 -8
- omlish/dataclasses/tools/iter.py +27 -0
- omlish/dataclasses/tools/modifiers.py +52 -0
- omlish/dataclasses/tools/replace.py +17 -0
- omlish/dataclasses/tools/repr.py +12 -0
- omlish/dataclasses/{static.py → tools/static.py} +25 -4
- omlish/dataclasses/utils.py +54 -109
- omlish/diag/__init__.py +4 -4
- omlish/inject/impl/origins.py +1 -1
- omlish/lang/cached/function.py +4 -2
- omlish/lang/maybes.py +17 -0
- omlish/lite/maybes.py +17 -0
- omlish/marshal/objects/dataclasses.py +3 -7
- omlish/marshal/objects/helpers.py +3 -3
- omlish/secrets/marshal.py +1 -1
- omlish/secrets/secrets.py +1 -1
- omlish/sql/queries/base.py +1 -1
- omlish/text/minja.py +81 -25
- omlish/text/templating.py +116 -0
- omlish/typedvalues/marshal.py +2 -2
- {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev286.dist-info}/METADATA +4 -1
- {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev286.dist-info}/RECORD +87 -46
- omlish/dataclasses/impl/LICENSE +0 -279
- omlish/dataclasses/impl/__init__.py +0 -33
- omlish/dataclasses/impl/api.py +0 -278
- omlish/dataclasses/impl/copy.py +0 -30
- omlish/dataclasses/impl/errors.py +0 -53
- omlish/dataclasses/impl/fields.py +0 -245
- omlish/dataclasses/impl/frozen.py +0 -93
- omlish/dataclasses/impl/hashing.py +0 -86
- omlish/dataclasses/impl/init.py +0 -199
- omlish/dataclasses/impl/main.py +0 -93
- omlish/dataclasses/impl/metaclass.py +0 -235
- omlish/dataclasses/impl/metadata.py +0 -75
- omlish/dataclasses/impl/order.py +0 -57
- omlish/dataclasses/impl/overrides.py +0 -53
- omlish/dataclasses/impl/params.py +0 -128
- omlish/dataclasses/impl/processing.py +0 -24
- omlish/dataclasses/impl/replace.py +0 -40
- omlish/dataclasses/impl/repr.py +0 -66
- omlish/dataclasses/impl/simple.py +0 -50
- omlish/dataclasses/impl/utils.py +0 -167
- {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev286.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev286.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev286.dist-info}/licenses/LICENSE +0 -0
- {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev286.dist-info}/top_level.txt +0 -0
@@ -8,9 +8,9 @@ import copy
|
|
8
8
|
import dataclasses as dc
|
9
9
|
import typing as ta
|
10
10
|
|
11
|
-
from
|
12
|
-
from
|
13
|
-
from .
|
11
|
+
from ... import lang
|
12
|
+
from ...lite.dataclasses import is_immediate_dataclass
|
13
|
+
from ..api.classes.decorator import dataclass
|
14
14
|
|
15
15
|
|
16
16
|
##
|
@@ -122,6 +122,27 @@ class Static(lang.Abstract):
|
|
122
122
|
# Use a default_factory to allow unsafe (mutable) values.
|
123
123
|
new_fld.default_factory = (lambda v2: lambda: v2)(v) # noqa
|
124
124
|
|
125
|
+
# FIXME
|
126
|
+
from ..api.fields.metadata import _ExtraFieldParamsMetadata # noqa
|
127
|
+
from ..specs import FieldSpec
|
128
|
+
try:
|
129
|
+
x_fs = fld.metadata[FieldSpec]
|
130
|
+
except KeyError:
|
131
|
+
pass
|
132
|
+
else:
|
133
|
+
n_md = {
|
134
|
+
k: v
|
135
|
+
for k, v in fld.metadata.items()
|
136
|
+
if k not in (FieldSpec, _ExtraFieldParamsMetadata)
|
137
|
+
}
|
138
|
+
n_md[_ExtraFieldParamsMetadata] = {
|
139
|
+
fs_f.name: getattr(x_fs, fs_f.name)
|
140
|
+
for fs_f in dc.fields(FieldSpec) # noqa
|
141
|
+
if fs_f not in dc.Field.__slots__ # type: ignore[attr-defined]
|
142
|
+
and fs_f.name not in ('default', 'default_factory')
|
143
|
+
}
|
144
|
+
new_fld.metadata = n_md # type: ignore[assignment]
|
145
|
+
|
125
146
|
setattr(cls, fld.name, new_fld)
|
126
147
|
new_anns[fld.name] = fld.type
|
127
148
|
|
@@ -144,7 +165,7 @@ class Static(lang.Abstract):
|
|
144
165
|
)
|
145
166
|
|
146
167
|
# Explicitly forbid dc transforms that rebuild the class, such as slots.
|
147
|
-
if (dc_cls := dataclass(
|
168
|
+
if (dc_cls := dataclass(frozen=True)(cls)) is not cls:
|
148
169
|
raise TypeError(dc_cls)
|
149
170
|
|
150
171
|
dc_flds = dc.fields(cls) # type: ignore[arg-type] # noqa
|
omlish/dataclasses/utils.py
CHANGED
@@ -1,153 +1,98 @@
|
|
1
|
+
import ast
|
1
2
|
import collections
|
2
|
-
import
|
3
|
+
import functools
|
3
4
|
import types
|
4
5
|
import typing as ta
|
5
6
|
|
6
7
|
from .. import check
|
7
|
-
from .impl.metadata import METADATA_ATTR
|
8
|
-
from .impl.metadata import UserMetadata
|
9
|
-
from .impl.params import DEFAULT_FIELD_EXTRAS
|
10
|
-
from .impl.params import FieldExtras
|
11
|
-
from .impl.params import get_field_extras
|
12
8
|
|
13
9
|
|
14
10
|
T = ta.TypeVar('T')
|
15
11
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
def opt_repr(o: ta.Any) -> str | None:
|
21
|
-
return repr(o) if o is not None else None
|
22
|
-
|
23
|
-
|
24
|
-
def truthy_repr(o: ta.Any) -> str | None:
|
25
|
-
return repr(o) if o else None
|
12
|
+
K = ta.TypeVar('K')
|
13
|
+
V = ta.TypeVar('V')
|
26
14
|
|
27
15
|
|
28
16
|
##
|
29
17
|
|
30
18
|
|
31
|
-
def
|
32
|
-
|
19
|
+
def repr_round_trip_value(v: T) -> T:
|
20
|
+
r = repr(v)
|
21
|
+
v2 = ast.literal_eval(r)
|
22
|
+
if v != v2:
|
23
|
+
raise ValueError(v)
|
24
|
+
return v2
|
33
25
|
|
34
26
|
|
35
27
|
##
|
36
28
|
|
37
29
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
30
|
+
def set_qualname(cls: type, value: T) -> T:
|
31
|
+
if isinstance(value, types.FunctionType):
|
32
|
+
value.__qualname__ = f'{cls.__qualname__}.{value.__name__}'
|
33
|
+
return value
|
42
34
|
|
43
|
-
def __ror__(self, other: T) -> T:
|
44
|
-
return self(other)
|
45
35
|
|
46
|
-
|
47
|
-
|
36
|
+
def set_new_attribute(cls: type, name: str, value: ta.Any) -> bool:
|
37
|
+
if name in cls.__dict__:
|
38
|
+
return True
|
39
|
+
set_qualname(cls, value)
|
40
|
+
setattr(cls, name, value)
|
41
|
+
return False
|
48
42
|
|
49
43
|
|
50
|
-
|
51
|
-
return types.MappingProxyType(collections.ChainMap(*mds)) # type: ignore # noqa
|
44
|
+
##
|
52
45
|
|
53
46
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
md.setdefault(UserMetadata, []).extend(args)
|
58
|
-
return cls
|
47
|
+
class SealableRegistry(ta.Generic[K, V]):
|
48
|
+
def __init__(self) -> None:
|
49
|
+
super().__init__()
|
59
50
|
|
51
|
+
self._dct: dict[K, V] = {}
|
52
|
+
self._sealed = False
|
60
53
|
|
61
|
-
def
|
62
|
-
|
63
|
-
f.metadata = chain_metadata(nmd, f.metadata)
|
64
|
-
return f
|
54
|
+
def seal(self) -> None:
|
55
|
+
self._sealed = True
|
65
56
|
|
57
|
+
def __setitem__(self, k: K, v: V) -> None:
|
58
|
+
check.state(not self._sealed)
|
59
|
+
check.not_in(k, self._dct)
|
60
|
+
self._dct[k] = v
|
66
61
|
|
67
|
-
def
|
68
|
-
|
69
|
-
|
70
|
-
FieldExtras: dc.replace(fe, **{
|
71
|
-
k: v
|
72
|
-
for k, v in kwargs.items()
|
73
|
-
if not unless_non_default or v != getattr(DEFAULT_FIELD_EXTRAS, k)
|
74
|
-
}),
|
75
|
-
})
|
62
|
+
def __getitem__(self, k: K) -> V:
|
63
|
+
self.seal()
|
64
|
+
return self._dct[k]
|
76
65
|
|
66
|
+
def items(self) -> ta.Iterator[tuple[K, V]]:
|
67
|
+
self.seal()
|
68
|
+
return iter(self._dct.items())
|
77
69
|
|
78
|
-
def update_fields(
|
79
|
-
fn: ta.Callable[[str, dc.Field], dc.Field],
|
80
|
-
fields: ta.Iterable[str] | None = None,
|
81
|
-
) -> ta.Callable[[type[T]], type[T]]:
|
82
|
-
def inner(cls):
|
83
|
-
if fields is None:
|
84
|
-
for a, v in list(cls.__dict__.items()):
|
85
|
-
if isinstance(v, dc.Field):
|
86
|
-
setattr(cls, a, fn(a, v))
|
87
70
|
|
88
|
-
|
89
|
-
for a in fields:
|
90
|
-
try:
|
91
|
-
v = cls.__dict__[a]
|
92
|
-
except KeyError:
|
93
|
-
v = dc.field()
|
94
|
-
else:
|
95
|
-
if not isinstance(v, dc.Field):
|
96
|
-
v = dc.field(default=v)
|
97
|
-
setattr(cls, a, fn(a, v))
|
71
|
+
##
|
98
72
|
|
99
|
-
return cls
|
100
73
|
|
101
|
-
|
74
|
+
def class_decorator(fn):
|
75
|
+
@functools.wraps(fn)
|
76
|
+
def inner(cls=None, *args, **kwargs):
|
77
|
+
if cls is None:
|
78
|
+
return lambda cls: fn(cls, *args, **kwargs) # noqa
|
79
|
+
return fn(cls, *args, **kwargs)
|
102
80
|
return inner
|
103
81
|
|
104
82
|
|
105
|
-
def update_fields_metadata(
|
106
|
-
nmd: ta.Mapping,
|
107
|
-
fields: ta.Iterable[str] | None = None,
|
108
|
-
) -> ta.Callable[[type[T]], type[T]]:
|
109
|
-
def inner(a: str, f: dc.Field) -> dc.Field:
|
110
|
-
return update_field_metadata(f, nmd)
|
111
|
-
|
112
|
-
return update_fields(inner, fields)
|
113
|
-
|
114
|
-
|
115
83
|
##
|
116
84
|
|
117
85
|
|
118
|
-
|
119
|
-
return tuple(getattr(o, f.name) for f in dc.fields(o))
|
86
|
+
_EMPTY_MAPPING_PROXY: ta.Mapping = types.MappingProxyType({})
|
120
87
|
|
121
88
|
|
122
|
-
def
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
def deep_replace(o: T, *args: str | ta.Callable[[ta.Any], ta.Mapping[str, ta.Any]]) -> T:
|
130
|
-
if not args:
|
131
|
-
return o
|
132
|
-
elif len(args) == 1:
|
133
|
-
return dc.replace(o, **args[0](o)) # type: ignore
|
89
|
+
def chain_mapping_proxy(*ms: ta.Mapping) -> types.MappingProxyType:
|
90
|
+
m: ta.Any
|
91
|
+
if len(ms) > 1:
|
92
|
+
m = collections.ChainMap(*ms) # type: ignore[arg-type]
|
93
|
+
elif ms:
|
94
|
+
[m] = ms
|
134
95
|
else:
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
##
|
139
|
-
|
140
|
-
|
141
|
-
def iter_items(obj: ta.Any) -> ta.Iterator[tuple[str, ta.Any]]:
|
142
|
-
for f in dc.fields(obj):
|
143
|
-
yield (f.name, getattr(obj, f.name))
|
144
|
-
|
145
|
-
|
146
|
-
def iter_keys(obj: ta.Any) -> ta.Iterator[str]:
|
147
|
-
for f in dc.fields(obj):
|
148
|
-
yield f.name
|
149
|
-
|
96
|
+
m = _EMPTY_MAPPING_PROXY
|
150
97
|
|
151
|
-
|
152
|
-
for f in dc.fields(obj):
|
153
|
-
yield getattr(obj, f.name)
|
98
|
+
return types.MappingProxyType(m)
|
omlish/diag/__init__.py
CHANGED
@@ -17,10 +17,10 @@ Debuggers
|
|
17
17
|
|
18
18
|
CPU Profilers
|
19
19
|
- cProfile - https://docs.python.org/3/library/profile.html
|
20
|
-
-
|
21
|
-
-
|
22
|
-
-
|
23
|
-
-
|
20
|
+
- yappi - https://github.com/sumerc/yappi - tracing
|
21
|
+
- pyinstrument - https://github.com/joerick/pyinstrument - pretty
|
22
|
+
- py-spy - https://github.com/benfred/py-spy - busted on mac
|
23
|
+
- austin-dist - https://github.com/P403n1x87/austin - old
|
24
24
|
|
25
25
|
Memory Profilers
|
26
26
|
- tracemalloc - https://docs.python.org/3/library/tracemalloc.html
|
omlish/inject/impl/origins.py
CHANGED
omlish/lang/cached/function.py
CHANGED
@@ -151,11 +151,12 @@ def _make_cache_key_maker(
|
|
151
151
|
|
152
152
|
|
153
153
|
class _CachedFunction(ta.Generic[T], Abstract):
|
154
|
-
@dc.dataclass(frozen=True)
|
154
|
+
@dc.dataclass(frozen=True, kw_only=True)
|
155
155
|
class Opts:
|
156
156
|
map_maker: ta.Callable[[], ta.MutableMapping] = dict
|
157
157
|
lock: DefaultLockable = None
|
158
158
|
transient: bool = False
|
159
|
+
no_wrapper_update: bool = False
|
159
160
|
|
160
161
|
def __init__(
|
161
162
|
self,
|
@@ -175,7 +176,8 @@ class _CachedFunction(ta.Generic[T], Abstract):
|
|
175
176
|
self._lock = default_lock(opts.lock, False)() if opts.lock is not None else None
|
176
177
|
self._values = values if values is not None else opts.map_maker()
|
177
178
|
self._value_fn = value_fn if value_fn is not None else fn
|
178
|
-
|
179
|
+
if not self._opts.no_wrapper_update:
|
180
|
+
functools.update_wrapper(self, fn)
|
179
181
|
|
180
182
|
@property
|
181
183
|
def _fn(self):
|
omlish/lang/maybes.py
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
import abc
|
2
2
|
import typing as ta
|
3
3
|
|
4
|
+
from ..lite.maybes import Maybe as _LiteMaybe
|
5
|
+
from ..lite.maybes import as_maybe as _as_lite_maybe
|
6
|
+
|
4
7
|
|
5
8
|
T = ta.TypeVar('T')
|
6
9
|
U = ta.TypeVar('U')
|
@@ -123,6 +126,9 @@ class _Maybe(Maybe[T], tuple):
|
|
123
126
|
raise exception_supplier()
|
124
127
|
|
125
128
|
|
129
|
+
#
|
130
|
+
|
131
|
+
|
126
132
|
def just(v: T) -> Maybe[T]:
|
127
133
|
return tuple.__new__(_Maybe, (v,)) # noqa
|
128
134
|
|
@@ -138,3 +144,14 @@ def maybe(o: T | None) -> Maybe[T]:
|
|
138
144
|
if o is None:
|
139
145
|
return _empty # noqa
|
140
146
|
return just(o)
|
147
|
+
|
148
|
+
|
149
|
+
##
|
150
|
+
|
151
|
+
|
152
|
+
@_as_lite_maybe.register
|
153
|
+
def _(obj: Maybe) -> _LiteMaybe:
|
154
|
+
if obj.present:
|
155
|
+
return _LiteMaybe.just(obj.must())
|
156
|
+
else:
|
157
|
+
return _LiteMaybe.empty()
|
omlish/lite/maybes.py
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
import abc
|
2
|
+
import functools
|
2
3
|
import typing as ta
|
3
4
|
|
4
5
|
|
5
6
|
T = ta.TypeVar('T')
|
6
7
|
|
7
8
|
|
9
|
+
##
|
10
|
+
|
11
|
+
|
8
12
|
class Maybe(ta.Generic[T]):
|
9
13
|
@property
|
10
14
|
@abc.abstractmethod
|
@@ -43,3 +47,16 @@ class _Maybe(Maybe[T], tuple):
|
|
43
47
|
|
44
48
|
|
45
49
|
Maybe._empty = tuple.__new__(_Maybe, ()) # noqa
|
50
|
+
|
51
|
+
|
52
|
+
##
|
53
|
+
|
54
|
+
|
55
|
+
@functools.singledispatch
|
56
|
+
def as_maybe(obj: ta.Any) -> Maybe:
|
57
|
+
raise TypeError(obj)
|
58
|
+
|
59
|
+
|
60
|
+
@as_maybe.register
|
61
|
+
def _(obj: Maybe) -> Maybe:
|
62
|
+
return obj
|
@@ -34,11 +34,7 @@ from .unmarshal import ObjectUnmarshaler
|
|
34
34
|
|
35
35
|
|
36
36
|
def get_dataclass_metadata(ty: type) -> ObjectMetadata:
|
37
|
-
return check.
|
38
|
-
e
|
39
|
-
for e in dc.get_merged_metadata(ty).get(dc.UserMetadata, [])
|
40
|
-
if isinstance(e, ObjectMetadata)
|
41
|
-
) or ObjectMetadata()
|
37
|
+
return check.single(dc.reflect(ty).spec.metadata_by_type.get(ObjectMetadata) or [ObjectMetadata()])
|
42
38
|
|
43
39
|
|
44
40
|
def get_dataclass_field_infos(
|
@@ -76,10 +72,10 @@ def get_dataclass_field_infos(
|
|
76
72
|
|
77
73
|
f_ty: ta.Any
|
78
74
|
if (
|
79
|
-
|
75
|
+
dc_rfl.spec.generic_init or
|
80
76
|
(fmd is not None and fmd.options.generic_replace)
|
81
77
|
):
|
82
|
-
f_ty = rfl.to_annotation(dc_rfl.generic_replaced_field_type(field.name))
|
78
|
+
f_ty = rfl.to_annotation(dc_rfl.fields_inspection.generic_replaced_field_type(field.name))
|
83
79
|
else:
|
84
80
|
f_ty = type_hints[field.name]
|
85
81
|
|
@@ -14,7 +14,7 @@ T = ta.TypeVar('T')
|
|
14
14
|
def update_field_metadata(**kwargs: ta.Any) -> dc.field_modifier:
|
15
15
|
@dc.field_modifier
|
16
16
|
def inner(f: dc.Field) -> dc.Field:
|
17
|
-
return dc.
|
17
|
+
return dc.set_field_metadata(f, {
|
18
18
|
FieldMetadata: f.metadata.get(FieldMetadata, FieldMetadata()).update(**kwargs),
|
19
19
|
})
|
20
20
|
return inner
|
@@ -25,7 +25,7 @@ def update_fields_metadata(
|
|
25
25
|
**kwargs: ta.Any,
|
26
26
|
) -> ta.Callable[[type[T]], type[T]]:
|
27
27
|
def inner(a: str, f: dc.Field) -> dc.Field:
|
28
|
-
return dc.
|
28
|
+
return dc.set_field_metadata(f, {
|
29
29
|
FieldMetadata: f.metadata.get(FieldMetadata, FieldMetadata()).update(**kwargs),
|
30
30
|
})
|
31
31
|
|
@@ -37,7 +37,7 @@ def update_object_metadata(
|
|
37
37
|
**kwargs: ta.Any,
|
38
38
|
):
|
39
39
|
def inner(cls):
|
40
|
-
return dc.
|
40
|
+
return dc.append_class_metadata(cls, ObjectMetadata(**kwargs))
|
41
41
|
|
42
42
|
if cls is not None:
|
43
43
|
inner(cls)
|
omlish/secrets/marshal.py
CHANGED
@@ -44,7 +44,7 @@ class StrOrSecretRefMarshalerUnmarshaler(msh.Marshaler, msh.Unmarshaler):
|
|
44
44
|
def marshal_secret_field(f: dc.Field) -> dc.Field:
|
45
45
|
"""Mostly obsolete with auto-registration below."""
|
46
46
|
|
47
|
-
return dc.
|
47
|
+
return dc.set_field_metadata(f, {
|
48
48
|
msh.FieldMetadata: dc.replace(
|
49
49
|
f.metadata.get(msh.FieldMetadata, msh.FieldMetadata()),
|
50
50
|
marshaler=StrOrSecretRefMarshalerUnmarshaler(),
|
omlish/secrets/secrets.py
CHANGED
omlish/sql/queries/base.py
CHANGED
omlish/text/minja.py
CHANGED
@@ -5,12 +5,14 @@ TODO:
|
|
5
5
|
- raw
|
6
6
|
- blocks / inheritance
|
7
7
|
"""
|
8
|
+
import dataclasses as dc
|
8
9
|
import io
|
9
10
|
import re
|
10
11
|
import typing as ta
|
11
12
|
|
12
13
|
from ..lite.cached import cached_nullary
|
13
14
|
from ..lite.check import check
|
15
|
+
from ..lite.maybes import Maybe
|
14
16
|
|
15
17
|
|
16
18
|
##
|
@@ -29,6 +31,34 @@ class MinjaTemplate:
|
|
29
31
|
##
|
30
32
|
|
31
33
|
|
34
|
+
@dc.dataclass(frozen=True)
|
35
|
+
class MinjaTemplateParam:
|
36
|
+
name: str
|
37
|
+
default: Maybe[ta.Any] = Maybe.empty()
|
38
|
+
|
39
|
+
def __post_init__(self) -> None:
|
40
|
+
check.arg(self.name.isidentifier())
|
41
|
+
|
42
|
+
@classmethod
|
43
|
+
def of(cls, obj: ta.Union[str, 'MinjaTemplateParam']) -> 'MinjaTemplateParam':
|
44
|
+
if isinstance(obj, MinjaTemplateParam):
|
45
|
+
return obj
|
46
|
+
elif isinstance(obj, str):
|
47
|
+
return MinjaTemplateParam.new(obj)
|
48
|
+
else:
|
49
|
+
raise TypeError(obj)
|
50
|
+
|
51
|
+
@classmethod
|
52
|
+
def new(cls, name: str, *defaults: ta.Any) -> 'MinjaTemplateParam':
|
53
|
+
dfl: Maybe[ta.Any]
|
54
|
+
if defaults:
|
55
|
+
[dv] = defaults
|
56
|
+
dfl = Maybe.just(dv)
|
57
|
+
else:
|
58
|
+
dfl = Maybe.empty()
|
59
|
+
return cls(name, dfl)
|
60
|
+
|
61
|
+
|
32
62
|
class MinjaTemplateCompiler:
|
33
63
|
"""
|
34
64
|
Compiles a template string into a Python function. The returned function takes a dictionary 'context' and returns
|
@@ -43,23 +73,28 @@ class MinjaTemplateCompiler:
|
|
43
73
|
- {# comment #}: Ignored completely.
|
44
74
|
"""
|
45
75
|
|
46
|
-
DEFAULT_INDENT: str = '
|
76
|
+
DEFAULT_INDENT: str = ' ' * 4
|
47
77
|
|
48
78
|
def __init__(
|
49
79
|
self,
|
50
80
|
src: str,
|
51
|
-
|
81
|
+
params: ta.Sequence[ta.Union[str, MinjaTemplateParam]],
|
52
82
|
*,
|
53
83
|
indent: str = DEFAULT_INDENT,
|
54
84
|
) -> None:
|
55
85
|
super().__init__()
|
56
86
|
|
87
|
+
check.not_isinstance(params, str)
|
88
|
+
|
57
89
|
self._src = check.isinstance(src, str)
|
58
|
-
self.
|
90
|
+
self._params = [
|
91
|
+
MinjaTemplateParam.of(p)
|
92
|
+
for p in params
|
93
|
+
]
|
94
|
+
check.unique(p.name for p in self._params)
|
59
95
|
self._indent_str: str = check.non_empty_str(indent)
|
60
96
|
|
61
97
|
self._stack: ta.List[ta.Literal['for', 'if']] = []
|
62
|
-
self._lines: ta.List[str] = []
|
63
98
|
|
64
99
|
#
|
65
100
|
|
@@ -124,41 +159,63 @@ class MinjaTemplateCompiler:
|
|
124
159
|
|
125
160
|
_RENDER_FN_NAME = '__render'
|
126
161
|
|
162
|
+
class Rendered(ta.NamedTuple):
|
163
|
+
src: str
|
164
|
+
ns: ta.Dict[str, ta.Any]
|
165
|
+
|
127
166
|
@cached_nullary
|
128
|
-
def render(self) ->
|
167
|
+
def render(self) -> Rendered:
|
168
|
+
lines: ta.List[str] = []
|
169
|
+
|
170
|
+
ns: ta.Dict[str, ta.Any] = {
|
171
|
+
'__StringIO': io.StringIO,
|
172
|
+
}
|
173
|
+
|
129
174
|
parts = self._split_tags(self._src)
|
130
175
|
|
131
|
-
|
132
|
-
|
176
|
+
if not self._params:
|
177
|
+
lines.append(f'def {self._RENDER_FN_NAME}():')
|
178
|
+
else:
|
179
|
+
lines.append(f'def {self._RENDER_FN_NAME}(')
|
180
|
+
for p in self._params:
|
181
|
+
if p.default.present:
|
182
|
+
check.not_in(p.name, ns)
|
183
|
+
ns[p.name] = p.default.must()
|
184
|
+
lines.append(self._indent(f'{p.name}={p.name},'))
|
185
|
+
else:
|
186
|
+
lines.append(self._indent(f'{p.name},'))
|
187
|
+
lines.append('):')
|
188
|
+
|
189
|
+
lines.append(self._indent('__output = __StringIO()'))
|
133
190
|
|
134
191
|
for g, s in parts:
|
135
192
|
if g == '{':
|
136
193
|
expr = s.strip()
|
137
|
-
|
194
|
+
lines.append(self._indent(f'__output.write(str({expr}))'))
|
138
195
|
|
139
196
|
elif g == '%':
|
140
197
|
stmt = s.strip()
|
141
198
|
|
142
199
|
if stmt.startswith('for '):
|
143
|
-
|
200
|
+
lines.append(self._indent(stmt + ':'))
|
144
201
|
self._stack.append('for')
|
145
202
|
elif stmt.startswith('endfor'):
|
146
203
|
check.equal(self._stack.pop(), 'for')
|
147
204
|
|
148
205
|
elif stmt.startswith('if '):
|
149
|
-
|
206
|
+
lines.append(self._indent(stmt + ':'))
|
150
207
|
self._stack.append('if')
|
151
208
|
elif stmt.startswith('elif '):
|
152
209
|
check.equal(self._stack[-1], 'if')
|
153
|
-
|
210
|
+
lines.append(self._indent(stmt + ':', -1))
|
154
211
|
elif stmt.strip() == 'else':
|
155
212
|
check.equal(self._stack[-1], 'if')
|
156
|
-
|
213
|
+
lines.append(self._indent('else:', -1))
|
157
214
|
elif stmt.startswith('endif'):
|
158
215
|
check.equal(self._stack.pop(), 'if')
|
159
216
|
|
160
217
|
else:
|
161
|
-
|
218
|
+
lines.append(self._indent(stmt))
|
162
219
|
|
163
220
|
elif g == '#':
|
164
221
|
pass
|
@@ -166,20 +223,16 @@ class MinjaTemplateCompiler:
|
|
166
223
|
elif not g:
|
167
224
|
if s:
|
168
225
|
safe_text = s.replace('"""', '\\"""')
|
169
|
-
|
226
|
+
lines.append(self._indent(f'__output.write("""{safe_text}""")'))
|
170
227
|
|
171
228
|
else:
|
172
229
|
raise KeyError(g)
|
173
230
|
|
174
231
|
check.empty(self._stack)
|
175
232
|
|
176
|
-
|
177
|
-
|
178
|
-
ns = {
|
179
|
-
'__StringIO': io.StringIO,
|
180
|
-
}
|
233
|
+
lines.append(self._indent('return __output.getvalue()'))
|
181
234
|
|
182
|
-
return ('\n'.join(
|
235
|
+
return self.Rendered('\n'.join(lines), ns)
|
183
236
|
|
184
237
|
#
|
185
238
|
|
@@ -200,12 +253,12 @@ class MinjaTemplateCompiler:
|
|
200
253
|
|
201
254
|
@cached_nullary
|
202
255
|
def compile(self) -> MinjaTemplate:
|
203
|
-
|
256
|
+
rendered = self.render()
|
204
257
|
|
205
258
|
render_fn = self._make_fn(
|
206
259
|
self._RENDER_FN_NAME,
|
207
|
-
|
208
|
-
|
260
|
+
rendered.src,
|
261
|
+
rendered.ns,
|
209
262
|
)
|
210
263
|
|
211
264
|
return MinjaTemplate(render_fn)
|
@@ -214,8 +267,11 @@ class MinjaTemplateCompiler:
|
|
214
267
|
##
|
215
268
|
|
216
269
|
|
217
|
-
def compile_minja_template(
|
218
|
-
|
270
|
+
def compile_minja_template(
|
271
|
+
src: str,
|
272
|
+
params: ta.Sequence[ta.Union[str, MinjaTemplateParam]] = (),
|
273
|
+
) -> MinjaTemplate:
|
274
|
+
return MinjaTemplateCompiler(src, params).compile()
|
219
275
|
|
220
276
|
|
221
277
|
def render_minja_template(src: str, **kwargs: ta.Any) -> str:
|