omlish 0.0.0.dev284__py3-none-any.whl → 0.0.0.dev285__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 +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 +174 -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 +45 -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/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/typedvalues/marshal.py +2 -2
- {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev285.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev285.dist-info}/RECORD +83 -43
- 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.dev285.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev285.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev285.dist-info}/licenses/LICENSE +0 -0
- {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev285.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,83 @@
|
|
1
|
+
import typing as ta
|
2
|
+
|
3
|
+
from .globals import OBJECT_SETATTR_GLOBAL
|
4
|
+
from .globals import FnGlobal
|
5
|
+
from .idents import SELF_DICT_IDENT
|
6
|
+
from .idents import SELF_IDENT
|
7
|
+
|
8
|
+
|
9
|
+
##
|
10
|
+
|
11
|
+
|
12
|
+
def build_attr_tuple_body_src_lines(
|
13
|
+
obj_name: str,
|
14
|
+
*attrs: str,
|
15
|
+
prefix: str = '',
|
16
|
+
) -> list[str]:
|
17
|
+
return [
|
18
|
+
f'{prefix}{obj_name}.{a},'
|
19
|
+
for a in attrs
|
20
|
+
]
|
21
|
+
|
22
|
+
|
23
|
+
def build_attr_kwargs_body_src_lines(
|
24
|
+
obj_name: str,
|
25
|
+
*attrs: str,
|
26
|
+
prefix: str = '',
|
27
|
+
) -> list[str]:
|
28
|
+
return [
|
29
|
+
f'{prefix}{a}={obj_name}.{a},'
|
30
|
+
for a in attrs
|
31
|
+
]
|
32
|
+
|
33
|
+
|
34
|
+
class SetattrSrcBuilder:
|
35
|
+
def __init__(
|
36
|
+
self,
|
37
|
+
*,
|
38
|
+
object_ident: str = SELF_IDENT,
|
39
|
+
object_dict_ident: str | None = None,
|
40
|
+
) -> None:
|
41
|
+
super().__init__()
|
42
|
+
|
43
|
+
self._object_ident = object_ident
|
44
|
+
self._object_dict_ident = object_dict_ident
|
45
|
+
|
46
|
+
self._has_set_object_dict = False
|
47
|
+
self._global_refs: set[FnGlobal] = set()
|
48
|
+
|
49
|
+
@property
|
50
|
+
def refs(self) -> ta.AbstractSet[FnGlobal]:
|
51
|
+
return self._global_refs
|
52
|
+
|
53
|
+
def __call__(
|
54
|
+
self,
|
55
|
+
name: str,
|
56
|
+
value_src: str,
|
57
|
+
*,
|
58
|
+
frozen: bool,
|
59
|
+
override: bool,
|
60
|
+
) -> list[str]:
|
61
|
+
if override:
|
62
|
+
if self._object_dict_ident is not None:
|
63
|
+
return [f'{self._object_dict_ident}[{name!r}] = {value_src}']
|
64
|
+
|
65
|
+
else:
|
66
|
+
if not self._has_set_object_dict:
|
67
|
+
x = [
|
68
|
+
f'{SELF_DICT_IDENT} = {self._object_ident}.__dict__',
|
69
|
+
]
|
70
|
+
self._has_set_object_dict = True
|
71
|
+
else:
|
72
|
+
x = []
|
73
|
+
return [
|
74
|
+
*x,
|
75
|
+
f'{SELF_DICT_IDENT}[{name!r}] = {value_src}',
|
76
|
+
]
|
77
|
+
|
78
|
+
elif frozen:
|
79
|
+
self._global_refs.add(OBJECT_SETATTR_GLOBAL)
|
80
|
+
return [f'{OBJECT_SETATTR_GLOBAL.ident}({self._object_ident}, {name!r}, {value_src})']
|
81
|
+
|
82
|
+
else:
|
83
|
+
return [f'{self._object_ident}.{name} = {value_src}']
|
@@ -1,116 +1,90 @@
|
|
1
|
-
"""
|
2
|
-
TODO:
|
3
|
-
- more cache-recursive reuse - fields, mro, etc
|
4
|
-
"""
|
5
1
|
import dataclasses as dc
|
6
|
-
import sys
|
7
2
|
import typing as ta
|
8
3
|
import weakref
|
9
4
|
|
10
|
-
from
|
11
|
-
from
|
12
|
-
from
|
13
|
-
from
|
14
|
-
from
|
15
|
-
from .
|
16
|
-
from .internals import
|
17
|
-
from .internals import
|
18
|
-
from .internals import
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
from .metadata import get_merged_metadata
|
23
|
-
from .params import PARAMS_ATTR
|
24
|
-
from .params import ParamsExtras
|
25
|
-
from .params import get_params
|
26
|
-
from .params import get_params_extras
|
27
|
-
from .utils import Namespace
|
28
|
-
|
29
|
-
|
30
|
-
MISSING = dc.MISSING
|
5
|
+
from .. import cached
|
6
|
+
from .. import check
|
7
|
+
from .. import collections as col
|
8
|
+
from .. import lang
|
9
|
+
from .. import reflect as rfl
|
10
|
+
from .internals import STD_FIELDS_ATTR
|
11
|
+
from .internals import StdFieldType
|
12
|
+
from .internals import std_field_type
|
13
|
+
from .internals import std_is_kw_only
|
14
|
+
|
15
|
+
|
16
|
+
ClassAnnotations: ta.TypeAlias = ta.Mapping[str, ta.Any]
|
31
17
|
|
32
18
|
|
33
19
|
##
|
34
20
|
|
35
21
|
|
36
|
-
def
|
22
|
+
def _get_cls_annotations(cls: type) -> ClassAnnotations:
|
37
23
|
# Does not use ta.get_type_hints because that's what std dataclasses do [1]. Might be worth revisiting? A part
|
38
24
|
# of why they don't is to not import typing for efficiency but we don't care about that degree of startup speed.
|
39
25
|
# [1]: https://github.com/python/cpython/blob/54c63a32d06cb5f07a66245c375eac7d7efb964a/Lib/dataclasses.py#L985-L986 # noqa
|
40
26
|
return rfl.get_annotations(cls)
|
41
27
|
|
42
28
|
|
29
|
+
_CLS_ANNOTATIONS_CACHE: ta.MutableMapping[type, ClassAnnotations] = weakref.WeakKeyDictionary()
|
30
|
+
|
31
|
+
|
32
|
+
def get_cls_annotations(cls: type) -> ClassAnnotations:
|
33
|
+
try:
|
34
|
+
return _CLS_ANNOTATIONS_CACHE[cls]
|
35
|
+
except KeyError:
|
36
|
+
pass
|
37
|
+
ret = _get_cls_annotations(cls)
|
38
|
+
_CLS_ANNOTATIONS_CACHE[cls] = ret
|
39
|
+
return ret
|
40
|
+
|
41
|
+
|
43
42
|
##
|
44
43
|
|
45
44
|
|
46
|
-
class
|
47
|
-
def __init__(
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
45
|
+
class FieldsInspection:
|
46
|
+
def __init__(
|
47
|
+
self,
|
48
|
+
cls: type,
|
49
|
+
*,
|
50
|
+
cls_fields: ta.Mapping[str, dc.Field] | None = None,
|
51
|
+
) -> None:
|
52
52
|
super().__init__()
|
53
|
-
|
53
|
+
|
54
|
+
self._cls = cls
|
55
|
+
|
56
|
+
self._given_cls_fields = cls_fields
|
57
|
+
if cls_fields is None:
|
58
|
+
cls_fields = getattr(self._cls, STD_FIELDS_ATTR)
|
59
|
+
self._cls_fields = cls_fields
|
54
60
|
|
55
61
|
@property
|
56
62
|
def cls(self) -> type:
|
57
63
|
return self._cls # noqa
|
58
64
|
|
59
|
-
@
|
60
|
-
def
|
61
|
-
|
62
|
-
return sys.modules[self._cls.__module__].__dict__
|
63
|
-
else:
|
64
|
-
return {}
|
65
|
+
@property
|
66
|
+
def cls_fields(self) -> ta.Mapping[str, dc.Field]:
|
67
|
+
return self._cls_fields
|
65
68
|
|
66
69
|
@cached.property
|
67
70
|
def cls_annotations(self) -> ta.Mapping[str, ta.Any]:
|
68
71
|
return get_cls_annotations(self._cls)
|
69
72
|
|
70
|
-
|
71
|
-
|
72
|
-
@cached.property
|
73
|
-
def params(self) -> Params:
|
74
|
-
return get_params(self._cls)
|
75
|
-
|
76
|
-
@cached.property
|
77
|
-
def cls_params(self) -> Params | None:
|
78
|
-
return self._cls.__dict__.get(PARAMS_ATTR)
|
79
|
-
|
80
|
-
@cached.property
|
81
|
-
def params_extras(self) -> ParamsExtras:
|
82
|
-
return get_params_extras(self._cls)
|
83
|
-
|
84
|
-
@cached.property
|
85
|
-
def cls_params_extras(self) -> ParamsExtras | None:
|
86
|
-
return (self.cls_metadata or {}).get(ParamsExtras)
|
87
|
-
|
88
|
-
@cached.property
|
89
|
-
def cls_metadata(self) -> Metadata | None:
|
90
|
-
return self._cls.__dict__.get(METADATA_ATTR)
|
91
|
-
|
92
|
-
@cached.property
|
93
|
-
def merged_metadata(self) -> Metadata:
|
94
|
-
return get_merged_metadata(self._cls)
|
95
|
-
|
96
|
-
##
|
73
|
+
#
|
97
74
|
|
98
75
|
class _FoundFields(ta.NamedTuple):
|
99
76
|
fields: dict[str, dc.Field]
|
100
77
|
field_owners: dict[str, type]
|
101
78
|
|
102
|
-
@lang.cached_function
|
79
|
+
@lang.cached_function(no_wrapper_update=True)
|
103
80
|
def _find_fields(self) -> _FoundFields:
|
104
|
-
if self._constructing:
|
105
|
-
check.in_(FIELDS_ATTR, self._cls.__dict__)
|
106
|
-
|
107
81
|
fields: dict[str, dc.Field] = {}
|
108
82
|
field_owners: dict[str, type] = {}
|
109
83
|
|
110
84
|
for b in self._cls.__mro__[-1:0:-1]:
|
111
|
-
base_fields = getattr(b,
|
85
|
+
base_fields = getattr(b, STD_FIELDS_ATTR, None)
|
112
86
|
if base_fields is not None:
|
113
|
-
for name in
|
87
|
+
for name in get_cls_annotations(b):
|
114
88
|
try:
|
115
89
|
f = base_fields[name]
|
116
90
|
except KeyError:
|
@@ -118,14 +92,14 @@ class ClassInfo:
|
|
118
92
|
fields[f.name] = f
|
119
93
|
field_owners[f.name] = b
|
120
94
|
|
121
|
-
cls_fields =
|
95
|
+
cls_fields = self._cls_fields
|
122
96
|
for name, ann in self.cls_annotations.items():
|
123
|
-
if
|
97
|
+
if std_is_kw_only(self._cls, ann):
|
124
98
|
continue
|
125
99
|
fields[name] = cls_fields[name]
|
126
100
|
field_owners[name] = self._cls
|
127
101
|
|
128
|
-
return
|
102
|
+
return FieldsInspection._FoundFields(fields, field_owners)
|
129
103
|
|
130
104
|
@cached.property
|
131
105
|
def fields(self) -> ta.Mapping[str, dc.Field]:
|
@@ -133,7 +107,7 @@ class ClassInfo:
|
|
133
107
|
|
134
108
|
@cached.property
|
135
109
|
def instance_fields(self) -> ta.Sequence[dc.Field]:
|
136
|
-
return [f for f in self.fields.values() if
|
110
|
+
return [f for f in self.fields.values() if std_field_type(f) is StdFieldType.INSTANCE]
|
137
111
|
|
138
112
|
@cached.property
|
139
113
|
def field_owners(self) -> ta.Mapping[str, type]:
|
@@ -170,16 +144,5 @@ class ClassInfo:
|
|
170
144
|
return {k: rfl.to_annotation(v) for k, v in self.generic_replaced_field_types.items()}
|
171
145
|
|
172
146
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
_CLASS_INFO_CACHE: ta.MutableMapping[type, ClassInfo] = weakref.WeakKeyDictionary()
|
177
|
-
|
178
|
-
|
179
|
-
def reflect(obj: ta.Any) -> ClassInfo:
|
180
|
-
cls = obj if isinstance(obj, type) else type(obj)
|
181
|
-
try:
|
182
|
-
return _CLASS_INFO_CACHE[cls]
|
183
|
-
except KeyError:
|
184
|
-
_CLASS_INFO_CACHE[cls] = info = ClassInfo(cls)
|
185
|
-
return info
|
147
|
+
def inspect_fields(cls: type) -> FieldsInspection:
|
148
|
+
return FieldsInspection(cls)
|
@@ -8,48 +8,32 @@ import typing as ta
|
|
8
8
|
##
|
9
9
|
|
10
10
|
|
11
|
-
|
11
|
+
STD_HAS_DEFAULT_FACTORY = dc._HAS_DEFAULT_FACTORY # type: ignore # noqa
|
12
12
|
|
13
|
-
|
14
|
-
|
13
|
+
STD_FIELDS_ATTR = dc._FIELDS # type: ignore # noqa
|
14
|
+
STD_PARAMS_ATTR = dc._PARAMS # type: ignore # noqa
|
15
15
|
|
16
|
-
|
16
|
+
STD_POST_INIT_NAME = dc._POST_INIT_NAME # type: ignore # noqa
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
"""
|
22
|
-
@dc.dataclass(frozen=True)
|
23
|
-
class Params:
|
24
|
-
init = True
|
25
|
-
repr = True
|
26
|
-
eq = True
|
27
|
-
order = False
|
28
|
-
unsafe_hash = False
|
29
|
-
frozen = False
|
30
|
-
match_args = True
|
31
|
-
kw_only = False
|
32
|
-
slots = False
|
33
|
-
weakref_slot = False
|
34
|
-
"""
|
18
|
+
StdParams = dc._DataclassParams # type: ignore # noqa
|
35
19
|
|
36
20
|
|
37
21
|
##
|
38
22
|
|
39
23
|
|
40
|
-
|
24
|
+
std_is_dataclass_instance = dc._is_dataclass_instance # type: ignore # noqa
|
41
25
|
|
42
26
|
|
43
27
|
##
|
44
28
|
|
45
29
|
|
46
|
-
|
30
|
+
STD_ATOMIC_TYPES: frozenset[type]
|
47
31
|
|
48
32
|
if hasattr(dc, '_ATOMIC_TYPES'):
|
49
|
-
|
33
|
+
STD_ATOMIC_TYPES = getattr(dc, '_ATOMIC_TYPES')
|
50
34
|
|
51
35
|
else:
|
52
|
-
|
36
|
+
STD_ATOMIC_TYPES = frozenset({
|
53
37
|
types.NoneType,
|
54
38
|
bool,
|
55
39
|
int,
|
@@ -85,10 +69,20 @@ def _patch_missing_ctor() -> None:
|
|
85
69
|
##
|
86
70
|
|
87
71
|
|
88
|
-
class
|
72
|
+
class StdFieldType(enum.Enum):
|
89
73
|
INSTANCE = dc._FIELD # type: ignore # noqa
|
90
|
-
|
91
|
-
|
74
|
+
CLASS_VAR = dc._FIELD_CLASSVAR # type: ignore # noqa
|
75
|
+
INIT_VAR = dc._FIELD_INITVAR # type: ignore # noqa
|
76
|
+
|
77
|
+
|
78
|
+
def std_field_type(f: dc.Field) -> StdFieldType:
|
79
|
+
if (ft := getattr(f, '_field_type')) is not None:
|
80
|
+
return StdFieldType(ft)
|
81
|
+
else:
|
82
|
+
return StdFieldType.INSTANCE
|
83
|
+
|
84
|
+
|
85
|
+
##
|
92
86
|
|
93
87
|
|
94
88
|
_SELF_MODULE = None
|
@@ -97,18 +91,18 @@ _SELF_MODULE = None
|
|
97
91
|
def _self_module():
|
98
92
|
global _SELF_MODULE
|
99
93
|
if _SELF_MODULE is None:
|
100
|
-
_SELF_MODULE = sys.modules[__package__
|
94
|
+
_SELF_MODULE = sys.modules[__package__]
|
101
95
|
return _SELF_MODULE
|
102
96
|
|
103
97
|
|
104
|
-
def
|
98
|
+
def std_is_classvar(cls: type, ty: ta.Any) -> bool:
|
105
99
|
return (
|
106
100
|
dc._is_classvar(ty, ta) # type: ignore # noqa
|
107
101
|
or (isinstance(ty, str) and dc._is_type(ty, cls, ta, ta.ClassVar, dc._is_classvar)) # type: ignore # noqa
|
108
102
|
)
|
109
103
|
|
110
104
|
|
111
|
-
def
|
105
|
+
def std_is_initvar(cls: type, ty: ta.Any) -> bool:
|
112
106
|
return (
|
113
107
|
dc._is_initvar(ty, dc) # type: ignore # noqa
|
114
108
|
or (
|
@@ -121,7 +115,7 @@ def is_initvar(cls: type, ty: ta.Any) -> bool:
|
|
121
115
|
)
|
122
116
|
|
123
117
|
|
124
|
-
def
|
118
|
+
def std_is_kw_only(cls: type, ty: ta.Any) -> bool:
|
125
119
|
return (
|
126
120
|
dc._is_kw_only(ty, dc) # type: ignore # noqa
|
127
121
|
or (
|
File without changes
|
@@ -0,0 +1,69 @@
|
|
1
|
+
import typing as ta
|
2
|
+
|
3
|
+
from ..concerns.frozen import unchecked_frozen_base
|
4
|
+
from .meta import DataMeta
|
5
|
+
from .specs import get_metaclass_spec
|
6
|
+
|
7
|
+
|
8
|
+
T = ta.TypeVar('T')
|
9
|
+
|
10
|
+
|
11
|
+
##
|
12
|
+
|
13
|
+
|
14
|
+
# @ta.dataclass_transform(field_specifiers=(field,)) # FIXME: ctor
|
15
|
+
@unchecked_frozen_base
|
16
|
+
class Data(
|
17
|
+
eq=False,
|
18
|
+
order=False,
|
19
|
+
confer=frozenset([
|
20
|
+
'confer',
|
21
|
+
'final_subclasses',
|
22
|
+
]),
|
23
|
+
metaclass=DataMeta,
|
24
|
+
):
|
25
|
+
def __init__(self, *args, **kwargs):
|
26
|
+
# Typechecking barrier
|
27
|
+
super().__init__(*args, **kwargs)
|
28
|
+
|
29
|
+
def __init_subclass__(cls, **kwargs):
|
30
|
+
# Typechecking barrier
|
31
|
+
super().__init_subclass__(**kwargs)
|
32
|
+
|
33
|
+
|
34
|
+
class Frozen(
|
35
|
+
Data,
|
36
|
+
frozen=True,
|
37
|
+
eq=False,
|
38
|
+
order=False,
|
39
|
+
confer=frozenset([
|
40
|
+
*get_metaclass_spec(Data).confer,
|
41
|
+
'frozen',
|
42
|
+
'reorder',
|
43
|
+
'cache_hash',
|
44
|
+
'override',
|
45
|
+
]),
|
46
|
+
):
|
47
|
+
pass
|
48
|
+
|
49
|
+
|
50
|
+
class Case(
|
51
|
+
Frozen,
|
52
|
+
abstract=True,
|
53
|
+
override=True,
|
54
|
+
final_subclasses=True,
|
55
|
+
abstract_immediate_subclasses=True,
|
56
|
+
):
|
57
|
+
pass
|
58
|
+
|
59
|
+
|
60
|
+
class Box(
|
61
|
+
Frozen,
|
62
|
+
ta.Generic[T],
|
63
|
+
generic_init=True,
|
64
|
+
confer=frozenset([
|
65
|
+
*get_metaclass_spec(Frozen).confer,
|
66
|
+
'generic_init',
|
67
|
+
]),
|
68
|
+
):
|
69
|
+
v: T
|
@@ -0,0 +1,65 @@
|
|
1
|
+
import dataclasses as dc
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from ..api.classes.params import get_class_spec
|
5
|
+
from .specs import get_metaclass_spec
|
6
|
+
|
7
|
+
|
8
|
+
##
|
9
|
+
|
10
|
+
|
11
|
+
CONFER_CLASS_PARAMS: tuple[str, ...] = (
|
12
|
+
'eq',
|
13
|
+
'frozen',
|
14
|
+
'kw_only',
|
15
|
+
|
16
|
+
'reorder',
|
17
|
+
'cache_hash',
|
18
|
+
'generic_init',
|
19
|
+
'override',
|
20
|
+
)
|
21
|
+
|
22
|
+
CONFER_METACLASS_PARAMS: tuple[str, ...] = (
|
23
|
+
'confer',
|
24
|
+
'final_subclasses',
|
25
|
+
'abstract_immediate_subclasses',
|
26
|
+
)
|
27
|
+
|
28
|
+
|
29
|
+
def confer_kwarg(out: dict[str, ta.Any], k: str, v: ta.Any) -> None:
|
30
|
+
if k in out:
|
31
|
+
if out[k] != v:
|
32
|
+
raise ValueError
|
33
|
+
else:
|
34
|
+
out[k] = v
|
35
|
+
|
36
|
+
|
37
|
+
def confer_kwargs(
|
38
|
+
bases: ta.Sequence[type],
|
39
|
+
kwargs: ta.Mapping[str, ta.Any],
|
40
|
+
) -> dict[str, ta.Any]:
|
41
|
+
out: dict[str, ta.Any] = {}
|
42
|
+
for base in bases:
|
43
|
+
if not dc.is_dataclass(base):
|
44
|
+
continue
|
45
|
+
|
46
|
+
if (bcs := get_class_spec(base)) is None:
|
47
|
+
continue
|
48
|
+
|
49
|
+
if not (bmp := get_metaclass_spec(bcs)).confer:
|
50
|
+
continue
|
51
|
+
|
52
|
+
for ck in bmp.confer:
|
53
|
+
if ck in kwargs:
|
54
|
+
continue
|
55
|
+
|
56
|
+
if ck in CONFER_CLASS_PARAMS:
|
57
|
+
confer_kwarg(out, ck, getattr(bcs, ck))
|
58
|
+
|
59
|
+
elif ck in CONFER_METACLASS_PARAMS:
|
60
|
+
confer_kwarg(out, ck, getattr(bmp, ck))
|
61
|
+
|
62
|
+
else:
|
63
|
+
raise KeyError(ck)
|
64
|
+
|
65
|
+
return out
|
@@ -0,0 +1,115 @@
|
|
1
|
+
"""
|
2
|
+
TODO:
|
3
|
+
- Rewrite lol
|
4
|
+
- Enum - enforce Abstract or Final
|
5
|
+
"""
|
6
|
+
import abc
|
7
|
+
import dataclasses as dc
|
8
|
+
import typing as ta
|
9
|
+
|
10
|
+
from ... import lang
|
11
|
+
from ..api.classes.decorator import dataclass
|
12
|
+
from .confer import CONFER_METACLASS_PARAMS
|
13
|
+
from .confer import confer_kwargs
|
14
|
+
from .specs import MetaclassSpec
|
15
|
+
from .specs import get_metaclass_spec
|
16
|
+
|
17
|
+
|
18
|
+
T = ta.TypeVar('T')
|
19
|
+
|
20
|
+
|
21
|
+
##
|
22
|
+
|
23
|
+
|
24
|
+
class DataMeta(abc.ABCMeta):
|
25
|
+
def __new__(
|
26
|
+
mcls,
|
27
|
+
name,
|
28
|
+
bases,
|
29
|
+
namespace,
|
30
|
+
*,
|
31
|
+
|
32
|
+
abstract=False,
|
33
|
+
sealed=False,
|
34
|
+
final=False,
|
35
|
+
|
36
|
+
metadata=None,
|
37
|
+
**kwargs,
|
38
|
+
):
|
39
|
+
ckw = confer_kwargs(bases, kwargs)
|
40
|
+
nkw = {**kwargs, **ckw}
|
41
|
+
|
42
|
+
mcp = MetaclassSpec(**{
|
43
|
+
mpa: nkw.pop(mpa)
|
44
|
+
for mpa in CONFER_METACLASS_PARAMS
|
45
|
+
if mpa in nkw
|
46
|
+
})
|
47
|
+
|
48
|
+
metadata = [
|
49
|
+
*([metadata] if metadata is not None else []),
|
50
|
+
mcp,
|
51
|
+
]
|
52
|
+
|
53
|
+
#
|
54
|
+
|
55
|
+
xbs: list[type] = []
|
56
|
+
|
57
|
+
if any(
|
58
|
+
get_metaclass_spec(b).abstract_immediate_subclasses # type: ignore[arg-type]
|
59
|
+
for b in bases
|
60
|
+
if dc.is_dataclass(b)
|
61
|
+
):
|
62
|
+
abstract = True
|
63
|
+
|
64
|
+
final |= (mcp.final_subclasses and not abstract)
|
65
|
+
|
66
|
+
if final and abstract:
|
67
|
+
raise TypeError(f'Class cannot be abstract and final: {name!r}')
|
68
|
+
|
69
|
+
if abstract:
|
70
|
+
xbs.append(lang.Abstract)
|
71
|
+
if sealed:
|
72
|
+
xbs.append(lang.Sealed)
|
73
|
+
if final:
|
74
|
+
xbs.append(lang.Final)
|
75
|
+
|
76
|
+
if xbs:
|
77
|
+
if bases and bases[-1] is ta.Generic:
|
78
|
+
bases = (*bases[:-1], *xbs, bases[-1])
|
79
|
+
else:
|
80
|
+
bases = (*bases, *xbs)
|
81
|
+
if ob := namespace.get('__orig_bases__'):
|
82
|
+
if getattr(ob[-1], '__origin__', None) is ta.Generic:
|
83
|
+
namespace['__orig_bases__'] = (*ob[:-1], *xbs, ob[-1])
|
84
|
+
else:
|
85
|
+
namespace['__orig_bases__'] = (*ob, *xbs)
|
86
|
+
|
87
|
+
#
|
88
|
+
|
89
|
+
ofs: set[str] = set()
|
90
|
+
if any(issubclass(b, lang.Abstract) for b in bases) and nkw.get('override'):
|
91
|
+
ofs.update(a for a in namespace.get('__annotations__', []) if a not in namespace)
|
92
|
+
namespace.update((a, dc.MISSING) for a in ofs)
|
93
|
+
|
94
|
+
#
|
95
|
+
|
96
|
+
cls = lang.super_meta(
|
97
|
+
super(),
|
98
|
+
mcls,
|
99
|
+
name,
|
100
|
+
bases,
|
101
|
+
namespace,
|
102
|
+
)
|
103
|
+
|
104
|
+
#
|
105
|
+
|
106
|
+
for a in ofs:
|
107
|
+
delattr(cls, a)
|
108
|
+
|
109
|
+
#
|
110
|
+
|
111
|
+
return dataclass(
|
112
|
+
cls,
|
113
|
+
metadata=metadata,
|
114
|
+
**nkw,
|
115
|
+
)
|
@@ -0,0 +1,38 @@
|
|
1
|
+
import dataclasses as dc
|
2
|
+
|
3
|
+
from ... import lang
|
4
|
+
from ..api.classes.params import get_class_spec
|
5
|
+
from ..specs import ClassSpec
|
6
|
+
|
7
|
+
|
8
|
+
##
|
9
|
+
|
10
|
+
|
11
|
+
@dc.dataclass(frozen=True, kw_only=True, eq=False)
|
12
|
+
class MetaclassSpec(lang.Final):
|
13
|
+
confer: frozenset[str] = frozenset()
|
14
|
+
|
15
|
+
final_subclasses: bool = False
|
16
|
+
|
17
|
+
abstract_immediate_subclasses: bool = False
|
18
|
+
|
19
|
+
|
20
|
+
DEFAULT_METACLASS_SPEC = MetaclassSpec()
|
21
|
+
|
22
|
+
|
23
|
+
##
|
24
|
+
|
25
|
+
|
26
|
+
def get_metaclass_spec(obj: type | ClassSpec) -> MetaclassSpec:
|
27
|
+
cs: ClassSpec
|
28
|
+
if isinstance(obj, type):
|
29
|
+
if (cs := get_class_spec(obj)) is None: # type: ignore[assignment]
|
30
|
+
return DEFAULT_METACLASS_SPEC
|
31
|
+
|
32
|
+
elif isinstance(obj, ClassSpec):
|
33
|
+
cs = obj
|
34
|
+
|
35
|
+
else:
|
36
|
+
raise TypeError(obj)
|
37
|
+
|
38
|
+
return cs.get_last_metadata(MetaclassSpec, DEFAULT_METACLASS_SPEC)
|
File without changes
|