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
@@ -1,245 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
TODO:
|
3
|
-
- point validate / check exceptions to lambdas
|
4
|
-
"""
|
5
|
-
import dataclasses as dc
|
6
|
-
import types
|
7
|
-
import typing as ta
|
8
|
-
|
9
|
-
from ... import check as check_
|
10
|
-
from ... import lang
|
11
|
-
from .errors import FieldValidationError
|
12
|
-
from .internals import FIELDS_ATTR
|
13
|
-
from .internals import FieldType
|
14
|
-
from .internals import is_classvar
|
15
|
-
from .internals import is_initvar
|
16
|
-
from .internals import is_kw_only
|
17
|
-
from .params import get_field_extras
|
18
|
-
from .processing import Processor
|
19
|
-
|
20
|
-
|
21
|
-
if ta.TYPE_CHECKING:
|
22
|
-
from . import api
|
23
|
-
else:
|
24
|
-
api = lang.proxy_import('.api', __package__)
|
25
|
-
|
26
|
-
|
27
|
-
MISSING = dc.MISSING
|
28
|
-
|
29
|
-
|
30
|
-
##
|
31
|
-
|
32
|
-
|
33
|
-
def raise_field_validation_error(
|
34
|
-
obj: ta.Any,
|
35
|
-
field: str,
|
36
|
-
fn: ta.Callable,
|
37
|
-
value: ta.Any,
|
38
|
-
) -> ta.NoReturn:
|
39
|
-
raise FieldValidationError(
|
40
|
-
obj,
|
41
|
-
field,
|
42
|
-
fn,
|
43
|
-
value,
|
44
|
-
)
|
45
|
-
|
46
|
-
|
47
|
-
##
|
48
|
-
|
49
|
-
|
50
|
-
def field_type(f: dc.Field) -> FieldType:
|
51
|
-
if (ft := getattr(f, '_field_type')) is not None:
|
52
|
-
return FieldType(ft)
|
53
|
-
else:
|
54
|
-
return FieldType.INSTANCE
|
55
|
-
|
56
|
-
|
57
|
-
def has_default(f: dc.Field) -> bool:
|
58
|
-
return not (f.default is MISSING and f.default_factory is MISSING)
|
59
|
-
|
60
|
-
|
61
|
-
##
|
62
|
-
|
63
|
-
|
64
|
-
class FieldsProcessor(Processor):
|
65
|
-
def _process(self) -> None:
|
66
|
-
cls = self._info.cls
|
67
|
-
fields: dict[str, dc.Field] = {}
|
68
|
-
|
69
|
-
for b in cls.__mro__[-1:0:-1]:
|
70
|
-
base_fields = getattr(b, FIELDS_ATTR, None)
|
71
|
-
if base_fields is not None:
|
72
|
-
for f in base_fields.values():
|
73
|
-
fields[f.name] = f
|
74
|
-
|
75
|
-
cls_fields: list[dc.Field] = []
|
76
|
-
|
77
|
-
kw_only = self._info.params.kw_only
|
78
|
-
kw_only_seen = False
|
79
|
-
for name, ann in self._info.cls_annotations.items():
|
80
|
-
if is_kw_only(cls, ann):
|
81
|
-
if kw_only_seen:
|
82
|
-
raise TypeError(f'{name!r} is KW_ONLY, but KW_ONLY has already been specified')
|
83
|
-
kw_only_seen = True
|
84
|
-
kw_only = True
|
85
|
-
else:
|
86
|
-
cls_fields.append(preprocess_field(cls, name, ann, kw_only))
|
87
|
-
|
88
|
-
for f in cls_fields:
|
89
|
-
fields[f.name] = f
|
90
|
-
if isinstance(getattr(cls, f.name, None), dc.Field):
|
91
|
-
if f.default is MISSING:
|
92
|
-
delattr(cls, f.name)
|
93
|
-
else:
|
94
|
-
setattr(cls, f.name, f.default)
|
95
|
-
|
96
|
-
for name, value in cls.__dict__.items():
|
97
|
-
if isinstance(value, dc.Field) and name not in self._info.cls_annotations:
|
98
|
-
raise TypeError(f'{name!r} is a field but has no type annotation')
|
99
|
-
|
100
|
-
setattr(cls, FIELDS_ATTR, fields)
|
101
|
-
|
102
|
-
|
103
|
-
##
|
104
|
-
|
105
|
-
|
106
|
-
def preprocess_field(
|
107
|
-
cls: type,
|
108
|
-
a_name: str,
|
109
|
-
a_type: ta.Any,
|
110
|
-
default_kw_only: bool,
|
111
|
-
) -> dc.Field:
|
112
|
-
default = getattr(cls, a_name, MISSING)
|
113
|
-
if isinstance(default, dc.Field):
|
114
|
-
f = default
|
115
|
-
else:
|
116
|
-
if isinstance(default, types.MemberDescriptorType):
|
117
|
-
# This is a field in __slots__, so it has no default value.
|
118
|
-
default = MISSING
|
119
|
-
f = api.field(default=default)
|
120
|
-
|
121
|
-
f.name = a_name
|
122
|
-
f.type = a_type
|
123
|
-
|
124
|
-
ft = FieldType.INSTANCE
|
125
|
-
if is_classvar(cls, f.type):
|
126
|
-
ft = FieldType.CLASS
|
127
|
-
if is_initvar(cls, f.type):
|
128
|
-
ft = FieldType.INIT
|
129
|
-
if ft in (FieldType.CLASS, FieldType.INIT):
|
130
|
-
if f.default_factory is not MISSING:
|
131
|
-
raise TypeError(f'field {f.name} cannot have a default factory')
|
132
|
-
f._field_type = ft.value # type: ignore # noqa
|
133
|
-
|
134
|
-
if ft in (FieldType.INSTANCE, FieldType.INIT):
|
135
|
-
if f.kw_only is MISSING:
|
136
|
-
f.kw_only = default_kw_only
|
137
|
-
else:
|
138
|
-
check_.arg(ft is FieldType.CLASS)
|
139
|
-
if f.kw_only is not MISSING:
|
140
|
-
raise TypeError(f'field {f.name} is a ClassVar but specifies kw_only')
|
141
|
-
|
142
|
-
if ft is FieldType.INSTANCE and f.default is not MISSING and f.default.__class__.__hash__ is None:
|
143
|
-
raise ValueError(f'mutable default {type(f.default)} for field {f.name} is not allowed: use default_factory')
|
144
|
-
|
145
|
-
return f
|
146
|
-
|
147
|
-
|
148
|
-
def field_assign(
|
149
|
-
frozen: bool,
|
150
|
-
name: str,
|
151
|
-
value: ta.Any,
|
152
|
-
self_name: str,
|
153
|
-
override: bool,
|
154
|
-
) -> str:
|
155
|
-
if override:
|
156
|
-
return f'{self_name}.__dict__[{name!r}] = {value}'
|
157
|
-
if frozen:
|
158
|
-
return f'__dataclass_builtins_object__.__setattr__({self_name}, {name!r}, {value})'
|
159
|
-
return f'{self_name}.{name} = {value}'
|
160
|
-
|
161
|
-
|
162
|
-
def field_init(
|
163
|
-
f: dc.Field,
|
164
|
-
frozen: bool,
|
165
|
-
locals: dict[str, ta.Any], # noqa
|
166
|
-
self_name: str,
|
167
|
-
slots: bool,
|
168
|
-
cls_override: bool,
|
169
|
-
) -> ta.Sequence[str]:
|
170
|
-
default_name = f'__dataclass_dflt_{f.name}__'
|
171
|
-
fx = get_field_extras(f)
|
172
|
-
|
173
|
-
lines = []
|
174
|
-
|
175
|
-
value: str | None = None
|
176
|
-
if f.default_factory is not MISSING:
|
177
|
-
locals[default_name] = f.default_factory
|
178
|
-
if f.init:
|
179
|
-
lines.append(f'if {f.name} is __dataclass_HAS_DEFAULT_FACTORY__: {f.name} = {default_name}()')
|
180
|
-
else:
|
181
|
-
lines.append(f'{f.name} = {default_name}()')
|
182
|
-
value = f.name
|
183
|
-
|
184
|
-
elif f.init:
|
185
|
-
if f.default is MISSING:
|
186
|
-
value = f.name
|
187
|
-
elif f.default is not MISSING:
|
188
|
-
locals[default_name] = f.default # Not referenced her, just useful / consistent to have in function scope
|
189
|
-
value = f.name
|
190
|
-
|
191
|
-
elif slots and f.default is not MISSING:
|
192
|
-
locals[default_name] = f.default
|
193
|
-
lines.append(f'{f.name} = {default_name}')
|
194
|
-
value = default_name
|
195
|
-
|
196
|
-
else:
|
197
|
-
pass
|
198
|
-
|
199
|
-
if fx.derive is not None:
|
200
|
-
raise NotImplementedError
|
201
|
-
|
202
|
-
if fx.frozen:
|
203
|
-
raise NotImplementedError
|
204
|
-
|
205
|
-
if fx.coerce is not None:
|
206
|
-
cn = f'__dataclass_coerce__{f.name}__'
|
207
|
-
locals[cn] = fx.coerce
|
208
|
-
lines.append(f'{value} = {cn}({value})')
|
209
|
-
|
210
|
-
if fx.validate is not None:
|
211
|
-
cn = f'__dataclass_validate__{f.name}__'
|
212
|
-
locals[cn] = fx.validate
|
213
|
-
lines.append(
|
214
|
-
f'if not {cn}({value}): '
|
215
|
-
f'__dataclass_raise_field_validation_error__({self_name}, {f.name!r}, {cn}, {value})',
|
216
|
-
)
|
217
|
-
|
218
|
-
if fx.check_type:
|
219
|
-
cn = f'__dataclass_check_type__{f.name}__'
|
220
|
-
ct: ta.Any
|
221
|
-
if isinstance(fx.check_type, tuple):
|
222
|
-
ct = tuple(type(None) if e is None else check_.isinstance(e, type) for e in fx.check_type)
|
223
|
-
elif isinstance(fx.check_type, (type, tuple)):
|
224
|
-
ct = fx.check_type
|
225
|
-
# FIXME:
|
226
|
-
# elif info.params_extras.generic_init:
|
227
|
-
# ct = info.generic_replaced_field_annotations[f.name]
|
228
|
-
else:
|
229
|
-
ct = f.type
|
230
|
-
locals[cn] = ct
|
231
|
-
lines.append(
|
232
|
-
f'if not __dataclass_builtins_isinstance__({value}, {cn}): '
|
233
|
-
f'raise __dataclass_builtins_TypeError__({value}, {cn})',
|
234
|
-
)
|
235
|
-
|
236
|
-
if value is not None and field_type(f) is not FieldType.INIT:
|
237
|
-
lines.append(field_assign(
|
238
|
-
frozen,
|
239
|
-
f.name,
|
240
|
-
value,
|
241
|
-
self_name,
|
242
|
-
fx.override or cls_override,
|
243
|
-
))
|
244
|
-
|
245
|
-
return lines
|
@@ -1,93 +0,0 @@
|
|
1
|
-
import dataclasses as dc
|
2
|
-
import typing as ta
|
3
|
-
|
4
|
-
from ... import lang
|
5
|
-
from .internals import FIELDS_ATTR
|
6
|
-
from .internals import PARAMS_ATTR
|
7
|
-
from .processing import Processor
|
8
|
-
from .reflect import ClassInfo
|
9
|
-
from .utils import Namespace
|
10
|
-
from .utils import create_fn
|
11
|
-
from .utils import set_new_attribute
|
12
|
-
|
13
|
-
|
14
|
-
if ta.TYPE_CHECKING:
|
15
|
-
from . import metaclass
|
16
|
-
else:
|
17
|
-
metaclass = lang.proxy_import('.metaclass', __package__)
|
18
|
-
|
19
|
-
|
20
|
-
def check_frozen_bases(info: ClassInfo) -> None:
|
21
|
-
mc_base = getattr(metaclass, 'Data', None)
|
22
|
-
all_frozen_bases = None
|
23
|
-
any_frozen_base = False
|
24
|
-
has_dataclass_bases = False
|
25
|
-
for b in info.cls.__mro__[-1:0:-1]:
|
26
|
-
if b is mc_base:
|
27
|
-
continue
|
28
|
-
base_fields = getattr(b, FIELDS_ATTR, None)
|
29
|
-
if base_fields is not None:
|
30
|
-
has_dataclass_bases = True
|
31
|
-
if all_frozen_bases is None:
|
32
|
-
all_frozen_bases = True
|
33
|
-
current_frozen = getattr(b, PARAMS_ATTR).frozen
|
34
|
-
all_frozen_bases = all_frozen_bases and current_frozen
|
35
|
-
any_frozen_base = any_frozen_base or current_frozen
|
36
|
-
|
37
|
-
if has_dataclass_bases:
|
38
|
-
if any_frozen_base and not info.params.frozen:
|
39
|
-
raise TypeError('cannot inherit non-frozen dataclass from a frozen one')
|
40
|
-
|
41
|
-
if all_frozen_bases is False and info.params.frozen:
|
42
|
-
raise TypeError('cannot inherit frozen dataclass from a non-frozen one')
|
43
|
-
|
44
|
-
|
45
|
-
def frozen_get_del_attr(
|
46
|
-
cls: type,
|
47
|
-
fields: ta.Sequence[dc.Field],
|
48
|
-
globals: Namespace, # noqa
|
49
|
-
) -> tuple[ta.Callable, ta.Callable]:
|
50
|
-
locals = { # noqa
|
51
|
-
'cls': cls,
|
52
|
-
'FrozenInstanceError': dc.FrozenInstanceError,
|
53
|
-
}
|
54
|
-
condition = 'type(self) is cls'
|
55
|
-
if fields:
|
56
|
-
condition += ' or name in {' + ', '.join(repr(f.name) for f in fields) + '}'
|
57
|
-
return (
|
58
|
-
create_fn(
|
59
|
-
'__setattr__',
|
60
|
-
('self', 'name', 'value'),
|
61
|
-
[
|
62
|
-
f'if {condition}:',
|
63
|
-
' raise FrozenInstanceError(f"cannot assign to field {name!r}")',
|
64
|
-
f'super(cls, self).__setattr__(name, value)',
|
65
|
-
],
|
66
|
-
locals=locals,
|
67
|
-
globals=globals,
|
68
|
-
),
|
69
|
-
create_fn(
|
70
|
-
'__delattr__',
|
71
|
-
('self', 'name'),
|
72
|
-
[
|
73
|
-
f'if {condition}:',
|
74
|
-
' raise FrozenInstanceError(f"cannot delete field {name!r}")',
|
75
|
-
f'super(cls, self).__delattr__(name)',
|
76
|
-
],
|
77
|
-
locals=locals,
|
78
|
-
globals=globals,
|
79
|
-
),
|
80
|
-
)
|
81
|
-
|
82
|
-
|
83
|
-
class FrozenProcessor(Processor):
|
84
|
-
def check(self) -> None:
|
85
|
-
check_frozen_bases(self._info)
|
86
|
-
|
87
|
-
def _process(self) -> None:
|
88
|
-
if not self._info.params.frozen:
|
89
|
-
return
|
90
|
-
|
91
|
-
for fn in frozen_get_del_attr(self._cls, self._info.instance_fields, self._info.globals):
|
92
|
-
if set_new_attribute(self._cls, fn.__name__, fn):
|
93
|
-
raise TypeError(f'Cannot overwrite attribute {fn.__name__} in class {self._cls.__name__}')
|
@@ -1,86 +0,0 @@
|
|
1
|
-
import dataclasses as dc
|
2
|
-
import enum
|
3
|
-
import typing as ta
|
4
|
-
|
5
|
-
from .processing import Processor
|
6
|
-
from .utils import create_fn
|
7
|
-
from .utils import set_qualname
|
8
|
-
from .utils import tuple_str
|
9
|
-
|
10
|
-
|
11
|
-
class HashAction(enum.Enum):
|
12
|
-
SET_NONE = enum.auto()
|
13
|
-
ADD = enum.auto()
|
14
|
-
EXCEPTION = enum.auto()
|
15
|
-
|
16
|
-
|
17
|
-
# See https://bugs.python.org/issue32929#msg312829 for an if-statement version of this table.
|
18
|
-
HASH_ACTIONS: ta.Mapping[tuple[bool, bool, bool, bool], HashAction | None] = {
|
19
|
-
#
|
20
|
-
# +-------------------------------------- unsafe_hash?
|
21
|
-
# | +------------------------------- eq?
|
22
|
-
# | | +------------------------ frozen?
|
23
|
-
# | | | +---------------- has-explicit-hash?
|
24
|
-
# v v v v
|
25
|
-
(False, False, False, False): None,
|
26
|
-
(False, False, False, True): None,
|
27
|
-
(False, False, True, False): None,
|
28
|
-
(False, False, True, True): None,
|
29
|
-
(False, True, False, False): HashAction.SET_NONE,
|
30
|
-
(False, True, False, True): None,
|
31
|
-
(False, True, True, False): HashAction.ADD,
|
32
|
-
(False, True, True, True): None,
|
33
|
-
(True, False, False, False): HashAction.ADD,
|
34
|
-
(True, False, False, True): HashAction.EXCEPTION,
|
35
|
-
(True, False, True, False): HashAction.ADD,
|
36
|
-
(True, False, True, True): HashAction.EXCEPTION,
|
37
|
-
(True, True, False, False): HashAction.ADD,
|
38
|
-
(True, True, False, True): HashAction.EXCEPTION,
|
39
|
-
(True, True, True, False): HashAction.ADD,
|
40
|
-
(True, True, True, True): HashAction.EXCEPTION,
|
41
|
-
}
|
42
|
-
|
43
|
-
|
44
|
-
class HashProcessor(Processor):
|
45
|
-
CACHED_HASH_ATTR = '__dataclass_hash__'
|
46
|
-
|
47
|
-
def _build_hash_fn(self) -> ta.Callable:
|
48
|
-
flds = [f for f in self._info.instance_fields if (f.compare if f.hash is None else f.hash)]
|
49
|
-
self_tuple = tuple_str('self', flds)
|
50
|
-
if self._info.params_extras.cache_hash:
|
51
|
-
body = [
|
52
|
-
f'try: return self.{self.CACHED_HASH_ATTR}',
|
53
|
-
f'except AttributeError: pass',
|
54
|
-
f'object.__setattr__(self, {self.CACHED_HASH_ATTR!r}, h := hash({self_tuple}))',
|
55
|
-
f'return h',
|
56
|
-
]
|
57
|
-
else:
|
58
|
-
body = [f'return hash({self_tuple})']
|
59
|
-
hash_fn = create_fn(
|
60
|
-
'__hash__',
|
61
|
-
('self',),
|
62
|
-
body,
|
63
|
-
globals=self._info.globals,
|
64
|
-
)
|
65
|
-
return set_qualname(self._cls, hash_fn) # noqa
|
66
|
-
|
67
|
-
def _process(self) -> None:
|
68
|
-
class_hash = self._cls.__dict__.get('__hash__', dc.MISSING)
|
69
|
-
has_explicit_hash = not (class_hash is dc.MISSING or (class_hash is None and '__eq__' in self._cls.__dict__))
|
70
|
-
|
71
|
-
match (hash_action := HASH_ACTIONS[(
|
72
|
-
bool(self._info.params.unsafe_hash),
|
73
|
-
bool(self._info.params.eq),
|
74
|
-
bool(self._info.params.frozen),
|
75
|
-
has_explicit_hash,
|
76
|
-
)]):
|
77
|
-
case HashAction.SET_NONE:
|
78
|
-
self._cls.__hash__ = None # type: ignore
|
79
|
-
case HashAction.ADD:
|
80
|
-
self._cls.__hash__ = self._build_hash_fn() # type: ignore
|
81
|
-
case HashAction.EXCEPTION:
|
82
|
-
raise TypeError(f'Cannot overwrite attribute __hash__ in class {self._cls.__name__}')
|
83
|
-
case None:
|
84
|
-
pass
|
85
|
-
case _:
|
86
|
-
raise ValueError(hash_action)
|
omlish/dataclasses/impl/init.py
DELETED
@@ -1,199 +0,0 @@
|
|
1
|
-
import dataclasses as dc
|
2
|
-
import inspect
|
3
|
-
import typing as ta
|
4
|
-
|
5
|
-
from ... import lang
|
6
|
-
from .errors import ValidationError
|
7
|
-
from .fields import field_init
|
8
|
-
from .fields import field_type
|
9
|
-
from .fields import has_default
|
10
|
-
from .fields import raise_field_validation_error
|
11
|
-
from .internals import HAS_DEFAULT_FACTORY
|
12
|
-
from .internals import POST_INIT_NAME
|
13
|
-
from .internals import FieldType
|
14
|
-
from .metadata import Init
|
15
|
-
from .metadata import Validate
|
16
|
-
from .processing import Processor
|
17
|
-
from .reflect import ClassInfo
|
18
|
-
from .utils import Namespace
|
19
|
-
from .utils import create_fn
|
20
|
-
from .utils import set_new_attribute
|
21
|
-
|
22
|
-
|
23
|
-
MISSING = dc.MISSING
|
24
|
-
|
25
|
-
|
26
|
-
##
|
27
|
-
|
28
|
-
|
29
|
-
def raise_validation_error(
|
30
|
-
obj: ta.Any,
|
31
|
-
fn: ta.Callable,
|
32
|
-
) -> ta.NoReturn:
|
33
|
-
raise ValidationError(obj, fn)
|
34
|
-
|
35
|
-
|
36
|
-
##
|
37
|
-
|
38
|
-
|
39
|
-
class InitFields(ta.NamedTuple):
|
40
|
-
all: ta.Sequence[dc.Field]
|
41
|
-
ordered: ta.Sequence[dc.Field]
|
42
|
-
std: ta.Sequence[dc.Field]
|
43
|
-
kw_only: ta.Sequence[dc.Field]
|
44
|
-
|
45
|
-
|
46
|
-
def get_init_fields(fields: ta.Iterable[dc.Field], *, reorder: bool = False) -> InitFields:
|
47
|
-
all_init_fields = [f for f in fields if field_type(f) in (FieldType.INSTANCE, FieldType.INIT)]
|
48
|
-
ordered_init_fields = list(all_init_fields)
|
49
|
-
if reorder:
|
50
|
-
ordered_init_fields.sort(key=lambda f: (has_default(f), not f.kw_only))
|
51
|
-
std_init_fields, kw_only_init_fields = (
|
52
|
-
tuple(f1 for f1 in ordered_init_fields if f1.init and not f1.kw_only),
|
53
|
-
tuple(f1 for f1 in ordered_init_fields if f1.init and f1.kw_only),
|
54
|
-
)
|
55
|
-
return InitFields(
|
56
|
-
all=all_init_fields,
|
57
|
-
ordered=ordered_init_fields,
|
58
|
-
std=std_init_fields,
|
59
|
-
kw_only=kw_only_init_fields,
|
60
|
-
)
|
61
|
-
|
62
|
-
|
63
|
-
def init_param(f: dc.Field) -> str:
|
64
|
-
if not has_default(f):
|
65
|
-
default = ''
|
66
|
-
elif f.default is not MISSING:
|
67
|
-
default = f' = __dataclass_dflt_{f.name}__'
|
68
|
-
elif f.default_factory is not MISSING:
|
69
|
-
default = ' = __dataclass_HAS_DEFAULT_FACTORY__'
|
70
|
-
return f'{f.name}: __dataclass_type_{f.name}__{default}' # noqa
|
71
|
-
|
72
|
-
|
73
|
-
##
|
74
|
-
|
75
|
-
|
76
|
-
class InitBuilder:
|
77
|
-
def __init__(
|
78
|
-
self,
|
79
|
-
info: ClassInfo,
|
80
|
-
fields: ta.Mapping[str, dc.Field],
|
81
|
-
has_post_init: bool,
|
82
|
-
self_name: str,
|
83
|
-
globals: Namespace, # noqa
|
84
|
-
) -> None:
|
85
|
-
super().__init__()
|
86
|
-
|
87
|
-
self._info = info
|
88
|
-
self._fields = fields
|
89
|
-
self._has_post_init = has_post_init
|
90
|
-
self._self_name = self_name
|
91
|
-
self._globals = globals
|
92
|
-
|
93
|
-
@lang.cached_function
|
94
|
-
def build(self) -> ta.Callable:
|
95
|
-
ifs = get_init_fields(self._fields.values(), reorder=self._info.params_extras.reorder)
|
96
|
-
|
97
|
-
seen_default = None
|
98
|
-
for f in ifs.std:
|
99
|
-
if f.init:
|
100
|
-
if has_default(f):
|
101
|
-
seen_default = f
|
102
|
-
elif seen_default:
|
103
|
-
raise TypeError(f'non-default argument {f.name!r} follows default argument {seen_default.name!r}')
|
104
|
-
|
105
|
-
locals: dict[str, ta.Any] = {} # noqa
|
106
|
-
|
107
|
-
if self._info.params_extras.generic_init:
|
108
|
-
get_fty = lambda f: self._info.generic_replaced_field_annotations[f.name]
|
109
|
-
else:
|
110
|
-
get_fty = lambda f: f.type
|
111
|
-
locals.update({f'__dataclass_type_{f.name}__': get_fty(f) for f in ifs.all})
|
112
|
-
|
113
|
-
locals.update({
|
114
|
-
'__dataclass_HAS_DEFAULT_FACTORY__': HAS_DEFAULT_FACTORY,
|
115
|
-
'__dataclass_builtins_object__': object,
|
116
|
-
'__dataclass_builtins_isinstance__': isinstance,
|
117
|
-
'__dataclass_builtins_TypeError__': TypeError,
|
118
|
-
'__dataclass_raise_validation_error__': raise_validation_error,
|
119
|
-
'__dataclass_raise_field_validation_error__': raise_field_validation_error,
|
120
|
-
})
|
121
|
-
|
122
|
-
body_lines: list[str] = []
|
123
|
-
for f in ifs.all:
|
124
|
-
f_lines = field_init(
|
125
|
-
f,
|
126
|
-
self._info.params.frozen,
|
127
|
-
locals,
|
128
|
-
self._self_name,
|
129
|
-
self._info.params.slots,
|
130
|
-
self._info.params_extras.override,
|
131
|
-
)
|
132
|
-
|
133
|
-
if f_lines:
|
134
|
-
body_lines.extend(f_lines)
|
135
|
-
|
136
|
-
if self._has_post_init:
|
137
|
-
params_str = ','.join(f.name for f in ifs.all if field_type(f) is FieldType.INIT)
|
138
|
-
body_lines.append(f'{self._self_name}.{POST_INIT_NAME}({params_str})')
|
139
|
-
|
140
|
-
for i, fn in enumerate(self._info.merged_metadata.get(Validate, [])):
|
141
|
-
if isinstance(fn, staticmethod):
|
142
|
-
fn = fn.__func__
|
143
|
-
cn = f'__dataclass_validate_{i}__'
|
144
|
-
locals[cn] = fn
|
145
|
-
csig = inspect.signature(fn)
|
146
|
-
cas = ', '.join(p.name for p in csig.parameters.values())
|
147
|
-
body_lines.append(f'if not {cn}({cas}): __dataclass_raise_validation_error__({self._self_name}, {cn})')
|
148
|
-
|
149
|
-
inits = self._info.merged_metadata.get(Init, [])
|
150
|
-
mro_dct = lang.mro_dict(self._info.cls)
|
151
|
-
mro_v_ids = set(map(id, mro_dct.values()))
|
152
|
-
props_by_fget_id = {id(v.fget): v for v in mro_dct.values() if isinstance(v, property) and v.fget is not None}
|
153
|
-
for i, obj in enumerate(inits):
|
154
|
-
if (obj_id := id(obj)) not in mro_v_ids and obj_id in props_by_fget_id:
|
155
|
-
obj = props_by_fget_id[obj_id].__get__
|
156
|
-
elif isinstance(obj, property):
|
157
|
-
obj = obj.__get__
|
158
|
-
cn = f'__dataclass_init_{i}__'
|
159
|
-
locals[cn] = obj
|
160
|
-
body_lines.append(f'{cn}({self._self_name})')
|
161
|
-
|
162
|
-
if not body_lines:
|
163
|
-
body_lines = ['pass']
|
164
|
-
|
165
|
-
_init_params = [init_param(f) for f in ifs.std]
|
166
|
-
if ifs.kw_only:
|
167
|
-
_init_params += ['*']
|
168
|
-
_init_params += [init_param(f) for f in ifs.kw_only]
|
169
|
-
|
170
|
-
return create_fn(
|
171
|
-
'__init__',
|
172
|
-
[self._self_name, *_init_params],
|
173
|
-
body_lines,
|
174
|
-
locals=locals,
|
175
|
-
globals=self._globals,
|
176
|
-
return_type=lang.just(None),
|
177
|
-
)
|
178
|
-
|
179
|
-
|
180
|
-
class InitProcessor(Processor):
|
181
|
-
def _process(self) -> None:
|
182
|
-
if not self._info.params.init:
|
183
|
-
return
|
184
|
-
|
185
|
-
has_post_init = hasattr(self._cls, POST_INIT_NAME)
|
186
|
-
self_name = '__dataclass_self__' if 'self' in self._info.fields else 'self'
|
187
|
-
|
188
|
-
init = InitBuilder(
|
189
|
-
ClassInfo(self._cls),
|
190
|
-
self._info.fields,
|
191
|
-
has_post_init,
|
192
|
-
self_name,
|
193
|
-
self._info.globals,
|
194
|
-
).build()
|
195
|
-
set_new_attribute(
|
196
|
-
self._cls,
|
197
|
-
'__init__',
|
198
|
-
init,
|
199
|
-
)
|