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

@@ -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:
@@ -385,6 +389,36 @@ def eq_generator(cls, funcname="__eq__"):
385
389
  return GeneratedCode(code, globs)
386
390
 
387
391
 
392
+ def replace_generator(cls, funcname="__replace__"):
393
+ # Generate the replace method for built classes
394
+ # unlike the dataclasses implementation this is generated
395
+ attribs = get_fields(cls)
396
+
397
+ # This is essentially the as_dict generator for prefabs
398
+ # except based on attrib.init instead of .serialize
399
+ vals = ", ".join(
400
+ f"'{name}': self.{name}"
401
+ for name, attrib in attribs.items()
402
+ if attrib.init
403
+ )
404
+ init_dict = f"{{{vals}}}"
405
+
406
+ code = (
407
+ f"def {funcname}(self, /, **changes):\n"
408
+ f" new_kwargs = {init_dict}\n"
409
+ f" for name, value in changes.items():\n"
410
+ f" if name not in new_kwargs:\n"
411
+ f" raise TypeError(\n"
412
+ f" f\"{{name!r}} is not a valid replacable \"\n"
413
+ f" f\"field on {{self.__class__.__name__!r}}\"\n"
414
+ f" )\n"
415
+ f" new_kwargs[name] = value\n"
416
+ f" return self.__class__(**new_kwargs)\n"
417
+ )
418
+ globs = {}
419
+ return GeneratedCode(code, globs)
420
+
421
+
388
422
  def frozen_setattr_generator(cls, funcname="__setattr__"):
389
423
  globs = {}
390
424
  field_names = set(get_fields(cls))
@@ -433,6 +467,7 @@ def frozen_delattr_generator(cls, funcname="__delattr__"):
433
467
  init_maker = MethodMaker("__init__", init_generator)
434
468
  repr_maker = MethodMaker("__repr__", repr_generator)
435
469
  eq_maker = MethodMaker("__eq__", eq_generator)
470
+ replace_maker = MethodMaker("__replace__", replace_generator)
436
471
  frozen_setattr_maker = MethodMaker("__setattr__", frozen_setattr_generator)
437
472
  frozen_delattr_maker = MethodMaker("__delattr__", frozen_delattr_generator)
438
473
  default_methods = frozenset({init_maker, repr_maker, eq_maker})
@@ -540,21 +575,25 @@ class SlotMakerMeta(type):
540
575
 
541
576
  Will not convert `ClassVar` hinted values.
542
577
  """
543
- def __new__(cls, name, bases, ns, slots=True, **kwargs):
578
+ def __new__(cls, name, bases, ns, slots=True, gatherer=None, **kwargs):
544
579
  # This should only run if slots=True is declared
545
580
  # and __slots__ have not already been defined
546
- if slots and "__slots__" not in ns:
581
+ if slots and "__slots__" not in ns:
547
582
  # Check if a different gatherer has been set in any base classes
548
583
  # Default to unified gatherer
549
- gatherer = ns.get(META_GATHERER_NAME, None)
550
- if not gatherer:
551
- for base in bases:
552
- if g := getattr(base, META_GATHERER_NAME, None):
553
- gatherer = g
554
- break
584
+ if gatherer is None:
585
+ gatherer = ns.get(META_GATHERER_NAME, None)
586
+ if not gatherer:
587
+ for base in bases:
588
+ if g := getattr(base, META_GATHERER_NAME, None):
589
+ gatherer = g
590
+ break
591
+
592
+ if not gatherer:
593
+ gatherer = unified_gatherer
555
594
 
556
- if not gatherer:
557
- gatherer = unified_gatherer
595
+ # Set the gatherer in the namespace
596
+ ns[META_GATHERER_NAME] = gatherer
558
597
 
559
598
  # Obtain slots from annotations or attributes
560
599
  cls_fields, cls_modifications = gatherer(ns)
@@ -564,14 +603,54 @@ class SlotMakerMeta(type):
564
603
  else:
565
604
  ns[k] = v
566
605
 
606
+ slots = {}
607
+ fields = {}
608
+
609
+ for k, v in cls_fields.items():
610
+ slots[k] = v.doc
611
+ if k not in {"__weakref__", "__dict__"}:
612
+ fields[k] = v
613
+
567
614
  # Place slots *after* everything else to be safe
568
- ns["__slots__"] = SlotFields(cls_fields)
615
+ ns["__slots__"] = slots
616
+
617
+ # Place pre-gathered field data - modifications are already applied
618
+ modifications = {}
619
+ ns[GATHERED_DATA] = fields, modifications
620
+
621
+ else:
622
+ if gatherer is not None:
623
+ ns[META_GATHERER_NAME] = gatherer
569
624
 
570
625
  new_cls = super().__new__(cls, name, bases, ns, **kwargs)
571
626
 
572
627
  return new_cls
573
628
 
574
629
 
630
+ # This class is set up before fields as it will be used to generate the Fields
631
+ # for Field itself so Field can have generated __eq__, __repr__ and other methods
632
+ class GatheredFields:
633
+ """
634
+ Helper class to store gathered field data
635
+ """
636
+ __slots__ = ("fields", "modifications")
637
+
638
+ def __init__(self, fields, modifications):
639
+ self.fields = fields
640
+ self.modifications = modifications
641
+
642
+ def __eq__(self, other):
643
+ if type(self) is type(other):
644
+ return self.fields == other.fields and self.modifications == other.modifications
645
+
646
+ def __repr__(self):
647
+ return f"{type(self).__name__}(fields={self.fields!r}, modifications={self.modifications!r})"
648
+
649
+ def __call__(self, cls_dict):
650
+ # cls_dict will be provided, but isn't needed
651
+ return self.fields, self.modifications
652
+
653
+
575
654
  # The Field class can finally be defined.
576
655
  # The __init__ method has to be written manually so Fields can be created
577
656
  # However after this, the other methods can be generated.
@@ -597,17 +676,18 @@ class Field(metaclass=SlotMakerMeta):
597
676
  :param compare: Include in the class __eq__.
598
677
  :param kw_only: Make this a keyword only parameter in __init__.
599
678
  """
600
- # If this base class did not define __slots__ the metaclass would break it.
601
- # This will be replaced by the builder.
602
- __slots__ = SlotFields(
603
- default=NOTHING,
604
- default_factory=NOTHING,
605
- type=NOTHING,
606
- doc=None,
607
- init=True,
608
- repr=True,
609
- compare=True,
610
- kw_only=False,
679
+
680
+ # Plain slots are required as part of bootstrapping
681
+ # This prevents SlotMakerMeta from trying to generate 'Field's
682
+ __slots__ = (
683
+ "default",
684
+ "default_factory",
685
+ "type",
686
+ "doc",
687
+ "init",
688
+ "repr",
689
+ "compare",
690
+ "kw_only",
611
691
  )
612
692
 
613
693
  # noinspection PyShadowingBuiltins
@@ -641,6 +721,7 @@ class Field(metaclass=SlotMakerMeta):
641
721
  self.validate_field()
642
722
 
643
723
  def __init_subclass__(cls, frozen=False):
724
+ # Subclasses of Field can be created as if they are dataclasses
644
725
  field_methods = {_field_init_maker, repr_maker, eq_maker}
645
726
  if frozen or _UNDER_TESTING:
646
727
  field_methods.update({frozen_setattr_maker, frozen_delattr_maker})
@@ -676,6 +757,66 @@ class Field(metaclass=SlotMakerMeta):
676
757
  return cls(**argument_dict)
677
758
 
678
759
 
760
+ def _build_field():
761
+ # Complete the construction of the Field class
762
+ field_docs = {
763
+ "default": "Standard default value to be used for attributes with this field.",
764
+ "default_factory":
765
+ "A zero-argument function to be called to generate a default value, "
766
+ "useful for mutable obects like lists.",
767
+ "type": "The type of the attribute to be assigned by this field.",
768
+ "doc":
769
+ "The documentation for the attribute that appears when calling "
770
+ "help(...) on the class. (Only in slotted classes).",
771
+ "init": "Include this attribute in the class __init__ parameters.",
772
+ "repr": "Include this attribute in the class __repr__",
773
+ "compare": "Include this attribute in the class __eq__ method",
774
+ "kw_only": "Make this a keyword only parameter in __init__",
775
+ }
776
+
777
+ fields = {
778
+ "default": Field(default=NOTHING, doc=field_docs["default"]),
779
+ "default_factory": Field(default=NOTHING, doc=field_docs["default_factory"]),
780
+ "type": Field(default=NOTHING, doc=field_docs["type"]),
781
+ "doc": Field(default=None, doc=field_docs["doc"]),
782
+ "init": Field(default=True, doc=field_docs["init"]),
783
+ "repr": Field(default=True, doc=field_docs["repr"]),
784
+ "compare": Field(default=True, doc=field_docs["compare"]),
785
+ "kw_only": Field(default=False, doc=field_docs["kw_only"])
786
+ }
787
+ modifications = {"__slots__": field_docs}
788
+
789
+ field_methods = {repr_maker, eq_maker}
790
+ if _UNDER_TESTING:
791
+ field_methods.update({frozen_setattr_maker, frozen_delattr_maker})
792
+
793
+ builder(
794
+ Field,
795
+ gatherer=GatheredFields(fields, modifications),
796
+ methods=field_methods,
797
+ flags={"slotted": True, "kw_only": True},
798
+ )
799
+
800
+
801
+ _build_field()
802
+ del _build_field
803
+
804
+
805
+ def pre_gathered_gatherer(cls_or_ns):
806
+ """
807
+ Retrieve fields previously gathered by SlotMakerMeta
808
+
809
+ :param cls_or_ns: Class to gather field information from (or class namespace)
810
+ :return: dict of field_name: Field(...) and modifications to be performed by the builder
811
+ """
812
+ if isinstance(cls_or_ns, (_MappingProxyType, dict)):
813
+ cls_dict = cls_or_ns
814
+ else:
815
+ cls_dict = cls_or_ns.__dict__
816
+
817
+ return cls_dict[GATHERED_DATA]
818
+
819
+
679
820
  def make_slot_gatherer(field_type=Field):
680
821
  """
681
822
  Create a new annotation gatherer that will work with `Field` instances
@@ -690,7 +831,7 @@ def make_slot_gatherer(field_type=Field):
690
831
  Gather field information for class generation based on __slots__
691
832
 
692
833
  :param cls_or_ns: Class to gather field information from (or class namespace)
693
- :return: dict of field_name: Field(...)
834
+ :return: dict of field_name: Field(...) and modifications to be performed by the builder
694
835
  """
695
836
  if isinstance(cls_or_ns, (_MappingProxyType, dict)):
696
837
  cls_dict = cls_or_ns
@@ -852,6 +993,7 @@ def make_field_gatherer(
852
993
  def make_unified_gatherer(
853
994
  field_type=Field,
854
995
  leave_default_values=False,
996
+ ignore_annotations=False,
855
997
  ):
856
998
  """
857
999
  Create a gatherer that will work via first slots, then
@@ -860,6 +1002,7 @@ def make_unified_gatherer(
860
1002
 
861
1003
  :param field_type: The field class to use for gathering
862
1004
  :param leave_default_values: leave default values in place
1005
+ :param ignore_annotations: don't attempt to read annotations
863
1006
  :return: gatherer function
864
1007
  """
865
1008
  slot_g = make_slot_gatherer(field_type)
@@ -872,27 +1015,35 @@ def make_unified_gatherer(
872
1015
  else:
873
1016
  cls_dict = cls_or_ns.__dict__
874
1017
 
1018
+ cls_gathered = cls_dict.get(GATHERED_DATA)
1019
+ if cls_gathered:
1020
+ return pre_gathered_gatherer(cls_dict)
1021
+
875
1022
  cls_slots = cls_dict.get("__slots__")
876
1023
 
877
1024
  if isinstance(cls_slots, SlotFields):
878
1025
  return slot_g(cls_dict)
879
1026
 
880
- # To choose between annotation and attribute gatherers
881
- # compare sets of names.
882
- # Don't bother evaluating string annotations, as we only need names
883
- cls_annotations = get_ns_annotations(cls_dict)
884
- cls_attributes = {
885
- k: v for k, v in cls_dict.items() if isinstance(v, field_type)
886
- }
1027
+ if ignore_annotations:
1028
+ return attrib_g(cls_dict)
1029
+ else:
1030
+ # To choose between annotation and attribute gatherers
1031
+ # compare sets of names.
1032
+ # Don't bother evaluating string annotations, as we only need names
1033
+ cls_annotations = get_ns_annotations(cls_dict)
1034
+ cls_attributes = {
1035
+ k: v for k, v in cls_dict.items() if isinstance(v, field_type)
1036
+ }
887
1037
 
888
- cls_annotation_names = cls_annotations.keys()
889
- cls_attribute_names = cls_attributes.keys()
1038
+ cls_annotation_names = cls_annotations.keys()
1039
+ cls_attribute_names = cls_attributes.keys()
890
1040
 
891
- if set(cls_annotation_names).issuperset(set(cls_attribute_names)):
892
- # All `Field` values have annotations, so use annotation gatherer
893
- return anno_g(cls_dict)
1041
+ if set(cls_annotation_names).issuperset(set(cls_attribute_names)):
1042
+ # All `Field` values have annotations, so use annotation gatherer
1043
+ return anno_g(cls_dict)
894
1044
 
895
- return attrib_g(cls_dict)
1045
+ return attrib_g(cls_dict)
1046
+
896
1047
  return field_unified_gatherer
897
1048
 
898
1049
 
@@ -904,19 +1055,6 @@ annotation_gatherer = make_annotation_gatherer()
904
1055
  unified_gatherer = make_unified_gatherer()
905
1056
 
906
1057
 
907
- # Now the gatherers have been defined, add __repr__ and __eq__ to Field.
908
- _field_methods = {repr_maker, eq_maker}
909
- if _UNDER_TESTING:
910
- _field_methods.update({frozen_setattr_maker, frozen_delattr_maker})
911
-
912
- builder(
913
- Field,
914
- gatherer=slot_gatherer,
915
- methods=_field_methods,
916
- flags={"slotted": True, "kw_only": True},
917
- )
918
-
919
-
920
1058
  def check_argument_order(cls):
921
1059
  """
922
1060
  Raise a SyntaxError if the argument order will be invalid for a generated
@@ -959,17 +1097,3 @@ def slotclass(cls=None, /, *, methods=default_methods, syntax_check=True):
959
1097
  check_argument_order(cls)
960
1098
 
961
1099
  return cls
962
-
963
-
964
- @slotclass
965
- class GatheredFields:
966
- """
967
- A helper gatherer for fields that have been gathered externally.
968
- """
969
- __slots__ = SlotFields(
970
- fields=Field(),
971
- modifications=Field(),
972
- )
973
-
974
- def __call__(self, cls):
975
- return self.fields, self.modifications
@@ -13,6 +13,7 @@ __version__: str
13
13
  __version_tuple__: tuple[str | int, ...]
14
14
  INTERNALS_DICT: str
15
15
  META_GATHERER_NAME: str
16
+ GATHERED_DATA: str
16
17
 
17
18
  def get_fields(cls: type, *, local: bool = False) -> dict[str, Field]: ...
18
19
 
@@ -28,11 +29,11 @@ class _NothingType:
28
29
  NOTHING: _NothingType
29
30
  FIELD_NOTHING: _NothingType
30
31
 
31
- # noinspection PyPep8Naming
32
- class _KW_ONLY_TYPE:
32
+ class _KW_ONLY_META(type):
33
33
  def __repr__(self) -> str: ...
34
34
 
35
- KW_ONLY: _KW_ONLY_TYPE
35
+ class KW_ONLY(metaclass=_KW_ONLY_META): ...
36
+
36
37
  # Stub Only
37
38
  @typing.type_check_only
38
39
  class _CodegenType(typing.Protocol):
@@ -73,6 +74,7 @@ def get_repr_generator(
73
74
  ) -> _CodegenType: ...
74
75
  def repr_generator(cls: type, funcname: str = "__repr__") -> GeneratedCode: ...
75
76
  def eq_generator(cls: type, funcname: str = "__eq__") -> GeneratedCode: ...
77
+ def replace_generator(cls: type, funcname: str = "__replace__") -> GeneratedCode: ...
76
78
 
77
79
  def frozen_setattr_generator(cls: type, funcname: str = "__setattr__") -> GeneratedCode: ...
78
80
 
@@ -81,6 +83,7 @@ def frozen_delattr_generator(cls: type, funcname: str = "__delattr__") -> Genera
81
83
  init_maker: MethodMaker
82
84
  repr_maker: MethodMaker
83
85
  eq_maker: MethodMaker
86
+ replace_maker: MethodMaker
84
87
  frozen_setattr_maker: MethodMaker
85
88
  frozen_delattr_maker: MethodMaker
86
89
  default_methods: frozenset[MethodMaker]
@@ -120,7 +123,8 @@ class SlotMakerMeta(type):
120
123
  name: str,
121
124
  bases: tuple[type, ...],
122
125
  ns: dict[str, typing.Any],
123
- slots: bool = True,
126
+ slots: bool = ...,
127
+ gatherer: Callable[[type], tuple[dict[str, Field], dict[str, typing.Any]]] | None = ...,
124
128
  **kwargs: typing.Any,
125
129
  ) -> _T: ...
126
130
 
@@ -165,6 +169,9 @@ class Field(metaclass=SlotMakerMeta):
165
169
  _ReturnsField = Callable[..., Field]
166
170
  _FieldType = typing.TypeVar("_FieldType", bound=Field)
167
171
 
172
+ def pre_gathered_gatherer(
173
+ cls_or_ns: type | _CopiableMappings
174
+ ) -> tuple[dict[str, Field | _FieldType], dict[str, typing.Any]]: ...
168
175
 
169
176
  @typing.overload
170
177
  def make_slot_gatherer(
@@ -204,13 +211,15 @@ def make_field_gatherer(
204
211
  @typing.overload
205
212
  def make_unified_gatherer(
206
213
  field_type: type[_FieldType],
207
- leave_default_values: bool = False,
214
+ leave_default_values: bool = ...,
215
+ ignore_annotations: bool = ...,
208
216
  ) -> Callable[[type | _CopiableMappings], tuple[dict[str, _FieldType], dict[str, typing.Any]]]: ...
209
217
 
210
218
  @typing.overload
211
219
  def make_unified_gatherer(
212
- field_type: _ReturnsField = Field,
213
- leave_default_values: bool = False,
220
+ field_type: _ReturnsField = ...,
221
+ leave_default_values: bool = ...,
222
+ ignore_annotations: bool = ...,
214
223
  ) -> Callable[[type | _CopiableMappings], tuple[dict[str, Field], dict[str, typing.Any]]]: ...
215
224
 
216
225
 
@@ -244,7 +253,7 @@ def slotclass(
244
253
  _gatherer_type = Callable[[type | _CopiableMappings], tuple[dict[str, Field], dict[str, typing.Any]]]
245
254
 
246
255
  class GatheredFields:
247
- __slots__: dict[str, None]
256
+ __slots__: tuple[str, ...]
248
257
 
249
258
  fields: dict[str, Field]
250
259
  modifications: dict[str, typing.Any]
@@ -260,4 +269,4 @@ class GatheredFields:
260
269
 
261
270
  def __repr__(self) -> str: ...
262
271
  def __eq__(self, other) -> bool: ...
263
- def __call__(self, cls: type) -> tuple[dict[str, Field], dict[str, typing.Any]]: ...
272
+ 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.0"
2
- __version_tuple__ = (0, 9, 0)
1
+ __version__ = "0.10.0"
2
+ __version_tuple__ = (0, 10, 0)
@@ -70,19 +70,8 @@ def is_classvar(hint):
70
70
  else:
71
71
  _typing = sys.modules.get("typing")
72
72
  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
73
+ _Annotated = _typing.Annotated
74
+ _get_origin = _typing.get_origin
86
75
 
87
76
  if _Annotated and _get_origin(hint) is _Annotated:
88
77
  hint = getattr(hint, "__origin__", None)
@@ -30,7 +30,7 @@ from . import (
30
30
  Field, MethodMaker, GatheredFields, GeneratedCode, SlotMakerMeta,
31
31
  builder, get_flags, get_fields,
32
32
  make_unified_gatherer,
33
- frozen_setattr_maker, frozen_delattr_maker, eq_maker,
33
+ eq_maker, frozen_setattr_maker, frozen_delattr_maker, replace_maker,
34
34
  get_repr_generator,
35
35
  )
36
36
 
@@ -441,6 +441,8 @@ def _make_prefab(
441
441
  if dict_method:
442
442
  methods.add(asdict_maker)
443
443
 
444
+ methods.add(replace_maker)
445
+
444
446
  flags = {
445
447
  "kw_only": kw_only,
446
448
  "slotted": slotted,
@@ -549,8 +551,7 @@ def _make_prefab(
549
551
  return cls
550
552
 
551
553
 
552
- class Prefab(metaclass=SlotMakerMeta):
553
- _meta_gatherer = prefab_gatherer
554
+ class Prefab(metaclass=SlotMakerMeta, gatherer=prefab_gatherer):
554
555
  __slots__ = {} # type: ignore
555
556
 
556
557
  # noinspection PyShadowingBuiltins
@@ -779,3 +780,8 @@ def as_dict(o):
779
780
  for name, attrib in flds.items()
780
781
  if attrib.serialize
781
782
  }
783
+
784
+ def replace(obj, /, **changes):
785
+ if not is_prefab_instance(obj):
786
+ raise TypeError("replace() should be called on prefab instances")
787
+ return obj.__replace__(**changes)
@@ -48,6 +48,7 @@ hash_maker: MethodMaker
48
48
  class Attribute(Field):
49
49
  __slots__: dict
50
50
  __signature__: inspect.Signature
51
+ __classbuilder_gathered_fields__: tuple[dict[str, Field], dict[str, typing.Any]]
51
52
 
52
53
  iter: bool
53
54
  serialize: bool
@@ -238,3 +239,5 @@ def is_prefab(o: typing.Any) -> bool: ...
238
239
  def is_prefab_instance(o: object) -> bool: ...
239
240
 
240
241
  def as_dict(o) -> dict[str, typing.Any]: ...
242
+
243
+ def replace(obj: _T, /, **changes: typing.Any) -> _T: ...
@@ -1,34 +1,25 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: ducktools-classbuilder
3
- Version: 0.9.0
3
+ Version: 0.10.0
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
13
11
  Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Programming Language :: Python :: 3.14
14
13
  Classifier: Operating System :: OS Independent
15
14
  Classifier: License :: OSI Approved :: MIT License
16
- Requires-Python: >=3.8
15
+ Requires-Python: >=3.10
17
16
  Description-Content-Type: text/markdown
18
17
  License-File: LICENSE
19
18
  Provides-Extra: docs
20
- Requires-Dist: sphinx ; extra == 'docs'
21
- Requires-Dist: myst-parser ; extra == 'docs'
22
- Requires-Dist: sphinx-rtd-theme ; extra == 'docs'
23
- Provides-Extra: performance_tests
24
- Requires-Dist: attrs ; extra == 'performance_tests'
25
- Requires-Dist: pydantic ; extra == 'performance_tests'
26
- Provides-Extra: testing
27
- Requires-Dist: pytest >=8.2 ; extra == 'testing'
28
- Requires-Dist: pytest-cov ; extra == 'testing'
29
- Requires-Dist: typing-extensions ; extra == 'testing'
30
- Provides-Extra: type_checking
31
- 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
32
23
 
33
24
  # Ducktools: Class Builder #
34
25
 
@@ -0,0 +1,13 @@
1
+ ducktools/classbuilder/__init__.py,sha256=nZz_pQM6CN7yCT71oOclih4uls73a6SlW-HY2gxzYIs,37244
2
+ ducktools/classbuilder/__init__.pyi,sha256=v9QDO8A0AdPlAPNOXZGYXu1FaAq3yelwWNucdfWudrs,8143
3
+ ducktools/classbuilder/_version.py,sha256=MdjuHfU3W8hJQpD3kGrPvowqtRDz5JMifDCCjNbrskE,54
4
+ ducktools/classbuilder/annotations.py,sha256=GgBvNthDSRvKKZ9R_qxEVtCV3_vGuEBfuqQJFcDAe1s,3005
5
+ ducktools/classbuilder/annotations.pyi,sha256=c5vYtULdDgMYWtkzeYMsHIbmnEuT2Ru-nNZieWvYuQ4,247
6
+ ducktools/classbuilder/prefab.py,sha256=2GldQTRvOcFzB68qXbymeHKiXoyHx7eOjGxeilFIcn0,24632
7
+ ducktools/classbuilder/prefab.pyi,sha256=q5Zca5wAN80GE4uICY6v3EIB6LvIcjOEhzD1pVDK44U,6507
8
+ ducktools/classbuilder/py.typed,sha256=la67KBlbjXN-_-DfGNcdOcjYumVpKG_Tkw-8n5dnGB4,8
9
+ ducktools_classbuilder-0.10.0.dist-info/licenses/LICENSE,sha256=6Thz9Dbw8R4fWInl6sGl8Rj3UnKnRbDwrc6jZerpugQ,1070
10
+ ducktools_classbuilder-0.10.0.dist-info/METADATA,sha256=zrp9Q-klxkuTdsGSGz87rEZ7nxM8FCEppJfiGwqRh-0,9231
11
+ ducktools_classbuilder-0.10.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
+ ducktools_classbuilder-0.10.0.dist-info/top_level.txt,sha256=uSDLtio3ZFqdwcsMJ2O5yhjB4Q3ytbBWbA8rJREganc,10
13
+ ducktools_classbuilder-0.10.0.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=IfMN45iuJJ0BadtmjwjoYhXCLw6WOSQTMk9s_cxGT0M,32312
2
- ducktools/classbuilder/__init__.pyi,sha256=66VOHYndP7z83Bl7QzTOyoF8j2nLFRRFfaQ6-mTdnmo,7683
3
- ducktools/classbuilder/_version.py,sha256=RJaQx44-8b_pifglxqeP0c25pHPZ-Zu98duqjj_ZSOg,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=I1FRb1avVp6u1TPJgEs5QB1pnRH0xh8PQoI3oF8su5Q,24415
7
- ducktools/classbuilder/prefab.pyi,sha256=sd800Hx2jf3CIaIfcurzanKGV-7-Km6_dY8JrM2FRHw,6363
8
- ducktools/classbuilder/py.typed,sha256=la67KBlbjXN-_-DfGNcdOcjYumVpKG_Tkw-8n5dnGB4,8
9
- ducktools_classbuilder-0.9.0.dist-info/LICENSE,sha256=6Thz9Dbw8R4fWInl6sGl8Rj3UnKnRbDwrc6jZerpugQ,1070
10
- ducktools_classbuilder-0.9.0.dist-info/METADATA,sha256=l6eUeDAEMjJKTInGL2Q7oXGSfj0ur0sKy8UWdbtB15c,9636
11
- ducktools_classbuilder-0.9.0.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
12
- ducktools_classbuilder-0.9.0.dist-info/top_level.txt,sha256=uSDLtio3ZFqdwcsMJ2O5yhjB4Q3ytbBWbA8rJREganc,10
13
- ducktools_classbuilder-0.9.0.dist-info/RECORD,,