ducktools-classbuilder 0.10.2__tar.gz → 0.11.1__tar.gz

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.

Files changed (103) hide show
  1. ducktools_classbuilder-0.11.1/MANIFEST.in +5 -0
  2. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/PKG-INFO +2 -2
  3. ducktools_classbuilder-0.11.1/docs/api.md +82 -0
  4. ducktools_classbuilder-0.10.2/docs_code/docs_ex9_annotated.py → ducktools_classbuilder-0.11.1/docs/code_examples/docs_ex09_annotated.py +8 -6
  5. ducktools_classbuilder-0.11.1/docs/code_examples/outputs/docs_ex01_basic.output +36 -0
  6. ducktools_classbuilder-0.11.1/docs/code_examples/outputs/docs_ex02_register.output +4 -0
  7. ducktools_classbuilder-0.11.1/docs/code_examples/outputs/docs_ex03_iterable.output +2 -0
  8. ducktools_classbuilder-0.11.1/docs/code_examples/outputs/docs_ex05_frozen.output +4 -0
  9. ducktools_classbuilder-0.11.1/docs/code_examples/outputs/docs_ex07_posonly.output +4 -0
  10. ducktools_classbuilder-0.11.1/docs/code_examples/outputs/docs_ex08_converters.output +1 -0
  11. ducktools_classbuilder-0.11.1/docs/code_examples/outputs/docs_ex09_annotated.output +33 -0
  12. ducktools_classbuilder-0.11.1/docs/code_examples/outputs/docs_ex10_frozen_attributes.output +1 -0
  13. ducktools_classbuilder-0.11.1/docs/code_examples/outputs/index_example.output +1 -0
  14. ducktools_classbuilder-0.11.1/docs/code_examples/outputs/tutorial_code.output +13 -0
  15. {ducktools_classbuilder-0.10.2/docs_code → ducktools_classbuilder-0.11.1/docs/code_examples}/tutorial_code.py +2 -4
  16. ducktools_classbuilder-0.11.1/docs/extension_examples.md +94 -0
  17. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/docs/index.md +5 -53
  18. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/docs/prefab/index.md +22 -76
  19. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/docs/tutorial.md +8 -9
  20. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/pyproject.toml +1 -1
  21. ducktools_classbuilder-0.11.1/scripts/generate_code_outputs.py +10 -0
  22. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/src/ducktools/classbuilder/__init__.py +106 -65
  23. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/src/ducktools/classbuilder/__init__.pyi +11 -13
  24. ducktools_classbuilder-0.11.1/src/ducktools/classbuilder/_version.py +2 -0
  25. ducktools_classbuilder-0.11.1/src/ducktools/classbuilder/annotations/__init__.py +63 -0
  26. ducktools_classbuilder-0.11.1/src/ducktools/classbuilder/annotations/annotations_314.py +104 -0
  27. ducktools_classbuilder-0.11.1/src/ducktools/classbuilder/annotations/annotations_pre_314.py +42 -0
  28. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/src/ducktools/classbuilder/annotations.pyi +4 -4
  29. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/src/ducktools/classbuilder/prefab.py +108 -28
  30. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/src/ducktools/classbuilder/prefab.pyi +8 -1
  31. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/src/ducktools_classbuilder.egg-info/PKG-INFO +2 -2
  32. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/src/ducktools_classbuilder.egg-info/SOURCES.txt +30 -19
  33. ducktools_classbuilder-0.11.1/tests/_type_support.py +29 -0
  34. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/annotations/test_annotated.py +3 -1
  35. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/annotations/test_annotations_module.py +4 -3
  36. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_creation.py +5 -2
  37. ducktools_classbuilder-0.11.1/tests/prefab/test_ignored_annotations.py +72 -0
  38. ducktools_classbuilder-0.11.1/tests/prefab/test_inheritance.py +177 -0
  39. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_init.py +3 -3
  40. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_internals_dict.py +0 -1
  41. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_replace.py +27 -3
  42. ducktools_classbuilder-0.11.1/tests/py314_tests/test_annotation_unused.py +52 -0
  43. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/py314_tests/test_init_signature.py +55 -13
  44. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/py314_tests/test_init_signature_future.py +0 -1
  45. ducktools_classbuilder-0.11.1/tests/py314_tests/test_string_annotations.py +117 -0
  46. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/test_core.py +0 -1
  47. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/test_slotmakermeta.py +5 -4
  48. ducktools_classbuilder-0.11.1/tests/test_tutorial_code.py +34 -0
  49. ducktools_classbuilder-0.11.1/uv.lock +1065 -0
  50. ducktools_classbuilder-0.10.2/.github/dependabot.yml +0 -7
  51. ducktools_classbuilder-0.10.2/.github/workflows/auto_test.yml +0 -48
  52. ducktools_classbuilder-0.10.2/.github/workflows/publish_to_pypi.yml +0 -87
  53. ducktools_classbuilder-0.10.2/.github/workflows/publish_to_testpypi.yml +0 -55
  54. ducktools_classbuilder-0.10.2/.readthedocs.yaml +0 -24
  55. ducktools_classbuilder-0.10.2/MANIFEST.in +0 -10
  56. ducktools_classbuilder-0.10.2/docs/api.md +0 -42
  57. ducktools_classbuilder-0.10.2/docs/extension_examples.md +0 -680
  58. ducktools_classbuilder-0.10.2/src/ducktools/classbuilder/_version.py +0 -2
  59. ducktools_classbuilder-0.10.2/src/ducktools/classbuilder/annotations.py +0 -137
  60. ducktools_classbuilder-0.10.2/tests/prefab/test_inheritance.py +0 -123
  61. ducktools_classbuilder-0.10.2/tests/py314_tests/_test_support.py +0 -42
  62. ducktools_classbuilder-0.10.2/tests/py314_tests/test_forwardref_annotations.py +0 -59
  63. ducktools_classbuilder-0.10.2/uv.lock +0 -939
  64. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/.gitignore +0 -0
  65. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/LICENSE +0 -0
  66. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/README.md +0 -0
  67. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/docs/Makefile +0 -0
  68. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/docs/approach_vs_tool.md +0 -0
  69. /ducktools_classbuilder-0.10.2/docs_code/docs_ex1_basic.py → /ducktools_classbuilder-0.11.1/docs/code_examples/docs_ex01_basic.py +0 -0
  70. /ducktools_classbuilder-0.10.2/docs_code/docs_ex2_register.py → /ducktools_classbuilder-0.11.1/docs/code_examples/docs_ex02_register.py +0 -0
  71. /ducktools_classbuilder-0.10.2/docs_code/docs_ex3_iterable.py → /ducktools_classbuilder-0.11.1/docs/code_examples/docs_ex03_iterable.py +0 -0
  72. /ducktools_classbuilder-0.10.2/docs_code/docs_ex5_frozen.py → /ducktools_classbuilder-0.11.1/docs/code_examples/docs_ex05_frozen.py +0 -0
  73. /ducktools_classbuilder-0.10.2/docs_code/docs_ex7_posonly.py → /ducktools_classbuilder-0.11.1/docs/code_examples/docs_ex07_posonly.py +0 -0
  74. /ducktools_classbuilder-0.10.2/docs_code/docs_ex8_converters.py → /ducktools_classbuilder-0.11.1/docs/code_examples/docs_ex08_converters.py +0 -0
  75. {ducktools_classbuilder-0.10.2/docs_code → ducktools_classbuilder-0.11.1/docs/code_examples}/docs_ex10_frozen_attributes.py +0 -0
  76. {ducktools_classbuilder-0.10.2/docs_code → ducktools_classbuilder-0.11.1/docs/code_examples}/index_example.py +0 -0
  77. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/docs/conf.py +0 -0
  78. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/docs/generated_code.md +0 -0
  79. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/docs/make.bat +0 -0
  80. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/docs/perf/performance_tests.md +0 -0
  81. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/setup.cfg +0 -0
  82. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/src/ducktools/classbuilder/py.typed +0 -0
  83. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/src/ducktools_classbuilder.egg-info/dependency_links.txt +0 -0
  84. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/src/ducktools_classbuilder.egg-info/requires.txt +0 -0
  85. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/src/ducktools_classbuilder.egg-info/top_level.txt +0 -0
  86. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/annotations/test_future_annotations.py +0 -0
  87. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/conftest.py +0 -0
  88. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/helpers/utils.py +0 -0
  89. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_compare_attrib.py +0 -0
  90. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_construction.py +0 -0
  91. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_dunders.py +0 -0
  92. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_frozen.py +0 -0
  93. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_funcs.py +0 -0
  94. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_hint_syntax.py +0 -0
  95. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_kw_only.py +0 -0
  96. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_pre_post_init.py +0 -0
  97. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_private.py +0 -0
  98. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_repr.py +0 -0
  99. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_slots_novalues.py +0 -0
  100. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_slotted_class.py +0 -0
  101. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_subclass_implementation.py +0 -0
  102. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/py312_tests/test_generic_annotations.py +0 -0
  103. {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/test_field_flags.py +0 -0
@@ -0,0 +1,5 @@
1
+ exclude .readthedocs.yaml
2
+
3
+ prune .github
4
+ prune docs_code
5
+ prune perf
@@ -1,8 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ducktools-classbuilder
3
- Version: 0.10.2
3
+ Version: 0.11.1
4
4
  Summary: Toolkit for creating class boilerplate generators
5
5
  Author: David C Ellis
6
+ License-Expression: MIT
6
7
  Project-URL: Homepage, https://github.com/davidcellis/ducktools-classbuilder
7
8
  Classifier: Development Status :: 4 - Beta
8
9
  Classifier: Programming Language :: Python :: 3.10
@@ -11,7 +12,6 @@ Classifier: Programming Language :: Python :: 3.12
11
12
  Classifier: Programming Language :: Python :: 3.13
12
13
  Classifier: Programming Language :: Python :: 3.14
13
14
  Classifier: Operating System :: OS Independent
14
- Classifier: License :: OSI Approved :: MIT License
15
15
  Requires-Python: >=3.10
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
@@ -0,0 +1,82 @@
1
+ # Builder API Autodocs #
2
+
3
+ ## Slotclass example functions and classes ##
4
+
5
+ ```{eval-rst}
6
+ .. autoclass:: ducktools.classbuilder::Field
7
+ ```
8
+
9
+ ```{eval-rst}
10
+ .. autoclass:: ducktools.classbuilder::SlotFields
11
+ ```
12
+
13
+ ```{eval-rst}
14
+ .. autofunction:: ducktools.classbuilder::slotclass
15
+ ```
16
+
17
+
18
+ ## Builder functions and classes ##
19
+
20
+ ```{eval-rst}
21
+ .. autofunction:: ducktools.classbuilder::builder
22
+ ```
23
+
24
+ ```{eval-rst}
25
+ .. autofunction:: ducktools.classbuilder::get_fields
26
+ ```
27
+
28
+ ```{eval-rst}
29
+ .. autofunction:: ducktools.classbuilder::get_flags
30
+ ```
31
+
32
+ ```{eval-rst}
33
+ .. autofunction:: ducktools.classbuilder::get_methods
34
+ ```
35
+
36
+ ```{eval-rst}
37
+ .. autoclass:: ducktools.classbuilder::MethodMaker
38
+ ```
39
+
40
+ ```{eval-rst}
41
+ .. autofunction:: ducktools.classbuilder::make_unified_gatherer
42
+ ```
43
+
44
+ ## Prefab Class and Attributes ##
45
+
46
+ ```{eval-rst}
47
+ .. autofunction:: ducktools.classbuilder.prefab::prefab
48
+ ```
49
+
50
+ ```{eval-rst}
51
+ .. autoclass:: ducktools.classbuilder.prefab::Prefab
52
+ :members: __init_subclass__
53
+ ```
54
+
55
+ ```{eval-rst}
56
+ .. autofunction:: ducktools.classbuilder.prefab::attribute
57
+ ```
58
+
59
+ ```{eval-rst}
60
+ .. autofunction:: ducktools.classbuilder.prefab::get_attributes
61
+ ```
62
+
63
+ ```{eval-rst}
64
+ .. autofunction:: ducktools.classbuilder.prefab::build_prefab
65
+ ```
66
+
67
+ ```{eval-rst}
68
+ .. autofunction:: ducktools.classbuilder.prefab::is_prefab
69
+ ```
70
+
71
+ ```{eval-rst}
72
+ .. autofunction:: ducktools.classbuilder.prefab::is_prefab_instance
73
+ ```
74
+
75
+ ```{eval-rst}
76
+ .. autofunction:: ducktools.classbuilder.prefab::as_dict
77
+ ```
78
+
79
+ ```{eval-rst}
80
+ .. autofunction:: ducktools.classbuilder.prefab::replace
81
+ ```
82
+
@@ -1,3 +1,8 @@
1
+ # Don't use __future__ annotations with get_ns_annotations in this case
2
+ # as it doesn't evaluate string annotations.
3
+
4
+ # NOTE: In Python 3.14 this will currently only work if there are *no* forward references.
5
+
1
6
  import types
2
7
  from typing import Annotated, Any, ClassVar, get_origin
3
8
 
@@ -6,10 +11,8 @@ from ducktools.classbuilder import (
6
11
  default_methods,
7
12
  get_fields,
8
13
  get_methods,
9
- pre_gathered_gatherer,
10
14
  Field,
11
15
  SlotMakerMeta,
12
- GATHERED_DATA,
13
16
  NOTHING,
14
17
  )
15
18
 
@@ -112,14 +115,13 @@ def annotatedclass(cls=None, *, kw_only=False):
112
115
 
113
116
  # As a base class with slots
114
117
  class AnnotatedClass(metaclass=SlotMakerMeta, gatherer=annotated_gatherer):
115
-
118
+
116
119
  def __init_subclass__(cls, kw_only=False, **kwargs):
117
- pre_gathered = GATHERED_DATA in cls.__dict__
118
120
  slots = "__slots__" in cls.__dict__
119
121
 
120
122
  # if slots is True then fields will already be present in __slots__
121
123
  # Use the slot_gatherer for this case
122
- gatherer = pre_gathered_gatherer if pre_gathered else annotated_gatherer
124
+ gatherer = annotated_gatherer
123
125
 
124
126
  builder(
125
127
  cls,
@@ -177,7 +179,7 @@ if __name__ == "__main__":
177
179
  methods = get_methods(X)
178
180
 
179
181
  # Call the code generators to display the source code
180
- for method in methods.values():
182
+ for _, method in sorted(methods.items()):
181
183
  # Both classes generate identical source code
182
184
  genX = method.code_generator(X)
183
185
  genY = method.code_generator(Y)
@@ -0,0 +1,36 @@
1
+ SlottedDC(the_answer=42, the_question='What do you get if you multiply six by nine?')
2
+ Help on class SlottedDC in module __main__:
3
+
4
+ class SlottedDC(builtins.object)
5
+ | SlottedDC(
6
+ | the_answer=42,
7
+ | the_question='What do you get if you multiply six by nine?'
8
+ | )
9
+ |
10
+ | Methods defined here:
11
+ |
12
+ | __eq__(self, other) from SlottedDC
13
+ |
14
+ | __init__(
15
+ | self,
16
+ | the_answer=42,
17
+ | the_question='What do you get if you multiply six by nine?'
18
+ | ) from SlottedDC
19
+ |
20
+ | __repr__(self) from SlottedDC
21
+ |
22
+ | __signature__
23
+ |
24
+ | ----------------------------------------------------------------------
25
+ | Data descriptors defined here:
26
+ |
27
+ | the_answer
28
+ |
29
+ | the_question
30
+ | Life, the Universe, and Everything
31
+ |
32
+ | ----------------------------------------------------------------------
33
+ | Data and other attributes defined here:
34
+ |
35
+ | __classbuilder_internals__ = {'build_complete': True, 'fields': {'the_...
36
+
@@ -0,0 +1,4 @@
1
+ DataCoords(x=0.0, y=0.0)
2
+ SlotCoords(x=0.0, y=0.0)
3
+ DataCoords is class_register[DataCoords.__name__] = False
4
+ SlotCoords is class_register[SlotCoords.__name__] = True
@@ -0,0 +1,4 @@
1
+ FrozenEx(x=6, y=9, product=42)
2
+ 'FrozenEx' object does not support attribute assignment
3
+ 'FrozenEx' object does not support attribute assignment
4
+ 'FrozenEx' object does not support attribute deletion
@@ -0,0 +1,4 @@
1
+ WorkingEx(42, x=6, y=9)
2
+ WorkingEx(42, x=6, y=9)
3
+ WorkingEx.__init__() got some positional-only arguments passed as keyword arguments: 'a'
4
+ Positional only parameter 'x' follows keyword parameters on 'FailEx'
@@ -0,0 +1 @@
1
+ ConverterEx(unconverted='42', converted=42)
@@ -0,0 +1,33 @@
1
+ X(x='Value of x', a='Not In __init__ signature', c=[], e='Value of e')
2
+
3
+ {'x': Field(default=<NOTHING OBJECT>, default_factory=<NOTHING OBJECT>, type=<class 'str'>, doc=None, init=True, repr=True, compare=True, kw_only=False),
4
+ 'a': Field(default='Not In __init__ signature', default_factory=<NOTHING OBJECT>, type=<class 'int'>, doc=None, init=False, repr=True, compare=True, kw_only=False),
5
+ 'b': Field(default='Not In Repr', default_factory=<NOTHING OBJECT>, type=<class 'str'>, doc=None, init=True, repr=False, compare=True, kw_only=False),
6
+ 'c': Field(default=<NOTHING OBJECT>, default_factory=<class 'list'>, type=list[str], doc=None, init=True, repr=True, compare=False, kw_only=False),
7
+ 'd': Field(default='Not Anywhere', default_factory=<NOTHING OBJECT>, type=<class 'str'>, doc=None, init=False, repr=False, compare=False, kw_only=False),
8
+ 'e': Field(default=<NOTHING OBJECT>, default_factory=<NOTHING OBJECT>, type=<class 'str'>, doc=None, init=True, repr=True, compare=False, kw_only=True)}
9
+
10
+
11
+ Y(x='Value of x', a='Not In __init__ signature', c=[], e='Value of e')
12
+
13
+ Slots: {'x': None, 'a': None, 'b': None, 'c': None, 'd': None, 'e': None}
14
+
15
+ Source:
16
+ def __eq__(self, other):
17
+ return (
18
+ self.x == other.x
19
+ and self.a == other.a
20
+ and self.b == other.b
21
+ ) if self.__class__ is other.__class__ else NotImplemented
22
+
23
+ def __init__(self, x, b=_b_default, c=None, *, e):
24
+ self.x = x
25
+ self.a = _a_default
26
+ self.b = b
27
+ self.c = _c_factory() if c is None else c
28
+ self.d = _d_default
29
+ self.e = e
30
+
31
+ def __repr__(self):
32
+ return f'{type(self).__qualname__}(x={self.x!r}, a={self.a!r}, c={self.c!r}, e={self.e!r})'
33
+
@@ -0,0 +1 @@
1
+ AttributeError("Attribute 'b' does not support assignment")
@@ -0,0 +1 @@
1
+ AnnotatedDC(the_answer=42, the_question='What do you get if you multiply six by nine?')
@@ -0,0 +1,13 @@
1
+ ({'field_1': CustomField(default='First Field', default_factory=<NOTHING OBJECT>, type=<NOTHING OBJECT>, doc=None, init=True, repr=True, compare=True, kw_only=False, report=True),
2
+ 'field_2': CustomField(default='Second Field', default_factory=<NOTHING OBJECT>, type=<NOTHING OBJECT>, doc=None, init=True, repr=True, compare=True, kw_only=False, report=True),
3
+ 'field_3': CustomField(default='Third Field', default_factory=<NOTHING OBJECT>, type=<NOTHING OBJECT>, doc=None, init=True, repr=True, compare=True, kw_only=False, report=False)},
4
+ {'__fields__': None})
5
+ @property
6
+ def report(self):
7
+ return f"Class: CodegenDemo\nfield_1: {self.field_1!r}\nfield_2: {self.field_2!r}\nfield_3: {self.field_3!r}"
8
+ Class: Example
9
+ x: 42
10
+ y: <HIDDEN>
11
+ Class: ExampleSlots
12
+ x: 42
13
+ y: <HIDDEN>
@@ -117,11 +117,9 @@ slot_gatherer = dtbuild.make_slot_gatherer(CustomField)
117
117
 
118
118
  class ReportClass(metaclass=dtbuild.SlotMakerMeta, gatherer=fields_attribute_gatherer):
119
119
  __slots__ = {}
120
-
120
+
121
121
  def __init_subclass__(cls):
122
- # Check if the metaclass has pre-gathered data
123
- pre_gathered = dtbuild.GATHERED_DATA in vars(cls)
124
- gatherer = dtbuild.pre_gathered_gatherer if pre_gathered else fields_attribute_gatherer
122
+ gatherer = fields_attribute_gatherer
125
123
  methods = {
126
124
  dtbuild.eq_maker,
127
125
  dtbuild.repr_maker,
@@ -0,0 +1,94 @@
1
+ # Examples of extending builders #
2
+
3
+ Here are some examples of adding specific features to classes using the tools provided
4
+ by the `ducktools.classbuilder` module.
5
+
6
+ ## How can I add `<method>` to the class ##
7
+
8
+ To do this you need to write a code generator that returns source code
9
+ along with a 'globals' dictionary of any names the code needs to refer
10
+ to, or an empty dictionary if none are needed. Many methods don't require
11
+ any globals values, but it is essential for some.
12
+
13
+ ### Frozen Classes ###
14
+
15
+ In order to make frozen classes you need to replace `__setattr__` and `__delattr__`
16
+
17
+ The building blocks for this are actually already included as they're used to prevent
18
+ `Field` subclass instances from being mutated when under testing.
19
+
20
+ These methods can be reused to make `slotclasses` 'frozen'.
21
+
22
+ ```{literalinclude} code_examples/docs_ex05_frozen.py
23
+ ```
24
+
25
+ ### Iterable Classes ###
26
+
27
+ Say you want to make the class iterable, so you want to add `__iter__`.
28
+
29
+ ```{literalinclude} code_examples/docs_ex03_iterable.py
30
+ ```
31
+
32
+ You could also choose to yield tuples of `name, value` pairs in your implementation.
33
+
34
+ ## Extending Field ##
35
+
36
+ The `Field` class can also be extended as if it is a slotclass, with annotations or
37
+ with `Field` declarations.
38
+
39
+ One notable caveat - if you want to use a `default_factory` in extending `Field` you
40
+ need to declare `default=FIELD_NOTHING` also in order for default to be ignored. This
41
+ is a special case for `Field` and is not needed in general.
42
+
43
+ ```python
44
+ from ducktools.classbuilder import Field, FIELD_NOTHING
45
+
46
+ class MetadataField(Field):
47
+ metadata: dict = Field(default=FIELD_NOTHING, default_factory=dict)
48
+ ```
49
+
50
+ In regular classes the `__init__` function generator considers `NOTHING` to be an
51
+ ignored value, but for `Field` subclasses it is a valid value so `FIELD_NOTHING` is
52
+ the ignored term. This is all because `None` *is* a valid value and can't be used
53
+ as a sentinel for Fields (otherwise `Field(default=None)` couldn't work).
54
+
55
+ ### Positional Only Arguments? ###
56
+
57
+ This is possible, but a little longer as we also need to modify multiple methods
58
+ along with adding a check to the builder to catch likely errors before the `__init__`
59
+ method is generated.
60
+
61
+ For simplicity this demonstration version will ignore the existence of the kw_only
62
+ parameter for fields.
63
+
64
+ ```{literalinclude} code_examples/docs_ex07_posonly.py
65
+ ```
66
+
67
+ ### Frozen Attributes ###
68
+
69
+ Here's an implementation that allows freezing of individual attributes.
70
+
71
+ ```{literalinclude} code_examples/docs_ex10_frozen_attributes.py
72
+ ```
73
+
74
+ ### Converters ###
75
+
76
+ Here's an implementation of basic converters that always convert when
77
+ their attribute is set.
78
+
79
+ ```{literalinclude} code_examples/docs_ex08_converters.py
80
+ ```
81
+
82
+ ## Gatherers ##
83
+ ### What about using annotations instead of `Field(init=False, ...)` ###
84
+
85
+ This seems to be a feature people keep requesting for `dataclasses`.
86
+
87
+ To implement this you need to create a new annotated_gatherer function.
88
+
89
+ > Note: Field classes will be frozen when running under pytest.
90
+ > They should not be mutated by gatherers.
91
+ > If you need to change the value of a field use Field.from_field(...) to make a new instance.
92
+
93
+ ```{literalinclude} code_examples/docs_ex09_annotated.py
94
+ ```
@@ -7,12 +7,12 @@ caption: "Contents:"
7
7
  hidden: true
8
8
  ---
9
9
  tutorial
10
+ prefab/index
10
11
  extension_examples
11
12
  generated_code
12
13
  api
13
14
  perf/performance_tests
14
15
  approach_vs_tool
15
- prefab/index
16
16
  ```
17
17
 
18
18
  `ducktools-classbuilder` is *the* Python package that will bring you the **joy**
@@ -22,14 +22,14 @@ Maybe.
22
22
 
23
23
  This specific idea came about after seeing people making multiple feature requests
24
24
  to `attrs` or `dataclasses` to add features or to merge feature PRs. This project
25
- is supposed to both provide users with some basic tools to allow them to make
25
+ is supposed to both provide users with some basic tools to allow them to make
26
26
  custom class generators that work with the features they need.
27
27
 
28
28
  ## A little history ##
29
29
 
30
30
  Previously I had a project - `PrefabClasses` - which came about while getting
31
31
  frustrated at the need to write converters or wrappers for multiple methods when
32
- using `attrs`, where all I really wanted to do was coerce empty values to None
32
+ using `attrs`, where all I really wanted to do was coerce empty values to None
33
33
  (or the other way around).
34
34
 
35
35
  Further development came when I started investigating CLI tools and noticed the
@@ -48,21 +48,7 @@ modules, including stdlib ones that would have a significant impact on start tim
48
48
  The building toolkit includes a basic implementation that uses
49
49
  `__slots__` to define the fields by assigning a `SlotFields` instance.
50
50
 
51
- ```python
52
- from ducktools.classbuilder import slotclass, Field, SlotFields
53
-
54
- @slotclass
55
- class SlottedDC:
56
- __slots__ = SlotFields(
57
- the_answer=42,
58
- the_question=Field(
59
- default="What do you get if you multiply six by nine?",
60
- doc="Life, the Universe, and Everything",
61
- ),
62
- )
63
-
64
- ex = SlottedDC()
65
- print(ex)
51
+ ```{literalinclude} code_examples/docs_ex01_basic.py
66
52
  ```
67
53
 
68
54
  ## Using Annotations ##
@@ -75,41 +61,7 @@ In order to correctly implement `__slots__` this needs to be done
75
61
  using a metaclass as `__slots__` must be defined before the **class**
76
62
  is created.
77
63
 
78
- ```python
79
- from ducktools.classbuilder import (
80
- SlotMakerMeta,
81
- builder,
82
- check_argument_order,
83
- default_methods,
84
- unified_gatherer,
85
- )
86
-
87
-
88
- class AnnotationClass(metaclass=SlotMakerMeta):
89
- __slots__ = {}
90
-
91
- def __init_subclass__(
92
- cls,
93
- methods=default_methods,
94
- gatherer=unified_gatherer,
95
- **kwargs
96
- ):
97
- # Check class dict otherwise this will always be True as this base
98
- # class uses slots.
99
- slots = "__slots__" in cls.__dict__
100
-
101
- builder(cls, gatherer=gatherer, methods=methods, flags={"slotted": slots})
102
- check_argument_order(cls)
103
- super().__init_subclass__(**kwargs)
104
-
105
-
106
- class AnnotatedDC(AnnotationClass):
107
- the_answer: int = 42
108
- the_question: str = "What do you get if you multiply six by nine?"
109
-
110
-
111
- ex = AnnotatedDC()
112
- print(ex)
64
+ ```{literalinclude} code_examples/index_example.py
113
65
  ```
114
66
 
115
67
  ## Indices and tables ##
@@ -1,17 +1,13 @@
1
1
  # Prefab - A prebuilt classbuilder implementation #
2
2
 
3
- Writes the class boilerplate code so you don't have to.
4
-
5
- Unlike `slotclass` in classbuilder this is a more featureful implementation.
3
+ This is a more full featured dataclass replacement with some different design decisions
4
+ and features.
6
5
 
7
6
  Including:
8
7
  * Declaration by type hints, slots or `attribute(...)` assignment on the class
9
- * `attribute` arguments to include/exclude fields from specific methods or to make them keyword only
10
- * `prefab` arguments to modify class generation options
11
8
  * `__prefab_pre_init__` and `__prefab_post_init__` detection to allow for validation/conversion
12
- * Frozen classes
13
- * Optional `as_dict` method generation
14
- * Optional recursive `__repr__` handling
9
+ * Optional `as_dict` method generation to convert to a dictionary
10
+ * Optional recursive `__repr__` handling (off by default)
15
11
 
16
12
  ## Usage ##
17
13
 
@@ -27,7 +23,7 @@ class Settings:
27
23
  template_name = attribute(default='index')
28
24
  ```
29
25
 
30
- Or with type hinting:
26
+ Or with type hints:
31
27
 
32
28
  ```python
33
29
  from ducktools.classbuilder.prefab import prefab
@@ -49,24 +45,16 @@ Settings(hostname='localhost', template_folder='base/path', template_name='index
49
45
 
50
46
  ## Slots ##
51
47
 
52
- Classes can also be created using `__slots__` in the same way as `@slotclass` from the builder,
53
- but with all of the additional features added by `prefab`
54
-
55
- Similarly to the type hinted form, plain values given to a SlotFields instance are treated as defaults
56
- while `attribute` calls are handled normally. `doc` values will be seen when calling `help(...)` on the class
57
- while the `__annotations__` dictionary will be updated with `type` values given. Annotations can also still
58
- be given normally (which will probably be necessary for static typing tools).
48
+ Pre-slotted classes can be created by using the `Prefab` base class
59
49
 
60
50
  ```python
61
- from ducktools.classbuilder.prefab import prefab, attribute, SlotFields
51
+ from ducktools.classbuilder.prefab import Prefab
62
52
 
63
- @prefab
64
- class Settings:
65
- __slots__ = SlotFields(
66
- hostname="localhost",
67
- template_folder="base/path",
68
- template_name=attribute(default="index", type=str, doc="Name of the template"),
69
- )
53
+
54
+ class Settings(Prefab):
55
+ hostname: str = "localhost"
56
+ template_folder: str = 'base/path'
57
+ template_name: str = 'index'
70
58
  ```
71
59
 
72
60
  ## Why not just use attrs or dataclasses? ##
@@ -76,27 +64,10 @@ They are thoroughly tested, well supported packages. This is a new
76
64
  project and has not had the rigorous real world testing of either
77
65
  of those.
78
66
 
79
- This module has been created for situations where startup time is important,
67
+ This module has been created for situations where startup time is important,
80
68
  such as for CLI tools and for handling conversion of inputs in a way that
81
69
  was more useful to me than attrs converters (`__prefab_post_init__`).
82
70
 
83
- ## How does it work ##
84
-
85
- The `@prefab` decorator analyses the class it is decorating and prepares an internals dict, along
86
- with performing some other early checks.
87
- Once this is done it sets any direct values (`PREFAB_FIELDS` and `__match_args__` if required)
88
- and places non-data descriptors for all of the magic methods to be generated.
89
-
90
- The non-data descriptors for each of the magic methods perform code generation when first called
91
- in order to generate the actual methods. Once the method has been generated, the descriptor is
92
- replaced on the class with the resulting method so there is no overhead regenerating the method
93
- on each access.
94
-
95
- By only generating methods the first time they are used the start time can be
96
- improved and methods that are never used don't have to be created at all (for example the
97
- `__repr__` method is useful when debugging but may not be used in normal runtime). In contrast
98
- `dataclasses` generates all of its methods when the class is created.
99
-
100
71
  ## Pre and Post Init Methods ##
101
72
 
102
73
  Alongside the standard method generation `@prefab` decorated classes
@@ -105,7 +76,7 @@ methods are defined.
105
76
 
106
77
  For both methods if they have additional arguments with names that match
107
78
  defined attributes, the matching arguments to `__init__` will be passed
108
- through to the method.
79
+ through to the method.
109
80
 
110
81
  **If an argument is passed to `__prefab_post_init__`it will not be initialized
111
82
  in `__init__`**. It is expected that initialization will occur in the method
@@ -129,7 +100,7 @@ from ducktools.classbuilder.prefab import prefab
129
100
  @prefab(repr=False, eq=False)
130
101
  class ExampleValidate:
131
102
  x: int
132
-
103
+
133
104
  @staticmethod
134
105
  def __prefab_pre_init__(x):
135
106
  if x <= 0:
@@ -142,11 +113,11 @@ Equivalent code:
142
113
  class ExampleValidate:
143
114
  PREFAB_FIELDS = ['x']
144
115
  __match_args__ = ('x',)
145
-
116
+
146
117
  def __init__(self, x: int):
147
118
  self.__prefab_pre_init__(x=x)
148
119
  self.x = x
149
-
120
+
150
121
  @staticmethod
151
122
  def __prefab_pre_init__(x):
152
123
  if x <= 0:
@@ -176,12 +147,12 @@ from pathlib import Path
176
147
  class ExampleConvert:
177
148
  PREFAB_FIELDS = ['x']
178
149
  __match_args__ = ('x',)
179
-
150
+
180
151
  x: Path
181
-
152
+
182
153
  def __init__(self, x: Path | str = 'path/to/source'):
183
154
  self.__prefab_post_init__(x=x)
184
-
155
+
185
156
  def __prefab_post_init__(self, x: Path | str):
186
157
  self.x = Path(x)
187
158
  ```
@@ -202,7 +173,7 @@ or will be added to this list.
202
173
  1. prefabs do not generate the comparison methods other than `__eq__`.
203
174
  * This isn't generally a feature I want or use, however with the tools it is easy
204
175
  to add if this is a needed feature.
205
- 1. the `as_dict` method in `prefab_classes` does *not* behave the same as
176
+ 1. the `as_dict` method in `prefab_classes` does *not* behave the same as
206
177
  dataclasses' `asdict`.
207
178
  * `as_dict` does *not* deepcopy the included fields, modification of mutable
208
179
  fields in the dictionary will modify them in the original object.
@@ -233,7 +204,7 @@ or will be added to this list.
233
204
  but renamed to `__prefab_init__`.
234
205
  1. Slots are supported but not from annotations using the decorator `@prefab`
235
206
  * The support for slots in `attrs` and `dataclasses` involves recreating the
236
- class as it is not possible to effectively define `__slots__` after class
207
+ class as it is not possible to effectively define `__slots__` after class
237
208
  creation. This can cause bugs where decorators or caches hold references
238
209
  to the original class.
239
210
  * `@prefab` can be used if the slots are provided with a `__slots__ = SlotFields(...)`
@@ -251,28 +222,3 @@ or will be added to this list.
251
222
  look like it would `eval`.
252
223
  1. default_factory functions will be called if `None` is passed as an argument
253
224
  * This makes it easier to wrap the function.
254
-
255
-
256
- ## API Autodocs ##
257
-
258
- ### Core Functions ###
259
-
260
- ```{eval-rst}
261
- .. autofunction:: ducktools.classbuilder.prefab::prefab
262
- ```
263
-
264
- ```{eval-rst}
265
- .. autofunction:: ducktools.classbuilder.prefab::attribute
266
- ```
267
-
268
- ```{eval-rst}
269
- .. autofunction:: ducktools.classbuilder.prefab::build_prefab
270
- ```
271
-
272
- ### Helper functions ###
273
-
274
- ```{eval-rst}
275
- .. autofunction:: ducktools.classbuilder.prefab::is_prefab
276
- .. autofunction:: ducktools.classbuilder.prefab::is_prefab_instance
277
- .. autofunction:: ducktools.classbuilder.prefab::as_dict
278
- ```