ducktools-classbuilder 0.4.0__py3-none-any.whl → 0.5.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 +89 -34
- ducktools/classbuilder/__init__.pyi +38 -17
- ducktools/classbuilder/prefab.py +67 -34
- ducktools/classbuilder/prefab.pyi +12 -10
- {ducktools_classbuilder-0.4.0.dist-info → ducktools_classbuilder-0.5.1.dist-info}/METADATA +16 -15
- ducktools_classbuilder-0.5.1.dist-info/RECORD +10 -0
- ducktools_classbuilder-0.4.0.dist-info/RECORD +0 -10
- {ducktools_classbuilder-0.4.0.dist-info → ducktools_classbuilder-0.5.1.dist-info}/LICENSE.md +0 -0
- {ducktools_classbuilder-0.4.0.dist-info → ducktools_classbuilder-0.5.1.dist-info}/WHEEL +0 -0
- {ducktools_classbuilder-0.4.0.dist-info → ducktools_classbuilder-0.5.1.dist-info}/top_level.txt +0 -0
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
# SOFTWARE.
|
|
22
22
|
import sys
|
|
23
23
|
|
|
24
|
-
__version__ = "v0.
|
|
24
|
+
__version__ = "v0.5.1"
|
|
25
25
|
|
|
26
26
|
# Change this name if you make heavy modifications
|
|
27
27
|
INTERNALS_DICT = "__classbuilder_internals__"
|
|
@@ -65,7 +65,7 @@ def _get_inst_fields(inst):
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
|
|
68
|
-
# As 'None' can be a meaningful
|
|
68
|
+
# As 'None' can be a meaningful value we need a sentinel value
|
|
69
69
|
# to use to show no value has been provided.
|
|
70
70
|
class _NothingType:
|
|
71
71
|
def __repr__(self):
|
|
@@ -109,7 +109,7 @@ class MethodMaker:
|
|
|
109
109
|
return method.__get__(instance, cls)
|
|
110
110
|
|
|
111
111
|
|
|
112
|
-
def
|
|
112
|
+
def get_init_generator(null=NOTHING, extra_code=None):
|
|
113
113
|
def cls_init_maker(cls):
|
|
114
114
|
fields = get_fields(cls)
|
|
115
115
|
flags = get_flags(cls)
|
|
@@ -154,10 +154,10 @@ def get_init_maker(null=NOTHING, extra_code=None):
|
|
|
154
154
|
return cls_init_maker
|
|
155
155
|
|
|
156
156
|
|
|
157
|
-
|
|
157
|
+
init_generator = get_init_generator()
|
|
158
158
|
|
|
159
159
|
|
|
160
|
-
def
|
|
160
|
+
def repr_generator(cls):
|
|
161
161
|
fields = get_fields(cls)
|
|
162
162
|
content = ", ".join(
|
|
163
163
|
f"{name}={{self.{name}!r}}"
|
|
@@ -171,7 +171,7 @@ def repr_maker(cls):
|
|
|
171
171
|
return code, globs
|
|
172
172
|
|
|
173
173
|
|
|
174
|
-
def
|
|
174
|
+
def eq_generator(cls):
|
|
175
175
|
class_comparison = "self.__class__ is other.__class__"
|
|
176
176
|
field_names = get_fields(cls)
|
|
177
177
|
|
|
@@ -191,7 +191,7 @@ def eq_maker(cls):
|
|
|
191
191
|
return code, globs
|
|
192
192
|
|
|
193
193
|
|
|
194
|
-
def
|
|
194
|
+
def frozen_setattr_generator(cls):
|
|
195
195
|
globs = {}
|
|
196
196
|
field_names = set(get_fields(cls))
|
|
197
197
|
flags = get_flags(cls)
|
|
@@ -220,7 +220,7 @@ def frozen_setattr_maker(cls):
|
|
|
220
220
|
return code, globs
|
|
221
221
|
|
|
222
222
|
|
|
223
|
-
def
|
|
223
|
+
def frozen_delattr_generator(cls):
|
|
224
224
|
body = (
|
|
225
225
|
' raise TypeError(\n'
|
|
226
226
|
' f"{type(self).__name__!r} object "\n'
|
|
@@ -234,12 +234,12 @@ def frozen_delattr_maker(cls):
|
|
|
234
234
|
|
|
235
235
|
# As only the __get__ method refers to the class we can use the same
|
|
236
236
|
# Descriptor instances for every class.
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
default_methods = frozenset({
|
|
237
|
+
init_maker = MethodMaker("__init__", init_generator)
|
|
238
|
+
repr_maker = MethodMaker("__repr__", repr_generator)
|
|
239
|
+
eq_maker = MethodMaker("__eq__", eq_generator)
|
|
240
|
+
frozen_setattr_maker = MethodMaker("__setattr__", frozen_setattr_generator)
|
|
241
|
+
frozen_delattr_maker = MethodMaker("__delattr__", frozen_delattr_generator)
|
|
242
|
+
default_methods = frozenset({init_maker, repr_maker, eq_maker})
|
|
243
243
|
|
|
244
244
|
|
|
245
245
|
def builder(cls=None, /, *, gatherer, methods, flags=None):
|
|
@@ -248,7 +248,7 @@ def builder(cls=None, /, *, gatherer, methods, flags=None):
|
|
|
248
248
|
|
|
249
249
|
:param cls: Class to be analysed and have methods generated
|
|
250
250
|
:param gatherer: Function to gather field information
|
|
251
|
-
:type gatherer: Callable[[type], dict[str, Field]]
|
|
251
|
+
:type gatherer: Callable[[type], tuple[dict[str, Field], dict[str, Any]]]
|
|
252
252
|
:param methods: MethodMakers to add to the class
|
|
253
253
|
:type methods: set[MethodMaker]
|
|
254
254
|
:param flags: additional flags to store in the internals dictionary
|
|
@@ -267,7 +267,14 @@ def builder(cls=None, /, *, gatherer, methods, flags=None):
|
|
|
267
267
|
internals = {}
|
|
268
268
|
setattr(cls, INTERNALS_DICT, internals)
|
|
269
269
|
|
|
270
|
-
cls_fields = gatherer(cls)
|
|
270
|
+
cls_fields, modifications = gatherer(cls)
|
|
271
|
+
|
|
272
|
+
for name, value in modifications.items():
|
|
273
|
+
if value is NOTHING:
|
|
274
|
+
delattr(cls, name)
|
|
275
|
+
else:
|
|
276
|
+
setattr(cls, name, value)
|
|
277
|
+
|
|
271
278
|
internals["local_fields"] = cls_fields
|
|
272
279
|
|
|
273
280
|
mro = cls.__mro__[:-1] # skip 'object' base class
|
|
@@ -352,8 +359,19 @@ class Field:
|
|
|
352
359
|
return cls(**argument_dict)
|
|
353
360
|
|
|
354
361
|
|
|
362
|
+
class GatheredFields:
|
|
363
|
+
__slots__ = ("fields", "modifications")
|
|
364
|
+
|
|
365
|
+
def __init__(self, fields, modifications):
|
|
366
|
+
self.fields = fields
|
|
367
|
+
self.modifications = modifications
|
|
368
|
+
|
|
369
|
+
def __call__(self, cls):
|
|
370
|
+
return self.fields, self.modifications
|
|
371
|
+
|
|
372
|
+
|
|
355
373
|
# Use the builder to generate __repr__ and __eq__ methods
|
|
356
|
-
#
|
|
374
|
+
# for both Field and GatheredFields
|
|
357
375
|
_field_internal = {
|
|
358
376
|
"default": Field(default=NOTHING),
|
|
359
377
|
"default_factory": Field(default=NOTHING),
|
|
@@ -361,17 +379,29 @@ _field_internal = {
|
|
|
361
379
|
"doc": Field(default=None),
|
|
362
380
|
}
|
|
363
381
|
|
|
364
|
-
|
|
382
|
+
_gathered_field_internal = {
|
|
383
|
+
"fields": Field(default=NOTHING),
|
|
384
|
+
"modifications": Field(default=NOTHING),
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
_field_methods = {repr_maker, eq_maker}
|
|
365
388
|
if _UNDER_TESTING:
|
|
366
|
-
_field_methods.update({
|
|
389
|
+
_field_methods.update({frozen_setattr_maker, frozen_delattr_maker})
|
|
367
390
|
|
|
368
391
|
builder(
|
|
369
392
|
Field,
|
|
370
|
-
gatherer=
|
|
393
|
+
gatherer=GatheredFields(_field_internal, {}),
|
|
371
394
|
methods=_field_methods,
|
|
372
395
|
flags={"slotted": True, "kw_only": True},
|
|
373
396
|
)
|
|
374
397
|
|
|
398
|
+
builder(
|
|
399
|
+
GatheredFields,
|
|
400
|
+
gatherer=GatheredFields(_gathered_field_internal, {}),
|
|
401
|
+
methods={repr_maker, eq_maker},
|
|
402
|
+
flags={"slotted": True, "kw_only": False},
|
|
403
|
+
)
|
|
404
|
+
|
|
375
405
|
|
|
376
406
|
# Slot gathering tools
|
|
377
407
|
# Subclass of dict to be identifiable by isinstance checks
|
|
@@ -390,6 +420,14 @@ class SlotFields(dict):
|
|
|
390
420
|
|
|
391
421
|
|
|
392
422
|
def make_slot_gatherer(field_type=Field):
|
|
423
|
+
"""
|
|
424
|
+
Create a new annotation gatherer that will work with `Field` instances
|
|
425
|
+
of the creators definition.
|
|
426
|
+
|
|
427
|
+
:param field_type: The `Field` classes to be used when gathering fields
|
|
428
|
+
:return: A slot gatherer that will check for and generate Fields of
|
|
429
|
+
the type field_type.
|
|
430
|
+
"""
|
|
393
431
|
def field_slot_gatherer(cls):
|
|
394
432
|
"""
|
|
395
433
|
Gather field information for class generation based on __slots__
|
|
@@ -405,11 +443,23 @@ def make_slot_gatherer(field_type=Field):
|
|
|
405
443
|
"in order to generate a slotclass"
|
|
406
444
|
)
|
|
407
445
|
|
|
408
|
-
|
|
446
|
+
# Don't want to mutate original annotations so make a copy if it exists
|
|
447
|
+
# Looking at the dict is a Python3.9 or earlier requirement
|
|
448
|
+
cls_annotations = {
|
|
449
|
+
**cls.__dict__.get("__annotations__", {})
|
|
450
|
+
}
|
|
451
|
+
|
|
409
452
|
cls_fields = {}
|
|
410
453
|
slot_replacement = {}
|
|
411
454
|
|
|
412
455
|
for k, v in cls_slots.items():
|
|
456
|
+
# Special case __dict__ and __weakref__
|
|
457
|
+
# They should be included in the final `__slots__`
|
|
458
|
+
# But ignored as a value.
|
|
459
|
+
if k in {"__dict__", "__weakref__"}:
|
|
460
|
+
slot_replacement[k] = None
|
|
461
|
+
continue
|
|
462
|
+
|
|
413
463
|
if isinstance(v, field_type):
|
|
414
464
|
attrib = v
|
|
415
465
|
if attrib.type is not NOTHING:
|
|
@@ -421,13 +471,15 @@ def make_slot_gatherer(field_type=Field):
|
|
|
421
471
|
slot_replacement[k] = attrib.doc
|
|
422
472
|
cls_fields[k] = attrib
|
|
423
473
|
|
|
424
|
-
#
|
|
425
|
-
#
|
|
426
|
-
|
|
474
|
+
# Send the modifications to the builder for what should be changed
|
|
475
|
+
# On the class.
|
|
476
|
+
# In this case, slots with documentation and new annotations.
|
|
477
|
+
modifications = {
|
|
478
|
+
"__slots__": slot_replacement,
|
|
479
|
+
"__annotations__": cls_annotations,
|
|
480
|
+
}
|
|
427
481
|
|
|
428
|
-
|
|
429
|
-
setattr(cls, "__annotations__", cls_annotations)
|
|
430
|
-
return cls_fields
|
|
482
|
+
return cls_fields, modifications
|
|
431
483
|
|
|
432
484
|
return field_slot_gatherer
|
|
433
485
|
|
|
@@ -483,6 +535,8 @@ def make_annotation_gatherer(field_type=Field, leave_default_values=True):
|
|
|
483
535
|
|
|
484
536
|
cls_fields: dict[str, field_type] = {}
|
|
485
537
|
|
|
538
|
+
modifications = {}
|
|
539
|
+
|
|
486
540
|
for k, v in cls_annotations.items():
|
|
487
541
|
# Ignore ClassVar
|
|
488
542
|
if is_classvar(v):
|
|
@@ -494,20 +548,21 @@ def make_annotation_gatherer(field_type=Field, leave_default_values=True):
|
|
|
494
548
|
if isinstance(attrib, field_type):
|
|
495
549
|
attrib = field_type.from_field(attrib, type=v)
|
|
496
550
|
if attrib.default is not NOTHING and leave_default_values:
|
|
497
|
-
|
|
551
|
+
modifications[k] = attrib.default
|
|
498
552
|
else:
|
|
499
|
-
|
|
553
|
+
# NOTHING sentinel indicates a value should be removed
|
|
554
|
+
modifications[k] = NOTHING
|
|
500
555
|
else:
|
|
501
556
|
attrib = field_type(default=attrib, type=v)
|
|
502
557
|
if not leave_default_values:
|
|
503
|
-
|
|
558
|
+
modifications[k] = NOTHING
|
|
504
559
|
|
|
505
560
|
else:
|
|
506
561
|
attrib = field_type(type=v)
|
|
507
562
|
|
|
508
563
|
cls_fields[k] = attrib
|
|
509
564
|
|
|
510
|
-
return cls_fields
|
|
565
|
+
return cls_fields, modifications
|
|
511
566
|
|
|
512
567
|
return field_annotation_gatherer
|
|
513
568
|
|
|
@@ -569,7 +624,7 @@ def annotationclass(cls=None, /, *, methods=default_methods):
|
|
|
569
624
|
|
|
570
625
|
_field_init_desc = MethodMaker(
|
|
571
626
|
funcname="__init__",
|
|
572
|
-
code_generator=
|
|
627
|
+
code_generator=get_init_generator(
|
|
573
628
|
null=_NothingType(),
|
|
574
629
|
extra_code=["self.validate_field()"],
|
|
575
630
|
)
|
|
@@ -590,11 +645,11 @@ def fieldclass(cls=None, /, *, frozen=False):
|
|
|
590
645
|
if not cls:
|
|
591
646
|
return lambda cls_: fieldclass(cls_, frozen=frozen)
|
|
592
647
|
|
|
593
|
-
field_methods = {_field_init_desc,
|
|
648
|
+
field_methods = {_field_init_desc, repr_maker, eq_maker}
|
|
594
649
|
|
|
595
650
|
# Always freeze when running tests
|
|
596
651
|
if frozen or _UNDER_TESTING:
|
|
597
|
-
field_methods.update({
|
|
652
|
+
field_methods.update({frozen_setattr_maker, frozen_delattr_maker})
|
|
598
653
|
|
|
599
654
|
cls = builder(
|
|
600
655
|
cls,
|
|
@@ -26,24 +26,24 @@ class MethodMaker:
|
|
|
26
26
|
def __repr__(self) -> str: ...
|
|
27
27
|
def __get__(self, instance, cls) -> Callable: ...
|
|
28
28
|
|
|
29
|
-
def
|
|
29
|
+
def get_init_generator(
|
|
30
30
|
null: _NothingType = NOTHING,
|
|
31
31
|
extra_code: None | list[str] = None
|
|
32
32
|
) -> Callable[[type], tuple[str, dict[str, typing.Any]]]: ...
|
|
33
33
|
|
|
34
|
-
def
|
|
35
|
-
def
|
|
36
|
-
def
|
|
34
|
+
def init_generator(cls: type) -> tuple[str, dict[str, typing.Any]]: ...
|
|
35
|
+
def repr_generator(cls: type) -> tuple[str, dict[str, typing.Any]]: ...
|
|
36
|
+
def eq_generator(cls: type) -> tuple[str, dict[str, typing.Any]]: ...
|
|
37
37
|
|
|
38
|
-
def
|
|
38
|
+
def frozen_setattr_generator(cls: type) -> tuple[str, dict[str, typing.Any]]: ...
|
|
39
39
|
|
|
40
|
-
def
|
|
40
|
+
def frozen_delattr_generator(cls: type) -> tuple[str, dict[str, typing.Any]]: ...
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
init_maker: MethodMaker
|
|
43
|
+
repr_maker: MethodMaker
|
|
44
|
+
eq_maker: MethodMaker
|
|
45
|
+
frozen_setattr_maker: MethodMaker
|
|
46
|
+
frozen_delattr_maker: MethodMaker
|
|
47
47
|
default_methods: frozenset[MethodMaker]
|
|
48
48
|
|
|
49
49
|
_T = typing.TypeVar("_T")
|
|
@@ -53,7 +53,7 @@ def builder(
|
|
|
53
53
|
cls: type[_T],
|
|
54
54
|
/,
|
|
55
55
|
*,
|
|
56
|
-
gatherer: Callable[[type], dict[str, Field]],
|
|
56
|
+
gatherer: Callable[[type], tuple[dict[str, Field], dict[str, typing.Any]]],
|
|
57
57
|
methods: frozenset[MethodMaker] | set[MethodMaker],
|
|
58
58
|
flags: dict[str, bool] | None = None,
|
|
59
59
|
) -> type[_T]: ...
|
|
@@ -63,7 +63,7 @@ def builder(
|
|
|
63
63
|
cls: None = None,
|
|
64
64
|
/,
|
|
65
65
|
*,
|
|
66
|
-
gatherer: Callable[[type], dict[str, Field]],
|
|
66
|
+
gatherer: Callable[[type], tuple[dict[str, Field], dict[str, typing.Any]]],
|
|
67
67
|
methods: frozenset[MethodMaker] | set[MethodMaker],
|
|
68
68
|
flags: dict[str, bool] | None = None,
|
|
69
69
|
) -> Callable[[type[_T]], type[_T]]: ...
|
|
@@ -92,12 +92,33 @@ class Field:
|
|
|
92
92
|
def from_field(cls, fld: Field, /, **kwargs: typing.Any) -> Field: ...
|
|
93
93
|
|
|
94
94
|
|
|
95
|
+
class GatheredFields:
|
|
96
|
+
__slots__ = ("fields", "modifications")
|
|
97
|
+
|
|
98
|
+
fields: dict[str, Field]
|
|
99
|
+
modifications: dict[str, typing.Any]
|
|
100
|
+
|
|
101
|
+
__classbuilder_internals__: dict
|
|
102
|
+
|
|
103
|
+
def __init__(
|
|
104
|
+
self,
|
|
105
|
+
fields: dict[str, Field],
|
|
106
|
+
modifications: dict[str, typing.Any]
|
|
107
|
+
) -> None: ...
|
|
108
|
+
|
|
109
|
+
def __repr__(self) -> str: ...
|
|
110
|
+
def __eq__(self, other) -> bool: ...
|
|
111
|
+
def __call__(self, cls: type) -> tuple[dict[str, Field], dict[str, typing.Any]]: ...
|
|
112
|
+
|
|
113
|
+
|
|
95
114
|
class SlotFields(dict):
|
|
96
115
|
...
|
|
97
116
|
|
|
98
|
-
def make_slot_gatherer(
|
|
117
|
+
def make_slot_gatherer(
|
|
118
|
+
field_type: type[Field] = Field
|
|
119
|
+
) -> Callable[[type], tuple[dict[str, Field], dict[str, typing.Any]]]: ...
|
|
99
120
|
|
|
100
|
-
def slot_gatherer(cls: type) -> dict[str, Field]:
|
|
121
|
+
def slot_gatherer(cls: type) -> tuple[dict[str, Field], dict[str, typing.Any]]:
|
|
101
122
|
...
|
|
102
123
|
|
|
103
124
|
def is_classvar(hint: object) -> bool: ...
|
|
@@ -105,9 +126,9 @@ def is_classvar(hint: object) -> bool: ...
|
|
|
105
126
|
def make_annotation_gatherer(
|
|
106
127
|
field_type: type[Field] = Field,
|
|
107
128
|
leave_default_values: bool = True,
|
|
108
|
-
) -> Callable[[type], dict[str, Field]]: ...
|
|
129
|
+
) -> Callable[[type], tuple[dict[str, Field], dict[str, typing.Any]]]: ...
|
|
109
130
|
|
|
110
|
-
def annotation_gatherer(cls: type) -> dict[str, Field]: ...
|
|
131
|
+
def annotation_gatherer(cls: type) -> tuple[dict[str, Field], dict[str, typing.Any]]: ...
|
|
111
132
|
|
|
112
133
|
def check_argument_order(cls: type) -> None: ...
|
|
113
134
|
|
ducktools/classbuilder/prefab.py
CHANGED
|
@@ -30,9 +30,9 @@ import sys
|
|
|
30
30
|
|
|
31
31
|
from . import (
|
|
32
32
|
INTERNALS_DICT, NOTHING,
|
|
33
|
-
Field, MethodMaker, SlotFields,
|
|
33
|
+
Field, MethodMaker, SlotFields, GatheredFields,
|
|
34
34
|
builder, fieldclass, get_flags, get_fields, make_slot_gatherer,
|
|
35
|
-
|
|
35
|
+
frozen_setattr_maker, frozen_delattr_maker, is_classvar,
|
|
36
36
|
)
|
|
37
37
|
|
|
38
38
|
PREFAB_FIELDS = "PREFAB_FIELDS"
|
|
@@ -344,13 +344,13 @@ def get_asdict_maker():
|
|
|
344
344
|
return MethodMaker("as_dict", as_dict_gen)
|
|
345
345
|
|
|
346
346
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
347
|
+
init_maker = get_init_maker()
|
|
348
|
+
prefab_init_maker = get_init_maker(init_name=PREFAB_INIT_FUNC)
|
|
349
|
+
repr_maker = get_repr_maker()
|
|
350
|
+
recursive_repr_maker = get_repr_maker(recursion_safe=True)
|
|
351
|
+
eq_maker = get_eq_maker()
|
|
352
|
+
iter_maker = get_iter_maker()
|
|
353
|
+
asdict_maker = get_asdict_maker()
|
|
354
354
|
|
|
355
355
|
|
|
356
356
|
# Updated field with additional attributes
|
|
@@ -441,6 +441,8 @@ def attribute_gatherer(cls):
|
|
|
441
441
|
|
|
442
442
|
cls_attribute_names = cls_attributes.keys()
|
|
443
443
|
|
|
444
|
+
cls_modifications = {}
|
|
445
|
+
|
|
444
446
|
if set(cls_annotation_names).issuperset(set(cls_attribute_names)):
|
|
445
447
|
# replace the classes' attributes dict with one with the correct
|
|
446
448
|
# order from the annotations.
|
|
@@ -483,7 +485,7 @@ def attribute_gatherer(cls):
|
|
|
483
485
|
|
|
484
486
|
# Clear the attribute from the class after it has been used
|
|
485
487
|
# in the definition.
|
|
486
|
-
|
|
488
|
+
cls_modifications[name] = NOTHING
|
|
487
489
|
else:
|
|
488
490
|
attrib = attribute(**extras)
|
|
489
491
|
|
|
@@ -493,14 +495,14 @@ def attribute_gatherer(cls):
|
|
|
493
495
|
else:
|
|
494
496
|
for name in cls_attributes.keys():
|
|
495
497
|
attrib = cls_attributes[name]
|
|
496
|
-
|
|
498
|
+
cls_modifications[name] = NOTHING
|
|
497
499
|
|
|
498
500
|
# Some items can still be annotated.
|
|
499
501
|
if name in cls_annotations:
|
|
500
502
|
new_attrib = Attribute.from_field(attrib, type=cls_annotations[name])
|
|
501
503
|
cls_attributes[name] = new_attrib
|
|
502
504
|
|
|
503
|
-
return cls_attributes
|
|
505
|
+
return cls_attributes, cls_modifications
|
|
504
506
|
|
|
505
507
|
|
|
506
508
|
# Class Builders
|
|
@@ -517,6 +519,7 @@ def _make_prefab(
|
|
|
517
519
|
frozen=False,
|
|
518
520
|
dict_method=False,
|
|
519
521
|
recursive_repr=False,
|
|
522
|
+
gathered_fields=None,
|
|
520
523
|
):
|
|
521
524
|
"""
|
|
522
525
|
Generate boilerplate code for dunder methods in a class.
|
|
@@ -533,6 +536,7 @@ def _make_prefab(
|
|
|
533
536
|
such as lists)
|
|
534
537
|
:param dict_method: Include an as_dict method for faster dictionary creation
|
|
535
538
|
:param recursive_repr: Safely handle repr in case of recursion
|
|
539
|
+
:param gathered_fields: Pre-gathered fields callable, to skip re-collecting attributes
|
|
536
540
|
:return: class with __ methods defined
|
|
537
541
|
"""
|
|
538
542
|
cls_dict = cls.__dict__
|
|
@@ -544,34 +548,38 @@ def _make_prefab(
|
|
|
544
548
|
)
|
|
545
549
|
|
|
546
550
|
slots = cls_dict.get("__slots__")
|
|
547
|
-
if
|
|
548
|
-
|
|
549
|
-
|
|
551
|
+
if gathered_fields is None:
|
|
552
|
+
if isinstance(slots, SlotFields):
|
|
553
|
+
gatherer = slot_prefab_gatherer
|
|
554
|
+
slotted = True
|
|
555
|
+
else:
|
|
556
|
+
gatherer = attribute_gatherer
|
|
557
|
+
slotted = False
|
|
550
558
|
else:
|
|
551
|
-
gatherer =
|
|
552
|
-
slotted = False
|
|
559
|
+
gatherer = gathered_fields
|
|
560
|
+
slotted = False if slots is None else True
|
|
553
561
|
|
|
554
562
|
methods = set()
|
|
555
563
|
|
|
556
564
|
if init and "__init__" not in cls_dict:
|
|
557
|
-
methods.add(
|
|
565
|
+
methods.add(init_maker)
|
|
558
566
|
else:
|
|
559
|
-
methods.add(
|
|
567
|
+
methods.add(prefab_init_maker)
|
|
560
568
|
|
|
561
569
|
if repr and "__repr__" not in cls_dict:
|
|
562
570
|
if recursive_repr:
|
|
563
|
-
methods.add(
|
|
571
|
+
methods.add(recursive_repr_maker)
|
|
564
572
|
else:
|
|
565
|
-
methods.add(
|
|
573
|
+
methods.add(repr_maker)
|
|
566
574
|
if eq and "__eq__" not in cls_dict:
|
|
567
|
-
methods.add(
|
|
575
|
+
methods.add(eq_maker)
|
|
568
576
|
if iter and "__iter__" not in cls_dict:
|
|
569
|
-
methods.add(
|
|
577
|
+
methods.add(iter_maker)
|
|
570
578
|
if frozen:
|
|
571
|
-
methods.add(
|
|
572
|
-
methods.add(
|
|
579
|
+
methods.add(frozen_setattr_maker)
|
|
580
|
+
methods.add(frozen_delattr_maker)
|
|
573
581
|
if dict_method:
|
|
574
|
-
methods.add(
|
|
582
|
+
methods.add(asdict_maker)
|
|
575
583
|
|
|
576
584
|
flags = {
|
|
577
585
|
"kw_only": kw_only,
|
|
@@ -768,6 +776,7 @@ def build_prefab(
|
|
|
768
776
|
frozen=False,
|
|
769
777
|
dict_method=False,
|
|
770
778
|
recursive_repr=False,
|
|
779
|
+
slots=False,
|
|
771
780
|
):
|
|
772
781
|
"""
|
|
773
782
|
Dynamically construct a (dynamic) prefab.
|
|
@@ -788,12 +797,35 @@ def build_prefab(
|
|
|
788
797
|
(This does not prevent the modification of mutable attributes such as lists)
|
|
789
798
|
:param dict_method: Include an as_dict method for faster dictionary creation
|
|
790
799
|
:param recursive_repr: Safely handle repr in case of recursion
|
|
800
|
+
:param slots: Make the resulting class slotted
|
|
791
801
|
:return: class with __ methods defined
|
|
792
802
|
"""
|
|
793
|
-
class_dict = {} if class_dict is None else class_dict
|
|
794
|
-
|
|
803
|
+
class_dict = {} if class_dict is None else class_dict.copy()
|
|
804
|
+
|
|
805
|
+
class_annotations = {}
|
|
806
|
+
class_slots = {}
|
|
807
|
+
fields = {}
|
|
808
|
+
|
|
795
809
|
for name, attrib in attributes:
|
|
796
|
-
|
|
810
|
+
if isinstance(attrib, Attribute):
|
|
811
|
+
fields[name] = attrib
|
|
812
|
+
elif isinstance(attrib, Field):
|
|
813
|
+
fields[name] = Attribute.from_field(attrib)
|
|
814
|
+
else:
|
|
815
|
+
fields[name] = Attribute(default=attrib)
|
|
816
|
+
|
|
817
|
+
if attrib.type is not NOTHING:
|
|
818
|
+
class_annotations[name] = attrib.type
|
|
819
|
+
|
|
820
|
+
class_slots[name] = attrib.doc
|
|
821
|
+
|
|
822
|
+
if slots:
|
|
823
|
+
class_dict["__slots__"] = class_slots
|
|
824
|
+
|
|
825
|
+
class_dict["__annotations__"] = class_annotations
|
|
826
|
+
cls = type(class_name, bases, class_dict)
|
|
827
|
+
|
|
828
|
+
gathered_fields = GatheredFields(fields, {})
|
|
797
829
|
|
|
798
830
|
cls = _make_prefab(
|
|
799
831
|
cls,
|
|
@@ -806,6 +838,7 @@ def build_prefab(
|
|
|
806
838
|
frozen=frozen,
|
|
807
839
|
dict_method=dict_method,
|
|
808
840
|
recursive_repr=recursive_repr,
|
|
841
|
+
gathered_fields=gathered_fields,
|
|
809
842
|
)
|
|
810
843
|
|
|
811
844
|
return cls
|
|
@@ -847,17 +880,17 @@ def as_dict(o):
|
|
|
847
880
|
:param o: instance of a prefab class
|
|
848
881
|
:return: dictionary of {k: v} from fields
|
|
849
882
|
"""
|
|
883
|
+
cls = type(o)
|
|
884
|
+
if not hasattr(cls, PREFAB_FIELDS):
|
|
885
|
+
raise TypeError(f"{o!r} should be a prefab instance, not {cls}")
|
|
886
|
+
|
|
850
887
|
# Attempt to use the generated method if available
|
|
851
888
|
try:
|
|
852
889
|
return o.as_dict()
|
|
853
890
|
except AttributeError:
|
|
854
891
|
pass
|
|
855
892
|
|
|
856
|
-
|
|
857
|
-
try:
|
|
858
|
-
flds = get_attributes(cls)
|
|
859
|
-
except AttributeError:
|
|
860
|
-
raise TypeError(f"inst should be a prefab instance, not {cls}")
|
|
893
|
+
flds = get_attributes(cls)
|
|
861
894
|
|
|
862
895
|
return {
|
|
863
896
|
name: getattr(o, name)
|
|
@@ -6,7 +6,7 @@ from collections.abc import Callable
|
|
|
6
6
|
from . import (
|
|
7
7
|
INTERNALS_DICT, NOTHING,
|
|
8
8
|
Field, MethodMaker, SlotFields as SlotFields,
|
|
9
|
-
builder, fieldclass, get_flags, get_fields,
|
|
9
|
+
builder, fieldclass, get_flags, get_fields, make_slot_gatherer
|
|
10
10
|
)
|
|
11
11
|
|
|
12
12
|
# noinspection PyUnresolvedReferences
|
|
@@ -39,13 +39,13 @@ def get_iter_maker() -> MethodMaker: ...
|
|
|
39
39
|
def get_asdict_maker() -> MethodMaker: ...
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
42
|
+
init_maker: MethodMaker
|
|
43
|
+
prefab_init_maker: MethodMaker
|
|
44
|
+
repr_maker: MethodMaker
|
|
45
|
+
recursive_repr_maker: MethodMaker
|
|
46
|
+
eq_maker: MethodMaker
|
|
47
|
+
iter_maker: MethodMaker
|
|
48
|
+
asdict_maker: MethodMaker
|
|
49
49
|
|
|
50
50
|
class Attribute(Field):
|
|
51
51
|
__slots__: dict
|
|
@@ -93,9 +93,9 @@ def attribute(
|
|
|
93
93
|
exclude_field: bool = False,
|
|
94
94
|
) -> Attribute: ...
|
|
95
95
|
|
|
96
|
-
def slot_prefab_gatherer(cls: type) -> dict[str, Attribute]: ...
|
|
96
|
+
def slot_prefab_gatherer(cls: type) -> tuple[dict[str, Attribute], dict[str, typing.Any]]: ...
|
|
97
97
|
|
|
98
|
-
def attribute_gatherer(cls: type) -> dict[str, Attribute]: ...
|
|
98
|
+
def attribute_gatherer(cls: type) -> tuple[dict[str, Attribute], dict[str, typing.Any]]: ...
|
|
99
99
|
|
|
100
100
|
def _make_prefab(
|
|
101
101
|
cls: type,
|
|
@@ -109,6 +109,7 @@ def _make_prefab(
|
|
|
109
109
|
frozen: bool = False,
|
|
110
110
|
dict_method: bool = False,
|
|
111
111
|
recursive_repr: bool = False,
|
|
112
|
+
gathered_fields: Callable[[type], tuple[dict[str, Attribute], dict[str, typing.Any]]] | None = None,
|
|
112
113
|
) -> type: ...
|
|
113
114
|
|
|
114
115
|
_T = typing.TypeVar("_T")
|
|
@@ -146,6 +147,7 @@ def build_prefab(
|
|
|
146
147
|
frozen: bool = False,
|
|
147
148
|
dict_method: bool = False,
|
|
148
149
|
recursive_repr: bool = False,
|
|
150
|
+
slots: bool = False,
|
|
149
151
|
) -> type: ...
|
|
150
152
|
|
|
151
153
|
def is_prefab(o: typing.Any) -> bool: ...
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ducktools-classbuilder
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: Toolkit for creating class boilerplate generators
|
|
5
5
|
Author: David C Ellis
|
|
6
6
|
License: MIT License
|
|
@@ -69,11 +69,12 @@ Install from PyPI with:
|
|
|
69
69
|
In order to create a class decorator using `ducktools.classbuilder` there are
|
|
70
70
|
a few things you need to prepare.
|
|
71
71
|
|
|
72
|
-
1. A field gathering function to analyse the class and collect valid `Field`s
|
|
72
|
+
1. A field gathering function to analyse the class and collect valid `Field`s and provide
|
|
73
|
+
any modifications that need to be applied to the class attributes.
|
|
73
74
|
* An example `slot_gatherer` is included.
|
|
74
75
|
2. Code generators that can make use of the gathered `Field`s to create magic method
|
|
75
|
-
source code.
|
|
76
|
-
* Example `
|
|
76
|
+
source code. To be made into descriptors by `MethodMaker`.
|
|
77
|
+
* Example `init_generator`, `repr_generator` and `eq_generator` generators are included.
|
|
77
78
|
3. A function that calls the `builder` function to apply both of these steps.
|
|
78
79
|
|
|
79
80
|
A field gathering function needs to take the original class as an argument and
|
|
@@ -91,25 +92,26 @@ class[^1] where keyword arguments define the names and values for the fields.
|
|
|
91
92
|
|
|
92
93
|
Code generator functions need to be converted to descriptors before being used.
|
|
93
94
|
This is done using the provided `MethodMaker` descriptor class.
|
|
94
|
-
ex: `
|
|
95
|
+
ex: `init_maker = MethodMaker("__init__", init_generator)`.
|
|
95
96
|
|
|
96
97
|
These parts can then be used to make a basic class boilerplate generator by
|
|
97
98
|
providing them to the `builder` function.
|
|
98
99
|
|
|
99
100
|
```python
|
|
100
101
|
from ducktools.classbuilder import (
|
|
101
|
-
builder,
|
|
102
|
-
slot_gatherer,
|
|
103
|
-
|
|
102
|
+
builder,
|
|
103
|
+
slot_gatherer,
|
|
104
|
+
init_generator, eq_generator, repr_generator,
|
|
104
105
|
MethodMaker,
|
|
105
106
|
)
|
|
106
107
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
init_maker = MethodMaker("__init__", init_generator)
|
|
109
|
+
repr_maker = MethodMaker("__repr__", repr_generator)
|
|
110
|
+
eq_maker = MethodMaker("__eq__", eq_generator)
|
|
111
|
+
|
|
110
112
|
|
|
111
113
|
def slotclass(cls):
|
|
112
|
-
return builder(cls, gatherer=slot_gatherer, methods={
|
|
114
|
+
return builder(cls, gatherer=slot_gatherer, methods={init_maker, repr_maker, eq_maker})
|
|
113
115
|
```
|
|
114
116
|
|
|
115
117
|
## Slot Class Usage ##
|
|
@@ -238,9 +240,8 @@ It will copy values provided as the `type` to `Field` into the
|
|
|
238
240
|
Values provided to `doc` will be placed in the final `__slots__`
|
|
239
241
|
field so they are present on the class if `help(...)` is called.
|
|
240
242
|
|
|
241
|
-
A fairly basic `annotations_gatherer` and `annotationclass` are included
|
|
242
|
-
|
|
243
|
-
annotations.
|
|
243
|
+
A fairly basic `annotations_gatherer` and `annotationclass` are also included
|
|
244
|
+
and can be used to generate classbuilders that rely on annotations.
|
|
244
245
|
|
|
245
246
|
If you want something with more features you can look at the `prefab.py`
|
|
246
247
|
implementation which provides a 'prebuilt' implementation.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
ducktools/classbuilder/__init__.py,sha256=rl5oc0azae-32EjeCTzhM1DR5JNR-AgOriyH5zkf4WI,20856
|
|
2
|
+
ducktools/classbuilder/__init__.pyi,sha256=5gnHhSVMP5yArA-_cuvIeLnLAlmY_EmPMfVY9dRi0CA,4784
|
|
3
|
+
ducktools/classbuilder/prefab.py,sha256=K9_bKh24OiGJ40BbJWVfwDObArMUxi2JRbB5ZN-t--M,28984
|
|
4
|
+
ducktools/classbuilder/prefab.pyi,sha256=sNladRJM3lcZo8zQWvWCIInBx0wUD7PT6fhdE89OpG0,3960
|
|
5
|
+
ducktools/classbuilder/py.typed,sha256=la67KBlbjXN-_-DfGNcdOcjYumVpKG_Tkw-8n5dnGB4,8
|
|
6
|
+
ducktools_classbuilder-0.5.1.dist-info/LICENSE.md,sha256=6Thz9Dbw8R4fWInl6sGl8Rj3UnKnRbDwrc6jZerpugQ,1070
|
|
7
|
+
ducktools_classbuilder-0.5.1.dist-info/METADATA,sha256=B0KO5vvdZOUZ4o28GA4zlgXuIkA9j4SuplzFByvRhjs,10143
|
|
8
|
+
ducktools_classbuilder-0.5.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
9
|
+
ducktools_classbuilder-0.5.1.dist-info/top_level.txt,sha256=uSDLtio3ZFqdwcsMJ2O5yhjB4Q3ytbBWbA8rJREganc,10
|
|
10
|
+
ducktools_classbuilder-0.5.1.dist-info/RECORD,,
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
ducktools/classbuilder/__init__.py,sha256=7OkqvfHLSSJylkAeiErgZXB9E-lDUK-rvbu3CRWFSdc,19113
|
|
2
|
-
ducktools/classbuilder/__init__.pyi,sha256=64cp1-zpOh2bUVlwcoTTdaampFINIhdFbFDcleE3N0g,4091
|
|
3
|
-
ducktools/classbuilder/prefab.py,sha256=9K_02AJ3-gLR0Cb0RxKwd2n_cwVtE2mf1TE2n_VNp4o,27947
|
|
4
|
-
ducktools/classbuilder/prefab.pyi,sha256=5MiPuuT4hc-Er4xFfrEkBqz-dCjULYJfTM_c0upa0Dc,3758
|
|
5
|
-
ducktools/classbuilder/py.typed,sha256=la67KBlbjXN-_-DfGNcdOcjYumVpKG_Tkw-8n5dnGB4,8
|
|
6
|
-
ducktools_classbuilder-0.4.0.dist-info/LICENSE.md,sha256=6Thz9Dbw8R4fWInl6sGl8Rj3UnKnRbDwrc6jZerpugQ,1070
|
|
7
|
-
ducktools_classbuilder-0.4.0.dist-info/METADATA,sha256=teRrGNNsVoQJ1jLnzctDVFREtSDq9MVCS9GNZr1S01E,9982
|
|
8
|
-
ducktools_classbuilder-0.4.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
9
|
-
ducktools_classbuilder-0.4.0.dist-info/top_level.txt,sha256=uSDLtio3ZFqdwcsMJ2O5yhjB4Q3ytbBWbA8rJREganc,10
|
|
10
|
-
ducktools_classbuilder-0.4.0.dist-info/RECORD,,
|
{ducktools_classbuilder-0.4.0.dist-info → ducktools_classbuilder-0.5.1.dist-info}/LICENSE.md
RENAMED
|
File without changes
|
|
File without changes
|
{ducktools_classbuilder-0.4.0.dist-info → ducktools_classbuilder-0.5.1.dist-info}/top_level.txt
RENAMED
|
File without changes
|