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.
- ducktools_classbuilder-0.11.1/MANIFEST.in +5 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/PKG-INFO +2 -2
- ducktools_classbuilder-0.11.1/docs/api.md +82 -0
- 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
- ducktools_classbuilder-0.11.1/docs/code_examples/outputs/docs_ex01_basic.output +36 -0
- ducktools_classbuilder-0.11.1/docs/code_examples/outputs/docs_ex02_register.output +4 -0
- ducktools_classbuilder-0.11.1/docs/code_examples/outputs/docs_ex03_iterable.output +2 -0
- ducktools_classbuilder-0.11.1/docs/code_examples/outputs/docs_ex05_frozen.output +4 -0
- ducktools_classbuilder-0.11.1/docs/code_examples/outputs/docs_ex07_posonly.output +4 -0
- ducktools_classbuilder-0.11.1/docs/code_examples/outputs/docs_ex08_converters.output +1 -0
- ducktools_classbuilder-0.11.1/docs/code_examples/outputs/docs_ex09_annotated.output +33 -0
- ducktools_classbuilder-0.11.1/docs/code_examples/outputs/docs_ex10_frozen_attributes.output +1 -0
- ducktools_classbuilder-0.11.1/docs/code_examples/outputs/index_example.output +1 -0
- ducktools_classbuilder-0.11.1/docs/code_examples/outputs/tutorial_code.output +13 -0
- {ducktools_classbuilder-0.10.2/docs_code → ducktools_classbuilder-0.11.1/docs/code_examples}/tutorial_code.py +2 -4
- ducktools_classbuilder-0.11.1/docs/extension_examples.md +94 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/docs/index.md +5 -53
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/docs/prefab/index.md +22 -76
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/docs/tutorial.md +8 -9
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/pyproject.toml +1 -1
- ducktools_classbuilder-0.11.1/scripts/generate_code_outputs.py +10 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/src/ducktools/classbuilder/__init__.py +106 -65
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/src/ducktools/classbuilder/__init__.pyi +11 -13
- ducktools_classbuilder-0.11.1/src/ducktools/classbuilder/_version.py +2 -0
- ducktools_classbuilder-0.11.1/src/ducktools/classbuilder/annotations/__init__.py +63 -0
- ducktools_classbuilder-0.11.1/src/ducktools/classbuilder/annotations/annotations_314.py +104 -0
- ducktools_classbuilder-0.11.1/src/ducktools/classbuilder/annotations/annotations_pre_314.py +42 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/src/ducktools/classbuilder/annotations.pyi +4 -4
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/src/ducktools/classbuilder/prefab.py +108 -28
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/src/ducktools/classbuilder/prefab.pyi +8 -1
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/src/ducktools_classbuilder.egg-info/PKG-INFO +2 -2
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/src/ducktools_classbuilder.egg-info/SOURCES.txt +30 -19
- ducktools_classbuilder-0.11.1/tests/_type_support.py +29 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/annotations/test_annotated.py +3 -1
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/annotations/test_annotations_module.py +4 -3
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_creation.py +5 -2
- ducktools_classbuilder-0.11.1/tests/prefab/test_ignored_annotations.py +72 -0
- ducktools_classbuilder-0.11.1/tests/prefab/test_inheritance.py +177 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_init.py +3 -3
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_internals_dict.py +0 -1
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_replace.py +27 -3
- ducktools_classbuilder-0.11.1/tests/py314_tests/test_annotation_unused.py +52 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/py314_tests/test_init_signature.py +55 -13
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/py314_tests/test_init_signature_future.py +0 -1
- ducktools_classbuilder-0.11.1/tests/py314_tests/test_string_annotations.py +117 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/test_core.py +0 -1
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/test_slotmakermeta.py +5 -4
- ducktools_classbuilder-0.11.1/tests/test_tutorial_code.py +34 -0
- ducktools_classbuilder-0.11.1/uv.lock +1065 -0
- ducktools_classbuilder-0.10.2/.github/dependabot.yml +0 -7
- ducktools_classbuilder-0.10.2/.github/workflows/auto_test.yml +0 -48
- ducktools_classbuilder-0.10.2/.github/workflows/publish_to_pypi.yml +0 -87
- ducktools_classbuilder-0.10.2/.github/workflows/publish_to_testpypi.yml +0 -55
- ducktools_classbuilder-0.10.2/.readthedocs.yaml +0 -24
- ducktools_classbuilder-0.10.2/MANIFEST.in +0 -10
- ducktools_classbuilder-0.10.2/docs/api.md +0 -42
- ducktools_classbuilder-0.10.2/docs/extension_examples.md +0 -680
- ducktools_classbuilder-0.10.2/src/ducktools/classbuilder/_version.py +0 -2
- ducktools_classbuilder-0.10.2/src/ducktools/classbuilder/annotations.py +0 -137
- ducktools_classbuilder-0.10.2/tests/prefab/test_inheritance.py +0 -123
- ducktools_classbuilder-0.10.2/tests/py314_tests/_test_support.py +0 -42
- ducktools_classbuilder-0.10.2/tests/py314_tests/test_forwardref_annotations.py +0 -59
- ducktools_classbuilder-0.10.2/uv.lock +0 -939
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/.gitignore +0 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/LICENSE +0 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/README.md +0 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/docs/Makefile +0 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/docs/approach_vs_tool.md +0 -0
- /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
- /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
- /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
- /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
- /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
- /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
- {ducktools_classbuilder-0.10.2/docs_code → ducktools_classbuilder-0.11.1/docs/code_examples}/docs_ex10_frozen_attributes.py +0 -0
- {ducktools_classbuilder-0.10.2/docs_code → ducktools_classbuilder-0.11.1/docs/code_examples}/index_example.py +0 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/docs/conf.py +0 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/docs/generated_code.md +0 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/docs/make.bat +0 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/docs/perf/performance_tests.md +0 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/setup.cfg +0 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/src/ducktools/classbuilder/py.typed +0 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/src/ducktools_classbuilder.egg-info/dependency_links.txt +0 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/src/ducktools_classbuilder.egg-info/requires.txt +0 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/src/ducktools_classbuilder.egg-info/top_level.txt +0 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/annotations/test_future_annotations.py +0 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/conftest.py +0 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/helpers/utils.py +0 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_compare_attrib.py +0 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_construction.py +0 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_dunders.py +0 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_frozen.py +0 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_funcs.py +0 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_hint_syntax.py +0 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_kw_only.py +0 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_pre_post_init.py +0 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_private.py +0 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_repr.py +0 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_slots_novalues.py +0 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_slotted_class.py +0 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/prefab/test_subclass_implementation.py +0 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/py312_tests/test_generic_annotations.py +0 -0
- {ducktools_classbuilder-0.10.2 → ducktools_classbuilder-0.11.1}/tests/test_field_flags.py +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ducktools-classbuilder
|
|
3
|
-
Version: 0.
|
|
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 =
|
|
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.
|
|
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 @@
|
|
|
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
|
-
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
|
|
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
|
-
*
|
|
13
|
-
* Optional `
|
|
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
|
|
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
|
-
|
|
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
|
|
51
|
+
from ducktools.classbuilder.prefab import Prefab
|
|
62
52
|
|
|
63
|
-
|
|
64
|
-
class Settings:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
```
|