ducktools-classbuilder 0.5.1__py3-none-any.whl → 0.6.1__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 ducktools-classbuilder might be problematic. Click here for more details.
- ducktools/classbuilder/__init__.py +494 -210
- ducktools/classbuilder/__init__.pyi +145 -57
- ducktools/classbuilder/annotations.py +173 -0
- ducktools/classbuilder/annotations.pyi +26 -0
- ducktools/classbuilder/prefab.py +237 -385
- ducktools/classbuilder/prefab.pyi +30 -28
- ducktools_classbuilder-0.6.1.dist-info/METADATA +318 -0
- ducktools_classbuilder-0.6.1.dist-info/RECORD +12 -0
- {ducktools_classbuilder-0.5.1.dist-info → ducktools_classbuilder-0.6.1.dist-info}/WHEEL +1 -1
- ducktools_classbuilder-0.5.1.dist-info/METADATA +0 -270
- ducktools_classbuilder-0.5.1.dist-info/RECORD +0 -10
- {ducktools_classbuilder-0.5.1.dist-info → ducktools_classbuilder-0.6.1.dist-info}/LICENSE.md +0 -0
- {ducktools_classbuilder-0.5.1.dist-info → ducktools_classbuilder-0.6.1.dist-info}/top_level.txt +0 -0
ducktools/classbuilder/prefab.py
CHANGED
|
@@ -25,33 +25,25 @@ A 'prebuilt' implementation of class generation.
|
|
|
25
25
|
|
|
26
26
|
Includes pre and post init functions along with other methods.
|
|
27
27
|
"""
|
|
28
|
-
|
|
29
|
-
import sys
|
|
30
|
-
|
|
31
28
|
from . import (
|
|
32
29
|
INTERNALS_DICT, NOTHING,
|
|
33
|
-
Field, MethodMaker,
|
|
34
|
-
builder,
|
|
35
|
-
|
|
30
|
+
Field, MethodMaker, GatheredFields, GeneratedCode, SlotMakerMeta,
|
|
31
|
+
builder, get_flags, get_fields,
|
|
32
|
+
make_unified_gatherer,
|
|
33
|
+
frozen_setattr_maker, frozen_delattr_maker, eq_maker,
|
|
34
|
+
get_repr_generator,
|
|
36
35
|
)
|
|
37
36
|
|
|
37
|
+
# These aren't used but are re-exported for ease of use
|
|
38
|
+
# noinspection PyUnresolvedReferences
|
|
39
|
+
from . import SlotFields, KW_ONLY
|
|
40
|
+
|
|
38
41
|
PREFAB_FIELDS = "PREFAB_FIELDS"
|
|
39
42
|
PREFAB_INIT_FUNC = "__prefab_init__"
|
|
40
43
|
PRE_INIT_FUNC = "__prefab_pre_init__"
|
|
41
44
|
POST_INIT_FUNC = "__prefab_post_init__"
|
|
42
45
|
|
|
43
46
|
|
|
44
|
-
# KW_ONLY sentinel 'type' to use to indicate all subsequent attributes are
|
|
45
|
-
# keyword only
|
|
46
|
-
# noinspection PyPep8Naming
|
|
47
|
-
class _KW_ONLY_TYPE:
|
|
48
|
-
def __repr__(self):
|
|
49
|
-
return "<KW_ONLY Sentinel Object>"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
KW_ONLY = _KW_ONLY_TYPE()
|
|
53
|
-
|
|
54
|
-
|
|
55
47
|
class PrefabError(Exception):
|
|
56
48
|
pass
|
|
57
49
|
|
|
@@ -68,310 +60,226 @@ def get_attributes(cls):
|
|
|
68
60
|
|
|
69
61
|
|
|
70
62
|
# Method Generators
|
|
71
|
-
def
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
flags = get_flags(cls)
|
|
77
|
-
|
|
78
|
-
kw_only = flags.get("kw_only", False)
|
|
79
|
-
|
|
80
|
-
# Handle pre/post init first - post_init can change types for __init__
|
|
81
|
-
# Get pre and post init arguments
|
|
82
|
-
pre_init_args = []
|
|
83
|
-
post_init_args = []
|
|
84
|
-
post_init_annotations = {}
|
|
85
|
-
|
|
86
|
-
for func_name, func_arglist in [
|
|
87
|
-
(PRE_INIT_FUNC, pre_init_args),
|
|
88
|
-
(POST_INIT_FUNC, post_init_args),
|
|
89
|
-
]:
|
|
90
|
-
try:
|
|
91
|
-
func = getattr(cls, func_name)
|
|
92
|
-
func_code = func.__code__
|
|
93
|
-
except AttributeError:
|
|
94
|
-
pass
|
|
95
|
-
else:
|
|
96
|
-
argcount = func_code.co_argcount + func_code.co_kwonlyargcount
|
|
63
|
+
def init_generator(cls, funcname="__init__"):
|
|
64
|
+
globs = {}
|
|
65
|
+
# Get the internals dictionary and prepare attributes
|
|
66
|
+
attributes = get_attributes(cls)
|
|
67
|
+
flags = get_flags(cls)
|
|
97
68
|
|
|
98
|
-
|
|
99
|
-
is_static = type(cls.__dict__.get(func_name)) is staticmethod
|
|
69
|
+
kw_only = flags.get("kw_only", False)
|
|
100
70
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
71
|
+
# Handle pre/post init first - post_init can change types for __init__
|
|
72
|
+
# Get pre and post init arguments
|
|
73
|
+
pre_init_args = []
|
|
74
|
+
post_init_args = []
|
|
75
|
+
post_init_annotations = {}
|
|
76
|
+
|
|
77
|
+
for extra_funcname, func_arglist in [
|
|
78
|
+
(PRE_INIT_FUNC, pre_init_args),
|
|
79
|
+
(POST_INIT_FUNC, post_init_args),
|
|
80
|
+
]:
|
|
81
|
+
try:
|
|
82
|
+
func = getattr(cls, extra_funcname)
|
|
83
|
+
func_code = func.__code__
|
|
84
|
+
except AttributeError:
|
|
85
|
+
pass
|
|
86
|
+
else:
|
|
87
|
+
argcount = func_code.co_argcount + func_code.co_kwonlyargcount
|
|
106
88
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
arg = f"{name}=_{name}_default"
|
|
135
|
-
else:
|
|
136
|
-
arg = f"{name}: _{name}_type = _{name}_default"
|
|
137
|
-
globs[f"_{name}_default"] = attrib.default
|
|
138
|
-
elif attrib.default_factory is not NOTHING:
|
|
139
|
-
# Use NONE here and call the factory later
|
|
140
|
-
# This matches the behaviour of compiled
|
|
89
|
+
# Identify if method is static, if so include first arg, otherwise skip
|
|
90
|
+
is_static = type(cls.__dict__.get(extra_funcname)) is staticmethod
|
|
91
|
+
|
|
92
|
+
arglist = (
|
|
93
|
+
func_code.co_varnames[:argcount]
|
|
94
|
+
if is_static
|
|
95
|
+
else func_code.co_varnames[1:argcount]
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
func_arglist.extend(arglist)
|
|
99
|
+
|
|
100
|
+
if extra_funcname == POST_INIT_FUNC:
|
|
101
|
+
post_init_annotations.update(func.__annotations__)
|
|
102
|
+
|
|
103
|
+
pos_arglist = []
|
|
104
|
+
kw_only_arglist = []
|
|
105
|
+
for name, attrib in attributes.items():
|
|
106
|
+
# post_init annotations can be used to broaden types.
|
|
107
|
+
if name in post_init_annotations:
|
|
108
|
+
globs[f"_{name}_type"] = post_init_annotations[name]
|
|
109
|
+
elif attrib.type is not NOTHING:
|
|
110
|
+
globs[f"_{name}_type"] = attrib.type
|
|
111
|
+
|
|
112
|
+
if attrib.init:
|
|
113
|
+
if attrib.default is not NOTHING:
|
|
114
|
+
if isinstance(attrib.default, (str, int, float, bool)):
|
|
115
|
+
# Just use the literal in these cases
|
|
141
116
|
if attrib.type is NOTHING:
|
|
142
|
-
arg = f"{name}=
|
|
117
|
+
arg = f"{name}={attrib.default!r}"
|
|
143
118
|
else:
|
|
144
|
-
arg = f"{name}: _{name}_type =
|
|
145
|
-
globs[f"_{name}_factory"] = attrib.default_factory
|
|
119
|
+
arg = f"{name}: _{name}_type = {attrib.default!r}"
|
|
146
120
|
else:
|
|
121
|
+
# No guarantee repr will work for other objects
|
|
122
|
+
# so store the value in a variable and put it
|
|
123
|
+
# in the globals dict for eval
|
|
147
124
|
if attrib.type is NOTHING:
|
|
148
|
-
arg = name
|
|
125
|
+
arg = f"{name}=_{name}_default"
|
|
149
126
|
else:
|
|
150
|
-
arg = f"{name}: _{name}_type"
|
|
151
|
-
if attrib.kw_only or kw_only:
|
|
152
|
-
kw_only_arglist.append(arg)
|
|
153
|
-
else:
|
|
154
|
-
pos_arglist.append(arg)
|
|
155
|
-
# Not in init, but need to set defaults
|
|
156
|
-
else:
|
|
157
|
-
if attrib.default is not NOTHING:
|
|
127
|
+
arg = f"{name}: _{name}_type = _{name}_default"
|
|
158
128
|
globs[f"_{name}_default"] = attrib.default
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if pos_args and kw_args:
|
|
165
|
-
args = f"{pos_args}, *, {kw_args}"
|
|
166
|
-
elif kw_args:
|
|
167
|
-
args = f"*, {kw_args}"
|
|
168
|
-
else:
|
|
169
|
-
args = pos_args
|
|
170
|
-
|
|
171
|
-
assignments = []
|
|
172
|
-
processes = [] # post_init values still need default factories to be called.
|
|
173
|
-
for name, attrib in attributes.items():
|
|
174
|
-
if attrib.init:
|
|
175
|
-
if attrib.default_factory is not NOTHING:
|
|
176
|
-
value = f"{name} if {name} is not None else _{name}_factory()"
|
|
129
|
+
elif attrib.default_factory is not NOTHING:
|
|
130
|
+
# Use NONE here and call the factory later
|
|
131
|
+
# This matches the behaviour of compiled
|
|
132
|
+
if attrib.type is NOTHING:
|
|
133
|
+
arg = f"{name}=None"
|
|
177
134
|
else:
|
|
178
|
-
|
|
135
|
+
arg = f"{name}: _{name}_type = None"
|
|
136
|
+
globs[f"_{name}_factory"] = attrib.default_factory
|
|
179
137
|
else:
|
|
180
|
-
if attrib.
|
|
181
|
-
|
|
182
|
-
elif attrib.default is not NOTHING:
|
|
183
|
-
value = f"_{name}_default"
|
|
138
|
+
if attrib.type is NOTHING:
|
|
139
|
+
arg = name
|
|
184
140
|
else:
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
assignments.append((name, value))
|
|
192
|
-
|
|
193
|
-
if hasattr(cls, PRE_INIT_FUNC):
|
|
194
|
-
pre_init_arg_call = ", ".join(f"{name}={name}" for name in pre_init_args)
|
|
195
|
-
pre_init_call = f" self.{PRE_INIT_FUNC}({pre_init_arg_call})\n"
|
|
196
|
-
else:
|
|
197
|
-
pre_init_call = ""
|
|
198
|
-
|
|
199
|
-
if assignments or processes:
|
|
200
|
-
body = ""
|
|
201
|
-
body += "\n".join(
|
|
202
|
-
f" self.{name} = {value}" for name, value in assignments
|
|
203
|
-
)
|
|
204
|
-
body += "\n"
|
|
205
|
-
body += "\n".join(f" {name} = {value}" for name, value in processes)
|
|
206
|
-
else:
|
|
207
|
-
body = " pass"
|
|
208
|
-
|
|
209
|
-
if hasattr(cls, POST_INIT_FUNC):
|
|
210
|
-
post_init_arg_call = ", ".join(f"{name}={name}" for name in post_init_args)
|
|
211
|
-
post_init_call = f" self.{POST_INIT_FUNC}({post_init_arg_call})\n"
|
|
212
|
-
else:
|
|
213
|
-
post_init_call = ""
|
|
214
|
-
|
|
215
|
-
code = (
|
|
216
|
-
f"def {init_name}(self, {args}):\n"
|
|
217
|
-
f"{pre_init_call}\n"
|
|
218
|
-
f"{body}\n"
|
|
219
|
-
f"{post_init_call}\n"
|
|
220
|
-
)
|
|
221
|
-
return code, globs
|
|
222
|
-
|
|
223
|
-
return MethodMaker(init_name, __init__)
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
def get_repr_maker(*, recursion_safe=False):
|
|
227
|
-
def __repr__(cls: "type") -> "tuple[str, dict]":
|
|
228
|
-
attributes = get_attributes(cls)
|
|
229
|
-
|
|
230
|
-
globs = {}
|
|
231
|
-
|
|
232
|
-
will_eval = True
|
|
233
|
-
valid_names = []
|
|
234
|
-
for name, attrib in attributes.items():
|
|
235
|
-
if attrib.repr and not attrib.exclude_field:
|
|
236
|
-
valid_names.append(name)
|
|
237
|
-
|
|
238
|
-
# If the init fields don't match the repr, or some fields are excluded
|
|
239
|
-
# generate a repr that clearly will not evaluate
|
|
240
|
-
if will_eval and (attrib.exclude_field or (attrib.init ^ attrib.repr)):
|
|
241
|
-
will_eval = False
|
|
242
|
-
|
|
243
|
-
content = ", ".join(
|
|
244
|
-
f"{name}={{self.{name}!r}}"
|
|
245
|
-
for name in valid_names
|
|
246
|
-
)
|
|
247
|
-
|
|
248
|
-
if recursion_safe:
|
|
249
|
-
import reprlib
|
|
250
|
-
globs["_recursive_repr"] = reprlib.recursive_repr()
|
|
251
|
-
recursion_func = "@_recursive_repr\n"
|
|
141
|
+
arg = f"{name}: _{name}_type"
|
|
142
|
+
if attrib.kw_only or kw_only:
|
|
143
|
+
kw_only_arglist.append(arg)
|
|
144
|
+
else:
|
|
145
|
+
pos_arglist.append(arg)
|
|
146
|
+
# Not in init, but need to set defaults
|
|
252
147
|
else:
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
148
|
+
if attrib.default is not NOTHING:
|
|
149
|
+
globs[f"_{name}_default"] = attrib.default
|
|
150
|
+
elif attrib.default_factory is not NOTHING:
|
|
151
|
+
globs[f"_{name}_factory"] = attrib.default_factory
|
|
152
|
+
|
|
153
|
+
pos_args = ", ".join(pos_arglist)
|
|
154
|
+
kw_args = ", ".join(kw_only_arglist)
|
|
155
|
+
if pos_args and kw_args:
|
|
156
|
+
args = f"{pos_args}, *, {kw_args}"
|
|
157
|
+
elif kw_args:
|
|
158
|
+
args = f"*, {kw_args}"
|
|
159
|
+
else:
|
|
160
|
+
args = pos_args
|
|
161
|
+
|
|
162
|
+
assignments = []
|
|
163
|
+
processes = [] # post_init values still need default factories to be called.
|
|
164
|
+
for name, attrib in attributes.items():
|
|
165
|
+
if attrib.init:
|
|
166
|
+
if attrib.default_factory is not NOTHING:
|
|
167
|
+
value = f"{name} if {name} is not None else _{name}_factory()"
|
|
168
|
+
else:
|
|
169
|
+
value = name
|
|
261
170
|
else:
|
|
262
|
-
if
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
f" return f'<prefab {{type(self).__qualname__}}; {content}>'\n"
|
|
267
|
-
)
|
|
171
|
+
if attrib.default_factory is not NOTHING:
|
|
172
|
+
value = f"_{name}_factory()"
|
|
173
|
+
elif attrib.default is not NOTHING:
|
|
174
|
+
value = f"_{name}_default"
|
|
268
175
|
else:
|
|
269
|
-
|
|
270
|
-
f"{recursion_func}"
|
|
271
|
-
f"def __repr__(self):\n"
|
|
272
|
-
f" return f'<prefab {{type(self).__qualname__}}>'\n"
|
|
273
|
-
)
|
|
176
|
+
value = None
|
|
274
177
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
178
|
+
if name in post_init_args:
|
|
179
|
+
if attrib.default_factory is not NOTHING:
|
|
180
|
+
processes.append((name, value))
|
|
181
|
+
elif value is not None:
|
|
182
|
+
assignments.append((name, value))
|
|
278
183
|
|
|
184
|
+
if hasattr(cls, PRE_INIT_FUNC):
|
|
185
|
+
pre_init_arg_call = ", ".join(f"{name}={name}" for name in pre_init_args)
|
|
186
|
+
pre_init_call = f" self.{PRE_INIT_FUNC}({pre_init_arg_call})\n"
|
|
187
|
+
else:
|
|
188
|
+
pre_init_call = ""
|
|
279
189
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
190
|
+
if assignments or processes:
|
|
191
|
+
body = ""
|
|
192
|
+
body += "\n".join(
|
|
193
|
+
f" self.{name} = {value}" for name, value in assignments
|
|
194
|
+
)
|
|
195
|
+
body += "\n"
|
|
196
|
+
body += "\n".join(f" {name} = {value}" for name, value in processes)
|
|
197
|
+
else:
|
|
198
|
+
body = " pass"
|
|
289
199
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
instance_comparison = "True"
|
|
200
|
+
if hasattr(cls, POST_INIT_FUNC):
|
|
201
|
+
post_init_arg_call = ", ".join(f"{name}={name}" for name in post_init_args)
|
|
202
|
+
post_init_call = f" self.{POST_INIT_FUNC}({post_init_arg_call})\n"
|
|
203
|
+
else:
|
|
204
|
+
post_init_call = ""
|
|
296
205
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
206
|
+
code = (
|
|
207
|
+
f"def {funcname}(self, {args}):\n"
|
|
208
|
+
f"{pre_init_call}\n"
|
|
209
|
+
f"{body}\n"
|
|
210
|
+
f"{post_init_call}\n"
|
|
300
211
|
)
|
|
301
|
-
globs = {}
|
|
302
212
|
|
|
303
|
-
|
|
213
|
+
return GeneratedCode(code, globs)
|
|
304
214
|
|
|
305
|
-
return MethodMaker("__eq__", __eq__)
|
|
306
215
|
|
|
216
|
+
def iter_generator(cls, funcname="__iter__"):
|
|
217
|
+
fields = get_attributes(cls)
|
|
307
218
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
valid_fields = (
|
|
313
|
-
name for name, attrib in fields.items()
|
|
314
|
-
if attrib.iter and not attrib.exclude_field
|
|
315
|
-
)
|
|
316
|
-
|
|
317
|
-
values = "\n".join(f" yield self.{name}" for name in valid_fields)
|
|
219
|
+
valid_fields = (
|
|
220
|
+
name for name, attrib in fields.items()
|
|
221
|
+
if attrib.iter
|
|
222
|
+
)
|
|
318
223
|
|
|
319
|
-
|
|
320
|
-
if not values:
|
|
321
|
-
values = " yield from ()"
|
|
224
|
+
values = "\n".join(f" yield self.{name}" for name in valid_fields)
|
|
322
225
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
226
|
+
# if values is an empty string
|
|
227
|
+
if not values:
|
|
228
|
+
values = " yield from ()"
|
|
326
229
|
|
|
327
|
-
|
|
230
|
+
code = f"def {funcname}(self):\n{values}"
|
|
231
|
+
globs = {}
|
|
232
|
+
return GeneratedCode(code, globs)
|
|
328
233
|
|
|
329
234
|
|
|
330
|
-
def
|
|
331
|
-
|
|
332
|
-
fields = get_attributes(cls)
|
|
235
|
+
def as_dict_generator(cls, funcname="as_dict"):
|
|
236
|
+
fields = get_attributes(cls)
|
|
333
237
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
238
|
+
vals = ", ".join(
|
|
239
|
+
f"'{name}': self.{name}"
|
|
240
|
+
for name, attrib in fields.items()
|
|
241
|
+
if attrib.serialize
|
|
242
|
+
)
|
|
243
|
+
out_dict = f"{{{vals}}}"
|
|
244
|
+
code = f"def {funcname}(self): return {out_dict}"
|
|
341
245
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
return MethodMaker("as_dict", as_dict_gen)
|
|
246
|
+
globs = {}
|
|
247
|
+
return GeneratedCode(code, globs)
|
|
345
248
|
|
|
346
249
|
|
|
347
|
-
init_maker =
|
|
348
|
-
prefab_init_maker =
|
|
349
|
-
repr_maker =
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
250
|
+
init_maker = MethodMaker("__init__", init_generator)
|
|
251
|
+
prefab_init_maker = MethodMaker(PREFAB_INIT_FUNC, init_generator)
|
|
252
|
+
repr_maker = MethodMaker(
|
|
253
|
+
"__repr__",
|
|
254
|
+
get_repr_generator(recursion_safe=False, eval_safe=True)
|
|
255
|
+
)
|
|
256
|
+
recursive_repr_maker = MethodMaker(
|
|
257
|
+
"__repr__",
|
|
258
|
+
get_repr_generator(recursion_safe=True, eval_safe=True)
|
|
259
|
+
)
|
|
260
|
+
iter_maker = MethodMaker("__iter__", iter_generator)
|
|
261
|
+
asdict_maker = MethodMaker("as_dict", as_dict_generator)
|
|
354
262
|
|
|
355
263
|
|
|
356
264
|
# Updated field with additional attributes
|
|
357
|
-
@fieldclass
|
|
358
265
|
class Attribute(Field):
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
repr=True,
|
|
362
|
-
compare=True,
|
|
363
|
-
iter=True,
|
|
364
|
-
kw_only=False,
|
|
365
|
-
serialize=True,
|
|
366
|
-
exclude_field=False,
|
|
367
|
-
)
|
|
266
|
+
"""
|
|
267
|
+
Get an object to define a prefab attribute
|
|
368
268
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
269
|
+
:param default: Default value for this attribute
|
|
270
|
+
:param default_factory: 0 argument callable to give a default value
|
|
271
|
+
(for otherwise mutable defaults, eg: list)
|
|
272
|
+
:param init: Include this attribute in the __init__ parameters
|
|
273
|
+
:param repr: Include this attribute in the class __repr__
|
|
274
|
+
:param compare: Include this attribute in the class __eq__
|
|
275
|
+
:param iter: Include this attribute in the class __iter__ if generated
|
|
276
|
+
:param kw_only: Make this argument keyword only in init
|
|
277
|
+
:param serialize: Include this attribute in methods that serialize to dict
|
|
278
|
+
:param doc: Parameter documentation for slotted classes
|
|
279
|
+
:param type: Type of this attribute (for slotted classes)
|
|
280
|
+
"""
|
|
281
|
+
iter: bool = True
|
|
282
|
+
serialize: bool = True
|
|
375
283
|
|
|
376
284
|
|
|
377
285
|
# noinspection PyShadowingBuiltins
|
|
@@ -390,7 +298,7 @@ def attribute(
|
|
|
390
298
|
type=NOTHING,
|
|
391
299
|
):
|
|
392
300
|
"""
|
|
393
|
-
|
|
301
|
+
Helper function to get an object to define a prefab Attribute
|
|
394
302
|
|
|
395
303
|
:param default: Default value for this attribute
|
|
396
304
|
:param default_factory: 0 argument callable to give a default value
|
|
@@ -401,15 +309,18 @@ def attribute(
|
|
|
401
309
|
:param iter: Include this attribute in the class __iter__ if generated
|
|
402
310
|
:param kw_only: Make this argument keyword only in init
|
|
403
311
|
:param serialize: Include this attribute in methods that serialize to dict
|
|
404
|
-
:param exclude_field:
|
|
405
|
-
apart from __init__ signature
|
|
406
|
-
and do not include it in PREFAB_FIELDS
|
|
407
|
-
Must be assigned in __prefab_post_init__
|
|
312
|
+
:param exclude_field: Shorthand for setting repr, compare, iter and serialize to False
|
|
408
313
|
:param doc: Parameter documentation for slotted classes
|
|
409
314
|
:param type: Type of this attribute (for slotted classes)
|
|
410
315
|
|
|
411
316
|
:return: Attribute generated with these parameters.
|
|
412
317
|
"""
|
|
318
|
+
if exclude_field:
|
|
319
|
+
repr = False
|
|
320
|
+
compare = False
|
|
321
|
+
iter = False
|
|
322
|
+
serialize = False
|
|
323
|
+
|
|
413
324
|
return Attribute(
|
|
414
325
|
default=default,
|
|
415
326
|
default_factory=default_factory,
|
|
@@ -419,90 +330,12 @@ def attribute(
|
|
|
419
330
|
iter=iter,
|
|
420
331
|
kw_only=kw_only,
|
|
421
332
|
serialize=serialize,
|
|
422
|
-
exclude_field=exclude_field,
|
|
423
333
|
doc=doc,
|
|
424
334
|
type=type,
|
|
425
335
|
)
|
|
426
336
|
|
|
427
337
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
# Gatherer for classes built on attributes or annotations
|
|
432
|
-
def attribute_gatherer(cls):
|
|
433
|
-
cls_annotations = cls.__dict__.get("__annotations__", {})
|
|
434
|
-
cls_annotation_names = cls_annotations.keys()
|
|
435
|
-
|
|
436
|
-
cls_slots = cls.__dict__.get("__slots__", {})
|
|
437
|
-
|
|
438
|
-
cls_attributes = {
|
|
439
|
-
k: v for k, v in vars(cls).items() if isinstance(v, Attribute)
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
cls_attribute_names = cls_attributes.keys()
|
|
443
|
-
|
|
444
|
-
cls_modifications = {}
|
|
445
|
-
|
|
446
|
-
if set(cls_annotation_names).issuperset(set(cls_attribute_names)):
|
|
447
|
-
# replace the classes' attributes dict with one with the correct
|
|
448
|
-
# order from the annotations.
|
|
449
|
-
kw_flag = False
|
|
450
|
-
new_attributes = {}
|
|
451
|
-
for name, value in cls_annotations.items():
|
|
452
|
-
# Ignore ClassVar hints
|
|
453
|
-
if is_classvar(value):
|
|
454
|
-
continue
|
|
455
|
-
|
|
456
|
-
# Look for the KW_ONLY annotation
|
|
457
|
-
if value is KW_ONLY or value == "KW_ONLY":
|
|
458
|
-
if kw_flag:
|
|
459
|
-
raise PrefabError(
|
|
460
|
-
"Class can not be defined as keyword only twice"
|
|
461
|
-
)
|
|
462
|
-
kw_flag = True
|
|
463
|
-
else:
|
|
464
|
-
# Copy attributes that are already defined to the new dict
|
|
465
|
-
# generate Attribute() values for those that are not defined.
|
|
466
|
-
|
|
467
|
-
# Extra parameters to pass to each Attribute
|
|
468
|
-
extras = {
|
|
469
|
-
"type": cls_annotations[name]
|
|
470
|
-
}
|
|
471
|
-
if kw_flag:
|
|
472
|
-
extras["kw_only"] = True
|
|
473
|
-
|
|
474
|
-
# If a field name is also declared in slots it can't have a real
|
|
475
|
-
# default value and the attr will be the slot descriptor.
|
|
476
|
-
if hasattr(cls, name) and name not in cls_slots:
|
|
477
|
-
if name in cls_attribute_names:
|
|
478
|
-
attrib = Attribute.from_field(
|
|
479
|
-
cls_attributes[name],
|
|
480
|
-
**extras,
|
|
481
|
-
)
|
|
482
|
-
else:
|
|
483
|
-
attribute_default = getattr(cls, name)
|
|
484
|
-
attrib = attribute(default=attribute_default, **extras)
|
|
485
|
-
|
|
486
|
-
# Clear the attribute from the class after it has been used
|
|
487
|
-
# in the definition.
|
|
488
|
-
cls_modifications[name] = NOTHING
|
|
489
|
-
else:
|
|
490
|
-
attrib = attribute(**extras)
|
|
491
|
-
|
|
492
|
-
new_attributes[name] = attrib
|
|
493
|
-
|
|
494
|
-
cls_attributes = new_attributes
|
|
495
|
-
else:
|
|
496
|
-
for name in cls_attributes.keys():
|
|
497
|
-
attrib = cls_attributes[name]
|
|
498
|
-
cls_modifications[name] = NOTHING
|
|
499
|
-
|
|
500
|
-
# Some items can still be annotated.
|
|
501
|
-
if name in cls_annotations:
|
|
502
|
-
new_attrib = Attribute.from_field(attrib, type=cls_annotations[name])
|
|
503
|
-
cls_attributes[name] = new_attrib
|
|
504
|
-
|
|
505
|
-
return cls_attributes, cls_modifications
|
|
338
|
+
prefab_gatherer = make_unified_gatherer(Attribute, False)
|
|
506
339
|
|
|
507
340
|
|
|
508
341
|
# Class Builders
|
|
@@ -548,16 +381,13 @@ def _make_prefab(
|
|
|
548
381
|
)
|
|
549
382
|
|
|
550
383
|
slots = cls_dict.get("__slots__")
|
|
384
|
+
|
|
385
|
+
slotted = False if slots is None else True
|
|
386
|
+
|
|
551
387
|
if gathered_fields is None:
|
|
552
|
-
|
|
553
|
-
gatherer = slot_prefab_gatherer
|
|
554
|
-
slotted = True
|
|
555
|
-
else:
|
|
556
|
-
gatherer = attribute_gatherer
|
|
557
|
-
slotted = False
|
|
388
|
+
gatherer = prefab_gatherer
|
|
558
389
|
else:
|
|
559
390
|
gatherer = gathered_fields
|
|
560
|
-
slotted = False if slots is None else True
|
|
561
391
|
|
|
562
392
|
methods = set()
|
|
563
393
|
|
|
@@ -657,9 +487,9 @@ def _make_prefab(
|
|
|
657
487
|
post_init_args.extend(arglist)
|
|
658
488
|
|
|
659
489
|
# Gather values for match_args and do some syntax checking
|
|
660
|
-
|
|
661
490
|
default_defined = []
|
|
662
|
-
valid_args =
|
|
491
|
+
valid_args = list(fields.keys())
|
|
492
|
+
|
|
663
493
|
for name, attrib in fields.items():
|
|
664
494
|
# slot_gather and parent classes may use Fields
|
|
665
495
|
# prefabs require Attributes, so convert.
|
|
@@ -667,15 +497,6 @@ def _make_prefab(
|
|
|
667
497
|
attrib = Attribute.from_field(attrib)
|
|
668
498
|
fields[name] = attrib
|
|
669
499
|
|
|
670
|
-
# Excluded fields *MUST* be forwarded to post_init
|
|
671
|
-
if attrib.exclude_field:
|
|
672
|
-
if name not in post_init_args:
|
|
673
|
-
raise PrefabError(
|
|
674
|
-
f"{name!r} is an excluded attribute but is not passed to post_init"
|
|
675
|
-
)
|
|
676
|
-
else:
|
|
677
|
-
valid_args.append(name)
|
|
678
|
-
|
|
679
500
|
if not kw_only:
|
|
680
501
|
# Syntax check arguments for __init__ don't have non-default after default
|
|
681
502
|
if attrib.init and not attrib.kw_only:
|
|
@@ -698,6 +519,37 @@ def _make_prefab(
|
|
|
698
519
|
return cls
|
|
699
520
|
|
|
700
521
|
|
|
522
|
+
class Prefab(metaclass=SlotMakerMeta):
|
|
523
|
+
_meta_gatherer = prefab_gatherer
|
|
524
|
+
__slots__ = {}
|
|
525
|
+
|
|
526
|
+
# noinspection PyShadowingBuiltins
|
|
527
|
+
def __init_subclass__(
|
|
528
|
+
cls,
|
|
529
|
+
init=True,
|
|
530
|
+
repr=True,
|
|
531
|
+
eq=True,
|
|
532
|
+
iter=False,
|
|
533
|
+
match_args=True,
|
|
534
|
+
kw_only=False,
|
|
535
|
+
frozen=False,
|
|
536
|
+
dict_method=False,
|
|
537
|
+
recursive_repr=False,
|
|
538
|
+
):
|
|
539
|
+
_make_prefab(
|
|
540
|
+
cls,
|
|
541
|
+
init=init,
|
|
542
|
+
repr=repr,
|
|
543
|
+
eq=eq,
|
|
544
|
+
iter=iter,
|
|
545
|
+
match_args=match_args,
|
|
546
|
+
kw_only=kw_only,
|
|
547
|
+
frozen=frozen,
|
|
548
|
+
dict_method=dict_method,
|
|
549
|
+
recursive_repr=recursive_repr,
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
|
|
701
553
|
# noinspection PyShadowingBuiltins
|
|
702
554
|
def prefab(
|
|
703
555
|
cls=None,
|
|
@@ -895,5 +747,5 @@ def as_dict(o):
|
|
|
895
747
|
return {
|
|
896
748
|
name: getattr(o, name)
|
|
897
749
|
for name, attrib in flds.items()
|
|
898
|
-
if attrib.serialize
|
|
750
|
+
if attrib.serialize
|
|
899
751
|
}
|