ducktools-classbuilder 0.9.1__py3-none-any.whl → 0.10.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.

@@ -38,7 +38,7 @@ from ._version import __version__, __version_tuple__ # noqa: F401
38
38
  # Change this name if you make heavy modifications
39
39
  INTERNALS_DICT = "__classbuilder_internals__"
40
40
  META_GATHERER_NAME = "_meta_gatherer"
41
-
41
+ GATHERED_DATA = "__classbuilder_gathered_fields__"
42
42
 
43
43
  # If testing, make Field classes frozen to make sure attributes are not
44
44
  # overwritten. When running this is a performance penalty so it is not required.
@@ -114,12 +114,16 @@ FIELD_NOTHING = _NothingType("FIELD")
114
114
  # KW_ONLY sentinel 'type' to use to indicate all subsequent attributes are
115
115
  # keyword only
116
116
  # noinspection PyPep8Naming
117
- class _KW_ONLY_TYPE:
117
+ class _KW_ONLY_META(type):
118
118
  def __repr__(self):
119
- return "<KW_ONLY Sentinel Object>"
119
+ return "<KW_ONLY Sentinel>"
120
120
 
121
121
 
122
- KW_ONLY = _KW_ONLY_TYPE()
122
+ class KW_ONLY(metaclass=_KW_ONLY_META):
123
+ """
124
+ Sentinel Class to indicate that variables declared after
125
+ this sentinel are to be converted to KW_ONLY arguments.
126
+ """
123
127
 
124
128
 
125
129
  class GeneratedCode:
@@ -213,15 +217,23 @@ class _SignatureMaker:
213
217
  # help(cls) will fail along with inspect.signature(cls)
214
218
  # This signature maker descriptor is placed to override __signature__ and force
215
219
  # the `__init__` signature to be generated first if the signature is requested.
216
- def __get__(self, instance, cls):
217
- import inspect # Deferred inspect import
218
- _ = cls.__init__ # force generation of `__init__` function
219
- # Remove this attribute from the class
220
- # This prevents recursion back into this __get__ method.
221
- delattr(cls, "__signature__")
222
- sig = inspect.signature(cls)
223
- setattr(cls, "__signature__", sig)
224
- return sig
220
+ def __get__(self, instance, cls=None):
221
+ if cls is None:
222
+ cls = type(instance)
223
+
224
+ # force generation of `__init__` function
225
+ _ = cls.__init__
226
+
227
+ if instance is None:
228
+ raise AttributeError(
229
+ f"type object {cls.__name__!r} "
230
+ "has no attribute '__signature__'"
231
+ )
232
+ else:
233
+ raise AttributeError(
234
+ f"{cls.__name__!r} object"
235
+ "has no attribute '__signature__'"
236
+ )
225
237
 
226
238
 
227
239
  signature_maker = _SignatureMaker()
@@ -389,7 +401,7 @@ def replace_generator(cls, funcname="__replace__"):
389
401
  # Generate the replace method for built classes
390
402
  # unlike the dataclasses implementation this is generated
391
403
  attribs = get_fields(cls)
392
-
404
+
393
405
  # This is essentially the as_dict generator for prefabs
394
406
  # except based on attrib.init instead of .serialize
395
407
  vals = ", ".join(
@@ -403,7 +415,7 @@ def replace_generator(cls, funcname="__replace__"):
403
415
  f"def {funcname}(self, /, **changes):\n"
404
416
  f" new_kwargs = {init_dict}\n"
405
417
  f" for name, value in changes.items():\n"
406
- f" if name not in new_kwargs:\n"
418
+ f" if name not in new_kwargs:\n"
407
419
  f" raise TypeError(\n"
408
420
  f" f\"{{name!r}} is not a valid replacable \"\n"
409
421
  f" f\"field on {{self.__class__.__name__!r}}\"\n"
@@ -571,21 +583,25 @@ class SlotMakerMeta(type):
571
583
 
572
584
  Will not convert `ClassVar` hinted values.
573
585
  """
574
- def __new__(cls, name, bases, ns, slots=True, **kwargs):
586
+ def __new__(cls, name, bases, ns, slots=True, gatherer=None, **kwargs):
575
587
  # This should only run if slots=True is declared
576
588
  # and __slots__ have not already been defined
577
589
  if slots and "__slots__" not in ns:
578
590
  # Check if a different gatherer has been set in any base classes
579
591
  # Default to unified gatherer
580
- gatherer = ns.get(META_GATHERER_NAME, None)
581
- if not gatherer:
582
- for base in bases:
583
- if g := getattr(base, META_GATHERER_NAME, None):
584
- gatherer = g
585
- break
592
+ if gatherer is None:
593
+ gatherer = ns.get(META_GATHERER_NAME, None)
594
+ if not gatherer:
595
+ for base in bases:
596
+ if g := getattr(base, META_GATHERER_NAME, None):
597
+ gatherer = g
598
+ break
599
+
600
+ if not gatherer:
601
+ gatherer = unified_gatherer
586
602
 
587
- if not gatherer:
588
- gatherer = unified_gatherer
603
+ # Set the gatherer in the namespace
604
+ ns[META_GATHERER_NAME] = gatherer
589
605
 
590
606
  # Obtain slots from annotations or attributes
591
607
  cls_fields, cls_modifications = gatherer(ns)
@@ -595,14 +611,54 @@ class SlotMakerMeta(type):
595
611
  else:
596
612
  ns[k] = v
597
613
 
614
+ slots = {}
615
+ fields = {}
616
+
617
+ for k, v in cls_fields.items():
618
+ slots[k] = v.doc
619
+ if k not in {"__weakref__", "__dict__"}:
620
+ fields[k] = v
621
+
598
622
  # Place slots *after* everything else to be safe
599
- ns["__slots__"] = SlotFields(cls_fields)
623
+ ns["__slots__"] = slots
624
+
625
+ # Place pre-gathered field data - modifications are already applied
626
+ modifications = {}
627
+ ns[GATHERED_DATA] = fields, modifications
628
+
629
+ else:
630
+ if gatherer is not None:
631
+ ns[META_GATHERER_NAME] = gatherer
600
632
 
601
633
  new_cls = super().__new__(cls, name, bases, ns, **kwargs)
602
634
 
603
635
  return new_cls
604
636
 
605
637
 
638
+ # This class is set up before fields as it will be used to generate the Fields
639
+ # for Field itself so Field can have generated __eq__, __repr__ and other methods
640
+ class GatheredFields:
641
+ """
642
+ Helper class to store gathered field data
643
+ """
644
+ __slots__ = ("fields", "modifications")
645
+
646
+ def __init__(self, fields, modifications):
647
+ self.fields = fields
648
+ self.modifications = modifications
649
+
650
+ def __eq__(self, other):
651
+ if type(self) is type(other):
652
+ return self.fields == other.fields and self.modifications == other.modifications
653
+
654
+ def __repr__(self):
655
+ return f"{type(self).__name__}(fields={self.fields!r}, modifications={self.modifications!r})"
656
+
657
+ def __call__(self, cls_dict):
658
+ # cls_dict will be provided, but isn't needed
659
+ return self.fields, self.modifications
660
+
661
+
606
662
  # The Field class can finally be defined.
607
663
  # The __init__ method has to be written manually so Fields can be created
608
664
  # However after this, the other methods can be generated.
@@ -628,17 +684,18 @@ class Field(metaclass=SlotMakerMeta):
628
684
  :param compare: Include in the class __eq__.
629
685
  :param kw_only: Make this a keyword only parameter in __init__.
630
686
  """
631
- # If this base class did not define __slots__ the metaclass would break it.
632
- # This will be replaced by the builder.
633
- __slots__ = SlotFields(
634
- default=NOTHING,
635
- default_factory=NOTHING,
636
- type=NOTHING,
637
- doc=None,
638
- init=True,
639
- repr=True,
640
- compare=True,
641
- kw_only=False,
687
+
688
+ # Plain slots are required as part of bootstrapping
689
+ # This prevents SlotMakerMeta from trying to generate 'Field's
690
+ __slots__ = (
691
+ "default",
692
+ "default_factory",
693
+ "type",
694
+ "doc",
695
+ "init",
696
+ "repr",
697
+ "compare",
698
+ "kw_only",
642
699
  )
643
700
 
644
701
  # noinspection PyShadowingBuiltins
@@ -672,6 +729,7 @@ class Field(metaclass=SlotMakerMeta):
672
729
  self.validate_field()
673
730
 
674
731
  def __init_subclass__(cls, frozen=False):
732
+ # Subclasses of Field can be created as if they are dataclasses
675
733
  field_methods = {_field_init_maker, repr_maker, eq_maker}
676
734
  if frozen or _UNDER_TESTING:
677
735
  field_methods.update({frozen_setattr_maker, frozen_delattr_maker})
@@ -707,6 +765,66 @@ class Field(metaclass=SlotMakerMeta):
707
765
  return cls(**argument_dict)
708
766
 
709
767
 
768
+ def _build_field():
769
+ # Complete the construction of the Field class
770
+ field_docs = {
771
+ "default": "Standard default value to be used for attributes with this field.",
772
+ "default_factory":
773
+ "A zero-argument function to be called to generate a default value, "
774
+ "useful for mutable obects like lists.",
775
+ "type": "The type of the attribute to be assigned by this field.",
776
+ "doc":
777
+ "The documentation for the attribute that appears when calling "
778
+ "help(...) on the class. (Only in slotted classes).",
779
+ "init": "Include this attribute in the class __init__ parameters.",
780
+ "repr": "Include this attribute in the class __repr__",
781
+ "compare": "Include this attribute in the class __eq__ method",
782
+ "kw_only": "Make this a keyword only parameter in __init__",
783
+ }
784
+
785
+ fields = {
786
+ "default": Field(default=NOTHING, doc=field_docs["default"]),
787
+ "default_factory": Field(default=NOTHING, doc=field_docs["default_factory"]),
788
+ "type": Field(default=NOTHING, doc=field_docs["type"]),
789
+ "doc": Field(default=None, doc=field_docs["doc"]),
790
+ "init": Field(default=True, doc=field_docs["init"]),
791
+ "repr": Field(default=True, doc=field_docs["repr"]),
792
+ "compare": Field(default=True, doc=field_docs["compare"]),
793
+ "kw_only": Field(default=False, doc=field_docs["kw_only"])
794
+ }
795
+ modifications = {"__slots__": field_docs}
796
+
797
+ field_methods = {repr_maker, eq_maker}
798
+ if _UNDER_TESTING:
799
+ field_methods.update({frozen_setattr_maker, frozen_delattr_maker})
800
+
801
+ builder(
802
+ Field,
803
+ gatherer=GatheredFields(fields, modifications),
804
+ methods=field_methods,
805
+ flags={"slotted": True, "kw_only": True},
806
+ )
807
+
808
+
809
+ _build_field()
810
+ del _build_field
811
+
812
+
813
+ def pre_gathered_gatherer(cls_or_ns):
814
+ """
815
+ Retrieve fields previously gathered by SlotMakerMeta
816
+
817
+ :param cls_or_ns: Class to gather field information from (or class namespace)
818
+ :return: dict of field_name: Field(...) and modifications to be performed by the builder
819
+ """
820
+ if isinstance(cls_or_ns, (_MappingProxyType, dict)):
821
+ cls_dict = cls_or_ns
822
+ else:
823
+ cls_dict = cls_or_ns.__dict__
824
+
825
+ return cls_dict[GATHERED_DATA]
826
+
827
+
710
828
  def make_slot_gatherer(field_type=Field):
711
829
  """
712
830
  Create a new annotation gatherer that will work with `Field` instances
@@ -721,7 +839,7 @@ def make_slot_gatherer(field_type=Field):
721
839
  Gather field information for class generation based on __slots__
722
840
 
723
841
  :param cls_or_ns: Class to gather field information from (or class namespace)
724
- :return: dict of field_name: Field(...)
842
+ :return: dict of field_name: Field(...) and modifications to be performed by the builder
725
843
  """
726
844
  if isinstance(cls_or_ns, (_MappingProxyType, dict)):
727
845
  cls_dict = cls_or_ns
@@ -883,6 +1001,7 @@ def make_field_gatherer(
883
1001
  def make_unified_gatherer(
884
1002
  field_type=Field,
885
1003
  leave_default_values=False,
1004
+ ignore_annotations=False,
886
1005
  ):
887
1006
  """
888
1007
  Create a gatherer that will work via first slots, then
@@ -891,6 +1010,7 @@ def make_unified_gatherer(
891
1010
 
892
1011
  :param field_type: The field class to use for gathering
893
1012
  :param leave_default_values: leave default values in place
1013
+ :param ignore_annotations: don't attempt to read annotations
894
1014
  :return: gatherer function
895
1015
  """
896
1016
  slot_g = make_slot_gatherer(field_type)
@@ -903,27 +1023,35 @@ def make_unified_gatherer(
903
1023
  else:
904
1024
  cls_dict = cls_or_ns.__dict__
905
1025
 
1026
+ cls_gathered = cls_dict.get(GATHERED_DATA)
1027
+ if cls_gathered:
1028
+ return pre_gathered_gatherer(cls_dict)
1029
+
906
1030
  cls_slots = cls_dict.get("__slots__")
907
1031
 
908
1032
  if isinstance(cls_slots, SlotFields):
909
1033
  return slot_g(cls_dict)
910
1034
 
911
- # To choose between annotation and attribute gatherers
912
- # compare sets of names.
913
- # Don't bother evaluating string annotations, as we only need names
914
- cls_annotations = get_ns_annotations(cls_dict)
915
- cls_attributes = {
916
- k: v for k, v in cls_dict.items() if isinstance(v, field_type)
917
- }
1035
+ if ignore_annotations:
1036
+ return attrib_g(cls_dict)
1037
+ else:
1038
+ # To choose between annotation and attribute gatherers
1039
+ # compare sets of names.
1040
+ # Don't bother evaluating string annotations, as we only need names
1041
+ cls_annotations = get_ns_annotations(cls_dict)
1042
+ cls_attributes = {
1043
+ k: v for k, v in cls_dict.items() if isinstance(v, field_type)
1044
+ }
918
1045
 
919
- cls_annotation_names = cls_annotations.keys()
920
- cls_attribute_names = cls_attributes.keys()
1046
+ cls_annotation_names = cls_annotations.keys()
1047
+ cls_attribute_names = cls_attributes.keys()
921
1048
 
922
- if set(cls_annotation_names).issuperset(set(cls_attribute_names)):
923
- # All `Field` values have annotations, so use annotation gatherer
924
- return anno_g(cls_dict)
1049
+ if set(cls_annotation_names).issuperset(set(cls_attribute_names)):
1050
+ # All `Field` values have annotations, so use annotation gatherer
1051
+ return anno_g(cls_dict)
1052
+
1053
+ return attrib_g(cls_dict)
925
1054
 
926
- return attrib_g(cls_dict)
927
1055
  return field_unified_gatherer
928
1056
 
929
1057
 
@@ -935,19 +1063,6 @@ annotation_gatherer = make_annotation_gatherer()
935
1063
  unified_gatherer = make_unified_gatherer()
936
1064
 
937
1065
 
938
- # Now the gatherers have been defined, add __repr__ and __eq__ to Field.
939
- _field_methods = {repr_maker, eq_maker}
940
- if _UNDER_TESTING:
941
- _field_methods.update({frozen_setattr_maker, frozen_delattr_maker})
942
-
943
- builder(
944
- Field,
945
- gatherer=slot_gatherer,
946
- methods=_field_methods,
947
- flags={"slotted": True, "kw_only": True},
948
- )
949
-
950
-
951
1066
  def check_argument_order(cls):
952
1067
  """
953
1068
  Raise a SyntaxError if the argument order will be invalid for a generated
@@ -990,17 +1105,3 @@ def slotclass(cls=None, /, *, methods=default_methods, syntax_check=True):
990
1105
  check_argument_order(cls)
991
1106
 
992
1107
  return cls
993
-
994
-
995
- @slotclass
996
- class GatheredFields:
997
- """
998
- A helper gatherer for fields that have been gathered externally.
999
- """
1000
- __slots__ = SlotFields(
1001
- fields=Field(),
1002
- modifications=Field(),
1003
- )
1004
-
1005
- def __call__(self, cls):
1006
- return self.fields, self.modifications
@@ -1,5 +1,6 @@
1
1
  import types
2
2
  import typing
3
+ import typing_extensions
3
4
 
4
5
  import inspect
5
6
 
@@ -13,6 +14,7 @@ __version__: str
13
14
  __version_tuple__: tuple[str | int, ...]
14
15
  INTERNALS_DICT: str
15
16
  META_GATHERER_NAME: str
17
+ GATHERED_DATA: str
16
18
 
17
19
  def get_fields(cls: type, *, local: bool = False) -> dict[str, Field]: ...
18
20
 
@@ -28,11 +30,11 @@ class _NothingType:
28
30
  NOTHING: _NothingType
29
31
  FIELD_NOTHING: _NothingType
30
32
 
31
- # noinspection PyPep8Naming
32
- class _KW_ONLY_TYPE:
33
+ class _KW_ONLY_META(type):
33
34
  def __repr__(self) -> str: ...
34
35
 
35
- KW_ONLY: _KW_ONLY_TYPE
36
+ class KW_ONLY(metaclass=_KW_ONLY_META): ...
37
+
36
38
  # Stub Only
37
39
  @typing.type_check_only
38
40
  class _CodegenType(typing.Protocol):
@@ -56,7 +58,7 @@ class MethodMaker:
56
58
  def __get__(self, instance, cls) -> Callable: ...
57
59
 
58
60
  class _SignatureMaker:
59
- def __get__(self, instance, cls) -> inspect.Signature: ...
61
+ def __get__(self, instance, cls=None) -> typing_extensions.Never: ...
60
62
 
61
63
  signature_maker: _SignatureMaker
62
64
 
@@ -122,7 +124,8 @@ class SlotMakerMeta(type):
122
124
  name: str,
123
125
  bases: tuple[type, ...],
124
126
  ns: dict[str, typing.Any],
125
- slots: bool = True,
127
+ slots: bool = ...,
128
+ gatherer: Callable[[type], tuple[dict[str, Field], dict[str, typing.Any]]] | None = ...,
126
129
  **kwargs: typing.Any,
127
130
  ) -> _T: ...
128
131
 
@@ -139,7 +142,7 @@ class Field(metaclass=SlotMakerMeta):
139
142
 
140
143
  __slots__: dict[str, str]
141
144
  __classbuilder_internals__: dict
142
- __signature__: inspect.Signature
145
+ __signature__: _SignatureMaker
143
146
 
144
147
  def __init__(
145
148
  self,
@@ -167,6 +170,9 @@ class Field(metaclass=SlotMakerMeta):
167
170
  _ReturnsField = Callable[..., Field]
168
171
  _FieldType = typing.TypeVar("_FieldType", bound=Field)
169
172
 
173
+ def pre_gathered_gatherer(
174
+ cls_or_ns: type | _CopiableMappings
175
+ ) -> tuple[dict[str, Field | _FieldType], dict[str, typing.Any]]: ...
170
176
 
171
177
  @typing.overload
172
178
  def make_slot_gatherer(
@@ -206,13 +212,15 @@ def make_field_gatherer(
206
212
  @typing.overload
207
213
  def make_unified_gatherer(
208
214
  field_type: type[_FieldType],
209
- leave_default_values: bool = False,
215
+ leave_default_values: bool = ...,
216
+ ignore_annotations: bool = ...,
210
217
  ) -> Callable[[type | _CopiableMappings], tuple[dict[str, _FieldType], dict[str, typing.Any]]]: ...
211
218
 
212
219
  @typing.overload
213
220
  def make_unified_gatherer(
214
- field_type: _ReturnsField = Field,
215
- leave_default_values: bool = False,
221
+ field_type: _ReturnsField = ...,
222
+ leave_default_values: bool = ...,
223
+ ignore_annotations: bool = ...,
216
224
  ) -> Callable[[type | _CopiableMappings], tuple[dict[str, Field], dict[str, typing.Any]]]: ...
217
225
 
218
226
 
@@ -246,7 +254,7 @@ def slotclass(
246
254
  _gatherer_type = Callable[[type | _CopiableMappings], tuple[dict[str, Field], dict[str, typing.Any]]]
247
255
 
248
256
  class GatheredFields:
249
- __slots__: dict[str, None]
257
+ __slots__: tuple[str, ...]
250
258
 
251
259
  fields: dict[str, Field]
252
260
  modifications: dict[str, typing.Any]
@@ -262,4 +270,4 @@ class GatheredFields:
262
270
 
263
271
  def __repr__(self) -> str: ...
264
272
  def __eq__(self, other) -> bool: ...
265
- def __call__(self, cls: type) -> tuple[dict[str, Field], dict[str, typing.Any]]: ...
273
+ def __call__(self, cls_dict: type | dict[str, typing.Any]) -> tuple[dict[str, Field], dict[str, typing.Any]]: ...
@@ -1,2 +1,2 @@
1
- __version__ = "0.9.1"
2
- __version_tuple__ = (0, 9, 1)
1
+ __version__ = "0.10.1"
2
+ __version_tuple__ = (0, 10, 1)
@@ -33,6 +33,29 @@ class _LazyAnnotationLib:
33
33
  _lazy_annotationlib = _LazyAnnotationLib()
34
34
 
35
35
 
36
+ def get_func_annotations(func):
37
+ """
38
+ Given a function, return the annotations dictionary
39
+
40
+ :param func: function object
41
+ :return: dictionary of annotations
42
+ """
43
+ # This method exists for use by prefab in getting annotations from
44
+ # the __prefab_post_init__ function
45
+ try:
46
+ annotations = func.__annotations__
47
+ except Exception:
48
+ if sys.version_info >= (3, 14):
49
+ annotations = _lazy_annotationlib.get_annotations(
50
+ func,
51
+ format=_lazy_annotationlib.Format.FORWARDREF,
52
+ )
53
+ else:
54
+ raise
55
+
56
+ return annotations
57
+
58
+
36
59
  def get_ns_annotations(ns):
37
60
  """
38
61
  Given a class namespace, attempt to retrieve the
@@ -70,19 +93,8 @@ def is_classvar(hint):
70
93
  else:
71
94
  _typing = sys.modules.get("typing")
72
95
  if _typing:
73
- # Annotated is a nightmare I'm never waking up from
74
- # 3.8 and 3.9 need Annotated from typing_extensions
75
- # 3.8 also needs get_origin from typing_extensions
76
- if sys.version_info < (3, 10):
77
- _typing_extensions = sys.modules.get("typing_extensions")
78
- if _typing_extensions:
79
- _Annotated = _typing_extensions.Annotated
80
- _get_origin = _typing_extensions.get_origin
81
- else:
82
- _Annotated, _get_origin = None, None
83
- else:
84
- _Annotated = _typing.Annotated
85
- _get_origin = _typing.get_origin
96
+ _Annotated = _typing.Annotated
97
+ _get_origin = _typing.get_origin
86
98
 
87
99
  if _Annotated and _get_origin(hint) is _Annotated:
88
100
  hint = getattr(hint, "__origin__", None)
@@ -3,6 +3,10 @@ import types
3
3
 
4
4
  _CopiableMappings = dict[str, typing.Any] | types.MappingProxyType[str, typing.Any]
5
5
 
6
+ def get_func_annotations(
7
+ func: types.FunctionType,
8
+ ) -> dict[str, typing.Any]: ...
9
+
6
10
  def get_ns_annotations(
7
11
  ns: _CopiableMappings,
8
12
  ) -> dict[str, typing.Any]: ...
@@ -34,6 +34,8 @@ from . import (
34
34
  get_repr_generator,
35
35
  )
36
36
 
37
+ from .annotations import get_func_annotations
38
+
37
39
  # These aren't used but are re-exported for ease of use
38
40
  # noinspection PyUnresolvedReferences
39
41
  from . import SlotFields, KW_ONLY # noqa: F401
@@ -98,7 +100,7 @@ def init_generator(cls, funcname="__init__"):
98
100
  func_arglist.extend(arglist)
99
101
 
100
102
  if extra_funcname == POST_INIT_FUNC:
101
- post_init_annotations.update(func.__annotations__)
103
+ post_init_annotations.update(get_func_annotations(func))
102
104
 
103
105
  pos_arglist = []
104
106
  kw_only_arglist = []
@@ -204,11 +206,11 @@ def init_generator(cls, funcname="__init__"):
204
206
  post_init_call = ""
205
207
 
206
208
  code = (
207
- f"def {funcname}(self, {args}):\n"
209
+ f"def {funcname}(self, {args}) -> None:\n"
208
210
  f"{pre_init_call}\n"
209
211
  f"{body}\n"
210
212
  f"{post_init_call}\n"
211
- )
213
+ )
212
214
 
213
215
  return GeneratedCode(code, globs)
214
216
 
@@ -551,8 +553,7 @@ def _make_prefab(
551
553
  return cls
552
554
 
553
555
 
554
- class Prefab(metaclass=SlotMakerMeta):
555
- _meta_gatherer = prefab_gatherer
556
+ class Prefab(metaclass=SlotMakerMeta, gatherer=prefab_gatherer):
556
557
  __slots__ = {} # type: ignore
557
558
 
558
559
  # noinspection PyShadowingBuiltins
@@ -13,6 +13,7 @@ from . import (
13
13
  GeneratedCode,
14
14
  MethodMaker,
15
15
  SlotMakerMeta,
16
+ _SignatureMaker
16
17
  )
17
18
 
18
19
  from . import SlotFields as SlotFields, KW_ONLY as KW_ONLY
@@ -47,7 +48,8 @@ hash_maker: MethodMaker
47
48
 
48
49
  class Attribute(Field):
49
50
  __slots__: dict
50
- __signature__: inspect.Signature
51
+ __signature__: _SignatureMaker
52
+ __classbuilder_gathered_fields__: tuple[dict[str, Field], dict[str, typing.Any]]
51
53
 
52
54
  iter: bool
53
55
  serialize: bool
@@ -1,12 +1,10 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: ducktools-classbuilder
3
- Version: 0.9.1
3
+ Version: 0.10.1
4
4
  Summary: Toolkit for creating class boilerplate generators
5
5
  Author: David C Ellis
6
6
  Project-URL: Homepage, https://github.com/davidcellis/ducktools-classbuilder
7
7
  Classifier: Development Status :: 4 - Beta
8
- Classifier: Programming Language :: Python :: 3.8
9
- Classifier: Programming Language :: Python :: 3.9
10
8
  Classifier: Programming Language :: Python :: 3.10
11
9
  Classifier: Programming Language :: Python :: 3.11
12
10
  Classifier: Programming Language :: Python :: 3.12
@@ -14,22 +12,14 @@ Classifier: Programming Language :: Python :: 3.13
14
12
  Classifier: Programming Language :: Python :: 3.14
15
13
  Classifier: Operating System :: OS Independent
16
14
  Classifier: License :: OSI Approved :: MIT License
17
- Requires-Python: >=3.8
15
+ Requires-Python: >=3.10
18
16
  Description-Content-Type: text/markdown
19
17
  License-File: LICENSE
20
18
  Provides-Extra: docs
21
- Requires-Dist: sphinx ; extra == 'docs'
22
- Requires-Dist: myst-parser ; extra == 'docs'
23
- Requires-Dist: sphinx-rtd-theme ; extra == 'docs'
24
- Provides-Extra: performance_tests
25
- Requires-Dist: attrs ; extra == 'performance_tests'
26
- Requires-Dist: pydantic ; extra == 'performance_tests'
27
- Provides-Extra: testing
28
- Requires-Dist: pytest >=8.2 ; extra == 'testing'
29
- Requires-Dist: pytest-cov ; extra == 'testing'
30
- Requires-Dist: typing-extensions ; extra == 'testing'
31
- Provides-Extra: type_checking
32
- Requires-Dist: mypy ; extra == 'type_checking'
19
+ Requires-Dist: sphinx>=8.1; extra == "docs"
20
+ Requires-Dist: myst-parser>=4.0; extra == "docs"
21
+ Requires-Dist: sphinx_rtd_theme>=3.0; extra == "docs"
22
+ Dynamic: license-file
33
23
 
34
24
  # Ducktools: Class Builder #
35
25
 
@@ -0,0 +1,13 @@
1
+ ducktools/classbuilder/__init__.py,sha256=zJuGJ8_1EPEYJqO0XXDuknwGD1MxPJSZXS5_wFAW6B0,37299
2
+ ducktools/classbuilder/__init__.pyi,sha256=NazFRfLcMpU533jGixaWHV_kARtc9J4-xXX-gkD8rFY,8177
3
+ ducktools/classbuilder/_version.py,sha256=kdUPXlUQWcTv2wnqFkwrCx7Ue7WU2JVPnG1UU0gmLck,54
4
+ ducktools/classbuilder/annotations.py,sha256=ImEEuzEFUrGNRMlAl7YN-H8PZT1FOK8D_yL1LygNdmo,3626
5
+ ducktools/classbuilder/annotations.pyi,sha256=zAKJbEN1klvZu71ShuvKbf6OE7ddKb5USC6MbOjRPfY,336
6
+ ducktools/classbuilder/prefab.py,sha256=RJfOv9yzJsySaBz6w4e5ZP8Q7K3LWX6_Buuh2zD9YDQ,24689
7
+ ducktools/classbuilder/prefab.pyi,sha256=eZLjFhM-oHeRBONpCWSb1wLJKGs5Xiq0w_Jeor8Go-w,6525
8
+ ducktools/classbuilder/py.typed,sha256=la67KBlbjXN-_-DfGNcdOcjYumVpKG_Tkw-8n5dnGB4,8
9
+ ducktools_classbuilder-0.10.1.dist-info/licenses/LICENSE,sha256=6Thz9Dbw8R4fWInl6sGl8Rj3UnKnRbDwrc6jZerpugQ,1070
10
+ ducktools_classbuilder-0.10.1.dist-info/METADATA,sha256=LkjNjXyb49v3vYBEvbldkThl9xAgCHhVc8dPNgiMsxw,9231
11
+ ducktools_classbuilder-0.10.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
+ ducktools_classbuilder-0.10.1.dist-info/top_level.txt,sha256=uSDLtio3ZFqdwcsMJ2O5yhjB4Q3ytbBWbA8rJREganc,10
13
+ ducktools_classbuilder-0.10.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.3.2)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,13 +0,0 @@
1
- ducktools/classbuilder/__init__.py,sha256=5vagbDiErapPudNG5-gP3yarlA6-q8IriMcRrAzBBr4,33433
2
- ducktools/classbuilder/__init__.pyi,sha256=6nEfVyeF1HE4YlvcNkFetACqMd1EObu3hYwoLYBbRNo,7796
3
- ducktools/classbuilder/_version.py,sha256=Ti7IekFg9SsQHXFYa9yzblMN29C0yL2OG6aLOfnSQOc,52
4
- ducktools/classbuilder/annotations.py,sha256=VEZsCM8lwfhaWrQi8dUOAkicYHxUHaSAyM-FzL34wXI,3583
5
- ducktools/classbuilder/annotations.pyi,sha256=c5vYtULdDgMYWtkzeYMsHIbmnEuT2Ru-nNZieWvYuQ4,247
6
- ducktools/classbuilder/prefab.py,sha256=RiVbESwFiPv3Y4PpclSZAwdwBomILbhGEF8XHAaDzW4,24643
7
- ducktools/classbuilder/prefab.pyi,sha256=_Tm2V97Udt3-WlbBRuOSt11PPYdw1TrFeSd4Mcs2EKM,6422
8
- ducktools/classbuilder/py.typed,sha256=la67KBlbjXN-_-DfGNcdOcjYumVpKG_Tkw-8n5dnGB4,8
9
- ducktools_classbuilder-0.9.1.dist-info/LICENSE,sha256=6Thz9Dbw8R4fWInl6sGl8Rj3UnKnRbDwrc6jZerpugQ,1070
10
- ducktools_classbuilder-0.9.1.dist-info/METADATA,sha256=45E7CeSoBEii1Tra1U16WUvcBepEQ4vCsay_y_2UinY,9687
11
- ducktools_classbuilder-0.9.1.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
12
- ducktools_classbuilder-0.9.1.dist-info/top_level.txt,sha256=uSDLtio3ZFqdwcsMJ2O5yhjB4Q3ytbBWbA8rJREganc,10
13
- ducktools_classbuilder-0.9.1.dist-info/RECORD,,