omlish 0.0.0.dev40__py3-none-any.whl → 0.0.0.dev42__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 +2 -2
- omlish/dataclasses/__init__.py +1 -0
- omlish/dataclasses/impl/__init__.py +1 -0
- omlish/dataclasses/utils.py +4 -0
- omlish/docker/__init__.py +27 -0
- omlish/docker/cli.py +101 -0
- omlish/docker/compose.py +51 -0
- omlish/docker/helpers.py +48 -0
- omlish/docker/hub.py +75 -0
- omlish/docker/manifests.py +166 -0
- omlish/lang/__init__.py +1 -0
- omlish/lang/strings.py +25 -37
- omlish/marshal/__init__.py +1 -0
- omlish/marshal/dataclasses.py +68 -15
- omlish/marshal/helpers.py +2 -8
- omlish/marshal/objects.py +58 -15
- omlish/specs/openapi/__init__.py +5 -0
- omlish/specs/openapi/marshal.py +63 -0
- omlish/specs/openapi/openapi.py +443 -0
- {omlish-0.0.0.dev40.dist-info → omlish-0.0.0.dev42.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev40.dist-info → omlish-0.0.0.dev42.dist-info}/RECORD +25 -17
- omlish/docker.py +0 -273
- {omlish-0.0.0.dev40.dist-info → omlish-0.0.0.dev42.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev40.dist-info → omlish-0.0.0.dev42.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev40.dist-info → omlish-0.0.0.dev42.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev40.dist-info → omlish-0.0.0.dev42.dist-info}/top_level.txt +0 -0
omlish/marshal/dataclasses.py
CHANGED
@@ -13,8 +13,11 @@ from .base import Unmarshaler
|
|
13
13
|
from .base import UnmarshalerFactory
|
14
14
|
from .naming import Naming
|
15
15
|
from .naming import translate_name
|
16
|
+
from .objects import DEFAULT_FIELD_OPTIONS
|
17
|
+
from .objects import FIELD_OPTIONS_KWARGS
|
16
18
|
from .objects import FieldInfo
|
17
19
|
from .objects import FieldMetadata
|
20
|
+
from .objects import FieldOptions
|
18
21
|
from .objects import ObjectMarshaler
|
19
22
|
from .objects import ObjectMetadata
|
20
23
|
from .objects import ObjectUnmarshaler
|
@@ -43,6 +46,11 @@ def get_field_infos(
|
|
43
46
|
for k, v in dc.asdict(dc_md.field_defaults).items()
|
44
47
|
if v is not None
|
45
48
|
}
|
49
|
+
fo_defaults = {
|
50
|
+
k: v
|
51
|
+
for k, v in fi_defaults.pop('options').items()
|
52
|
+
if v != getattr(DEFAULT_FIELD_OPTIONS, k)
|
53
|
+
}
|
46
54
|
|
47
55
|
type_hints = ta.get_type_hints(ty)
|
48
56
|
|
@@ -53,8 +61,10 @@ def get_field_infos(
|
|
53
61
|
else:
|
54
62
|
um_name = field.name
|
55
63
|
|
56
|
-
|
57
|
-
|
64
|
+
fi_kw = dict(fi_defaults)
|
65
|
+
fo_kw = dict(fo_defaults)
|
66
|
+
|
67
|
+
fi_kw.update(
|
58
68
|
name=field.name,
|
59
69
|
type=type_hints[field.name],
|
60
70
|
metadata=FieldMetadata(),
|
@@ -63,20 +73,35 @@ def get_field_infos(
|
|
63
73
|
unmarshal_names=[um_name],
|
64
74
|
)
|
65
75
|
|
76
|
+
has_set_name = False
|
66
77
|
if (fmd := field.metadata.get(FieldMetadata)) is not None:
|
67
|
-
|
78
|
+
fi_kw.update(
|
68
79
|
metadata=fmd,
|
69
|
-
omit_if=fmd.omit_if,
|
70
|
-
default=fmd.default,
|
71
80
|
)
|
72
81
|
|
82
|
+
for fo_k in FIELD_OPTIONS_KWARGS:
|
83
|
+
if (fo_v := getattr(fmd.options, fo_k)) != getattr(DEFAULT_FIELD_OPTIONS, fo_k):
|
84
|
+
fo_kw[fo_k] = fo_v
|
85
|
+
|
73
86
|
if fmd.name is not None:
|
74
|
-
|
87
|
+
has_set_name = True
|
88
|
+
fi_kw.update(
|
75
89
|
marshal_name=fmd.name,
|
76
90
|
unmarshal_names=col.unique([fmd.name, *(fmd.alts or ())]),
|
77
91
|
)
|
78
92
|
|
79
|
-
|
93
|
+
if fo_kw.get('embed') and not has_set_name:
|
94
|
+
fi_kw.update(
|
95
|
+
marshal_name=fi_kw['marshal_name'] + '_',
|
96
|
+
unmarshal_names=[n + '_' for n in fi_kw['unmarshal_names']],
|
97
|
+
)
|
98
|
+
|
99
|
+
ret.append(
|
100
|
+
FieldInfo(
|
101
|
+
options=FieldOptions(**fo_kw),
|
102
|
+
**fi_kw,
|
103
|
+
),
|
104
|
+
)
|
80
105
|
|
81
106
|
return ret
|
82
107
|
|
@@ -123,23 +148,51 @@ class DataclassUnmarshalerFactory(UnmarshalerFactory):
|
|
123
148
|
def fn(self, ctx: UnmarshalContext, rty: rfl.Type) -> Unmarshaler:
|
124
149
|
ty = check.isinstance(rty, type)
|
125
150
|
check.state(dc.is_dataclass(ty))
|
126
|
-
|
127
151
|
dc_md = get_dataclass_metadata(ty)
|
128
152
|
|
129
153
|
d: dict[str, tuple[FieldInfo, Unmarshaler]] = {}
|
130
154
|
defaults: dict[str, ta.Any] = {}
|
155
|
+
embeds: dict[str, type] = {}
|
156
|
+
embeds_by_unmarshal_name: dict[str, tuple[str, str]] = {}
|
157
|
+
|
158
|
+
def add_field(fi: FieldInfo, *, prefixes: ta.Iterable[str] = ('',)) -> ta.Iterable[str]:
|
159
|
+
ret: list[str] = []
|
160
|
+
|
161
|
+
if fi.options.embed:
|
162
|
+
e_ty = check.isinstance(fi.type, type)
|
163
|
+
check.state(dc.is_dataclass(e_ty))
|
164
|
+
# e_dc_md = get_dataclass_metadata(e_ty)
|
165
|
+
|
166
|
+
embeds[fi.name] = e_ty
|
167
|
+
for e_fi in get_field_infos(e_ty, ctx.options):
|
168
|
+
e_ns = add_field(e_fi, prefixes=[p + ep for p in prefixes for ep in fi.unmarshal_names])
|
169
|
+
embeds_by_unmarshal_name.update({e_f: (fi.name, e_fi.name) for e_f in e_ns})
|
170
|
+
ret.extend(e_ns)
|
171
|
+
|
172
|
+
else:
|
173
|
+
tup = (fi, _make_field_obj(ctx, fi.type, fi.metadata.unmarshaler, fi.metadata.unmarshaler_factory))
|
174
|
+
|
175
|
+
for pfx in prefixes:
|
176
|
+
for un in fi.unmarshal_names:
|
177
|
+
un = pfx + un
|
178
|
+
if un in d:
|
179
|
+
raise KeyError(f'Duplicate fields for name {un!r}: {fi.name!r}, {d[un][0].name!r}')
|
180
|
+
d[un] = tup
|
181
|
+
ret.append(un)
|
182
|
+
|
183
|
+
if fi.options.default.present:
|
184
|
+
defaults[fi.name] = fi.options.default.must()
|
185
|
+
|
186
|
+
return ret
|
187
|
+
|
131
188
|
for fi in get_field_infos(ty, ctx.options):
|
132
|
-
|
133
|
-
for un in fi.unmarshal_names:
|
134
|
-
if un in d:
|
135
|
-
raise KeyError(f'Duplicate fields for name {un!r}: {fi.name!r}, {d[un][0].name!r}')
|
136
|
-
d[un] = tup
|
137
|
-
if fi.default.present:
|
138
|
-
defaults[fi.name] = fi.default.must()
|
189
|
+
add_field(fi)
|
139
190
|
|
140
191
|
return ObjectUnmarshaler(
|
141
192
|
ty,
|
142
193
|
d,
|
143
194
|
unknown_field=dc_md.unknown_field,
|
144
195
|
defaults=defaults,
|
196
|
+
embeds=embeds,
|
197
|
+
embeds_by_unmarshal_name=embeds_by_unmarshal_name,
|
145
198
|
)
|
omlish/marshal/helpers.py
CHANGED
@@ -12,10 +12,7 @@ def update_field_metadata(**kwargs: ta.Any) -> dc.field_modifier:
|
|
12
12
|
@dc.field_modifier
|
13
13
|
def inner(f: dc.Field) -> dc.Field:
|
14
14
|
return dc.update_field_metadata(f, {
|
15
|
-
FieldMetadata:
|
16
|
-
f.metadata.get(FieldMetadata, FieldMetadata()),
|
17
|
-
**kwargs,
|
18
|
-
),
|
15
|
+
FieldMetadata: f.metadata.get(FieldMetadata, FieldMetadata()).update(**kwargs),
|
19
16
|
})
|
20
17
|
return inner
|
21
18
|
|
@@ -26,10 +23,7 @@ def update_fields_metadata(
|
|
26
23
|
) -> ta.Callable[[type[T]], type[T]]:
|
27
24
|
def inner(a: str, f: dc.Field) -> dc.Field:
|
28
25
|
return dc.update_field_metadata(f, {
|
29
|
-
FieldMetadata:
|
30
|
-
f.metadata.get(FieldMetadata, FieldMetadata()),
|
31
|
-
**kwargs,
|
32
|
-
),
|
26
|
+
FieldMetadata: f.metadata.get(FieldMetadata, FieldMetadata()).update(**kwargs),
|
33
27
|
})
|
34
28
|
|
35
29
|
return dc.update_fields(inner, fields)
|
omlish/marshal/objects.py
CHANGED
@@ -1,11 +1,9 @@
|
|
1
1
|
"""
|
2
2
|
TODO:
|
3
3
|
- cfg naming
|
4
|
-
- adapters for dataclasses / namedtuples / user objects (as
|
4
|
+
- adapters for dataclasses / namedtuples / user objects (as configured)
|
5
5
|
- mro-merge ObjectMetadata
|
6
6
|
- key ordering override - like slice, -1 means last
|
7
|
-
- dedupe omit_if/default fields on Metadata/Info - FieldOptions prob
|
8
|
-
- impl merge, update helpers:update_fields_metadata
|
9
7
|
"""
|
10
8
|
import collections.abc
|
11
9
|
import typing as ta
|
@@ -27,13 +25,25 @@ from .values import Value
|
|
27
25
|
##
|
28
26
|
|
29
27
|
|
28
|
+
@dc.dataclass(frozen=True, kw_only=True)
|
29
|
+
class FieldOptions:
|
30
|
+
omit_if: ta.Callable[[ta.Any], bool] | None = None
|
31
|
+
|
32
|
+
default: lang.Maybe[ta.Any] = dc.xfield(default=lang.empty(), check_type=lang.Maybe)
|
33
|
+
|
34
|
+
embed: bool = False
|
35
|
+
|
36
|
+
|
37
|
+
DEFAULT_FIELD_OPTIONS = FieldOptions()
|
38
|
+
FIELD_OPTIONS_KWARGS: frozenset[str] = frozenset(dc.fields_dict(FieldOptions).keys())
|
39
|
+
|
40
|
+
|
30
41
|
@dc.dataclass(frozen=True, kw_only=True)
|
31
42
|
class FieldMetadata:
|
32
43
|
name: str | None = None
|
33
44
|
alts: ta.Iterable[str] | None = None
|
34
45
|
|
35
|
-
|
36
|
-
default: lang.Maybe[ta.Any] = dc.xfield(lang.empty(), check_type=lang.Maybe)
|
46
|
+
options: FieldOptions = DEFAULT_FIELD_OPTIONS
|
37
47
|
|
38
48
|
marshaler: Marshaler | None = None
|
39
49
|
marshaler_factory: MarshalerFactory | None = None
|
@@ -41,6 +51,15 @@ class FieldMetadata:
|
|
41
51
|
unmarshaler: Unmarshaler | None = None
|
42
52
|
unmarshaler_factory: UnmarshalerFactory | None = None
|
43
53
|
|
54
|
+
def update(self, **kwargs: ta.Any) -> 'FieldMetadata':
|
55
|
+
okw = {k: v for k, v in kwargs.items() if k in FIELD_OPTIONS_KWARGS}
|
56
|
+
mkw = {k: v for k, v in kwargs.items() if k not in FIELD_OPTIONS_KWARGS}
|
57
|
+
return dc.replace(
|
58
|
+
self,
|
59
|
+
**(dict(options=dc.replace(self.options, **okw)) if okw else {}),
|
60
|
+
**mkw,
|
61
|
+
)
|
62
|
+
|
44
63
|
|
45
64
|
@dc.dataclass(frozen=True, kw_only=True)
|
46
65
|
class ObjectMetadata:
|
@@ -64,8 +83,7 @@ class FieldInfo:
|
|
64
83
|
|
65
84
|
metadata: FieldMetadata = FieldMetadata()
|
66
85
|
|
67
|
-
|
68
|
-
default: lang.Maybe[ta.Any] = lang.empty()
|
86
|
+
options: FieldOptions = FieldOptions()
|
69
87
|
|
70
88
|
|
71
89
|
##
|
@@ -80,12 +98,21 @@ class ObjectMarshaler(Marshaler):
|
|
80
98
|
unknown_field: str | None = None
|
81
99
|
|
82
100
|
def marshal(self, ctx: MarshalContext, o: ta.Any) -> Value:
|
83
|
-
ret = {}
|
101
|
+
ret: dict[str, ta.Any] = {}
|
84
102
|
for fi, m in self.fields:
|
85
103
|
v = getattr(o, fi.name)
|
86
|
-
|
104
|
+
|
105
|
+
if fi.options.omit_if is not None and fi.options.omit_if(v):
|
87
106
|
continue
|
88
|
-
|
107
|
+
|
108
|
+
mv = m.marshal(ctx, v)
|
109
|
+
|
110
|
+
if fi.options.embed:
|
111
|
+
for ek, ev in check.isinstance(mv, collections.abc.Mapping).items():
|
112
|
+
ret[fi.marshal_name + check.non_empty_str(ek)] = ev # type: ignore
|
113
|
+
|
114
|
+
else:
|
115
|
+
ret[fi.marshal_name] = mv
|
89
116
|
|
90
117
|
if self.unknown_field is not None:
|
91
118
|
if (ukf := getattr(o, self.unknown_field)):
|
@@ -135,8 +162,12 @@ class ObjectUnmarshaler(Unmarshaler):
|
|
135
162
|
_: dc.KW_ONLY
|
136
163
|
|
137
164
|
unknown_field: str | None = None
|
165
|
+
|
138
166
|
defaults: ta.Mapping[str, ta.Any] | None = None
|
139
167
|
|
168
|
+
embeds: ta.Mapping[str, type] | None = None
|
169
|
+
embeds_by_unmarshal_name: ta.Mapping[str, tuple[str, str]] | None = None
|
170
|
+
|
140
171
|
def unmarshal(self, ctx: UnmarshalContext, v: Value) -> ta.Any:
|
141
172
|
ma = check.isinstance(v, collections.abc.Mapping)
|
142
173
|
|
@@ -144,6 +175,8 @@ class ObjectUnmarshaler(Unmarshaler):
|
|
144
175
|
kw: dict[str, ta.Any] = {}
|
145
176
|
ukf: dict[str, ta.Any] | None = None
|
146
177
|
|
178
|
+
ekws: dict[str, dict[str, ta.Any]] = {en: {} for en in self.embeds or ()}
|
179
|
+
|
147
180
|
if self.unknown_field is not None:
|
148
181
|
kw[self.unknown_field] = ukf = {}
|
149
182
|
|
@@ -159,10 +192,20 @@ class ObjectUnmarshaler(Unmarshaler):
|
|
159
192
|
continue
|
160
193
|
raise
|
161
194
|
|
162
|
-
if
|
163
|
-
|
195
|
+
if self.embeds_by_unmarshal_name and (en := self.embeds_by_unmarshal_name.get(ks)):
|
196
|
+
tkw, tk = ekws[en[0]], en[1]
|
197
|
+
else:
|
198
|
+
tkw, tk = kw, fi.name
|
199
|
+
|
200
|
+
if tk in tkw:
|
201
|
+
raise KeyError(f'Duplicate keys for field {tk!r}: {ks!r}')
|
202
|
+
|
203
|
+
tkw[tk] = u.unmarshal(ctx, mv)
|
164
204
|
|
165
|
-
|
205
|
+
for em, ecls in self.embeds.items() if self.embeds else ():
|
206
|
+
ekw = ekws[em]
|
207
|
+
ev = ecls(**ekw)
|
208
|
+
kw[em] = ev
|
166
209
|
|
167
210
|
if self.defaults:
|
168
211
|
for dk, dv in self.defaults.items():
|
@@ -194,9 +237,9 @@ class SimpleObjectUnmarshalerFactory(UnmarshalerFactory):
|
|
194
237
|
}
|
195
238
|
|
196
239
|
defaults = {
|
197
|
-
fi.name: fi.default.must()
|
240
|
+
fi.name: fi.options.default.must()
|
198
241
|
for fi in flds
|
199
|
-
if fi.default.present
|
242
|
+
if fi.options.default.present
|
200
243
|
}
|
201
244
|
|
202
245
|
return ObjectUnmarshaler(
|
@@ -0,0 +1,63 @@
|
|
1
|
+
import typing as ta
|
2
|
+
|
3
|
+
from ... import check
|
4
|
+
from ... import dataclasses as dc
|
5
|
+
from ... import lang
|
6
|
+
from ... import marshal as msh
|
7
|
+
from ... import matchfns as mfs
|
8
|
+
from ... import reflect as rfl
|
9
|
+
from .openapi import Reference
|
10
|
+
|
11
|
+
|
12
|
+
#
|
13
|
+
|
14
|
+
|
15
|
+
def _reference_union_arg(rty: rfl.Type) -> rfl.Type | None:
|
16
|
+
if isinstance(rty, rfl.Union) and len(rty.args) == 2 and Reference in rty.args:
|
17
|
+
return check.single(a for a in rty.args if a is not Reference)
|
18
|
+
else:
|
19
|
+
return None
|
20
|
+
|
21
|
+
|
22
|
+
@dc.dataclass(frozen=True)
|
23
|
+
class _ReferenceUnionMarshaler(msh.Marshaler):
|
24
|
+
m: msh.Marshaler
|
25
|
+
r: msh.Marshaler
|
26
|
+
|
27
|
+
def marshal(self, ctx: msh.MarshalContext, o: ta.Any) -> msh.Value:
|
28
|
+
if isinstance(o, Reference):
|
29
|
+
return self.r.marshal(ctx, o)
|
30
|
+
else:
|
31
|
+
return self.m.marshal(ctx, o)
|
32
|
+
|
33
|
+
|
34
|
+
class _ReferenceUnionMarshalerFactory(msh.MarshalerFactoryMatchClass):
|
35
|
+
@mfs.simple(lambda _, ctx, rty: _reference_union_arg(rty) is not None)
|
36
|
+
def _build(self, ctx: msh.MarshalContext, rty: rfl.Type) -> msh.Marshaler:
|
37
|
+
return _ReferenceUnionMarshaler(ctx.make(check.not_none(_reference_union_arg(rty))), ctx.make(Reference))
|
38
|
+
|
39
|
+
|
40
|
+
@dc.dataclass(frozen=True)
|
41
|
+
class _ReferenceUnionUnmarshaler(msh.Unmarshaler):
|
42
|
+
u: msh.Unmarshaler
|
43
|
+
r: msh.Unmarshaler
|
44
|
+
|
45
|
+
def unmarshal(self, ctx: msh.UnmarshalContext, v: msh.Value) -> ta.Any:
|
46
|
+
if not isinstance(v, ta.Mapping):
|
47
|
+
raise TypeError(v)
|
48
|
+
elif '$ref' in v:
|
49
|
+
return self.r.unmarshal(ctx, v)
|
50
|
+
else:
|
51
|
+
return self.u.unmarshal(ctx, v) # noqa
|
52
|
+
|
53
|
+
|
54
|
+
class _ReferenceUnionUnmarshalerFactory(msh.UnmarshalerFactoryMatchClass):
|
55
|
+
@mfs.simple(lambda _, ctx, rty: _reference_union_arg(rty) is not None)
|
56
|
+
def _build(self, ctx: msh.UnmarshalContext, rty: rfl.Type) -> msh.Unmarshaler:
|
57
|
+
return _ReferenceUnionUnmarshaler(ctx.make(check.not_none(_reference_union_arg(rty))), ctx.make(Reference))
|
58
|
+
|
59
|
+
|
60
|
+
@lang.static_init
|
61
|
+
def _install_standard_marshalling() -> None:
|
62
|
+
msh.STANDARD_MARSHALER_FACTORIES[0:0] = [_ReferenceUnionMarshalerFactory()]
|
63
|
+
msh.STANDARD_UNMARSHALER_FACTORIES[0:0] = [_ReferenceUnionUnmarshalerFactory()]
|