ducktools-classbuilder 0.5.0__py3-none-any.whl → 0.6.0__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 +493 -179
- ducktools/classbuilder/__init__.pyi +143 -39
- ducktools/classbuilder/annotations.py +173 -0
- ducktools/classbuilder/annotations.pyi +26 -0
- ducktools/classbuilder/prefab.py +120 -230
- ducktools/classbuilder/prefab.pyi +28 -22
- ducktools_classbuilder-0.6.0.dist-info/METADATA +318 -0
- ducktools_classbuilder-0.6.0.dist-info/RECORD +12 -0
- ducktools_classbuilder-0.5.0.dist-info/METADATA +0 -270
- ducktools_classbuilder-0.5.0.dist-info/RECORD +0 -10
- {ducktools_classbuilder-0.5.0.dist-info → ducktools_classbuilder-0.6.0.dist-info}/LICENSE.md +0 -0
- {ducktools_classbuilder-0.5.0.dist-info → ducktools_classbuilder-0.6.0.dist-info}/WHEEL +0 -0
- {ducktools_classbuilder-0.5.0.dist-info → ducktools_classbuilder-0.6.0.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
|
|
|
@@ -69,7 +61,7 @@ def get_attributes(cls):
|
|
|
69
61
|
|
|
70
62
|
# Method Generators
|
|
71
63
|
def get_init_maker(*, init_name="__init__"):
|
|
72
|
-
def __init__(cls:
|
|
64
|
+
def __init__(cls: type) -> GeneratedCode:
|
|
73
65
|
globs = {}
|
|
74
66
|
# Get the internals dictionary and prepare attributes
|
|
75
67
|
attributes = get_attributes(cls)
|
|
@@ -218,100 +210,18 @@ def get_init_maker(*, init_name="__init__"):
|
|
|
218
210
|
f"{body}\n"
|
|
219
211
|
f"{post_init_call}\n"
|
|
220
212
|
)
|
|
221
|
-
return code, globs
|
|
213
|
+
return GeneratedCode(code, globs)
|
|
222
214
|
|
|
223
215
|
return MethodMaker(init_name, __init__)
|
|
224
216
|
|
|
225
217
|
|
|
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"
|
|
252
|
-
else:
|
|
253
|
-
recursion_func = ""
|
|
254
|
-
|
|
255
|
-
if will_eval:
|
|
256
|
-
code = (
|
|
257
|
-
f"{recursion_func}"
|
|
258
|
-
f"def __repr__(self):\n"
|
|
259
|
-
f" return f'{{type(self).__qualname__}}({content})'\n"
|
|
260
|
-
)
|
|
261
|
-
else:
|
|
262
|
-
if content:
|
|
263
|
-
code = (
|
|
264
|
-
f"{recursion_func}"
|
|
265
|
-
f"def __repr__(self):\n"
|
|
266
|
-
f" return f'<prefab {{type(self).__qualname__}}; {content}>'\n"
|
|
267
|
-
)
|
|
268
|
-
else:
|
|
269
|
-
code = (
|
|
270
|
-
f"{recursion_func}"
|
|
271
|
-
f"def __repr__(self):\n"
|
|
272
|
-
f" return f'<prefab {{type(self).__qualname__}}>'\n"
|
|
273
|
-
)
|
|
274
|
-
|
|
275
|
-
return code, globs
|
|
276
|
-
|
|
277
|
-
return MethodMaker("__repr__", __repr__)
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
def get_eq_maker():
|
|
281
|
-
def __eq__(cls: "type") -> "tuple[str, dict]":
|
|
282
|
-
class_comparison = "self.__class__ is other.__class__"
|
|
283
|
-
attribs = get_attributes(cls)
|
|
284
|
-
field_names = [
|
|
285
|
-
name
|
|
286
|
-
for name, attrib in attribs.items()
|
|
287
|
-
if attrib.compare and not attrib.exclude_field
|
|
288
|
-
]
|
|
289
|
-
|
|
290
|
-
if field_names:
|
|
291
|
-
selfvals = ",".join(f"self.{name}" for name in field_names)
|
|
292
|
-
othervals = ",".join(f"other.{name}" for name in field_names)
|
|
293
|
-
instance_comparison = f"({selfvals},) == ({othervals},)"
|
|
294
|
-
else:
|
|
295
|
-
instance_comparison = "True"
|
|
296
|
-
|
|
297
|
-
code = (
|
|
298
|
-
f"def __eq__(self, other):\n"
|
|
299
|
-
f" return {instance_comparison} if {class_comparison} else NotImplemented\n"
|
|
300
|
-
)
|
|
301
|
-
globs = {}
|
|
302
|
-
|
|
303
|
-
return code, globs
|
|
304
|
-
|
|
305
|
-
return MethodMaker("__eq__", __eq__)
|
|
306
|
-
|
|
307
|
-
|
|
308
218
|
def get_iter_maker():
|
|
309
|
-
def __iter__(cls:
|
|
219
|
+
def __iter__(cls: type) -> GeneratedCode:
|
|
310
220
|
fields = get_attributes(cls)
|
|
311
221
|
|
|
312
222
|
valid_fields = (
|
|
313
223
|
name for name, attrib in fields.items()
|
|
314
|
-
if attrib.iter
|
|
224
|
+
if attrib.iter
|
|
315
225
|
)
|
|
316
226
|
|
|
317
227
|
values = "\n".join(f" yield self.{name}" for name in valid_fields)
|
|
@@ -322,56 +232,61 @@ def get_iter_maker():
|
|
|
322
232
|
|
|
323
233
|
code = f"def __iter__(self):\n{values}"
|
|
324
234
|
globs = {}
|
|
325
|
-
return code, globs
|
|
235
|
+
return GeneratedCode(code, globs)
|
|
326
236
|
|
|
327
237
|
return MethodMaker("__iter__", __iter__)
|
|
328
238
|
|
|
329
239
|
|
|
330
240
|
def get_asdict_maker():
|
|
331
|
-
def as_dict_gen(cls:
|
|
241
|
+
def as_dict_gen(cls: type) -> GeneratedCode:
|
|
332
242
|
fields = get_attributes(cls)
|
|
333
243
|
|
|
334
244
|
vals = ", ".join(
|
|
335
245
|
f"'{name}': self.{name}"
|
|
336
246
|
for name, attrib in fields.items()
|
|
337
|
-
if attrib.serialize
|
|
247
|
+
if attrib.serialize
|
|
338
248
|
)
|
|
339
249
|
out_dict = f"{{{vals}}}"
|
|
340
250
|
code = f"def as_dict(self): return {out_dict}"
|
|
341
251
|
|
|
342
252
|
globs = {}
|
|
343
|
-
return code, globs
|
|
253
|
+
return GeneratedCode(code, globs)
|
|
344
254
|
return MethodMaker("as_dict", as_dict_gen)
|
|
345
255
|
|
|
346
256
|
|
|
347
257
|
init_maker = get_init_maker()
|
|
348
258
|
prefab_init_maker = get_init_maker(init_name=PREFAB_INIT_FUNC)
|
|
349
|
-
repr_maker =
|
|
350
|
-
|
|
351
|
-
|
|
259
|
+
repr_maker = MethodMaker(
|
|
260
|
+
"__repr__",
|
|
261
|
+
get_repr_generator(recursion_safe=False, eval_safe=True)
|
|
262
|
+
)
|
|
263
|
+
recursive_repr_maker = MethodMaker(
|
|
264
|
+
"__repr__",
|
|
265
|
+
get_repr_generator(recursion_safe=True, eval_safe=True)
|
|
266
|
+
)
|
|
352
267
|
iter_maker = get_iter_maker()
|
|
353
268
|
asdict_maker = get_asdict_maker()
|
|
354
269
|
|
|
355
270
|
|
|
356
271
|
# Updated field with additional attributes
|
|
357
|
-
@fieldclass
|
|
358
272
|
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
|
-
)
|
|
273
|
+
"""
|
|
274
|
+
Get an object to define a prefab attribute
|
|
368
275
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
276
|
+
:param default: Default value for this attribute
|
|
277
|
+
:param default_factory: 0 argument callable to give a default value
|
|
278
|
+
(for otherwise mutable defaults, eg: list)
|
|
279
|
+
:param init: Include this attribute in the __init__ parameters
|
|
280
|
+
:param repr: Include this attribute in the class __repr__
|
|
281
|
+
:param compare: Include this attribute in the class __eq__
|
|
282
|
+
:param iter: Include this attribute in the class __iter__ if generated
|
|
283
|
+
:param kw_only: Make this argument keyword only in init
|
|
284
|
+
:param serialize: Include this attribute in methods that serialize to dict
|
|
285
|
+
:param doc: Parameter documentation for slotted classes
|
|
286
|
+
:param type: Type of this attribute (for slotted classes)
|
|
287
|
+
"""
|
|
288
|
+
iter: bool = True
|
|
289
|
+
serialize: bool = True
|
|
375
290
|
|
|
376
291
|
|
|
377
292
|
# noinspection PyShadowingBuiltins
|
|
@@ -390,7 +305,7 @@ def attribute(
|
|
|
390
305
|
type=NOTHING,
|
|
391
306
|
):
|
|
392
307
|
"""
|
|
393
|
-
|
|
308
|
+
Helper function to get an object to define a prefab Attribute
|
|
394
309
|
|
|
395
310
|
:param default: Default value for this attribute
|
|
396
311
|
:param default_factory: 0 argument callable to give a default value
|
|
@@ -401,15 +316,18 @@ def attribute(
|
|
|
401
316
|
:param iter: Include this attribute in the class __iter__ if generated
|
|
402
317
|
:param kw_only: Make this argument keyword only in init
|
|
403
318
|
: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__
|
|
319
|
+
:param exclude_field: Shorthand for setting repr, compare, iter and serialize to False
|
|
408
320
|
:param doc: Parameter documentation for slotted classes
|
|
409
321
|
:param type: Type of this attribute (for slotted classes)
|
|
410
322
|
|
|
411
323
|
:return: Attribute generated with these parameters.
|
|
412
324
|
"""
|
|
325
|
+
if exclude_field:
|
|
326
|
+
repr = False
|
|
327
|
+
compare = False
|
|
328
|
+
iter = False
|
|
329
|
+
serialize = False
|
|
330
|
+
|
|
413
331
|
return Attribute(
|
|
414
332
|
default=default,
|
|
415
333
|
default_factory=default_factory,
|
|
@@ -419,90 +337,12 @@ def attribute(
|
|
|
419
337
|
iter=iter,
|
|
420
338
|
kw_only=kw_only,
|
|
421
339
|
serialize=serialize,
|
|
422
|
-
exclude_field=exclude_field,
|
|
423
340
|
doc=doc,
|
|
424
341
|
type=type,
|
|
425
342
|
)
|
|
426
343
|
|
|
427
344
|
|
|
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
|
|
345
|
+
prefab_gatherer = make_unified_gatherer(Attribute, False)
|
|
506
346
|
|
|
507
347
|
|
|
508
348
|
# Class Builders
|
|
@@ -519,6 +359,7 @@ def _make_prefab(
|
|
|
519
359
|
frozen=False,
|
|
520
360
|
dict_method=False,
|
|
521
361
|
recursive_repr=False,
|
|
362
|
+
gathered_fields=None,
|
|
522
363
|
):
|
|
523
364
|
"""
|
|
524
365
|
Generate boilerplate code for dunder methods in a class.
|
|
@@ -535,6 +376,7 @@ def _make_prefab(
|
|
|
535
376
|
such as lists)
|
|
536
377
|
:param dict_method: Include an as_dict method for faster dictionary creation
|
|
537
378
|
:param recursive_repr: Safely handle repr in case of recursion
|
|
379
|
+
:param gathered_fields: Pre-gathered fields callable, to skip re-collecting attributes
|
|
538
380
|
:return: class with __ methods defined
|
|
539
381
|
"""
|
|
540
382
|
cls_dict = cls.__dict__
|
|
@@ -546,12 +388,13 @@ def _make_prefab(
|
|
|
546
388
|
)
|
|
547
389
|
|
|
548
390
|
slots = cls_dict.get("__slots__")
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
391
|
+
|
|
392
|
+
slotted = False if slots is None else True
|
|
393
|
+
|
|
394
|
+
if gathered_fields is None:
|
|
395
|
+
gatherer = prefab_gatherer
|
|
552
396
|
else:
|
|
553
|
-
gatherer =
|
|
554
|
-
slotted = False
|
|
397
|
+
gatherer = gathered_fields
|
|
555
398
|
|
|
556
399
|
methods = set()
|
|
557
400
|
|
|
@@ -651,9 +494,9 @@ def _make_prefab(
|
|
|
651
494
|
post_init_args.extend(arglist)
|
|
652
495
|
|
|
653
496
|
# Gather values for match_args and do some syntax checking
|
|
654
|
-
|
|
655
497
|
default_defined = []
|
|
656
|
-
valid_args =
|
|
498
|
+
valid_args = list(fields.keys())
|
|
499
|
+
|
|
657
500
|
for name, attrib in fields.items():
|
|
658
501
|
# slot_gather and parent classes may use Fields
|
|
659
502
|
# prefabs require Attributes, so convert.
|
|
@@ -661,15 +504,6 @@ def _make_prefab(
|
|
|
661
504
|
attrib = Attribute.from_field(attrib)
|
|
662
505
|
fields[name] = attrib
|
|
663
506
|
|
|
664
|
-
# Excluded fields *MUST* be forwarded to post_init
|
|
665
|
-
if attrib.exclude_field:
|
|
666
|
-
if name not in post_init_args:
|
|
667
|
-
raise PrefabError(
|
|
668
|
-
f"{name!r} is an excluded attribute but is not passed to post_init"
|
|
669
|
-
)
|
|
670
|
-
else:
|
|
671
|
-
valid_args.append(name)
|
|
672
|
-
|
|
673
507
|
if not kw_only:
|
|
674
508
|
# Syntax check arguments for __init__ don't have non-default after default
|
|
675
509
|
if attrib.init and not attrib.kw_only:
|
|
@@ -692,6 +526,37 @@ def _make_prefab(
|
|
|
692
526
|
return cls
|
|
693
527
|
|
|
694
528
|
|
|
529
|
+
class Prefab(metaclass=SlotMakerMeta):
|
|
530
|
+
_meta_gatherer = prefab_gatherer
|
|
531
|
+
__slots__ = {}
|
|
532
|
+
|
|
533
|
+
# noinspection PyShadowingBuiltins
|
|
534
|
+
def __init_subclass__(
|
|
535
|
+
cls,
|
|
536
|
+
init=True,
|
|
537
|
+
repr=True,
|
|
538
|
+
eq=True,
|
|
539
|
+
iter=False,
|
|
540
|
+
match_args=True,
|
|
541
|
+
kw_only=False,
|
|
542
|
+
frozen=False,
|
|
543
|
+
dict_method=False,
|
|
544
|
+
recursive_repr=False,
|
|
545
|
+
):
|
|
546
|
+
_make_prefab(
|
|
547
|
+
cls,
|
|
548
|
+
init=init,
|
|
549
|
+
repr=repr,
|
|
550
|
+
eq=eq,
|
|
551
|
+
iter=iter,
|
|
552
|
+
match_args=match_args,
|
|
553
|
+
kw_only=kw_only,
|
|
554
|
+
frozen=frozen,
|
|
555
|
+
dict_method=dict_method,
|
|
556
|
+
recursive_repr=recursive_repr,
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
|
|
695
560
|
# noinspection PyShadowingBuiltins
|
|
696
561
|
def prefab(
|
|
697
562
|
cls=None,
|
|
@@ -770,6 +635,7 @@ def build_prefab(
|
|
|
770
635
|
frozen=False,
|
|
771
636
|
dict_method=False,
|
|
772
637
|
recursive_repr=False,
|
|
638
|
+
slots=False,
|
|
773
639
|
):
|
|
774
640
|
"""
|
|
775
641
|
Dynamically construct a (dynamic) prefab.
|
|
@@ -790,12 +656,35 @@ def build_prefab(
|
|
|
790
656
|
(This does not prevent the modification of mutable attributes such as lists)
|
|
791
657
|
:param dict_method: Include an as_dict method for faster dictionary creation
|
|
792
658
|
:param recursive_repr: Safely handle repr in case of recursion
|
|
659
|
+
:param slots: Make the resulting class slotted
|
|
793
660
|
:return: class with __ methods defined
|
|
794
661
|
"""
|
|
795
|
-
class_dict = {} if class_dict is None else class_dict
|
|
796
|
-
|
|
662
|
+
class_dict = {} if class_dict is None else class_dict.copy()
|
|
663
|
+
|
|
664
|
+
class_annotations = {}
|
|
665
|
+
class_slots = {}
|
|
666
|
+
fields = {}
|
|
667
|
+
|
|
797
668
|
for name, attrib in attributes:
|
|
798
|
-
|
|
669
|
+
if isinstance(attrib, Attribute):
|
|
670
|
+
fields[name] = attrib
|
|
671
|
+
elif isinstance(attrib, Field):
|
|
672
|
+
fields[name] = Attribute.from_field(attrib)
|
|
673
|
+
else:
|
|
674
|
+
fields[name] = Attribute(default=attrib)
|
|
675
|
+
|
|
676
|
+
if attrib.type is not NOTHING:
|
|
677
|
+
class_annotations[name] = attrib.type
|
|
678
|
+
|
|
679
|
+
class_slots[name] = attrib.doc
|
|
680
|
+
|
|
681
|
+
if slots:
|
|
682
|
+
class_dict["__slots__"] = class_slots
|
|
683
|
+
|
|
684
|
+
class_dict["__annotations__"] = class_annotations
|
|
685
|
+
cls = type(class_name, bases, class_dict)
|
|
686
|
+
|
|
687
|
+
gathered_fields = GatheredFields(fields, {})
|
|
799
688
|
|
|
800
689
|
cls = _make_prefab(
|
|
801
690
|
cls,
|
|
@@ -808,6 +697,7 @@ def build_prefab(
|
|
|
808
697
|
frozen=frozen,
|
|
809
698
|
dict_method=dict_method,
|
|
810
699
|
recursive_repr=recursive_repr,
|
|
700
|
+
gathered_fields=gathered_fields,
|
|
811
701
|
)
|
|
812
702
|
|
|
813
703
|
return cls
|
|
@@ -864,5 +754,5 @@ def as_dict(o):
|
|
|
864
754
|
return {
|
|
865
755
|
name: getattr(o, name)
|
|
866
756
|
for name, attrib in flds.items()
|
|
867
|
-
if attrib.serialize
|
|
757
|
+
if attrib.serialize
|
|
868
758
|
}
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import typing
|
|
2
|
+
from types import MappingProxyType
|
|
2
3
|
from typing_extensions import dataclass_transform
|
|
3
4
|
|
|
4
5
|
from collections.abc import Callable
|
|
5
6
|
|
|
6
7
|
from . import (
|
|
7
|
-
|
|
8
|
-
Field,
|
|
9
|
-
|
|
8
|
+
NOTHING,
|
|
9
|
+
Field,
|
|
10
|
+
MethodMaker,
|
|
11
|
+
SlotMakerMeta,
|
|
10
12
|
)
|
|
11
13
|
|
|
14
|
+
from . import SlotFields as SlotFields, KW_ONLY as KW_ONLY
|
|
15
|
+
|
|
12
16
|
# noinspection PyUnresolvedReferences
|
|
13
17
|
from . import _NothingType
|
|
14
18
|
|
|
@@ -17,12 +21,7 @@ PREFAB_INIT_FUNC: str
|
|
|
17
21
|
PRE_INIT_FUNC: str
|
|
18
22
|
POST_INIT_FUNC: str
|
|
19
23
|
|
|
20
|
-
|
|
21
|
-
# noinspection PyPep8Naming
|
|
22
|
-
class _KW_ONLY_TYPE:
|
|
23
|
-
def __repr__(self) -> str: ...
|
|
24
|
-
|
|
25
|
-
KW_ONLY: _KW_ONLY_TYPE
|
|
24
|
+
_CopiableMappings = dict[str, typing.Any] | MappingProxyType[str, typing.Any]
|
|
26
25
|
|
|
27
26
|
class PrefabError(Exception): ...
|
|
28
27
|
|
|
@@ -30,10 +29,6 @@ def get_attributes(cls: type) -> dict[str, Attribute]: ...
|
|
|
30
29
|
|
|
31
30
|
def get_init_maker(*, init_name: str="__init__") -> MethodMaker: ...
|
|
32
31
|
|
|
33
|
-
def get_repr_maker(*, recursion_safe: bool = False) -> MethodMaker: ...
|
|
34
|
-
|
|
35
|
-
def get_eq_maker() -> MethodMaker: ...
|
|
36
|
-
|
|
37
32
|
def get_iter_maker() -> MethodMaker: ...
|
|
38
33
|
|
|
39
34
|
def get_asdict_maker() -> MethodMaker: ...
|
|
@@ -50,13 +45,8 @@ asdict_maker: MethodMaker
|
|
|
50
45
|
class Attribute(Field):
|
|
51
46
|
__slots__: dict
|
|
52
47
|
|
|
53
|
-
init: bool
|
|
54
|
-
repr: bool
|
|
55
|
-
compare: bool
|
|
56
48
|
iter: bool
|
|
57
|
-
kw_only: bool
|
|
58
49
|
serialize: bool
|
|
59
|
-
exclude_field: bool
|
|
60
50
|
|
|
61
51
|
def __init__(
|
|
62
52
|
self,
|
|
@@ -71,7 +61,6 @@ class Attribute(Field):
|
|
|
71
61
|
iter: bool = True,
|
|
72
62
|
kw_only: bool = False,
|
|
73
63
|
serialize: bool = True,
|
|
74
|
-
exclude_field: bool = False,
|
|
75
64
|
) -> None: ...
|
|
76
65
|
|
|
77
66
|
def __repr__(self) -> str: ...
|
|
@@ -93,9 +82,7 @@ def attribute(
|
|
|
93
82
|
exclude_field: bool = False,
|
|
94
83
|
) -> Attribute: ...
|
|
95
84
|
|
|
96
|
-
def
|
|
97
|
-
|
|
98
|
-
def attribute_gatherer(cls: type) -> tuple[dict[str, Attribute], dict[str, typing.Any]]: ...
|
|
85
|
+
def prefab_gatherer(cls_or_ns: type | MappingProxyType) -> tuple[dict[str, Attribute], dict[str, typing.Any]]: ...
|
|
99
86
|
|
|
100
87
|
def _make_prefab(
|
|
101
88
|
cls: type,
|
|
@@ -109,10 +96,28 @@ def _make_prefab(
|
|
|
109
96
|
frozen: bool = False,
|
|
110
97
|
dict_method: bool = False,
|
|
111
98
|
recursive_repr: bool = False,
|
|
99
|
+
gathered_fields: Callable[[type], tuple[dict[str, Attribute], dict[str, typing.Any]]] | None = None,
|
|
112
100
|
) -> type: ...
|
|
113
101
|
|
|
114
102
|
_T = typing.TypeVar("_T")
|
|
115
103
|
|
|
104
|
+
# noinspection PyUnresolvedReferences
|
|
105
|
+
@dataclass_transform(field_specifiers=(Attribute, attribute))
|
|
106
|
+
class Prefab(metaclass=SlotMakerMeta):
|
|
107
|
+
_meta_gatherer: Callable[[type | _CopiableMappings], tuple[dict[str, Field], dict[str, typing.Any]]]
|
|
108
|
+
def __init_subclass__(
|
|
109
|
+
cls,
|
|
110
|
+
init: bool = True,
|
|
111
|
+
repr: bool = True,
|
|
112
|
+
eq: bool = True,
|
|
113
|
+
iter: bool = False,
|
|
114
|
+
match_args: bool = True,
|
|
115
|
+
kw_only: bool = False,
|
|
116
|
+
frozen: bool = False,
|
|
117
|
+
dict_method: bool = False,
|
|
118
|
+
recursive_repr: bool = False,
|
|
119
|
+
) -> None: ...
|
|
120
|
+
|
|
116
121
|
|
|
117
122
|
# For some reason PyCharm can't see 'attribute'?!?
|
|
118
123
|
# noinspection PyUnresolvedReferences
|
|
@@ -146,6 +151,7 @@ def build_prefab(
|
|
|
146
151
|
frozen: bool = False,
|
|
147
152
|
dict_method: bool = False,
|
|
148
153
|
recursive_repr: bool = False,
|
|
154
|
+
slots: bool = False,
|
|
149
155
|
) -> type: ...
|
|
150
156
|
|
|
151
157
|
def is_prefab(o: typing.Any) -> bool: ...
|