omlish 0.0.0.dev1__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 +7 -0
- omlish/__init__.py +0 -0
- omlish/argparse.py +223 -0
- omlish/asyncs/__init__.py +17 -0
- omlish/asyncs/anyio.py +23 -0
- omlish/asyncs/asyncio.py +19 -0
- omlish/asyncs/asyncs.py +76 -0
- omlish/asyncs/futures.py +179 -0
- omlish/asyncs/trio.py +11 -0
- omlish/c3.py +173 -0
- omlish/cached.py +9 -0
- omlish/check.py +231 -0
- omlish/collections/__init__.py +63 -0
- omlish/collections/_abc.py +156 -0
- omlish/collections/_io_abc.py +78 -0
- omlish/collections/cache/__init__.py +11 -0
- omlish/collections/cache/descriptor.py +188 -0
- omlish/collections/cache/impl.py +485 -0
- omlish/collections/cache/types.py +37 -0
- omlish/collections/coerce.py +337 -0
- omlish/collections/frozen.py +148 -0
- omlish/collections/identity.py +106 -0
- omlish/collections/indexed.py +75 -0
- omlish/collections/mappings.py +127 -0
- omlish/collections/ordered.py +81 -0
- omlish/collections/persistent.py +36 -0
- omlish/collections/skiplist.py +193 -0
- omlish/collections/sorted.py +126 -0
- omlish/collections/treap.py +228 -0
- omlish/collections/treapmap.py +144 -0
- omlish/collections/unmodifiable.py +174 -0
- omlish/collections/utils.py +110 -0
- omlish/configs/__init__.py +0 -0
- omlish/configs/flattening.py +147 -0
- omlish/configs/props.py +64 -0
- omlish/dataclasses/__init__.py +83 -0
- omlish/dataclasses/impl/__init__.py +6 -0
- omlish/dataclasses/impl/api.py +260 -0
- omlish/dataclasses/impl/as_.py +76 -0
- omlish/dataclasses/impl/exceptions.py +2 -0
- omlish/dataclasses/impl/fields.py +148 -0
- omlish/dataclasses/impl/frozen.py +55 -0
- omlish/dataclasses/impl/hashing.py +85 -0
- omlish/dataclasses/impl/init.py +173 -0
- omlish/dataclasses/impl/internals.py +118 -0
- omlish/dataclasses/impl/main.py +150 -0
- omlish/dataclasses/impl/metaclass.py +126 -0
- omlish/dataclasses/impl/metadata.py +74 -0
- omlish/dataclasses/impl/order.py +47 -0
- omlish/dataclasses/impl/params.py +150 -0
- omlish/dataclasses/impl/processing.py +16 -0
- omlish/dataclasses/impl/reflect.py +173 -0
- omlish/dataclasses/impl/replace.py +40 -0
- omlish/dataclasses/impl/repr.py +34 -0
- omlish/dataclasses/impl/simple.py +92 -0
- omlish/dataclasses/impl/slots.py +80 -0
- omlish/dataclasses/impl/utils.py +167 -0
- omlish/defs.py +193 -0
- omlish/dispatch/__init__.py +3 -0
- omlish/dispatch/dispatch.py +137 -0
- omlish/dispatch/functions.py +52 -0
- omlish/dispatch/methods.py +162 -0
- omlish/docker.py +149 -0
- omlish/dynamic.py +220 -0
- omlish/graphs/__init__.py +0 -0
- omlish/graphs/dot/__init__.py +19 -0
- omlish/graphs/dot/items.py +162 -0
- omlish/graphs/dot/rendering.py +147 -0
- omlish/graphs/dot/utils.py +30 -0
- omlish/graphs/trees.py +249 -0
- omlish/http/__init__.py +0 -0
- omlish/http/consts.py +20 -0
- omlish/http/wsgi.py +34 -0
- omlish/inject/__init__.py +85 -0
- omlish/inject/binder.py +12 -0
- omlish/inject/bindings.py +49 -0
- omlish/inject/eagers.py +21 -0
- omlish/inject/elements.py +43 -0
- omlish/inject/exceptions.py +49 -0
- omlish/inject/impl/__init__.py +0 -0
- omlish/inject/impl/bindings.py +19 -0
- omlish/inject/impl/elements.py +154 -0
- omlish/inject/impl/injector.py +182 -0
- omlish/inject/impl/inspect.py +98 -0
- omlish/inject/impl/private.py +109 -0
- omlish/inject/impl/providers.py +132 -0
- omlish/inject/impl/scopes.py +198 -0
- omlish/inject/injector.py +40 -0
- omlish/inject/inspect.py +14 -0
- omlish/inject/keys.py +43 -0
- omlish/inject/managed.py +24 -0
- omlish/inject/overrides.py +18 -0
- omlish/inject/private.py +29 -0
- omlish/inject/providers.py +111 -0
- omlish/inject/proxy.py +48 -0
- omlish/inject/scopes.py +84 -0
- omlish/inject/types.py +21 -0
- omlish/iterators.py +184 -0
- omlish/json.py +194 -0
- omlish/lang/__init__.py +112 -0
- omlish/lang/cached.py +267 -0
- omlish/lang/classes/__init__.py +24 -0
- omlish/lang/classes/abstract.py +74 -0
- omlish/lang/classes/restrict.py +137 -0
- omlish/lang/classes/simple.py +120 -0
- omlish/lang/classes/test/__init__.py +0 -0
- omlish/lang/classes/test/test_abstract.py +89 -0
- omlish/lang/classes/test/test_restrict.py +71 -0
- omlish/lang/classes/test/test_simple.py +58 -0
- omlish/lang/classes/test/test_virtual.py +72 -0
- omlish/lang/classes/virtual.py +130 -0
- omlish/lang/clsdct.py +67 -0
- omlish/lang/cmp.py +63 -0
- omlish/lang/contextmanagers.py +249 -0
- omlish/lang/datetimes.py +67 -0
- omlish/lang/descriptors.py +52 -0
- omlish/lang/functions.py +126 -0
- omlish/lang/imports.py +153 -0
- omlish/lang/iterables.py +54 -0
- omlish/lang/maybes.py +136 -0
- omlish/lang/objects.py +103 -0
- omlish/lang/resolving.py +50 -0
- omlish/lang/strings.py +128 -0
- omlish/lang/typing.py +92 -0
- omlish/libc.py +532 -0
- omlish/logs/__init__.py +9 -0
- omlish/logs/_abc.py +247 -0
- omlish/logs/configs.py +62 -0
- omlish/logs/filters.py +9 -0
- omlish/logs/formatters.py +67 -0
- omlish/logs/utils.py +20 -0
- omlish/marshal/__init__.py +52 -0
- omlish/marshal/any.py +25 -0
- omlish/marshal/base.py +201 -0
- omlish/marshal/base64.py +25 -0
- omlish/marshal/dataclasses.py +115 -0
- omlish/marshal/datetimes.py +90 -0
- omlish/marshal/enums.py +43 -0
- omlish/marshal/exceptions.py +7 -0
- omlish/marshal/factories.py +129 -0
- omlish/marshal/global_.py +33 -0
- omlish/marshal/iterables.py +57 -0
- omlish/marshal/mappings.py +66 -0
- omlish/marshal/naming.py +17 -0
- omlish/marshal/objects.py +106 -0
- omlish/marshal/optionals.py +49 -0
- omlish/marshal/polymorphism.py +147 -0
- omlish/marshal/primitives.py +43 -0
- omlish/marshal/registries.py +57 -0
- omlish/marshal/standard.py +80 -0
- omlish/marshal/utils.py +23 -0
- omlish/marshal/uuids.py +29 -0
- omlish/marshal/values.py +30 -0
- omlish/math.py +184 -0
- omlish/os.py +32 -0
- omlish/reflect.py +359 -0
- omlish/replserver/__init__.py +5 -0
- omlish/replserver/__main__.py +4 -0
- omlish/replserver/console.py +247 -0
- omlish/replserver/server.py +146 -0
- omlish/runmodule.py +28 -0
- omlish/stats.py +342 -0
- omlish/term.py +222 -0
- omlish/testing/__init__.py +7 -0
- omlish/testing/pydevd.py +225 -0
- omlish/testing/pytest/__init__.py +8 -0
- omlish/testing/pytest/helpers.py +35 -0
- omlish/testing/pytest/inject/__init__.py +1 -0
- omlish/testing/pytest/inject/harness.py +159 -0
- omlish/testing/pytest/plugins/__init__.py +20 -0
- omlish/testing/pytest/plugins/_registry.py +6 -0
- omlish/testing/pytest/plugins/logging.py +13 -0
- omlish/testing/pytest/plugins/pycharm.py +54 -0
- omlish/testing/pytest/plugins/repeat.py +19 -0
- omlish/testing/pytest/plugins/skips.py +32 -0
- omlish/testing/pytest/plugins/spacing.py +19 -0
- omlish/testing/pytest/plugins/switches.py +70 -0
- omlish/testing/testing.py +102 -0
- omlish/text/__init__.py +0 -0
- omlish/text/delimit.py +171 -0
- omlish/text/indent.py +50 -0
- omlish/text/parts.py +265 -0
- omlish-0.0.0.dev1.dist-info/LICENSE +21 -0
- omlish-0.0.0.dev1.dist-info/METADATA +17 -0
- omlish-0.0.0.dev1.dist-info/RECORD +187 -0
- omlish-0.0.0.dev1.dist-info/WHEEL +5 -0
- omlish-0.0.0.dev1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import typing as ta
|
|
2
|
+
|
|
3
|
+
from .. import check
|
|
4
|
+
from .. import collections as col
|
|
5
|
+
from .. import dataclasses as dc
|
|
6
|
+
from .. import reflect as rfl
|
|
7
|
+
from .base import MarshalContext
|
|
8
|
+
from .base import Marshaler
|
|
9
|
+
from .base import MarshalerFactory
|
|
10
|
+
from .base import Option
|
|
11
|
+
from .base import UnmarshalContext
|
|
12
|
+
from .base import Unmarshaler
|
|
13
|
+
from .base import UnmarshalerFactory
|
|
14
|
+
from .naming import Naming
|
|
15
|
+
from .naming import translate_name
|
|
16
|
+
from .objects import FieldInfo
|
|
17
|
+
from .objects import FieldMetadata
|
|
18
|
+
from .objects import ObjectMarshaler
|
|
19
|
+
from .objects import ObjectMetadata
|
|
20
|
+
from .objects import ObjectUnmarshaler
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
##
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_dataclass_metadata(ty: type) -> ObjectMetadata:
|
|
27
|
+
return check.optional_single(
|
|
28
|
+
e
|
|
29
|
+
for e in dc.get_merged_metadata(ty).get(dc.UserMetadata, [])
|
|
30
|
+
if isinstance(e, ObjectMetadata)
|
|
31
|
+
) or ObjectMetadata()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_field_infos(ty: type, opts: col.TypeMap[Option] = col.TypeMap()) -> ta.Sequence[FieldInfo]:
|
|
35
|
+
dc_md = get_dataclass_metadata(ty)
|
|
36
|
+
dc_naming = dc_md.field_naming or opts.get(Naming)
|
|
37
|
+
|
|
38
|
+
type_hints = ta.get_type_hints(ty)
|
|
39
|
+
|
|
40
|
+
ret: list[FieldInfo] = []
|
|
41
|
+
for field in dc.fields(ty):
|
|
42
|
+
if (f_naming := field.metadata.get(Naming, dc_naming)) is not None:
|
|
43
|
+
um_name = translate_name(field.name, f_naming)
|
|
44
|
+
else:
|
|
45
|
+
um_name = field.name
|
|
46
|
+
|
|
47
|
+
kw = dict(
|
|
48
|
+
name=field.name,
|
|
49
|
+
type=type_hints[field.name],
|
|
50
|
+
metadata=FieldMetadata(),
|
|
51
|
+
|
|
52
|
+
marshal_name=um_name,
|
|
53
|
+
unmarshal_names=[um_name],
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
if (fmd := field.metadata.get(FieldMetadata)) is not None:
|
|
57
|
+
kw.update(metadata=fmd)
|
|
58
|
+
|
|
59
|
+
if fmd.name is not None:
|
|
60
|
+
kw.update(
|
|
61
|
+
marshal_name=fmd.name,
|
|
62
|
+
unmarshal_names=col.unique([fmd.name, *(fmd.alts or ())]),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
ret.append(FieldInfo(**kw))
|
|
66
|
+
|
|
67
|
+
return ret
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _make_field_obj(ctx, ty, obj, fac):
|
|
71
|
+
if obj is not None:
|
|
72
|
+
return obj
|
|
73
|
+
if fac is not None:
|
|
74
|
+
return fac(ctx, ty)
|
|
75
|
+
return ctx.make(ty)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
##
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class DataclassMarshalerFactory(MarshalerFactory):
|
|
82
|
+
def __call__(self, ctx: MarshalContext, rty: rfl.Type) -> ta.Optional[Marshaler]:
|
|
83
|
+
if isinstance(rty, type) and dc.is_dataclass(rty):
|
|
84
|
+
dc_md = get_dataclass_metadata(rty)
|
|
85
|
+
fields = [
|
|
86
|
+
(fi, _make_field_obj(ctx, fi.type, fi.metadata.marshaler, fi.metadata.marshaler_factory))
|
|
87
|
+
for fi in get_field_infos(rty, ctx.options)
|
|
88
|
+
]
|
|
89
|
+
return ObjectMarshaler(
|
|
90
|
+
fields,
|
|
91
|
+
unknown_field=dc_md.unknown_field,
|
|
92
|
+
)
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
##
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class DataclassUnmarshalerFactory(UnmarshalerFactory):
|
|
100
|
+
def __call__(self, ctx: UnmarshalContext, rty: rfl.Type) -> ta.Optional[Unmarshaler]:
|
|
101
|
+
if isinstance(rty, type) and dc.is_dataclass(rty):
|
|
102
|
+
dc_md = get_dataclass_metadata(rty)
|
|
103
|
+
d: dict[str, tuple[FieldInfo, Unmarshaler]] = {}
|
|
104
|
+
for fi in get_field_infos(rty, ctx.options):
|
|
105
|
+
tup = (fi, _make_field_obj(ctx, fi.type, fi.metadata.unmarshaler, fi.metadata.unmarshaler_factory))
|
|
106
|
+
for un in fi.unmarshal_names:
|
|
107
|
+
if un in d:
|
|
108
|
+
raise KeyError(f'Duplicate fields for name {un!r}: {fi.name!r}, {d[un][0].name!r}')
|
|
109
|
+
d[un] = tup
|
|
110
|
+
return ObjectUnmarshaler(
|
|
111
|
+
rty,
|
|
112
|
+
d,
|
|
113
|
+
unknown_field=dc_md.unknown_field,
|
|
114
|
+
)
|
|
115
|
+
return None
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import dataclasses as dc
|
|
2
|
+
import datetime
|
|
3
|
+
import typing as ta
|
|
4
|
+
|
|
5
|
+
from .. import check
|
|
6
|
+
from .base import MarshalContext
|
|
7
|
+
from .base import Marshaler
|
|
8
|
+
from .base import MarshalerFactory
|
|
9
|
+
from .base import UnmarshalContext
|
|
10
|
+
from .base import Unmarshaler
|
|
11
|
+
from .base import UnmarshalerFactory
|
|
12
|
+
from .factories import TypeMapFactory
|
|
13
|
+
from .values import Value
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
DATE_FORMATS: ta.Sequence[str] = [
|
|
17
|
+
'%Y-%m-%d',
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
TIME_FORMATS: ta.Sequence[str] = [
|
|
21
|
+
' '.join([tp, *tz])
|
|
22
|
+
for tp in [
|
|
23
|
+
'%H:%M:%S.%f',
|
|
24
|
+
'%H:%M:%S',
|
|
25
|
+
'%H:%M',
|
|
26
|
+
]
|
|
27
|
+
for tz in ta.cast(list[list[str]], [
|
|
28
|
+
[],
|
|
29
|
+
['%z'],
|
|
30
|
+
['%Z'],
|
|
31
|
+
['%z', '%Z'],
|
|
32
|
+
['%Z', '%z'],
|
|
33
|
+
])
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
SEPS: ta.Sequence[str] = ['T', ' ']
|
|
37
|
+
|
|
38
|
+
DATETIME_FORMATS: ta.Sequence[str] = [
|
|
39
|
+
s.join([df, tf])
|
|
40
|
+
for s in SEPS
|
|
41
|
+
for df in DATE_FORMATS
|
|
42
|
+
for tf in TIME_FORMATS
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dc.dataclass(frozen=True)
|
|
47
|
+
class DatetimeMarshaler(Marshaler):
|
|
48
|
+
fmt: str
|
|
49
|
+
|
|
50
|
+
def marshal(self, ctx: MarshalContext, o: datetime.datetime) -> Value:
|
|
51
|
+
return o.strftime(self.fmt)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dc.dataclass(frozen=True)
|
|
55
|
+
class DatetimeUnmarshaler(Unmarshaler):
|
|
56
|
+
fmts: ta.Sequence[str]
|
|
57
|
+
try_iso: bool = False
|
|
58
|
+
|
|
59
|
+
def unmarshal(self, ctx: UnmarshalContext, v: Value) -> datetime.datetime:
|
|
60
|
+
v = check.isinstance(v, str)
|
|
61
|
+
|
|
62
|
+
if self.try_iso:
|
|
63
|
+
try:
|
|
64
|
+
return datetime.datetime.fromisoformat(v)
|
|
65
|
+
except ValueError:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
for fmt in self.fmts:
|
|
69
|
+
try:
|
|
70
|
+
return datetime.datetime.strptime(v, fmt)
|
|
71
|
+
except ValueError:
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
raise ValueError(v)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
DATETIME_MARSHALER = DatetimeMarshaler(DATETIME_FORMATS[0])
|
|
78
|
+
DATETIME_UNMARSHALER = DatetimeUnmarshaler(DATETIME_FORMATS, try_iso=True)
|
|
79
|
+
|
|
80
|
+
DATETIME_MARSHALER_FACTORY: MarshalerFactory = TypeMapFactory({datetime.datetime: DATETIME_MARSHALER})
|
|
81
|
+
DATETIME_UNMARSHALER_FACTORY: UnmarshalerFactory = TypeMapFactory({datetime.datetime: DATETIME_UNMARSHALER})
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class IsoDatetimeMarshalerUnmarshaler(Marshaler, Unmarshaler):
|
|
85
|
+
|
|
86
|
+
def marshal(self, ctx: MarshalContext, o: datetime.datetime) -> Value:
|
|
87
|
+
return o.isoformat()
|
|
88
|
+
|
|
89
|
+
def unmarshal(self, ctx: UnmarshalContext, v: Value) -> datetime.datetime:
|
|
90
|
+
return datetime.datetime.fromisoformat(v) # type: ignore
|
omlish/marshal/enums.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import dataclasses as dc
|
|
2
|
+
import enum
|
|
3
|
+
import typing as ta
|
|
4
|
+
|
|
5
|
+
from .. import check
|
|
6
|
+
from .. import reflect as rfl
|
|
7
|
+
from .base import MarshalContext
|
|
8
|
+
from .base import Marshaler
|
|
9
|
+
from .base import MarshalerFactory
|
|
10
|
+
from .base import UnmarshalContext
|
|
11
|
+
from .base import Unmarshaler
|
|
12
|
+
from .base import UnmarshalerFactory
|
|
13
|
+
from .values import Value
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dc.dataclass(frozen=True)
|
|
17
|
+
class EnumMarshaler(Marshaler):
|
|
18
|
+
ty: type[enum.Enum]
|
|
19
|
+
|
|
20
|
+
def marshal(self, ctx: MarshalContext, o: ta.Any) -> Value:
|
|
21
|
+
return o.name
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class EnumMarshalerFactory(MarshalerFactory):
|
|
25
|
+
def __call__(self, ctx: MarshalContext, rty: rfl.Type) -> ta.Optional[Marshaler]:
|
|
26
|
+
if isinstance(rty, type) and issubclass(rty, enum.Enum):
|
|
27
|
+
return EnumMarshaler(rty)
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dc.dataclass(frozen=True)
|
|
32
|
+
class EnumUnmarshaler(Unmarshaler):
|
|
33
|
+
ty: type[enum.Enum]
|
|
34
|
+
|
|
35
|
+
def unmarshal(self, ctx: UnmarshalContext, v: Value) -> ta.Any:
|
|
36
|
+
return self.ty[check.isinstance(v, str)]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class EnumUnmarshalerFactory(UnmarshalerFactory):
|
|
40
|
+
def __call__(self, ctx: UnmarshalContext, rty: rfl.Type) -> ta.Optional[Unmarshaler]:
|
|
41
|
+
if isinstance(rty, type) and issubclass(rty, enum.Enum):
|
|
42
|
+
return EnumUnmarshaler(rty)
|
|
43
|
+
return None
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import dataclasses as dc
|
|
3
|
+
import enum
|
|
4
|
+
import threading
|
|
5
|
+
import typing as ta
|
|
6
|
+
|
|
7
|
+
from .. import reflect as rfl
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
R = ta.TypeVar('R')
|
|
11
|
+
C = ta.TypeVar('C')
|
|
12
|
+
A = ta.TypeVar('A')
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
##
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Factory(abc.ABC, ta.Generic[R, C, A]):
|
|
19
|
+
@abc.abstractmethod
|
|
20
|
+
def __call__(self, ctx: C, arg: A) -> ta.Optional[R]:
|
|
21
|
+
raise NotImplementedError
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
##
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dc.dataclass(frozen=True)
|
|
28
|
+
class FuncFactory(ta.Generic[R, C, A]):
|
|
29
|
+
fn: ta.Callable[[C, A], ta.Optional[R]]
|
|
30
|
+
|
|
31
|
+
def __call__(self, ctx: C, arg: A) -> ta.Optional[R]:
|
|
32
|
+
return self.fn(ctx, arg)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
##
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dc.dataclass(frozen=True)
|
|
39
|
+
class TypeMapFactory(Factory[R, C, rfl.Type]):
|
|
40
|
+
m: ta.Mapping[rfl.Type, R] = dc.field(default_factory=dict)
|
|
41
|
+
|
|
42
|
+
def __call__(self, ctx: C, rty: rfl.Type) -> ta.Optional[R]:
|
|
43
|
+
return self.m.get(rty)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
##
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class TypeCacheFactory(Factory[R, C, rfl.Type]):
|
|
50
|
+
def __init__(self, f: Factory[R, C, rfl.Type]) -> None:
|
|
51
|
+
super().__init__()
|
|
52
|
+
self._f = f
|
|
53
|
+
self._dct: dict[rfl.Type, ta.Optional[R]] = {}
|
|
54
|
+
self._mtx = threading.RLock()
|
|
55
|
+
|
|
56
|
+
def __call__(self, ctx: C, rty: rfl.Type) -> ta.Optional[R]:
|
|
57
|
+
try:
|
|
58
|
+
return self._dct[rty]
|
|
59
|
+
except KeyError:
|
|
60
|
+
pass
|
|
61
|
+
with self._mtx:
|
|
62
|
+
try:
|
|
63
|
+
return self._dct[rty]
|
|
64
|
+
except KeyError:
|
|
65
|
+
ret = self._dct[rty] = self._f(ctx, rty)
|
|
66
|
+
return ret
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
##
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class RecursiveTypeFactory(Factory[R, C, rfl.Type]):
|
|
73
|
+
def __init__(
|
|
74
|
+
self,
|
|
75
|
+
f: Factory[R, C, rfl.Type],
|
|
76
|
+
prx: ta.Callable[[], tuple[ta.Optional[R], ta.Callable[[ta.Optional[R]], None]]],
|
|
77
|
+
) -> None:
|
|
78
|
+
super().__init__()
|
|
79
|
+
self._f = f
|
|
80
|
+
self._prx = prx
|
|
81
|
+
self._dct: dict[rfl.Type, ta.Optional[R]] = {}
|
|
82
|
+
|
|
83
|
+
def __call__(self, ctx: C, rty: rfl.Type) -> ta.Optional[R]:
|
|
84
|
+
try:
|
|
85
|
+
return self._dct[rty]
|
|
86
|
+
except KeyError:
|
|
87
|
+
pass
|
|
88
|
+
p, sp = self._prx()
|
|
89
|
+
self._dct[rty] = p
|
|
90
|
+
try:
|
|
91
|
+
r = self._f(ctx, rty)
|
|
92
|
+
sp(r)
|
|
93
|
+
return r
|
|
94
|
+
finally:
|
|
95
|
+
del self._dct[rty]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
##
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class CompositeFactory(Factory[R, C, A]):
|
|
102
|
+
class Strategy(enum.Enum):
|
|
103
|
+
FIRST = enum.auto()
|
|
104
|
+
ONE = enum.auto()
|
|
105
|
+
|
|
106
|
+
def __init__(self, *fs: Factory[R, C, A], strategy: Strategy = Strategy.FIRST) -> None:
|
|
107
|
+
super().__init__()
|
|
108
|
+
self._fs = fs
|
|
109
|
+
self._st = strategy
|
|
110
|
+
|
|
111
|
+
def __call__(self, ctx: C, arg: A) -> ta.Optional[R]:
|
|
112
|
+
w: list[R] = []
|
|
113
|
+
for c in self._fs:
|
|
114
|
+
if (r := c(ctx, arg)) is None:
|
|
115
|
+
continue
|
|
116
|
+
if self._st is CompositeFactory.Strategy.FIRST:
|
|
117
|
+
return r
|
|
118
|
+
w.append(r)
|
|
119
|
+
|
|
120
|
+
if not w:
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
if self._st is CompositeFactory.Strategy.ONE:
|
|
124
|
+
if len(w) == 1:
|
|
125
|
+
return w[0]
|
|
126
|
+
|
|
127
|
+
raise TypeError(f'multiple implementations: {arg} {w}')
|
|
128
|
+
|
|
129
|
+
raise TypeError(f'unknown composite strategy: {self._st}')
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from .registries import Registry
|
|
2
|
+
from .standard import new_standard_marshaler_factory
|
|
3
|
+
from .base import MarshalContext
|
|
4
|
+
from .base import UnmarshalContext
|
|
5
|
+
from .standard import new_standard_unmarshaler_factory
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
##
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
GLOBAL_REGISTRY = Registry()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
##
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
GLOBAL_MARSHALER_FACTORY = new_standard_marshaler_factory()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def marshal(obj, ty=None, **kwargs):
|
|
21
|
+
mc = MarshalContext(GLOBAL_REGISTRY, factory=GLOBAL_MARSHALER_FACTORY, **kwargs)
|
|
22
|
+
return mc.make(ty if ty is not None else type(obj)).marshal(mc, obj)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
##
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
GLOBAL_UNMARSHALER_FACTORY = new_standard_unmarshaler_factory()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def unmarshal(v, ty, **kwargs):
|
|
32
|
+
uc = UnmarshalContext(GLOBAL_REGISTRY, factory=GLOBAL_UNMARSHALER_FACTORY, **kwargs)
|
|
33
|
+
return uc.make(ty).unmarshal(uc, v)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import collections.abc
|
|
2
|
+
import dataclasses as dc
|
|
3
|
+
import functools
|
|
4
|
+
import typing as ta
|
|
5
|
+
|
|
6
|
+
from .. import check
|
|
7
|
+
from .. import reflect as rfl
|
|
8
|
+
from .base import MarshalContext
|
|
9
|
+
from .base import Marshaler
|
|
10
|
+
from .base import MarshalerFactory
|
|
11
|
+
from .base import UnmarshalContext
|
|
12
|
+
from .base import Unmarshaler
|
|
13
|
+
from .base import UnmarshalerFactory
|
|
14
|
+
from .values import Value
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dc.dataclass(frozen=True)
|
|
18
|
+
class IterableMarshaler(Marshaler):
|
|
19
|
+
e: Marshaler
|
|
20
|
+
|
|
21
|
+
def marshal(self, ctx: MarshalContext, o: ta.Iterable) -> Value:
|
|
22
|
+
return list(map(functools.partial(self.e.marshal, ctx), o))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class IterableMarshalerFactory(MarshalerFactory):
|
|
26
|
+
def __call__(self, ctx: MarshalContext, rty: rfl.Type) -> ta.Optional[Marshaler]:
|
|
27
|
+
if isinstance(rty, rfl.Generic) and issubclass(rty.cls, collections.abc.Iterable):
|
|
28
|
+
if (e := ctx.make(check.single(rty.args))) is None:
|
|
29
|
+
return None # type: ignore
|
|
30
|
+
return IterableMarshaler(e)
|
|
31
|
+
if isinstance(rty, type) and issubclass(rty, collections.abc.Iterable):
|
|
32
|
+
if (e := ctx.make(ta.Any)) is None:
|
|
33
|
+
return None # type: ignore
|
|
34
|
+
return IterableMarshaler(e)
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dc.dataclass(frozen=True)
|
|
39
|
+
class IterableUnmarshaler(Unmarshaler):
|
|
40
|
+
ctor: ta.Callable[[ta.Iterable[ta.Any]], ta.Iterable]
|
|
41
|
+
e: Unmarshaler
|
|
42
|
+
|
|
43
|
+
def unmarshal(self, ctx: UnmarshalContext, v: Value) -> ta.Iterable:
|
|
44
|
+
return self.ctor(map(functools.partial(self.e.unmarshal, ctx), check.isinstance(v, collections.abc.Iterable)))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class IterableUnmarshalerFactory(UnmarshalerFactory):
|
|
48
|
+
def __call__(self, ctx: UnmarshalContext, rty: rfl.Type) -> ta.Optional[Unmarshaler]:
|
|
49
|
+
if isinstance(rty, rfl.Generic) and issubclass(rty.cls, collections.abc.Iterable):
|
|
50
|
+
if (e := ctx.make(check.single(rty.args))) is None:
|
|
51
|
+
return None # type: ignore
|
|
52
|
+
return IterableUnmarshaler(rty.cls, e) # noqa
|
|
53
|
+
if isinstance(rty, type) and issubclass(rty, collections.abc.Iterable):
|
|
54
|
+
if (e := ctx.make(ta.Any)) is None:
|
|
55
|
+
return None # type: ignore
|
|
56
|
+
return IterableUnmarshaler(rty, e) # noqa
|
|
57
|
+
return None
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import collections.abc
|
|
2
|
+
import dataclasses as dc
|
|
3
|
+
import typing as ta
|
|
4
|
+
|
|
5
|
+
from .. import check
|
|
6
|
+
from .. import reflect as rfl
|
|
7
|
+
from .base import MarshalContext
|
|
8
|
+
from .base import Marshaler
|
|
9
|
+
from .base import MarshalerFactory
|
|
10
|
+
from .base import UnmarshalContext
|
|
11
|
+
from .base import Unmarshaler
|
|
12
|
+
from .base import UnmarshalerFactory
|
|
13
|
+
from .values import Value
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dc.dataclass(frozen=True)
|
|
17
|
+
class MappingMarshaler(Marshaler):
|
|
18
|
+
ke: Marshaler
|
|
19
|
+
ve: Marshaler
|
|
20
|
+
|
|
21
|
+
def marshal(self, ctx: MarshalContext, o: ta.Mapping) -> Value:
|
|
22
|
+
return {
|
|
23
|
+
self.ke.marshal(ctx, uk): self.ve.marshal(ctx, uv)
|
|
24
|
+
for uk, uv in check.isinstance(o, collections.abc.Mapping).items()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class MappingMarshalerFactory(MarshalerFactory):
|
|
29
|
+
def __call__(self, ctx: MarshalContext, rty: rfl.Type) -> ta.Optional[Marshaler]:
|
|
30
|
+
if isinstance(rty, rfl.Generic) and issubclass(rty.cls, collections.abc.Mapping):
|
|
31
|
+
kt, vt = rty.args
|
|
32
|
+
if (ke := ctx.make(kt)) is None or (ve := ctx.make(vt)) is None:
|
|
33
|
+
return None # type: ignore
|
|
34
|
+
return MappingMarshaler(ke, ve)
|
|
35
|
+
if isinstance(rty, type) and issubclass(rty, collections.abc.Mapping):
|
|
36
|
+
if (e := ctx.make(ta.Any)) is None:
|
|
37
|
+
return None # type: ignore
|
|
38
|
+
return MappingMarshaler(e, e)
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dc.dataclass(frozen=True)
|
|
43
|
+
class MappingUnmarshaler(Unmarshaler):
|
|
44
|
+
ctor: ta.Callable[[ta.Mapping[ta.Any, ta.Any]], ta.Mapping]
|
|
45
|
+
ke: Unmarshaler
|
|
46
|
+
ve: Unmarshaler
|
|
47
|
+
|
|
48
|
+
def unmarshal(self, ctx: UnmarshalContext, v: Value) -> ta.Mapping:
|
|
49
|
+
dct: dict = {}
|
|
50
|
+
for mk, mv in check.isinstance(v, collections.abc.Mapping).items():
|
|
51
|
+
dct[self.ke.unmarshal(ctx, mk)] = self.ve.unmarshal(ctx, mv) # type: ignore
|
|
52
|
+
return self.ctor(dct)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class MappingUnmarshalerFactory(UnmarshalerFactory):
|
|
56
|
+
def __call__(self, ctx: UnmarshalContext, rty: rfl.Type) -> ta.Optional[Unmarshaler]:
|
|
57
|
+
if isinstance(rty, rfl.Generic) and issubclass(rty.cls, collections.abc.Mapping):
|
|
58
|
+
kt, vt = rty.args
|
|
59
|
+
if (ke := ctx.make(kt)) is None or (ve := ctx.make(vt)) is None:
|
|
60
|
+
return None # type: ignore
|
|
61
|
+
return MappingUnmarshaler(rty.cls, ke, ve)
|
|
62
|
+
if isinstance(rty, type) and issubclass(rty, collections.abc.Mapping):
|
|
63
|
+
if (e := ctx.make(ta.Any)) is None:
|
|
64
|
+
return None # type: ignore
|
|
65
|
+
return MappingUnmarshaler(rty, e, e)
|
|
66
|
+
return None
|
omlish/marshal/naming.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import enum
|
|
2
|
+
|
|
3
|
+
from .. import lang
|
|
4
|
+
from .base import Option
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Naming(Option, enum.Enum):
|
|
8
|
+
SNAKE = 'snake'
|
|
9
|
+
CAMEL = 'camel'
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def translate_name(n: str, e: Naming) -> str:
|
|
13
|
+
if e is Naming.SNAKE:
|
|
14
|
+
return lang.snake_case(n)
|
|
15
|
+
if e is Naming.CAMEL:
|
|
16
|
+
return lang.camel_case(n)
|
|
17
|
+
raise ValueError(e)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- cfg naming
|
|
4
|
+
- adapters for dataclasses / namedtuples / user objects (as confitured)
|
|
5
|
+
"""
|
|
6
|
+
import collections.abc
|
|
7
|
+
import typing as ta
|
|
8
|
+
|
|
9
|
+
from .. import check
|
|
10
|
+
from .. import dataclasses as dc
|
|
11
|
+
from .base import MarshalContext
|
|
12
|
+
from .base import Marshaler
|
|
13
|
+
from .base import MarshalerFactory
|
|
14
|
+
from .base import UnmarshalContext
|
|
15
|
+
from .base import Unmarshaler
|
|
16
|
+
from .base import UnmarshalerFactory
|
|
17
|
+
from .naming import Naming
|
|
18
|
+
from .values import Value
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
##
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dc.dataclass(frozen=True)
|
|
25
|
+
class ObjectMetadata:
|
|
26
|
+
field_naming: Naming | None = None
|
|
27
|
+
|
|
28
|
+
unknown_field: str | None = None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dc.dataclass(frozen=True)
|
|
32
|
+
class FieldMetadata:
|
|
33
|
+
name: str | None = None
|
|
34
|
+
alts: ta.Iterable[str] | None = None
|
|
35
|
+
|
|
36
|
+
marshaler: Marshaler | None = None
|
|
37
|
+
marshaler_factory: MarshalerFactory | None = None
|
|
38
|
+
|
|
39
|
+
unmarshaler: Unmarshaler | None = None
|
|
40
|
+
unmarshaler_factory: UnmarshalerFactory | None = None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
##
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dc.dataclass(frozen=True)
|
|
47
|
+
class FieldInfo:
|
|
48
|
+
name: str
|
|
49
|
+
type: ta.Any
|
|
50
|
+
metadata: FieldMetadata
|
|
51
|
+
|
|
52
|
+
marshal_name: str
|
|
53
|
+
unmarshal_names: ta.Sequence[str]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
##
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dc.dataclass(frozen=True)
|
|
60
|
+
class ObjectMarshaler(Marshaler):
|
|
61
|
+
fields: ta.Sequence[tuple[FieldInfo, Marshaler]]
|
|
62
|
+
unknown_field: str | None = None
|
|
63
|
+
|
|
64
|
+
def marshal(self, ctx: MarshalContext, o: ta.Any) -> Value:
|
|
65
|
+
ret = {
|
|
66
|
+
fi.marshal_name: m.marshal(ctx, getattr(o, fi.name))
|
|
67
|
+
for fi, m in self.fields
|
|
68
|
+
}
|
|
69
|
+
if self.unknown_field is not None:
|
|
70
|
+
if (ukf := getattr(o, self.unknown_field)):
|
|
71
|
+
if (dks := set(ret) & set(ukf)):
|
|
72
|
+
raise KeyError(f'Unknown field keys duplicate fields: {dks!r}')
|
|
73
|
+
ret.update(ukf) # FIXME: marshal?
|
|
74
|
+
return ret
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
##
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dc.dataclass(frozen=True)
|
|
81
|
+
class ObjectUnmarshaler(Unmarshaler):
|
|
82
|
+
cls: type
|
|
83
|
+
fields_by_unmarshal_name: ta.Mapping[str, tuple[FieldInfo, Unmarshaler]]
|
|
84
|
+
unknown_field: str | None = None
|
|
85
|
+
|
|
86
|
+
def unmarshal(self, ctx: UnmarshalContext, v: Value) -> ta.Any:
|
|
87
|
+
ma = check.isinstance(v, collections.abc.Mapping)
|
|
88
|
+
u: ta.Any
|
|
89
|
+
kw: dict[str, ta.Any] = {}
|
|
90
|
+
ukf: dict[str, ta.Any] | None = None
|
|
91
|
+
if self.unknown_field is not None:
|
|
92
|
+
kw[self.unknown_field] = ukf = {}
|
|
93
|
+
for k, mv in ma.items():
|
|
94
|
+
ks = check.isinstance(k, str)
|
|
95
|
+
try:
|
|
96
|
+
fi, u = self.fields_by_unmarshal_name[ks]
|
|
97
|
+
except KeyError:
|
|
98
|
+
if ukf is not None:
|
|
99
|
+
ukf[ks] = mv # FIXME: unmarshal?
|
|
100
|
+
continue
|
|
101
|
+
else:
|
|
102
|
+
raise
|
|
103
|
+
if fi.name in kw:
|
|
104
|
+
raise KeyError(f'Duplicate keys for field {fi.name!r}: {ks!r}')
|
|
105
|
+
kw[fi.name] = u.unmarshal(ctx, mv)
|
|
106
|
+
return self.cls(**kw)
|