ducktools-classbuilder 0.1.0__tar.gz → 0.1.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.1.1/MANIFEST.in +9 -0
- {ducktools-classbuilder-0.1.0 → ducktools_classbuilder-0.1.1}/PKG-INFO +89 -23
- ducktools_classbuilder-0.1.1/README.md +197 -0
- ducktools_classbuilder-0.1.1/docs/Makefile +20 -0
- ducktools_classbuilder-0.1.1/docs/api.md +41 -0
- ducktools_classbuilder-0.1.1/docs/approach_vs_tool.md +16 -0
- ducktools_classbuilder-0.1.1/docs/conf.py +29 -0
- ducktools_classbuilder-0.1.1/docs/extension_examples.md +733 -0
- ducktools_classbuilder-0.1.1/docs/index.md +69 -0
- ducktools_classbuilder-0.1.1/docs/make.bat +35 -0
- ducktools_classbuilder-0.1.1/docs/perf/performance_tests.md +63 -0
- ducktools_classbuilder-0.1.1/docs/prefab/index.md +279 -0
- {ducktools-classbuilder-0.1.0 → ducktools_classbuilder-0.1.1}/pyproject.toml +1 -1
- {ducktools-classbuilder-0.1.0 → ducktools_classbuilder-0.1.1}/src/ducktools/classbuilder/__init__.py +2 -2
- {ducktools-classbuilder-0.1.0 → ducktools_classbuilder-0.1.1}/src/ducktools/classbuilder/__init__.pyi +18 -18
- {ducktools-classbuilder-0.1.0 → ducktools_classbuilder-0.1.1}/src/ducktools/classbuilder/prefab.py +18 -7
- {ducktools-classbuilder-0.1.0 → ducktools_classbuilder-0.1.1}/src/ducktools/classbuilder/prefab.pyi +23 -13
- {ducktools-classbuilder-0.1.0 → ducktools_classbuilder-0.1.1}/src/ducktools_classbuilder.egg-info/PKG-INFO +89 -23
- ducktools_classbuilder-0.1.1/src/ducktools_classbuilder.egg-info/SOURCES.txt +55 -0
- {ducktools-classbuilder-0.1.0 → ducktools_classbuilder-0.1.1}/src/ducktools_classbuilder.egg-info/requires.txt +1 -0
- ducktools_classbuilder-0.1.1/tests/prefab/dynamic/test_compare_attrib.py +17 -0
- ducktools_classbuilder-0.1.1/tests/prefab/dynamic/test_construction.py +93 -0
- ducktools_classbuilder-0.1.1/tests/prefab/dynamic/test_internals.py +27 -0
- ducktools_classbuilder-0.1.1/tests/prefab/dynamic/test_pre_post_init.py +57 -0
- ducktools_classbuilder-0.1.1/tests/prefab/dynamic/test_slots_novalues.py +17 -0
- ducktools_classbuilder-0.1.1/tests/prefab/dynamic/test_slotted_class.py +107 -0
- ducktools_classbuilder-0.1.1/tests/prefab/shared/conftest.py +17 -0
- ducktools_classbuilder-0.1.1/tests/prefab/shared/examples/creation.py +122 -0
- ducktools_classbuilder-0.1.1/tests/prefab/shared/examples/creation_empty.py +17 -0
- ducktools_classbuilder-0.1.1/tests/prefab/shared/examples/dunders.py +66 -0
- ducktools_classbuilder-0.1.1/tests/prefab/shared/examples/fails/creation_1.py +6 -0
- ducktools_classbuilder-0.1.1/tests/prefab/shared/examples/fails/creation_2.py +7 -0
- ducktools_classbuilder-0.1.1/tests/prefab/shared/examples/fails/creation_3.py +7 -0
- ducktools_classbuilder-0.1.1/tests/prefab/shared/examples/fails/creation_5.py +6 -0
- ducktools_classbuilder-0.1.1/tests/prefab/shared/examples/fails/inheritance_1.py +11 -0
- ducktools_classbuilder-0.1.1/tests/prefab/shared/examples/fails/inheritance_2.py +12 -0
- ducktools_classbuilder-0.1.1/tests/prefab/shared/examples/frozen_prefabs.py +8 -0
- ducktools_classbuilder-0.1.1/tests/prefab/shared/examples/funcs_prefabs.py +20 -0
- ducktools_classbuilder-0.1.1/tests/prefab/shared/examples/hint_syntax.py +19 -0
- ducktools_classbuilder-0.1.1/tests/prefab/shared/examples/inheritance.py +65 -0
- ducktools_classbuilder-0.1.1/tests/prefab/shared/examples/init_ex.py +117 -0
- ducktools_classbuilder-0.1.1/tests/prefab/shared/examples/kw_only.py +49 -0
- ducktools_classbuilder-0.1.1/tests/prefab/shared/examples/repr_func.py +45 -0
- ducktools_classbuilder-0.1.1/tests/prefab/shared/test_creation.py +222 -0
- ducktools_classbuilder-0.1.1/tests/prefab/shared/test_dunders.py +120 -0
- ducktools_classbuilder-0.1.1/tests/prefab/shared/test_frozen.py +60 -0
- ducktools_classbuilder-0.1.1/tests/prefab/shared/test_funcs.py +97 -0
- ducktools_classbuilder-0.1.1/tests/prefab/shared/test_hint_syntax.py +21 -0
- ducktools_classbuilder-0.1.1/tests/prefab/shared/test_inheritance.py +61 -0
- ducktools_classbuilder-0.1.1/tests/prefab/shared/test_init.py +190 -0
- ducktools_classbuilder-0.1.1/tests/prefab/shared/test_kw_only.py +92 -0
- ducktools_classbuilder-0.1.1/tests/prefab/shared/test_repr.py +49 -0
- ducktools-classbuilder-0.1.0/README.md +0 -132
- ducktools-classbuilder-0.1.0/src/ducktools_classbuilder.egg-info/SOURCES.txt +0 -13
- {ducktools-classbuilder-0.1.0 → ducktools_classbuilder-0.1.1}/LICENSE.md +0 -0
- {ducktools-classbuilder-0.1.0 → ducktools_classbuilder-0.1.1}/setup.cfg +0 -0
- {ducktools-classbuilder-0.1.0 → ducktools_classbuilder-0.1.1}/src/ducktools/classbuilder/py.typed +0 -0
- {ducktools-classbuilder-0.1.0 → ducktools_classbuilder-0.1.1}/src/ducktools_classbuilder.egg-info/dependency_links.txt +0 -0
- {ducktools-classbuilder-0.1.0 → ducktools_classbuilder-0.1.1}/src/ducktools_classbuilder.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ducktools-classbuilder
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Toolkit for creating class boilerplate generators
|
|
5
5
|
Author: David C Ellis
|
|
6
6
|
License: MIT License
|
|
@@ -40,6 +40,7 @@ License-File: LICENSE.md
|
|
|
40
40
|
Provides-Extra: testing
|
|
41
41
|
Requires-Dist: pytest; extra == "testing"
|
|
42
42
|
Requires-Dist: pytest-cov; extra == "testing"
|
|
43
|
+
Requires-Dist: mypy; extra == "testing"
|
|
43
44
|
Provides-Extra: docs
|
|
44
45
|
Requires-Dist: sphinx; extra == "docs"
|
|
45
46
|
Requires-Dist: myst-parser; extra == "docs"
|
|
@@ -53,19 +54,74 @@ of writing... functions... that will bring back the **joy** of writing classes.
|
|
|
53
54
|
Maybe.
|
|
54
55
|
|
|
55
56
|
While `attrs` and `dataclasses` are class boilerplate generators,
|
|
56
|
-
`ducktools.classbuilder` is intended to be a
|
|
57
|
+
`ducktools.classbuilder` is intended to be a `@dataclass`-like generator.
|
|
57
58
|
The goal is to handle some of the basic functions and to allow for flexible
|
|
58
59
|
customization of both the field collection and the method generation.
|
|
59
60
|
|
|
60
61
|
`ducktools.classbuilder.prefab` includes a prebuilt implementation using these tools.
|
|
61
62
|
|
|
63
|
+
Install from PyPI with:
|
|
64
|
+
`python -m pip install ducktools-classbuilder`
|
|
65
|
+
|
|
66
|
+
## Usage: building a class decorator ##
|
|
67
|
+
|
|
68
|
+
In order to create a class decorator using `ducktools.classbuilder` there are
|
|
69
|
+
a few things you need to prepare.
|
|
70
|
+
|
|
71
|
+
1. A field gathering function to analyse the class and collect valid `Field`s.
|
|
72
|
+
* An example `slot_gatherer` is included.
|
|
73
|
+
2. Code generators that can make use of the gathered `Field`s to create magic method
|
|
74
|
+
source code.
|
|
75
|
+
* Example `init_maker`, `repr_maker` and `eq_maker` generators are included.
|
|
76
|
+
3. A function that calls the `builder` function to apply both of these steps.
|
|
77
|
+
|
|
78
|
+
A field gathering function needs to take the original class as an argument and
|
|
79
|
+
return a dictionary of `{key: Field(...)}` pairs.
|
|
80
|
+
|
|
81
|
+
> [!NOTE]
|
|
82
|
+
> The `builder` will handle inheritance so do not collect fields from parent classes.
|
|
83
|
+
|
|
84
|
+
The code generators take the class as the only argument and return a tuple
|
|
85
|
+
of method source code and globals to be provided to `exec(code, globs)` in order
|
|
86
|
+
to generate the actual method.
|
|
87
|
+
|
|
88
|
+
The provided `slot_gatherer` looks for `__slots__` being assigned a `SlotFields`
|
|
89
|
+
class[^1] where keyword arguments define the names and values for the fields.
|
|
90
|
+
|
|
91
|
+
Code generator functions need to be converted to descriptors before being used.
|
|
92
|
+
This is done using the provided `MethodMaker` descriptor class.
|
|
93
|
+
ex: `init_desc = MethodMaker("__init__", init_maker)`
|
|
94
|
+
|
|
95
|
+
These parts can then be used to make a basic class boilerplate generator by
|
|
96
|
+
providing them to the `builder` function.
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from ducktools.classbuilder import (
|
|
100
|
+
builder,
|
|
101
|
+
slot_gatherer,
|
|
102
|
+
init_maker, eq_maker, repr_maker,
|
|
103
|
+
MethodMaker,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
init_desc = MethodMaker("__init__", init_maker)
|
|
107
|
+
repr_desc = MethodMaker("__repr__", repr_maker)
|
|
108
|
+
eq_desc = MethodMaker("__eq__", eq_maker)
|
|
109
|
+
|
|
110
|
+
def slotclass(cls):
|
|
111
|
+
return builder(cls, gatherer=slot_gatherer, methods={init_desc, repr_desc, eq_desc})
|
|
112
|
+
```
|
|
113
|
+
|
|
62
114
|
## Slot Class Usage ##
|
|
63
115
|
|
|
64
|
-
|
|
65
|
-
|
|
116
|
+
This created `slotclass` function can then be used as a decorator to generate classes in
|
|
117
|
+
a similar manner to the `@dataclass` decorator from `dataclasses`.
|
|
118
|
+
|
|
119
|
+
> [!NOTE]
|
|
120
|
+
> `ducktools.classbuilder` includes a premade version of `slotclass` that can
|
|
121
|
+
> be used directly. (The included version has some extra features).
|
|
66
122
|
|
|
67
123
|
```python
|
|
68
|
-
from ducktools.classbuilder import
|
|
124
|
+
from ducktools.classbuilder import Field, SlotFields
|
|
69
125
|
|
|
70
126
|
@slotclass
|
|
71
127
|
class SlottedDC:
|
|
@@ -81,28 +137,36 @@ ex = SlottedDC()
|
|
|
81
137
|
print(ex)
|
|
82
138
|
```
|
|
83
139
|
|
|
84
|
-
|
|
140
|
+
> [!TIP]
|
|
141
|
+
> For more information and examples of creating class generators with additional
|
|
142
|
+
> features using the builder see
|
|
143
|
+
> [the docs](https://ducktools-classbuilder.readthedocs.io/en/latest/extension_examples.html)
|
|
144
|
+
|
|
145
|
+
## Why does your example use `__slots__` instead of annotations? ##
|
|
85
146
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
`__slots__` and decorators work.
|
|
147
|
+
If you want to use `__slots__` in order to save memory you have to declare
|
|
148
|
+
them when the class is originally created as you can't add them later.
|
|
89
149
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
150
|
+
When you use `@dataclass(slots=True)`[^2] with `dataclasses` in order for
|
|
151
|
+
this to work, `dataclasses` has to make a new class and attempt to
|
|
152
|
+
copy over everything from the original.
|
|
153
|
+
This is because decorators operate on classes *after they have been created*
|
|
154
|
+
while slots need to be declared beforehand.
|
|
155
|
+
While you can change the value of `__slots__` after a class has been created,
|
|
156
|
+
this will have no effect on the internal structure of the class.
|
|
96
157
|
|
|
97
158
|
By declaring the class using `__slots__` on the other hand, we can take
|
|
98
159
|
advantage of the fact that it accepts a mapping, where the keys will be
|
|
99
160
|
used as the attributes to create as slots. The values can then be used as
|
|
100
|
-
the default values equivalently to how type hints are used in dataclasses
|
|
161
|
+
the default values equivalently to how type hints are used in `dataclasses`.
|
|
101
162
|
|
|
102
|
-
For example these two classes would be roughly equivalent, except
|
|
163
|
+
For example these two classes would be roughly equivalent, except that
|
|
103
164
|
`@dataclass` has had to recreate the class from scratch while `@slotclass`
|
|
104
|
-
has
|
|
105
|
-
|
|
165
|
+
has added the methods on to the original class.
|
|
166
|
+
This means that any references stored to the original class *before*
|
|
167
|
+
`@dataclass` has rebuilt the class will not be pointing towards the
|
|
168
|
+
correct class.
|
|
169
|
+
This can be demonstrated using a simple class register decorator.
|
|
106
170
|
|
|
107
171
|
> This example requires Python 3.10 as earlier versions of
|
|
108
172
|
> `dataclasses` did not support the `slots` argument.
|
|
@@ -140,7 +204,6 @@ print(SlotCoords())
|
|
|
140
204
|
|
|
141
205
|
print(f"{DataCoords is class_register[DataCoords.__name__] = }")
|
|
142
206
|
print(f"{SlotCoords is class_register[SlotCoords.__name__] = }")
|
|
143
|
-
|
|
144
207
|
```
|
|
145
208
|
|
|
146
209
|
## What features does this have? ##
|
|
@@ -158,9 +221,6 @@ field so they are present on the class if `help(...)` is called.
|
|
|
158
221
|
If you want something with more features you can look at the `prefab.py`
|
|
159
222
|
implementation which provides a 'prebuilt' implementation.
|
|
160
223
|
|
|
161
|
-
For more information on creating class generators using the builder
|
|
162
|
-
see [the docs](https://ducktools-classbuilder.readthedocs.io/en/latest/extension_examples.html)
|
|
163
|
-
|
|
164
224
|
## Will you add \<feature\> to `classbuilder.prefab`? ##
|
|
165
225
|
|
|
166
226
|
No. Not unless it's something I need or find interesting.
|
|
@@ -177,3 +237,9 @@ with a specific feature, you can create or add it yourself.
|
|
|
177
237
|
## Credit ##
|
|
178
238
|
|
|
179
239
|
Heavily inspired by [David Beazley's Cluegen](https://github.com/dabeaz/cluegen)
|
|
240
|
+
|
|
241
|
+
[^1]: `SlotFields` is actually just a subclassed `dict` with no changes. `__slots__`
|
|
242
|
+
works with dictionaries using the values of the keys, while fields are normally
|
|
243
|
+
used for documentation.
|
|
244
|
+
|
|
245
|
+
[^2]: or `@attrs.define`.
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# Ducktools: Class Builder #
|
|
2
|
+
|
|
3
|
+
`ducktools-classbuilder` is *the* Python package that will bring you the **joy**
|
|
4
|
+
of writing... functions... that will bring back the **joy** of writing classes.
|
|
5
|
+
|
|
6
|
+
Maybe.
|
|
7
|
+
|
|
8
|
+
While `attrs` and `dataclasses` are class boilerplate generators,
|
|
9
|
+
`ducktools.classbuilder` is intended to be a `@dataclass`-like generator.
|
|
10
|
+
The goal is to handle some of the basic functions and to allow for flexible
|
|
11
|
+
customization of both the field collection and the method generation.
|
|
12
|
+
|
|
13
|
+
`ducktools.classbuilder.prefab` includes a prebuilt implementation using these tools.
|
|
14
|
+
|
|
15
|
+
Install from PyPI with:
|
|
16
|
+
`python -m pip install ducktools-classbuilder`
|
|
17
|
+
|
|
18
|
+
## Usage: building a class decorator ##
|
|
19
|
+
|
|
20
|
+
In order to create a class decorator using `ducktools.classbuilder` there are
|
|
21
|
+
a few things you need to prepare.
|
|
22
|
+
|
|
23
|
+
1. A field gathering function to analyse the class and collect valid `Field`s.
|
|
24
|
+
* An example `slot_gatherer` is included.
|
|
25
|
+
2. Code generators that can make use of the gathered `Field`s to create magic method
|
|
26
|
+
source code.
|
|
27
|
+
* Example `init_maker`, `repr_maker` and `eq_maker` generators are included.
|
|
28
|
+
3. A function that calls the `builder` function to apply both of these steps.
|
|
29
|
+
|
|
30
|
+
A field gathering function needs to take the original class as an argument and
|
|
31
|
+
return a dictionary of `{key: Field(...)}` pairs.
|
|
32
|
+
|
|
33
|
+
> [!NOTE]
|
|
34
|
+
> The `builder` will handle inheritance so do not collect fields from parent classes.
|
|
35
|
+
|
|
36
|
+
The code generators take the class as the only argument and return a tuple
|
|
37
|
+
of method source code and globals to be provided to `exec(code, globs)` in order
|
|
38
|
+
to generate the actual method.
|
|
39
|
+
|
|
40
|
+
The provided `slot_gatherer` looks for `__slots__` being assigned a `SlotFields`
|
|
41
|
+
class[^1] where keyword arguments define the names and values for the fields.
|
|
42
|
+
|
|
43
|
+
Code generator functions need to be converted to descriptors before being used.
|
|
44
|
+
This is done using the provided `MethodMaker` descriptor class.
|
|
45
|
+
ex: `init_desc = MethodMaker("__init__", init_maker)`
|
|
46
|
+
|
|
47
|
+
These parts can then be used to make a basic class boilerplate generator by
|
|
48
|
+
providing them to the `builder` function.
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from ducktools.classbuilder import (
|
|
52
|
+
builder,
|
|
53
|
+
slot_gatherer,
|
|
54
|
+
init_maker, eq_maker, repr_maker,
|
|
55
|
+
MethodMaker,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
init_desc = MethodMaker("__init__", init_maker)
|
|
59
|
+
repr_desc = MethodMaker("__repr__", repr_maker)
|
|
60
|
+
eq_desc = MethodMaker("__eq__", eq_maker)
|
|
61
|
+
|
|
62
|
+
def slotclass(cls):
|
|
63
|
+
return builder(cls, gatherer=slot_gatherer, methods={init_desc, repr_desc, eq_desc})
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Slot Class Usage ##
|
|
67
|
+
|
|
68
|
+
This created `slotclass` function can then be used as a decorator to generate classes in
|
|
69
|
+
a similar manner to the `@dataclass` decorator from `dataclasses`.
|
|
70
|
+
|
|
71
|
+
> [!NOTE]
|
|
72
|
+
> `ducktools.classbuilder` includes a premade version of `slotclass` that can
|
|
73
|
+
> be used directly. (The included version has some extra features).
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from ducktools.classbuilder import Field, SlotFields
|
|
77
|
+
|
|
78
|
+
@slotclass
|
|
79
|
+
class SlottedDC:
|
|
80
|
+
__slots__ = SlotFields(
|
|
81
|
+
the_answer=42,
|
|
82
|
+
the_question=Field(
|
|
83
|
+
default="What do you get if you multiply six by nine?",
|
|
84
|
+
doc="Life, the Universe, and Everything",
|
|
85
|
+
),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
ex = SlottedDC()
|
|
89
|
+
print(ex)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
> [!TIP]
|
|
93
|
+
> For more information and examples of creating class generators with additional
|
|
94
|
+
> features using the builder see
|
|
95
|
+
> [the docs](https://ducktools-classbuilder.readthedocs.io/en/latest/extension_examples.html)
|
|
96
|
+
|
|
97
|
+
## Why does your example use `__slots__` instead of annotations? ##
|
|
98
|
+
|
|
99
|
+
If you want to use `__slots__` in order to save memory you have to declare
|
|
100
|
+
them when the class is originally created as you can't add them later.
|
|
101
|
+
|
|
102
|
+
When you use `@dataclass(slots=True)`[^2] with `dataclasses` in order for
|
|
103
|
+
this to work, `dataclasses` has to make a new class and attempt to
|
|
104
|
+
copy over everything from the original.
|
|
105
|
+
This is because decorators operate on classes *after they have been created*
|
|
106
|
+
while slots need to be declared beforehand.
|
|
107
|
+
While you can change the value of `__slots__` after a class has been created,
|
|
108
|
+
this will have no effect on the internal structure of the class.
|
|
109
|
+
|
|
110
|
+
By declaring the class using `__slots__` on the other hand, we can take
|
|
111
|
+
advantage of the fact that it accepts a mapping, where the keys will be
|
|
112
|
+
used as the attributes to create as slots. The values can then be used as
|
|
113
|
+
the default values equivalently to how type hints are used in `dataclasses`.
|
|
114
|
+
|
|
115
|
+
For example these two classes would be roughly equivalent, except that
|
|
116
|
+
`@dataclass` has had to recreate the class from scratch while `@slotclass`
|
|
117
|
+
has added the methods on to the original class.
|
|
118
|
+
This means that any references stored to the original class *before*
|
|
119
|
+
`@dataclass` has rebuilt the class will not be pointing towards the
|
|
120
|
+
correct class.
|
|
121
|
+
This can be demonstrated using a simple class register decorator.
|
|
122
|
+
|
|
123
|
+
> This example requires Python 3.10 as earlier versions of
|
|
124
|
+
> `dataclasses` did not support the `slots` argument.
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from dataclasses import dataclass
|
|
128
|
+
from ducktools.classbuilder import slotclass, SlotFields
|
|
129
|
+
|
|
130
|
+
class_register = {}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def register(cls):
|
|
134
|
+
class_register[cls.__name__] = cls
|
|
135
|
+
return cls
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@dataclass(slots=True)
|
|
139
|
+
@register
|
|
140
|
+
class DataCoords:
|
|
141
|
+
x: float = 0.0
|
|
142
|
+
y: float = 0.0
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@slotclass
|
|
146
|
+
@register
|
|
147
|
+
class SlotCoords:
|
|
148
|
+
__slots__ = SlotFields(x=0.0, y=0.0)
|
|
149
|
+
# Type hints don't affect class construction, these are optional.
|
|
150
|
+
x: float
|
|
151
|
+
y: float
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
print(DataCoords())
|
|
155
|
+
print(SlotCoords())
|
|
156
|
+
|
|
157
|
+
print(f"{DataCoords is class_register[DataCoords.__name__] = }")
|
|
158
|
+
print(f"{SlotCoords is class_register[SlotCoords.__name__] = }")
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## What features does this have? ##
|
|
162
|
+
|
|
163
|
+
Included as an example implementation, the `slotclass` generator supports
|
|
164
|
+
`default_factory` for creating mutable defaults like lists, dicts etc.
|
|
165
|
+
It also supports default values that are not builtins (try this on
|
|
166
|
+
[Cluegen](https://github.com/dabeaz/cluegen)).
|
|
167
|
+
|
|
168
|
+
It will copy values provided as the `type` to `Field` into the
|
|
169
|
+
`__annotations__` dictionary of the class.
|
|
170
|
+
Values provided to `doc` will be placed in the final `__slots__`
|
|
171
|
+
field so they are present on the class if `help(...)` is called.
|
|
172
|
+
|
|
173
|
+
If you want something with more features you can look at the `prefab.py`
|
|
174
|
+
implementation which provides a 'prebuilt' implementation.
|
|
175
|
+
|
|
176
|
+
## Will you add \<feature\> to `classbuilder.prefab`? ##
|
|
177
|
+
|
|
178
|
+
No. Not unless it's something I need or find interesting.
|
|
179
|
+
|
|
180
|
+
The original version of `prefab_classes` was intended to have every feature
|
|
181
|
+
anybody could possibly require, but this is no longer the case with this
|
|
182
|
+
rebuilt version.
|
|
183
|
+
|
|
184
|
+
I will fix bugs (assuming they're not actually intended behaviour).
|
|
185
|
+
|
|
186
|
+
However the whole goal of this module is if you want to have a class generator
|
|
187
|
+
with a specific feature, you can create or add it yourself.
|
|
188
|
+
|
|
189
|
+
## Credit ##
|
|
190
|
+
|
|
191
|
+
Heavily inspired by [David Beazley's Cluegen](https://github.com/dabeaz/cluegen)
|
|
192
|
+
|
|
193
|
+
[^1]: `SlotFields` is actually just a subclassed `dict` with no changes. `__slots__`
|
|
194
|
+
works with dictionaries using the values of the keys, while fields are normally
|
|
195
|
+
used for documentation.
|
|
196
|
+
|
|
197
|
+
[^2]: or `@attrs.define`.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Minimal makefile for Sphinx documentation
|
|
2
|
+
#
|
|
3
|
+
|
|
4
|
+
# You can set these variables from the command line, and also
|
|
5
|
+
# from the environment for the first two.
|
|
6
|
+
SPHINXOPTS ?=
|
|
7
|
+
SPHINXBUILD ?= sphinx-build
|
|
8
|
+
SOURCEDIR = .
|
|
9
|
+
BUILDDIR = _build
|
|
10
|
+
|
|
11
|
+
# Put it first so that "make" without argument is like "make help".
|
|
12
|
+
help:
|
|
13
|
+
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
|
14
|
+
|
|
15
|
+
.PHONY: help Makefile
|
|
16
|
+
|
|
17
|
+
# Catch-all target: route all unknown targets to Sphinx using the new
|
|
18
|
+
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
|
19
|
+
%: Makefile
|
|
20
|
+
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
|
@@ -0,0 +1,41 @@
|
|
|
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
|
+
## Builder functions and classes ##
|
|
18
|
+
|
|
19
|
+
```{eval-rst}
|
|
20
|
+
.. autofunction:: ducktools.classbuilder::builder
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```{eval-rst}
|
|
24
|
+
.. autofunction:: ducktools.classbuilder::get_fields
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```{eval-rst}
|
|
28
|
+
.. autofunction:: ducktools.classbuilder::get_internals
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```{eval-rst}
|
|
32
|
+
.. autoclass:: ducktools.classbuilder::MethodMaker
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```{eval-rst}
|
|
36
|
+
.. autofunction:: ducktools.classbuilder::slot_gatherer
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```{eval-rst}
|
|
40
|
+
.. autofunction:: ducktools.classbuilder::fieldclass
|
|
41
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Approaches and tools #
|
|
2
|
+
|
|
3
|
+
As this module's code generation is inspired by the workings of [David Beazley's Cluegen](https://github.com/dabeaz/cluegen)
|
|
4
|
+
I thought it was briefly worth discussing his note on learning an approach vs using a tool.
|
|
5
|
+
|
|
6
|
+
I think that learning an approach is valuable, this module would not exist without the
|
|
7
|
+
example given by `cluegen`. It also wouldn't exist if I hadn't needed to extend `cluegen`
|
|
8
|
+
for some basic features (try using `Path` default values with `cluegen`).
|
|
9
|
+
|
|
10
|
+
In the general spirit though, this module intends to provide some basic tools to help
|
|
11
|
+
build your own custom class generators.
|
|
12
|
+
The generator included in the base module is intended to be used to help 'bootstrap' a
|
|
13
|
+
modified generator with features that work how **you** want them to work.
|
|
14
|
+
|
|
15
|
+
The `prefab` module is the more fully featured powertool *I* built with these tools.
|
|
16
|
+
However, much like a prefabricated building, it may not be what you desire.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Configuration file for the Sphinx documentation builder.
|
|
2
|
+
#
|
|
3
|
+
# For the full list of built-in configuration values, see the documentation:
|
|
4
|
+
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
|
5
|
+
|
|
6
|
+
# -- Project information -----------------------------------------------------
|
|
7
|
+
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
|
8
|
+
|
|
9
|
+
project = 'ducktools-classbuilder'
|
|
10
|
+
copyright = '2024, David C Ellis'
|
|
11
|
+
author = 'David C Ellis'
|
|
12
|
+
|
|
13
|
+
# -- General configuration ---------------------------------------------------
|
|
14
|
+
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
|
15
|
+
|
|
16
|
+
extensions = ["myst_parser", "sphinx.ext.autodoc", "sphinx_rtd_theme"]
|
|
17
|
+
|
|
18
|
+
autodoc_typehints = "description"
|
|
19
|
+
|
|
20
|
+
templates_path = ['_templates']
|
|
21
|
+
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
|
22
|
+
|
|
23
|
+
autoclass_content = "both"
|
|
24
|
+
|
|
25
|
+
# -- Options for HTML output -------------------------------------------------
|
|
26
|
+
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
|
27
|
+
|
|
28
|
+
html_theme = 'sphinx_rtd_theme'
|
|
29
|
+
html_static_path = ['_static']
|