omlish 0.0.0.dev19__py3-none-any.whl → 0.0.0.dev21__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 +3 -3
- omlish/asyncs/anyio.py +13 -6
- omlish/dataclasses/__init__.py +2 -0
- omlish/dataclasses/impl/fields.py +51 -0
- omlish/dataclasses/impl/frozen.py +38 -0
- omlish/dataclasses/impl/main.py +24 -88
- omlish/dataclasses/impl/metadata.py +2 -1
- omlish/dataclasses/impl/processing.py +10 -2
- omlish/dataclasses/utils.py +45 -0
- omlish/fnpairs.py +1 -1
- omlish/formats/json.py +1 -0
- omlish/lang/classes/simple.py +3 -0
- omlish/lang/clsdct.py +2 -0
- omlish/lang/contextmanagers.py +41 -0
- omlish/lang/descriptors.py +8 -0
- omlish/lang/objects.py +4 -0
- omlish/lang/resolving.py +9 -0
- omlish/lite/logs.py +2 -2
- omlish/lite/marshal.py +4 -2
- omlish/marshal/__init__.py +4 -0
- omlish/marshal/dataclasses.py +16 -3
- omlish/marshal/helpers.py +22 -0
- omlish/marshal/objects.py +33 -14
- omlish/multiprocessing.py +36 -4
- omlish/specs/__init__.py +0 -0
- omlish/specs/jsonschema/__init__.py +0 -0
- omlish/specs/jsonschema/keywords/__init__.py +42 -0
- omlish/specs/jsonschema/keywords/base.py +86 -0
- omlish/specs/jsonschema/keywords/core.py +26 -0
- omlish/specs/jsonschema/keywords/metadata.py +22 -0
- omlish/specs/jsonschema/keywords/parse.py +69 -0
- omlish/specs/jsonschema/keywords/render.py +47 -0
- omlish/specs/jsonschema/keywords/validation.py +68 -0
- omlish/specs/jsonschema/schemas/__init__.py +0 -0
- omlish/specs/jsonschema/schemas/draft202012/__init__.py +0 -0
- omlish/specs/jsonschema/schemas/draft202012/vocabularies/__init__.py +0 -0
- omlish/specs/jsonschema/types.py +21 -0
- {omlish-0.0.0.dev19.dist-info → omlish-0.0.0.dev21.dist-info}/METADATA +3 -3
- {omlish-0.0.0.dev19.dist-info → omlish-0.0.0.dev21.dist-info}/RECORD +42 -28
- {omlish-0.0.0.dev19.dist-info → omlish-0.0.0.dev21.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev19.dist-info → omlish-0.0.0.dev21.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev19.dist-info → omlish-0.0.0.dev21.dist-info}/top_level.txt +0 -0
omlish/lite/logs.py
CHANGED
@@ -69,8 +69,8 @@ class JsonLogFormatter(logging.Formatter):
|
|
69
69
|
STANDARD_LOG_FORMAT_PARTS = [
|
70
70
|
('asctime', '%(asctime)-15s'),
|
71
71
|
('process', 'pid=%(process)-6s'),
|
72
|
-
('thread', 'tid=%(thread)
|
73
|
-
('levelname', '%(levelname)
|
72
|
+
('thread', 'tid=%(thread)x'),
|
73
|
+
('levelname', '%(levelname)s'),
|
74
74
|
('name', '%(name)s'),
|
75
75
|
('separator', '::'),
|
76
76
|
('message', '%(message)s'),
|
omlish/lite/marshal.py
CHANGED
@@ -232,8 +232,10 @@ _OBJ_MARSHALER_GENERIC_MAPPING_TYPES: ta.Dict[ta.Any, type] = {
|
|
232
232
|
|
233
233
|
_OBJ_MARSHALER_GENERIC_ITERABLE_TYPES: ta.Dict[ta.Any, type] = {
|
234
234
|
**{t: t for t in (list, tuple, set, frozenset)},
|
235
|
-
|
236
|
-
|
235
|
+
collections.abc.Set: frozenset,
|
236
|
+
collections.abc.MutableSet: set,
|
237
|
+
collections.abc.Sequence: tuple,
|
238
|
+
collections.abc.MutableSequence: list,
|
237
239
|
}
|
238
240
|
|
239
241
|
|
omlish/marshal/__init__.py
CHANGED
omlish/marshal/dataclasses.py
CHANGED
@@ -31,10 +31,19 @@ def get_dataclass_metadata(ty: type) -> ObjectMetadata:
|
|
31
31
|
) or ObjectMetadata()
|
32
32
|
|
33
33
|
|
34
|
-
def get_field_infos(
|
34
|
+
def get_field_infos(
|
35
|
+
ty: type,
|
36
|
+
opts: col.TypeMap[Option] = col.TypeMap(),
|
37
|
+
) -> ta.Sequence[FieldInfo]:
|
35
38
|
dc_md = get_dataclass_metadata(ty)
|
36
39
|
dc_naming = dc_md.field_naming or opts.get(Naming)
|
37
40
|
|
41
|
+
fi_defaults = {
|
42
|
+
k: v
|
43
|
+
for k, v in dc.asdict(dc_md.field_defaults).items()
|
44
|
+
if v is not None
|
45
|
+
}
|
46
|
+
|
38
47
|
type_hints = ta.get_type_hints(ty)
|
39
48
|
|
40
49
|
ret: list[FieldInfo] = []
|
@@ -44,7 +53,8 @@ def get_field_infos(ty: type, opts: col.TypeMap[Option] = col.TypeMap()) -> ta.S
|
|
44
53
|
else:
|
45
54
|
um_name = field.name
|
46
55
|
|
47
|
-
kw = dict(
|
56
|
+
kw = dict(fi_defaults)
|
57
|
+
kw.update(
|
48
58
|
name=field.name,
|
49
59
|
type=type_hints[field.name],
|
50
60
|
metadata=FieldMetadata(),
|
@@ -54,7 +64,10 @@ def get_field_infos(ty: type, opts: col.TypeMap[Option] = col.TypeMap()) -> ta.S
|
|
54
64
|
)
|
55
65
|
|
56
66
|
if (fmd := field.metadata.get(FieldMetadata)) is not None:
|
57
|
-
kw.update(
|
67
|
+
kw.update(
|
68
|
+
metadata=fmd,
|
69
|
+
omit_if=fmd.omit_if,
|
70
|
+
)
|
58
71
|
|
59
72
|
if fmd.name is not None:
|
60
73
|
kw.update(
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import typing as ta
|
2
|
+
|
3
|
+
from .. import dataclasses as dc
|
4
|
+
from .objects import FieldMetadata
|
5
|
+
|
6
|
+
|
7
|
+
T = ta.TypeVar('T')
|
8
|
+
|
9
|
+
|
10
|
+
def update_fields_metadata(
|
11
|
+
fields: ta.Iterable[str] | None = None,
|
12
|
+
**kwargs: ta.Any,
|
13
|
+
) -> ta.Callable[[type[T]], type[T]]:
|
14
|
+
def inner(a: str, f: dc.Field) -> dc.Field:
|
15
|
+
return dc.update_field_metadata(f, {
|
16
|
+
FieldMetadata: dc.replace(
|
17
|
+
f.metadata.get(FieldMetadata, FieldMetadata()),
|
18
|
+
**kwargs,
|
19
|
+
),
|
20
|
+
})
|
21
|
+
|
22
|
+
return dc.update_fields(inner, fields)
|
omlish/marshal/objects.py
CHANGED
@@ -22,18 +22,13 @@ from .values import Value
|
|
22
22
|
##
|
23
23
|
|
24
24
|
|
25
|
-
@dc.dataclass(frozen=True)
|
26
|
-
class ObjectMetadata:
|
27
|
-
field_naming: Naming | None = None
|
28
|
-
|
29
|
-
unknown_field: str | None = None
|
30
|
-
|
31
|
-
|
32
|
-
@dc.dataclass(frozen=True)
|
25
|
+
@dc.dataclass(frozen=True, kw_only=True)
|
33
26
|
class FieldMetadata:
|
34
27
|
name: str | None = None
|
35
28
|
alts: ta.Iterable[str] | None = None
|
36
29
|
|
30
|
+
omit_if: ta.Callable[[ta.Any], bool] | None = None
|
31
|
+
|
37
32
|
marshaler: Marshaler | None = None
|
38
33
|
marshaler_factory: MarshalerFactory | None = None
|
39
34
|
|
@@ -41,18 +36,30 @@ class FieldMetadata:
|
|
41
36
|
unmarshaler_factory: UnmarshalerFactory | None = None
|
42
37
|
|
43
38
|
|
39
|
+
@dc.dataclass(frozen=True, kw_only=True)
|
40
|
+
class ObjectMetadata:
|
41
|
+
field_naming: Naming | None = None
|
42
|
+
|
43
|
+
unknown_field: str | None = None
|
44
|
+
|
45
|
+
field_defaults: FieldMetadata = FieldMetadata()
|
46
|
+
|
47
|
+
|
44
48
|
##
|
45
49
|
|
46
50
|
|
47
|
-
@dc.dataclass(frozen=True)
|
51
|
+
@dc.dataclass(frozen=True, kw_only=True)
|
48
52
|
class FieldInfo:
|
49
53
|
name: str
|
50
54
|
type: ta.Any
|
51
|
-
metadata: FieldMetadata
|
52
55
|
|
53
56
|
marshal_name: str
|
54
57
|
unmarshal_names: ta.Sequence[str]
|
55
58
|
|
59
|
+
metadata: FieldMetadata = FieldMetadata()
|
60
|
+
|
61
|
+
omit_if: ta.Callable[[ta.Any], bool] | None = None
|
62
|
+
|
56
63
|
|
57
64
|
##
|
58
65
|
|
@@ -63,15 +70,20 @@ class ObjectMarshaler(Marshaler):
|
|
63
70
|
unknown_field: str | None = None
|
64
71
|
|
65
72
|
def marshal(self, ctx: MarshalContext, o: ta.Any) -> Value:
|
66
|
-
ret = {
|
67
|
-
|
68
|
-
|
69
|
-
|
73
|
+
ret = {}
|
74
|
+
for fi, m in self.fields:
|
75
|
+
v = getattr(o, fi.name)
|
76
|
+
if fi.omit_if is not None and fi.omit_if(v):
|
77
|
+
continue
|
78
|
+
ret[fi.marshal_name] = m.marshal(ctx, v)
|
79
|
+
|
70
80
|
if self.unknown_field is not None:
|
71
81
|
if (ukf := getattr(o, self.unknown_field)):
|
72
82
|
if (dks := set(ret) & set(ukf)):
|
73
83
|
raise KeyError(f'Unknown field keys duplicate fields: {dks!r}')
|
84
|
+
|
74
85
|
ret.update(ukf) # FIXME: marshal?
|
86
|
+
|
75
87
|
return ret
|
76
88
|
|
77
89
|
|
@@ -89,18 +101,25 @@ class ObjectUnmarshaler(Unmarshaler):
|
|
89
101
|
u: ta.Any
|
90
102
|
kw: dict[str, ta.Any] = {}
|
91
103
|
ukf: dict[str, ta.Any] | None = None
|
104
|
+
|
92
105
|
if self.unknown_field is not None:
|
93
106
|
kw[self.unknown_field] = ukf = {}
|
107
|
+
|
94
108
|
for k, mv in ma.items():
|
95
109
|
ks = check.isinstance(k, str)
|
110
|
+
|
96
111
|
try:
|
97
112
|
fi, u = self.fields_by_unmarshal_name[ks]
|
113
|
+
|
98
114
|
except KeyError:
|
99
115
|
if ukf is not None:
|
100
116
|
ukf[ks] = mv # FIXME: unmarshal?
|
101
117
|
continue
|
102
118
|
raise
|
119
|
+
|
103
120
|
if fi.name in kw:
|
104
121
|
raise KeyError(f'Duplicate keys for field {fi.name!r}: {ks!r}')
|
122
|
+
|
105
123
|
kw[fi.name] = u.unmarshal(ctx, mv)
|
124
|
+
|
106
125
|
return self.cls(**kw)
|
omlish/multiprocessing.py
CHANGED
@@ -9,6 +9,7 @@ import time
|
|
9
9
|
import typing as ta
|
10
10
|
|
11
11
|
from . import check
|
12
|
+
from . import lang
|
12
13
|
from . import libc
|
13
14
|
|
14
15
|
|
@@ -18,21 +19,47 @@ T = ta.TypeVar('T')
|
|
18
19
|
##
|
19
20
|
|
20
21
|
|
22
|
+
@ta.runtime_checkable
|
23
|
+
class ValueProxy(ta.Protocol[T]):
|
24
|
+
# value = property(get, set)
|
25
|
+
|
26
|
+
def get(self) -> T:
|
27
|
+
...
|
28
|
+
|
29
|
+
def set(self, value: T) -> None:
|
30
|
+
...
|
31
|
+
|
32
|
+
|
33
|
+
@dc.dataclass()
|
34
|
+
@lang.protocol_check(ValueProxy)
|
35
|
+
class DummyValueProxy(ValueProxy[T]):
|
36
|
+
value: T
|
37
|
+
|
38
|
+
def get(self) -> T:
|
39
|
+
return self.value
|
40
|
+
|
41
|
+
def set(self, value: T) -> None:
|
42
|
+
self.value = value
|
43
|
+
|
44
|
+
|
45
|
+
##
|
46
|
+
|
47
|
+
|
21
48
|
@dc.dataclass(frozen=True, kw_only=True)
|
22
49
|
class SpawnExtras:
|
23
|
-
|
50
|
+
pass_fds: ta.AbstractSet[int] | None = None
|
24
51
|
deathsig: int | None = None
|
25
52
|
|
26
53
|
|
27
54
|
class ExtrasSpawnPosixPopen(mp.popen_spawn_posix.Popen):
|
28
55
|
def __init__(self, process_obj: 'ExtrasSpawnProcess', *, extras: SpawnExtras) -> None:
|
29
56
|
self.__extras = extras
|
30
|
-
self.
|
57
|
+
self.__pass_fds = extras.pass_fds
|
31
58
|
super().__init__(process_obj)
|
32
59
|
|
33
60
|
def _launch(self, process_obj: 'ExtrasSpawnProcess') -> None:
|
34
|
-
if self.
|
35
|
-
for fd in self.
|
61
|
+
if self.__pass_fds:
|
62
|
+
for fd in self.__pass_fds:
|
36
63
|
self.duplicate_for_child(fd)
|
37
64
|
self._extra_fds = None
|
38
65
|
|
@@ -75,6 +102,11 @@ class Deathpact(abc.ABC):
|
|
75
102
|
raise NotImplementedError
|
76
103
|
|
77
104
|
|
105
|
+
class NopDeathpact(Deathpact):
|
106
|
+
def poll(self) -> None:
|
107
|
+
pass
|
108
|
+
|
109
|
+
|
78
110
|
#
|
79
111
|
|
80
112
|
|
omlish/specs/__init__.py
ADDED
File without changes
|
File without changes
|
@@ -0,0 +1,42 @@
|
|
1
|
+
from .base import ( # noqa
|
2
|
+
Keyword,
|
3
|
+
Keywords,
|
4
|
+
)
|
5
|
+
|
6
|
+
from .core import ( # noqa
|
7
|
+
CoreKeyword,
|
8
|
+
Id,
|
9
|
+
Ref,
|
10
|
+
SchemaKeyword,
|
11
|
+
)
|
12
|
+
|
13
|
+
from .metadata import ( # noqa
|
14
|
+
Description,
|
15
|
+
MetadataKeyword,
|
16
|
+
Title,
|
17
|
+
)
|
18
|
+
|
19
|
+
from .parse import ( # noqa
|
20
|
+
parse_keyword,
|
21
|
+
parse_keywords,
|
22
|
+
)
|
23
|
+
|
24
|
+
from .render import ( # noqa
|
25
|
+
render_keyword,
|
26
|
+
render_keywords,
|
27
|
+
)
|
28
|
+
|
29
|
+
from .validation import ( # noqa
|
30
|
+
ExclusiveMaximum,
|
31
|
+
ExclusiveMinimum,
|
32
|
+
Items,
|
33
|
+
MaxItems,
|
34
|
+
Maximum,
|
35
|
+
MinItems,
|
36
|
+
Minimum,
|
37
|
+
Properties,
|
38
|
+
Required,
|
39
|
+
Type,
|
40
|
+
UniqueItems,
|
41
|
+
ValidationKeyword,
|
42
|
+
)
|
@@ -0,0 +1,86 @@
|
|
1
|
+
import operator
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from omlish import cached
|
5
|
+
from omlish import check
|
6
|
+
from omlish import collections as col
|
7
|
+
from omlish import dataclasses as dc
|
8
|
+
from omlish import lang
|
9
|
+
|
10
|
+
|
11
|
+
KeywordT = ta.TypeVar('KeywordT', bound='Keyword')
|
12
|
+
|
13
|
+
|
14
|
+
##
|
15
|
+
|
16
|
+
|
17
|
+
class Keyword(lang.Abstract, lang.PackageSealed):
|
18
|
+
tag: ta.ClassVar[str]
|
19
|
+
|
20
|
+
def __init_subclass__(cls, *, tag: str | None = None, **kwargs: ta.Any) -> None:
|
21
|
+
super().__init_subclass__(**kwargs)
|
22
|
+
check.not_in('tag', dir(cls))
|
23
|
+
if not lang.is_abstract_class(cls):
|
24
|
+
check.issubclass(cls, lang.Final)
|
25
|
+
cls.tag = check.non_empty_str(tag)
|
26
|
+
else:
|
27
|
+
check.none(tag)
|
28
|
+
|
29
|
+
|
30
|
+
##
|
31
|
+
|
32
|
+
|
33
|
+
@dc.dataclass(frozen=True)
|
34
|
+
class Keywords(lang.Final):
|
35
|
+
lst: ta.Sequence[Keyword]
|
36
|
+
|
37
|
+
@cached.property
|
38
|
+
@dc.init
|
39
|
+
def by_type(self) -> ta.Mapping[type[Keyword], Keyword]:
|
40
|
+
return col.unique_map_by(type, self.lst, strict=True) # noqa
|
41
|
+
|
42
|
+
@cached.property
|
43
|
+
@dc.init
|
44
|
+
def by_tag(self) -> ta.Mapping[str, Keyword]:
|
45
|
+
return col.unique_map_by(operator.attrgetter('tag'), self.lst, strict=True) # noqa
|
46
|
+
|
47
|
+
def __getitem__(self, item: type[KeywordT] | str) -> KeywordT:
|
48
|
+
if isinstance(item, type):
|
49
|
+
return self.by_type[item] # noqa
|
50
|
+
elif isinstance(item, str):
|
51
|
+
return self.by_tag[item]
|
52
|
+
else:
|
53
|
+
raise TypeError(item)
|
54
|
+
|
55
|
+
|
56
|
+
##
|
57
|
+
|
58
|
+
|
59
|
+
@dc.dataclass(frozen=True)
|
60
|
+
class BooleanKeyword(Keyword, lang.Abstract):
|
61
|
+
b: bool
|
62
|
+
|
63
|
+
|
64
|
+
@dc.dataclass(frozen=True)
|
65
|
+
class NumberKeyword(Keyword, lang.Abstract):
|
66
|
+
n: int | float
|
67
|
+
|
68
|
+
|
69
|
+
@dc.dataclass(frozen=True)
|
70
|
+
class StrKeyword(Keyword, lang.Abstract):
|
71
|
+
s: str
|
72
|
+
|
73
|
+
|
74
|
+
@dc.dataclass(frozen=True)
|
75
|
+
class StrOrStrsKeyword(Keyword, lang.Abstract):
|
76
|
+
ss: str | ta.Sequence[str]
|
77
|
+
|
78
|
+
|
79
|
+
@dc.dataclass(frozen=True)
|
80
|
+
class KeywordsKeyword(Keyword, lang.Abstract):
|
81
|
+
kw: Keywords
|
82
|
+
|
83
|
+
|
84
|
+
@dc.dataclass(frozen=True)
|
85
|
+
class StrToKeywordsKeyword(Keyword, lang.Abstract):
|
86
|
+
m: ta.Mapping[str, Keywords]
|
@@ -0,0 +1,26 @@
|
|
1
|
+
from omlish import lang
|
2
|
+
|
3
|
+
from .base import Keyword
|
4
|
+
from .base import StrKeyword
|
5
|
+
|
6
|
+
|
7
|
+
##
|
8
|
+
|
9
|
+
|
10
|
+
class CoreKeyword(Keyword, lang.Abstract):
|
11
|
+
pass
|
12
|
+
|
13
|
+
|
14
|
+
##
|
15
|
+
|
16
|
+
|
17
|
+
class Id(StrKeyword, CoreKeyword, lang.Final, tag='$id'):
|
18
|
+
pass
|
19
|
+
|
20
|
+
|
21
|
+
class SchemaKeyword(StrKeyword, CoreKeyword, lang.Final, tag='$schema'):
|
22
|
+
pass
|
23
|
+
|
24
|
+
|
25
|
+
class Ref(StrKeyword, CoreKeyword, lang.Final, tag='$ref'):
|
26
|
+
pass
|
@@ -0,0 +1,22 @@
|
|
1
|
+
from omlish import lang
|
2
|
+
|
3
|
+
from .base import Keyword
|
4
|
+
from .base import StrKeyword
|
5
|
+
|
6
|
+
|
7
|
+
##
|
8
|
+
|
9
|
+
|
10
|
+
class MetadataKeyword(Keyword, lang.Abstract):
|
11
|
+
pass
|
12
|
+
|
13
|
+
|
14
|
+
##
|
15
|
+
|
16
|
+
|
17
|
+
class Title(StrKeyword, MetadataKeyword, lang.Final, tag='title'):
|
18
|
+
pass
|
19
|
+
|
20
|
+
|
21
|
+
class Description(StrKeyword, MetadataKeyword, lang.Final, tag='description'):
|
22
|
+
pass
|
@@ -0,0 +1,69 @@
|
|
1
|
+
import operator
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from omlish import check
|
5
|
+
from omlish import collections as col
|
6
|
+
from omlish import lang
|
7
|
+
|
8
|
+
from . import core # noqa
|
9
|
+
from . import metadata # noqa
|
10
|
+
from . import validation # noqa
|
11
|
+
from .base import BooleanKeyword
|
12
|
+
from .base import Keyword
|
13
|
+
from .base import Keywords
|
14
|
+
from .base import KeywordsKeyword
|
15
|
+
from .base import NumberKeyword
|
16
|
+
from .base import StrKeyword
|
17
|
+
from .base import StrOrStrsKeyword
|
18
|
+
from .base import StrToKeywordsKeyword
|
19
|
+
|
20
|
+
|
21
|
+
KeywordT = ta.TypeVar('KeywordT', bound=Keyword)
|
22
|
+
|
23
|
+
|
24
|
+
##
|
25
|
+
|
26
|
+
|
27
|
+
KEYWORD_TYPES_BY_TAG: ta.Mapping[str, type[Keyword]] = col.unique_map_by( # noqa
|
28
|
+
operator.attrgetter('tag'),
|
29
|
+
(cls for cls in lang.deep_subclasses(Keyword) if not lang.is_abstract_class(cls)),
|
30
|
+
strict=True,
|
31
|
+
)
|
32
|
+
|
33
|
+
|
34
|
+
def parse_keyword(cls: type[KeywordT], v: ta.Any) -> KeywordT:
|
35
|
+
if issubclass(cls, BooleanKeyword):
|
36
|
+
return cls(check.isinstance(v, bool)) # type: ignore
|
37
|
+
|
38
|
+
elif issubclass(cls, NumberKeyword):
|
39
|
+
return cls(check.isinstance(v, (int, float))) # type: ignore
|
40
|
+
|
41
|
+
elif issubclass(cls, StrKeyword):
|
42
|
+
return cls(check.isinstance(v, str)) # type: ignore
|
43
|
+
|
44
|
+
elif issubclass(cls, StrOrStrsKeyword):
|
45
|
+
ss: str | ta.Sequence[str]
|
46
|
+
if isinstance(v, str):
|
47
|
+
ss = v
|
48
|
+
elif isinstance(v, ta.Iterable):
|
49
|
+
ss = col.seq_of(check.of_isinstance(str))(v)
|
50
|
+
else:
|
51
|
+
raise TypeError(v)
|
52
|
+
return cls(ss) # type: ignore
|
53
|
+
|
54
|
+
elif issubclass(cls, KeywordsKeyword):
|
55
|
+
return cls(parse_keywords(v)) # type: ignore
|
56
|
+
|
57
|
+
elif issubclass(cls, StrToKeywordsKeyword):
|
58
|
+
return cls({k: parse_keywords(mv) for k, mv in v.items()}) # type: ignore
|
59
|
+
|
60
|
+
else:
|
61
|
+
raise TypeError(cls)
|
62
|
+
|
63
|
+
|
64
|
+
def parse_keywords(dct: ta.Mapping[str, ta.Any]) -> Keywords:
|
65
|
+
lst: list[Keyword] = []
|
66
|
+
for k, v in dct.items():
|
67
|
+
cls = KEYWORD_TYPES_BY_TAG[k]
|
68
|
+
lst.append(parse_keyword(cls, v))
|
69
|
+
return Keywords(lst)
|
@@ -0,0 +1,47 @@
|
|
1
|
+
import typing as ta
|
2
|
+
|
3
|
+
from .base import BooleanKeyword
|
4
|
+
from .base import Keyword
|
5
|
+
from .base import Keywords
|
6
|
+
from .base import KeywordsKeyword
|
7
|
+
from .base import NumberKeyword
|
8
|
+
from .base import StrKeyword
|
9
|
+
from .base import StrOrStrsKeyword
|
10
|
+
from .base import StrToKeywordsKeyword
|
11
|
+
|
12
|
+
|
13
|
+
def render_keyword(kw: Keyword) -> dict[str, ta.Any]:
|
14
|
+
if isinstance(kw, BooleanKeyword):
|
15
|
+
return {kw.tag: kw.b}
|
16
|
+
|
17
|
+
elif isinstance(kw, NumberKeyword):
|
18
|
+
return {kw.tag: kw.n}
|
19
|
+
|
20
|
+
elif isinstance(kw, StrKeyword):
|
21
|
+
return {kw.tag: kw.s}
|
22
|
+
|
23
|
+
elif isinstance(kw, StrOrStrsKeyword):
|
24
|
+
if isinstance(kw.ss, str):
|
25
|
+
return {kw.tag: kw.ss}
|
26
|
+
else:
|
27
|
+
return {kw.tag: list(kw.ss)}
|
28
|
+
|
29
|
+
elif isinstance(kw, KeywordsKeyword):
|
30
|
+
return {kw.tag: render_keywords(kw.kw)}
|
31
|
+
|
32
|
+
elif isinstance(kw, StrToKeywordsKeyword):
|
33
|
+
return {kw.tag: {k: render_keywords(v) for k, v in kw.m.items()}}
|
34
|
+
|
35
|
+
else:
|
36
|
+
raise TypeError(kw)
|
37
|
+
|
38
|
+
|
39
|
+
def render_keywords(kws: Keywords) -> dict[str, ta.Any]:
|
40
|
+
dct: dict[str, ta.Any] = {}
|
41
|
+
for kw in kws.lst:
|
42
|
+
kwd = render_keyword(kw)
|
43
|
+
[(tag, val)] = kwd.items()
|
44
|
+
if tag in dct:
|
45
|
+
raise KeyError(tag)
|
46
|
+
dct[tag] = val
|
47
|
+
return dct
|
@@ -0,0 +1,68 @@
|
|
1
|
+
from omlish import lang
|
2
|
+
|
3
|
+
from .base import BooleanKeyword
|
4
|
+
from .base import Keyword
|
5
|
+
from .base import KeywordsKeyword
|
6
|
+
from .base import NumberKeyword
|
7
|
+
from .base import StrOrStrsKeyword
|
8
|
+
from .base import StrToKeywordsKeyword
|
9
|
+
|
10
|
+
|
11
|
+
##
|
12
|
+
|
13
|
+
|
14
|
+
class ValidationKeyword(Keyword, lang.Abstract):
|
15
|
+
pass
|
16
|
+
|
17
|
+
|
18
|
+
##
|
19
|
+
|
20
|
+
|
21
|
+
class Type(StrOrStrsKeyword, ValidationKeyword, lang.Final, tag='type'):
|
22
|
+
pass
|
23
|
+
|
24
|
+
|
25
|
+
class Items(KeywordsKeyword, ValidationKeyword, lang.Final, tag='items'):
|
26
|
+
pass
|
27
|
+
|
28
|
+
|
29
|
+
class Required(StrOrStrsKeyword, ValidationKeyword, lang.Final, tag='required'):
|
30
|
+
pass
|
31
|
+
|
32
|
+
|
33
|
+
class Properties(StrToKeywordsKeyword, ValidationKeyword, lang.Final, tag='properties'):
|
34
|
+
pass
|
35
|
+
|
36
|
+
|
37
|
+
##
|
38
|
+
|
39
|
+
|
40
|
+
class MaxItems(NumberKeyword, ValidationKeyword, lang.Final, tag='maxItems'):
|
41
|
+
pass
|
42
|
+
|
43
|
+
|
44
|
+
class MinItems(NumberKeyword, ValidationKeyword, lang.Final, tag='minItems'):
|
45
|
+
pass
|
46
|
+
|
47
|
+
|
48
|
+
class UniqueItems(BooleanKeyword, ValidationKeyword, lang.Final, tag='uniqueItems'):
|
49
|
+
pass
|
50
|
+
|
51
|
+
|
52
|
+
#
|
53
|
+
|
54
|
+
|
55
|
+
class Maximum(NumberKeyword, ValidationKeyword, lang.Final, tag='maximum'):
|
56
|
+
pass
|
57
|
+
|
58
|
+
|
59
|
+
class ExclusiveMaximum(NumberKeyword, ValidationKeyword, lang.Final, tag='exclusiveMaximum'):
|
60
|
+
pass
|
61
|
+
|
62
|
+
|
63
|
+
class Minimum(NumberKeyword, ValidationKeyword, lang.Final, tag='minimum'):
|
64
|
+
pass
|
65
|
+
|
66
|
+
|
67
|
+
class ExclusiveMinimum(NumberKeyword, ValidationKeyword, lang.Final, tag='exclusiveMinimum'):
|
68
|
+
pass
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import enum
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
|
5
|
+
class JsonType(enum.Enum):
|
6
|
+
NULL = enum.auto()
|
7
|
+
BOOLEAN = enum.auto()
|
8
|
+
OBJECT = enum.auto()
|
9
|
+
ARRAY = enum.auto()
|
10
|
+
NUMBER = enum.auto()
|
11
|
+
STRING = enum.auto()
|
12
|
+
|
13
|
+
|
14
|
+
TYPE_SETS_BY_JSON_TYPE: ta.Mapping[JsonType, ta.AbstractSet[type]] = {
|
15
|
+
JsonType.NULL: {type(None)},
|
16
|
+
JsonType.BOOLEAN: {bool},
|
17
|
+
JsonType.OBJECT: {dict},
|
18
|
+
JsonType.ARRAY: {list},
|
19
|
+
JsonType.NUMBER: {int, float},
|
20
|
+
JsonType.STRING: {str},
|
21
|
+
}
|