omlish 0.0.0.dev284__py3-none-any.whl → 0.0.0.dev286__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 +6 -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 +179 -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 +49 -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/lang/maybes.py +17 -0
- omlish/lite/maybes.py +17 -0
- 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/text/minja.py +81 -25
- omlish/text/templating.py +116 -0
- omlish/typedvalues/marshal.py +2 -2
- {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev286.dist-info}/METADATA +4 -1
- {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev286.dist-info}/RECORD +87 -46
- 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.dev286.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev286.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev286.dist-info}/licenses/LICENSE +0 -0
- {omlish-0.0.0.dev284.dist-info → omlish-0.0.0.dev286.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,453 @@
|
|
1
|
+
"""
|
2
|
+
TODO:
|
3
|
+
- ensure all 'init' fields work - non-instance
|
4
|
+
- special case 'None' default, most common
|
5
|
+
"""
|
6
|
+
import dataclasses as dc
|
7
|
+
import itertools
|
8
|
+
import typing as ta
|
9
|
+
|
10
|
+
from ... import check
|
11
|
+
from ..generation.base import Generator
|
12
|
+
from ..generation.base import Plan
|
13
|
+
from ..generation.base import PlanResult
|
14
|
+
from ..generation.globals import FIELD_FN_VALIDATION_ERROR_GLOBAL
|
15
|
+
from ..generation.globals import FIELD_TYPE_VALIDATION_ERROR_GLOBAL
|
16
|
+
from ..generation.globals import FN_VALIDATION_ERROR_GLOBAL
|
17
|
+
from ..generation.globals import HAS_DEFAULT_FACTORY_GLOBAL
|
18
|
+
from ..generation.globals import ISINSTANCE_GLOBAL
|
19
|
+
from ..generation.globals import NONE_GLOBAL
|
20
|
+
from ..generation.idents import SELF_IDENT
|
21
|
+
from ..generation.ops import AddMethodOp
|
22
|
+
from ..generation.ops import Op
|
23
|
+
from ..generation.ops import OpRef
|
24
|
+
from ..generation.ops import Ref
|
25
|
+
from ..generation.registry import register_generator_type
|
26
|
+
from ..generation.utils import SetattrSrcBuilder
|
27
|
+
from ..inspect import FieldsInspection
|
28
|
+
from ..internals import STD_POST_INIT_NAME
|
29
|
+
from ..processing.base import ProcessingContext
|
30
|
+
from ..specs import CoerceFn
|
31
|
+
from ..specs import DefaultFactory
|
32
|
+
from ..specs import FieldSpec
|
33
|
+
from ..specs import FieldType
|
34
|
+
from ..specs import InitFn
|
35
|
+
from ..specs import ValidateFn
|
36
|
+
from .fields import InitFields
|
37
|
+
from .mro import MroDict
|
38
|
+
|
39
|
+
|
40
|
+
##
|
41
|
+
|
42
|
+
|
43
|
+
@dc.dataclass(frozen=True, kw_only=True)
|
44
|
+
class InitPlan(Plan):
|
45
|
+
@dc.dataclass(frozen=True)
|
46
|
+
class Field:
|
47
|
+
name: str
|
48
|
+
annotation: OpRef[ta.Any]
|
49
|
+
|
50
|
+
default: OpRef[ta.Any] | None
|
51
|
+
default_factory: OpRef[ta.Any] | None
|
52
|
+
|
53
|
+
init: bool
|
54
|
+
|
55
|
+
override: bool
|
56
|
+
|
57
|
+
field_type: FieldType
|
58
|
+
|
59
|
+
coerce: bool | OpRef[CoerceFn] | None
|
60
|
+
validate: OpRef[ValidateFn] | None
|
61
|
+
|
62
|
+
check_type: OpRef[type | tuple[type, ...]] | None
|
63
|
+
|
64
|
+
fields: tuple[Field, ...]
|
65
|
+
|
66
|
+
self_param: str
|
67
|
+
std_params: tuple[str, ...]
|
68
|
+
kw_only_params: tuple[str, ...]
|
69
|
+
|
70
|
+
frozen: bool
|
71
|
+
|
72
|
+
slots: bool
|
73
|
+
|
74
|
+
post_init_params: tuple[str, ...] | None
|
75
|
+
|
76
|
+
init_fns: tuple[OpRef[InitFn], ...]
|
77
|
+
|
78
|
+
@dc.dataclass(frozen=True)
|
79
|
+
class ValidateFnWithParams:
|
80
|
+
fn: OpRef[ValidateFn]
|
81
|
+
params: tuple[str, ...]
|
82
|
+
|
83
|
+
validate_fns: ta.Sequence[ValidateFnWithParams] | None
|
84
|
+
|
85
|
+
|
86
|
+
@register_generator_type(InitPlan)
|
87
|
+
class InitGenerator(Generator[InitPlan]):
|
88
|
+
def _plan_field(
|
89
|
+
self,
|
90
|
+
ctx: ProcessingContext,
|
91
|
+
i: int,
|
92
|
+
f: FieldSpec,
|
93
|
+
ann: ta.Any,
|
94
|
+
ref_map: dict,
|
95
|
+
) -> InitPlan.Field:
|
96
|
+
ann_ref: OpRef = OpRef(f'init.fields.{i}.annotation')
|
97
|
+
ref_map[ann_ref] = ann
|
98
|
+
|
99
|
+
default_ref: OpRef[ta.Any] | None = None
|
100
|
+
default_factory_ref: OpRef[ta.Any] | None = None
|
101
|
+
if f.default.present:
|
102
|
+
dfl = f.default.must()
|
103
|
+
if isinstance(dfl, DefaultFactory):
|
104
|
+
default_factory_ref = OpRef(f'init.fields.{i}.default_factory')
|
105
|
+
ref_map[default_factory_ref] = dfl.fn
|
106
|
+
else:
|
107
|
+
default_ref = OpRef(f'init.fields.{i}.default')
|
108
|
+
ref_map[default_ref] = dfl
|
109
|
+
|
110
|
+
coerce: bool | OpRef[CoerceFn] | None = None
|
111
|
+
if isinstance(f.coerce, bool):
|
112
|
+
coerce = f.coerce
|
113
|
+
elif f.coerce is not None:
|
114
|
+
coerce = OpRef(f'init.fields.{i}.coerce')
|
115
|
+
ref_map[coerce] = f.coerce
|
116
|
+
|
117
|
+
validate_ref: OpRef[ValidateFn] | None = None
|
118
|
+
if f.validate is not None:
|
119
|
+
validate_ref = OpRef(f'init.fields.{i}.validate')
|
120
|
+
ref_map[validate_ref] = f.validate
|
121
|
+
|
122
|
+
check_type_ref: OpRef[type | tuple[type, ...]] | None = None
|
123
|
+
if f.check_type is not None and f.check_type is not False:
|
124
|
+
check_type_arg: ta.Any
|
125
|
+
if isinstance(f.check_type, tuple):
|
126
|
+
check_type_arg = tuple(type(None) if e is None else check.isinstance(e, type) for e in f.check_type)
|
127
|
+
elif isinstance(f.check_type, type):
|
128
|
+
check_type_arg = f.check_type
|
129
|
+
elif f.check_type is True:
|
130
|
+
check_type_arg = f.annotation
|
131
|
+
else:
|
132
|
+
raise TypeError(f.check_type)
|
133
|
+
check_type_ref = OpRef(f'init.fields.{i}.check_type')
|
134
|
+
ref_map[check_type_ref] = check_type_arg
|
135
|
+
|
136
|
+
return InitPlan.Field(
|
137
|
+
name=f.name,
|
138
|
+
annotation=ann_ref,
|
139
|
+
|
140
|
+
default=default_ref,
|
141
|
+
default_factory=default_factory_ref,
|
142
|
+
|
143
|
+
init=f.init,
|
144
|
+
|
145
|
+
override=f.override or ctx.cs.override,
|
146
|
+
|
147
|
+
field_type=f.field_type,
|
148
|
+
|
149
|
+
coerce=coerce,
|
150
|
+
validate=validate_ref,
|
151
|
+
|
152
|
+
check_type=check_type_ref,
|
153
|
+
)
|
154
|
+
|
155
|
+
def plan(self, ctx: ProcessingContext) -> PlanResult[InitPlan] | None:
|
156
|
+
if '__init__' in ctx.cls.__dict__ or not ctx.cs.init:
|
157
|
+
return None
|
158
|
+
|
159
|
+
init_fields = ctx[InitFields]
|
160
|
+
seen_default = None
|
161
|
+
for f in init_fields.std:
|
162
|
+
if not f.init:
|
163
|
+
continue
|
164
|
+
if f.default.present:
|
165
|
+
seen_default = f
|
166
|
+
elif seen_default:
|
167
|
+
raise TypeError(f'non-default argument {f.name!r} follows default argument {seen_default.name!r}')
|
168
|
+
|
169
|
+
if ctx.cs.generic_init:
|
170
|
+
gr_field_anns = ctx[FieldsInspection].generic_replaced_field_annotations
|
171
|
+
get_field_ann = lambda f: gr_field_anns[f.name]
|
172
|
+
else:
|
173
|
+
get_field_ann = lambda f: f.annotation
|
174
|
+
|
175
|
+
ref_map: dict = {}
|
176
|
+
|
177
|
+
plan_fields: list[InitPlan.Field] = []
|
178
|
+
for i, f in enumerate(ctx.cs.fields):
|
179
|
+
plan_fields.append(self._plan_field(
|
180
|
+
ctx,
|
181
|
+
i,
|
182
|
+
f,
|
183
|
+
get_field_ann(f),
|
184
|
+
ref_map,
|
185
|
+
))
|
186
|
+
|
187
|
+
mro_v_ids = set(map(id, ctx[MroDict].values()))
|
188
|
+
props_by_fget_id = {
|
189
|
+
id(v.fget): v
|
190
|
+
for v in ctx[MroDict].values()
|
191
|
+
if isinstance(v, property)
|
192
|
+
and v.fget is not None
|
193
|
+
}
|
194
|
+
init_fns: list[OpRef[InitFn]] = []
|
195
|
+
for i, init_fn in enumerate(ctx.cs.init_fns or []):
|
196
|
+
if (obj_id := id(init_fn)) not in mro_v_ids and obj_id in props_by_fget_id:
|
197
|
+
init_fn = props_by_fget_id[obj_id].__get__
|
198
|
+
elif isinstance(init_fn, property):
|
199
|
+
init_fn = init_fn.__get__
|
200
|
+
init_fn_ref: OpRef = OpRef(f'init.init_fns.{i}')
|
201
|
+
ref_map[init_fn_ref] = init_fn
|
202
|
+
init_fns.append(init_fn_ref)
|
203
|
+
|
204
|
+
validate_fns: list[InitPlan.ValidateFnWithParams] = []
|
205
|
+
for i, validate_fn in enumerate(ctx.cs.validate_fns or []):
|
206
|
+
validate_fn_ref: OpRef = OpRef(f'init.validate_fns.{i}')
|
207
|
+
ref_map[validate_fn_ref] = validate_fn.fn
|
208
|
+
validate_fns.append(InitPlan.ValidateFnWithParams(
|
209
|
+
fn=validate_fn_ref,
|
210
|
+
params=tuple(validate_fn.params),
|
211
|
+
))
|
212
|
+
|
213
|
+
post_init_params: tuple[str, ...] | None = None
|
214
|
+
if hasattr(ctx.cls, STD_POST_INIT_NAME):
|
215
|
+
post_init_params = tuple(f.name for f in init_fields.all if f.field_type is FieldType.INIT_VAR)
|
216
|
+
|
217
|
+
return PlanResult(
|
218
|
+
InitPlan(
|
219
|
+
fields=tuple(plan_fields),
|
220
|
+
|
221
|
+
self_param=SELF_IDENT if 'self' in ctx.cs.fields_by_name else 'self',
|
222
|
+
std_params=tuple(f.name for f in init_fields.std),
|
223
|
+
kw_only_params=tuple(f.name for f in init_fields.kw_only),
|
224
|
+
|
225
|
+
frozen=ctx.cs.frozen,
|
226
|
+
|
227
|
+
slots=ctx.cs.slots,
|
228
|
+
|
229
|
+
post_init_params=post_init_params,
|
230
|
+
|
231
|
+
init_fns=tuple(init_fns),
|
232
|
+
|
233
|
+
validate_fns=tuple(validate_fns),
|
234
|
+
),
|
235
|
+
ref_map,
|
236
|
+
)
|
237
|
+
|
238
|
+
def generate(self, plan: InitPlan) -> ta.Iterable[Op]:
|
239
|
+
refs: set[Ref] = set()
|
240
|
+
|
241
|
+
fields_by_name = {f.name: f for f in plan.fields}
|
242
|
+
|
243
|
+
# proto
|
244
|
+
|
245
|
+
params: list[str] = []
|
246
|
+
seen_kw_only = False
|
247
|
+
for fn, kw_only in itertools.chain(
|
248
|
+
[(fn, False) for fn in plan.std_params],
|
249
|
+
[(fn, True) for fn in plan.kw_only_params],
|
250
|
+
):
|
251
|
+
f = fields_by_name[fn]
|
252
|
+
if kw_only:
|
253
|
+
if not seen_kw_only:
|
254
|
+
params.append('*')
|
255
|
+
seen_kw_only = True
|
256
|
+
elif seen_kw_only:
|
257
|
+
raise TypeError(f'non-keyword-only argument {f.name!r} follows keyword-only argument(s)')
|
258
|
+
|
259
|
+
refs.add(f.annotation)
|
260
|
+
p = f'{f.name}: {f.annotation.ident()}'
|
261
|
+
|
262
|
+
if f.default_factory is not None:
|
263
|
+
check.none(f.default)
|
264
|
+
p += f' = {HAS_DEFAULT_FACTORY_GLOBAL.ident}'
|
265
|
+
refs.add(HAS_DEFAULT_FACTORY_GLOBAL)
|
266
|
+
elif f.default is not None:
|
267
|
+
check.none(f.default_factory)
|
268
|
+
refs.add(f.default)
|
269
|
+
p += f' = {f.default.ident()}'
|
270
|
+
|
271
|
+
params.append(p)
|
272
|
+
|
273
|
+
proto_lines = [
|
274
|
+
f'def __init__(',
|
275
|
+
f' {plan.self_param},',
|
276
|
+
*[
|
277
|
+
f' {p},'
|
278
|
+
for p in params
|
279
|
+
],
|
280
|
+
f') -> {NONE_GLOBAL.ident}:',
|
281
|
+
]
|
282
|
+
refs.add(NONE_GLOBAL)
|
283
|
+
|
284
|
+
# body
|
285
|
+
|
286
|
+
lines = []
|
287
|
+
|
288
|
+
# defaults
|
289
|
+
|
290
|
+
values: dict[str, str] = {
|
291
|
+
plan.self_param: plan.self_param,
|
292
|
+
}
|
293
|
+
|
294
|
+
for f in plan.fields:
|
295
|
+
if f.default_factory is not None:
|
296
|
+
check.none(f.default)
|
297
|
+
refs.add(f.default_factory)
|
298
|
+
if f.init:
|
299
|
+
lines.extend([
|
300
|
+
f' if {f.name} is {HAS_DEFAULT_FACTORY_GLOBAL.ident}:',
|
301
|
+
f' {f.name} = {f.default_factory.ident()}()',
|
302
|
+
])
|
303
|
+
refs.add(HAS_DEFAULT_FACTORY_GLOBAL)
|
304
|
+
else:
|
305
|
+
lines.append(
|
306
|
+
f' {f.name} = {f.default_factory.ident()}()',
|
307
|
+
)
|
308
|
+
values[f.name] = f.name
|
309
|
+
|
310
|
+
elif f.init:
|
311
|
+
if f.default is not None:
|
312
|
+
check.none(f.default_factory)
|
313
|
+
values[f.name] = f.name
|
314
|
+
|
315
|
+
else:
|
316
|
+
values[f.name] = f.name
|
317
|
+
|
318
|
+
elif plan.slots and f.default is not None:
|
319
|
+
refs.add(f.default)
|
320
|
+
lines.append(
|
321
|
+
f' {f.name} = {f.default.ident()}',
|
322
|
+
)
|
323
|
+
values[f.name] = f.name
|
324
|
+
|
325
|
+
# coercion
|
326
|
+
|
327
|
+
for f in plan.fields:
|
328
|
+
if isinstance(f.coerce, bool) and f.coerce:
|
329
|
+
lines.append(
|
330
|
+
f' {f.name} = {f.annotation.ident()}({values[f.name]})',
|
331
|
+
)
|
332
|
+
values[f.name] = f.name
|
333
|
+
elif isinstance(f.coerce, OpRef):
|
334
|
+
refs.add(f.coerce)
|
335
|
+
lines.append(
|
336
|
+
f' {f.name} = {f.coerce.ident()}({values[f.name]})',
|
337
|
+
)
|
338
|
+
values[f.name] = f.name
|
339
|
+
|
340
|
+
# field validation
|
341
|
+
|
342
|
+
for f in plan.fields:
|
343
|
+
if f.check_type is None:
|
344
|
+
continue
|
345
|
+
refs.add(f.check_type)
|
346
|
+
lines.extend([
|
347
|
+
f' if not {ISINSTANCE_GLOBAL.ident}({values[f.name]}, {f.check_type.ident()}): ',
|
348
|
+
f' raise {FIELD_TYPE_VALIDATION_ERROR_GLOBAL.ident}(',
|
349
|
+
f' obj={plan.self_param},',
|
350
|
+
f' type={f.check_type.ident()},',
|
351
|
+
f' field={f.name!r},',
|
352
|
+
f' value={values[f.name]},',
|
353
|
+
f' )',
|
354
|
+
])
|
355
|
+
refs.add(ISINSTANCE_GLOBAL)
|
356
|
+
refs.add(FIELD_TYPE_VALIDATION_ERROR_GLOBAL)
|
357
|
+
|
358
|
+
for f in plan.fields:
|
359
|
+
if f.validate is None:
|
360
|
+
continue
|
361
|
+
refs.add(f.validate)
|
362
|
+
lines.extend([
|
363
|
+
f' if not {f.validate.ident()}({values[f.name]}): ',
|
364
|
+
f' raise {FIELD_FN_VALIDATION_ERROR_GLOBAL.ident}(',
|
365
|
+
f' obj={plan.self_param},',
|
366
|
+
f' fn={f.validate.ident()},',
|
367
|
+
f' field={f.name!r},',
|
368
|
+
f' value={values[f.name]},',
|
369
|
+
f' )',
|
370
|
+
])
|
371
|
+
refs.add(FIELD_FN_VALIDATION_ERROR_GLOBAL)
|
372
|
+
|
373
|
+
# setattr
|
374
|
+
|
375
|
+
sab = SetattrSrcBuilder(
|
376
|
+
object_ident=plan.self_param,
|
377
|
+
)
|
378
|
+
for f in plan.fields:
|
379
|
+
if f.name not in values or f.field_type != FieldType.INSTANCE:
|
380
|
+
continue
|
381
|
+
lines.extend([
|
382
|
+
f' {l}'
|
383
|
+
for l in sab(
|
384
|
+
f.name,
|
385
|
+
values[f.name],
|
386
|
+
frozen=plan.frozen,
|
387
|
+
override=f.override,
|
388
|
+
)
|
389
|
+
])
|
390
|
+
refs.update(sab.refs)
|
391
|
+
|
392
|
+
# fn validation
|
393
|
+
|
394
|
+
for vfn in plan.validate_fns or []:
|
395
|
+
refs.add(vfn.fn)
|
396
|
+
if vfn.params:
|
397
|
+
lines.extend([
|
398
|
+
f' if not {vfn.fn.ident()}(',
|
399
|
+
*[
|
400
|
+
f' {values[p]},'
|
401
|
+
for p in vfn.params
|
402
|
+
],
|
403
|
+
f' ):',
|
404
|
+
])
|
405
|
+
else:
|
406
|
+
lines.append(
|
407
|
+
f' if not {vfn.fn.ident()}():',
|
408
|
+
)
|
409
|
+
lines.extend([
|
410
|
+
f' raise {FN_VALIDATION_ERROR_GLOBAL.ident}(',
|
411
|
+
f' obj={plan.self_param},',
|
412
|
+
f' fn={vfn.fn.ident()},',
|
413
|
+
f' )',
|
414
|
+
])
|
415
|
+
refs.add(FN_VALIDATION_ERROR_GLOBAL)
|
416
|
+
|
417
|
+
# post-init
|
418
|
+
|
419
|
+
if (pia := plan.post_init_params) is not None:
|
420
|
+
if pia:
|
421
|
+
lines.extend([
|
422
|
+
f' {plan.self_param}.{STD_POST_INIT_NAME}(',
|
423
|
+
*[
|
424
|
+
f' {values[p]},'
|
425
|
+
for p in pia
|
426
|
+
],
|
427
|
+
f' )',
|
428
|
+
])
|
429
|
+
else:
|
430
|
+
lines.append(
|
431
|
+
f' {plan.self_param}.{STD_POST_INIT_NAME}()',
|
432
|
+
)
|
433
|
+
|
434
|
+
for init_fn in plan.init_fns:
|
435
|
+
refs.add(init_fn)
|
436
|
+
lines.append(
|
437
|
+
f' {init_fn.ident()}({plan.self_param})',
|
438
|
+
)
|
439
|
+
|
440
|
+
#
|
441
|
+
|
442
|
+
if not lines:
|
443
|
+
lines.append(
|
444
|
+
' pass',
|
445
|
+
)
|
446
|
+
|
447
|
+
return [
|
448
|
+
AddMethodOp(
|
449
|
+
'__init__',
|
450
|
+
'\n'.join([*proto_lines, *lines]),
|
451
|
+
frozenset(refs),
|
452
|
+
),
|
453
|
+
]
|
@@ -0,0 +1,27 @@
|
|
1
|
+
from ... import check
|
2
|
+
from ..processing.base import Processor
|
3
|
+
from ..processing.priority import ProcessorPriority
|
4
|
+
from ..processing.registry import register_processor_type
|
5
|
+
from ..utils import set_new_attribute
|
6
|
+
from .fields import InitFields
|
7
|
+
|
8
|
+
|
9
|
+
##
|
10
|
+
|
11
|
+
|
12
|
+
@register_processor_type(priority=ProcessorPriority.POST_GENERATION)
|
13
|
+
class MatchArgsProcessor(Processor):
|
14
|
+
def check(self) -> None:
|
15
|
+
check.not_none(self._ctx[InitFields])
|
16
|
+
|
17
|
+
def process(self, cls: type) -> type:
|
18
|
+
if not self._ctx.cs.match_args or '__match_args__' in self._ctx.cls.__dict__:
|
19
|
+
return cls
|
20
|
+
|
21
|
+
set_new_attribute(
|
22
|
+
cls,
|
23
|
+
'__match_args__',
|
24
|
+
tuple(f.name for f in self._ctx[InitFields].std),
|
25
|
+
)
|
26
|
+
|
27
|
+
return cls
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import typing as ta
|
2
|
+
|
3
|
+
from ... import lang
|
4
|
+
from ..processing.base import ProcessingContext
|
5
|
+
from ..processing.registry import register_processing_context_item_factory
|
6
|
+
|
7
|
+
|
8
|
+
##
|
9
|
+
|
10
|
+
|
11
|
+
MroDict = ta.NewType('MroDict', ta.Mapping[str, ta.Any])
|
12
|
+
|
13
|
+
|
14
|
+
@register_processing_context_item_factory(MroDict)
|
15
|
+
def _mro_dict_processing_context_item_factory(ctx: ProcessingContext) -> MroDict:
|
16
|
+
return MroDict(lang.mro_dict(ctx.cls))
|
@@ -0,0 +1,87 @@
|
|
1
|
+
import dataclasses as dc
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from ..generation.base import Generator
|
5
|
+
from ..generation.base import Plan
|
6
|
+
from ..generation.base import PlanResult
|
7
|
+
from ..generation.ops import AddMethodOp
|
8
|
+
from ..generation.ops import Op
|
9
|
+
from ..generation.registry import register_generator_type
|
10
|
+
from ..generation.utils import build_attr_tuple_body_src_lines
|
11
|
+
from ..processing.base import ProcessingContext
|
12
|
+
from .fields import InstanceFields
|
13
|
+
|
14
|
+
|
15
|
+
##
|
16
|
+
|
17
|
+
|
18
|
+
NAME_OP_PAIRS = [
|
19
|
+
('__lt__', '<'),
|
20
|
+
('__le__', '<='),
|
21
|
+
('__gt__', '>'),
|
22
|
+
('__ge__', '>='),
|
23
|
+
]
|
24
|
+
|
25
|
+
|
26
|
+
##
|
27
|
+
|
28
|
+
|
29
|
+
@dc.dataclass(frozen=True)
|
30
|
+
class OrderPlan(Plan):
|
31
|
+
fields: tuple[str, ...]
|
32
|
+
|
33
|
+
|
34
|
+
@register_generator_type(OrderPlan)
|
35
|
+
class OrderGenerator(Generator[OrderPlan]):
|
36
|
+
def plan(self, ctx: ProcessingContext) -> PlanResult[OrderPlan] | None:
|
37
|
+
if not ctx.cs.order:
|
38
|
+
return None
|
39
|
+
|
40
|
+
for name, _ in NAME_OP_PAIRS:
|
41
|
+
if name in ctx.cls.__dict__:
|
42
|
+
raise TypeError(
|
43
|
+
f'Cannot overwrite attribute {name} in class {ctx.cls.__name__}. '
|
44
|
+
f'Consider using functools.total_ordering',
|
45
|
+
)
|
46
|
+
|
47
|
+
return PlanResult(OrderPlan(
|
48
|
+
tuple(f.name for f in ctx[InstanceFields] if f.compare),
|
49
|
+
))
|
50
|
+
|
51
|
+
def generate(self, pl: OrderPlan) -> ta.Iterable[Op]:
|
52
|
+
ops: list[AddMethodOp] = []
|
53
|
+
|
54
|
+
for name, op in NAME_OP_PAIRS:
|
55
|
+
ret_lines: list[str] = []
|
56
|
+
if pl.fields:
|
57
|
+
ret_lines.extend([
|
58
|
+
f' return (',
|
59
|
+
*build_attr_tuple_body_src_lines(
|
60
|
+
'self',
|
61
|
+
*pl.fields,
|
62
|
+
prefix=' ',
|
63
|
+
),
|
64
|
+
f' ) {op} (',
|
65
|
+
*build_attr_tuple_body_src_lines(
|
66
|
+
'other',
|
67
|
+
*pl.fields,
|
68
|
+
prefix=' ',
|
69
|
+
),
|
70
|
+
f' )',
|
71
|
+
])
|
72
|
+
else:
|
73
|
+
ret_lines.append(
|
74
|
+
f' return {"True" if "=" in op else "False"}',
|
75
|
+
)
|
76
|
+
|
77
|
+
ops.append(AddMethodOp(
|
78
|
+
name,
|
79
|
+
'\n'.join([
|
80
|
+
f'def {name}(self, other):',
|
81
|
+
f' if other.__class__ is not self.__class__:',
|
82
|
+
f' return NotImplemented',
|
83
|
+
*ret_lines,
|
84
|
+
]),
|
85
|
+
))
|
86
|
+
|
87
|
+
return ops
|
@@ -0,0 +1,98 @@
|
|
1
|
+
import dataclasses as dc
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from ..generation.base import Generator
|
5
|
+
from ..generation.base import Plan
|
6
|
+
from ..generation.base import PlanResult
|
7
|
+
from ..generation.globals import NONE_GLOBAL
|
8
|
+
from ..generation.idents import SELF_IDENT
|
9
|
+
from ..generation.idents import VALUE_IDENT
|
10
|
+
from ..generation.ops import AddPropertyOp
|
11
|
+
from ..generation.ops import Op
|
12
|
+
from ..generation.ops import OpRef
|
13
|
+
from ..generation.ops import Ref
|
14
|
+
from ..generation.registry import register_generator_type
|
15
|
+
from ..generation.utils import SetattrSrcBuilder
|
16
|
+
from ..processing.base import ProcessingContext
|
17
|
+
from .fields import InstanceFields
|
18
|
+
|
19
|
+
|
20
|
+
##
|
21
|
+
|
22
|
+
|
23
|
+
@dc.dataclass(frozen=True)
|
24
|
+
class OverridePlan(Plan):
|
25
|
+
@dc.dataclass(frozen=True)
|
26
|
+
class Field:
|
27
|
+
name: str
|
28
|
+
annotation: OpRef[ta.Any]
|
29
|
+
|
30
|
+
fields: tuple[Field, ...]
|
31
|
+
|
32
|
+
frozen: bool
|
33
|
+
|
34
|
+
|
35
|
+
@register_generator_type(OverridePlan)
|
36
|
+
class OverrideGenerator(Generator[OverridePlan]):
|
37
|
+
def plan(self, ctx: ProcessingContext) -> PlanResult[OverridePlan] | None:
|
38
|
+
orm = {}
|
39
|
+
|
40
|
+
flds: list[OverridePlan.Field] = []
|
41
|
+
for i, f in enumerate(ctx[InstanceFields]):
|
42
|
+
if not (f.override or ctx.cs.override):
|
43
|
+
continue
|
44
|
+
r: OpRef = OpRef(f'override.fields.{i}.annotation')
|
45
|
+
orm[r] = f.annotation
|
46
|
+
flds.append(OverridePlan.Field(
|
47
|
+
f.name,
|
48
|
+
r,
|
49
|
+
))
|
50
|
+
|
51
|
+
if not flds:
|
52
|
+
return None
|
53
|
+
|
54
|
+
return PlanResult(
|
55
|
+
OverridePlan(
|
56
|
+
tuple(flds),
|
57
|
+
ctx.cs.frozen,
|
58
|
+
),
|
59
|
+
orm,
|
60
|
+
)
|
61
|
+
|
62
|
+
def generate(self, pl: OverridePlan) -> ta.Iterable[Op]:
|
63
|
+
ops: list[Op] = []
|
64
|
+
|
65
|
+
for f in pl.fields:
|
66
|
+
op_refs: set[Ref] = {f.annotation}
|
67
|
+
|
68
|
+
get_src = '\n'.join([
|
69
|
+
f'def {f.name}({SELF_IDENT}) -> {f.annotation.ident()}:',
|
70
|
+
f' return {SELF_IDENT}.__dict__[{f.name!r}]',
|
71
|
+
])
|
72
|
+
|
73
|
+
set_src: str | None = None
|
74
|
+
if not pl.frozen:
|
75
|
+
sab = SetattrSrcBuilder()
|
76
|
+
set_src = '\n'.join([
|
77
|
+
f'def {f.name}({SELF_IDENT}, {VALUE_IDENT}) -> {NONE_GLOBAL.ident}:',
|
78
|
+
*[
|
79
|
+
f' {l}'
|
80
|
+
for l in sab(
|
81
|
+
f.name,
|
82
|
+
VALUE_IDENT,
|
83
|
+
frozen=pl.frozen,
|
84
|
+
override=True,
|
85
|
+
)
|
86
|
+
],
|
87
|
+
])
|
88
|
+
op_refs.add(NONE_GLOBAL)
|
89
|
+
op_refs.update(sab.refs)
|
90
|
+
|
91
|
+
ops.append(AddPropertyOp(
|
92
|
+
f.name,
|
93
|
+
get_src=get_src,
|
94
|
+
set_src=set_src,
|
95
|
+
refs=frozenset(op_refs),
|
96
|
+
))
|
97
|
+
|
98
|
+
return ops
|
@@ -0,0 +1,14 @@
|
|
1
|
+
from ... import check
|
2
|
+
from ..internals import STD_PARAMS_ATTR
|
3
|
+
from ..processing.base import Processor
|
4
|
+
from ..processing.priority import ProcessorPriority
|
5
|
+
from ..processing.registry import register_processor_type
|
6
|
+
|
7
|
+
|
8
|
+
##
|
9
|
+
|
10
|
+
|
11
|
+
@register_processor_type(priority=ProcessorPriority.BOOTSTRAP)
|
12
|
+
class ParamsProcessor(Processor):
|
13
|
+
def check(self) -> None:
|
14
|
+
check.in_(STD_PARAMS_ATTR, self._ctx.cls.__dict__)
|