ducktools-classbuilder 0.2.1__tar.gz → 0.4.0__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.2.1/src/ducktools_classbuilder.egg-info → ducktools_classbuilder-0.4.0}/PKG-INFO +26 -2
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/README.md +24 -1
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/docs/extension_examples.md +62 -166
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/docs/index.md +21 -0
- ducktools_classbuilder-0.4.0/docs/perf/performance_tests.md +66 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/pyproject.toml +1 -1
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/src/ducktools/classbuilder/__init__.py +269 -104
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/src/ducktools/classbuilder/__init__.pyi +46 -8
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/src/ducktools/classbuilder/prefab.py +23 -88
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/src/ducktools/classbuilder/prefab.pyi +1 -9
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0/src/ducktools_classbuilder.egg-info}/PKG-INFO +26 -2
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/src/ducktools_classbuilder.egg-info/SOURCES.txt +2 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/src/ducktools_classbuilder.egg-info/requires.txt +3 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/examples/creation.py +22 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/test_creation.py +37 -16
- ducktools_classbuilder-0.4.0/tests/test_annotated.py +140 -0
- ducktools_classbuilder-0.4.0/tests/test_core.py +373 -0
- ducktools_classbuilder-0.2.1/docs/perf/performance_tests.md +0 -63
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/LICENSE.md +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/MANIFEST.in +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/docs/Makefile +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/docs/api.md +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/docs/approach_vs_tool.md +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/docs/conf.py +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/docs/make.bat +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/docs/prefab/index.md +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/setup.cfg +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/src/ducktools/classbuilder/py.typed +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/src/ducktools_classbuilder.egg-info/dependency_links.txt +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/src/ducktools_classbuilder.egg-info/top_level.txt +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/tests/prefab/dynamic/test_compare_attrib.py +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/tests/prefab/dynamic/test_construction.py +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/tests/prefab/dynamic/test_internals.py +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/tests/prefab/dynamic/test_pre_post_init.py +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/tests/prefab/dynamic/test_slots_novalues.py +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/tests/prefab/dynamic/test_slotted_class.py +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/conftest.py +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/examples/creation_empty.py +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/examples/dunders.py +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/examples/fails/creation_1.py +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/examples/fails/creation_2.py +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/examples/fails/creation_3.py +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/examples/fails/creation_5.py +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/examples/fails/inheritance_1.py +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/examples/fails/inheritance_2.py +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/examples/frozen_prefabs.py +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/examples/funcs_prefabs.py +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/examples/hint_syntax.py +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/examples/inheritance.py +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/examples/init_ex.py +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/examples/kw_only.py +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/examples/repr_func.py +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/test_dunders.py +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/test_frozen.py +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/test_funcs.py +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/test_hint_syntax.py +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/test_inheritance.py +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/test_init.py +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/test_kw_only.py +0 -0
- {ducktools_classbuilder-0.2.1 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/test_repr.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ducktools-classbuilder
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Toolkit for creating class boilerplate generators
|
|
5
5
|
Author: David C Ellis
|
|
6
6
|
License: MIT License
|
|
@@ -41,6 +41,7 @@ Provides-Extra: testing
|
|
|
41
41
|
Requires-Dist: pytest; extra == "testing"
|
|
42
42
|
Requires-Dist: pytest-cov; extra == "testing"
|
|
43
43
|
Requires-Dist: mypy; extra == "testing"
|
|
44
|
+
Requires-Dist: typing_extensions; python_version < "3.10" and extra == "testing"
|
|
44
45
|
Provides-Extra: docs
|
|
45
46
|
Requires-Dist: sphinx; extra == "docs"
|
|
46
47
|
Requires-Dist: myst-parser; extra == "docs"
|
|
@@ -121,7 +122,7 @@ a similar manner to the `@dataclass` decorator from `dataclasses`.
|
|
|
121
122
|
> be used directly. (The included version has some extra features).
|
|
122
123
|
|
|
123
124
|
```python
|
|
124
|
-
from ducktools.classbuilder import Field, SlotFields
|
|
125
|
+
from ducktools.classbuilder import Field, SlotFields, slotclass
|
|
125
126
|
|
|
126
127
|
@slotclass
|
|
127
128
|
class SlottedDC:
|
|
@@ -206,6 +207,25 @@ print(f"{DataCoords is class_register[DataCoords.__name__] = }")
|
|
|
206
207
|
print(f"{SlotCoords is class_register[SlotCoords.__name__] = }")
|
|
207
208
|
```
|
|
208
209
|
|
|
210
|
+
## Using annotations anyway ##
|
|
211
|
+
|
|
212
|
+
For those that really want to use type annotations a basic `annotation_gatherer`
|
|
213
|
+
function and `@annotationclass` decorator are also included. Slots are not generated
|
|
214
|
+
in this case.
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
from ducktools.classbuilder import annotationclass
|
|
218
|
+
|
|
219
|
+
@annotationclass
|
|
220
|
+
class AnnotatedDC:
|
|
221
|
+
the_answer: int = 42
|
|
222
|
+
the_question: str = "What do you get if you multiply six by nine?"
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
ex = AnnotatedDC()
|
|
226
|
+
print(ex)
|
|
227
|
+
```
|
|
228
|
+
|
|
209
229
|
## What features does this have? ##
|
|
210
230
|
|
|
211
231
|
Included as an example implementation, the `slotclass` generator supports
|
|
@@ -218,6 +238,10 @@ It will copy values provided as the `type` to `Field` into the
|
|
|
218
238
|
Values provided to `doc` will be placed in the final `__slots__`
|
|
219
239
|
field so they are present on the class if `help(...)` is called.
|
|
220
240
|
|
|
241
|
+
A fairly basic `annotations_gatherer` and `annotationclass` are included
|
|
242
|
+
in `extras.py` which can be used to generate classbuilders that rely on
|
|
243
|
+
annotations.
|
|
244
|
+
|
|
221
245
|
If you want something with more features you can look at the `prefab.py`
|
|
222
246
|
implementation which provides a 'prebuilt' implementation.
|
|
223
247
|
|
|
@@ -73,7 +73,7 @@ a similar manner to the `@dataclass` decorator from `dataclasses`.
|
|
|
73
73
|
> be used directly. (The included version has some extra features).
|
|
74
74
|
|
|
75
75
|
```python
|
|
76
|
-
from ducktools.classbuilder import Field, SlotFields
|
|
76
|
+
from ducktools.classbuilder import Field, SlotFields, slotclass
|
|
77
77
|
|
|
78
78
|
@slotclass
|
|
79
79
|
class SlottedDC:
|
|
@@ -158,6 +158,25 @@ print(f"{DataCoords is class_register[DataCoords.__name__] = }")
|
|
|
158
158
|
print(f"{SlotCoords is class_register[SlotCoords.__name__] = }")
|
|
159
159
|
```
|
|
160
160
|
|
|
161
|
+
## Using annotations anyway ##
|
|
162
|
+
|
|
163
|
+
For those that really want to use type annotations a basic `annotation_gatherer`
|
|
164
|
+
function and `@annotationclass` decorator are also included. Slots are not generated
|
|
165
|
+
in this case.
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
from ducktools.classbuilder import annotationclass
|
|
169
|
+
|
|
170
|
+
@annotationclass
|
|
171
|
+
class AnnotatedDC:
|
|
172
|
+
the_answer: int = 42
|
|
173
|
+
the_question: str = "What do you get if you multiply six by nine?"
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
ex = AnnotatedDC()
|
|
177
|
+
print(ex)
|
|
178
|
+
```
|
|
179
|
+
|
|
161
180
|
## What features does this have? ##
|
|
162
181
|
|
|
163
182
|
Included as an example implementation, the `slotclass` generator supports
|
|
@@ -170,6 +189,10 @@ It will copy values provided as the `type` to `Field` into the
|
|
|
170
189
|
Values provided to `doc` will be placed in the final `__slots__`
|
|
171
190
|
field so they are present on the class if `help(...)` is called.
|
|
172
191
|
|
|
192
|
+
A fairly basic `annotations_gatherer` and `annotationclass` are included
|
|
193
|
+
in `extras.py` which can be used to generate classbuilders that rely on
|
|
194
|
+
annotations.
|
|
195
|
+
|
|
173
196
|
If you want something with more features you can look at the `prefab.py`
|
|
174
197
|
implementation which provides a 'prebuilt' implementation.
|
|
175
198
|
|
|
@@ -156,6 +156,9 @@ how to perform the generation. A convenient decorator `@fieldclass` is provided
|
|
|
156
156
|
to allow simple extension by adding additional slots. By using this decorator
|
|
157
157
|
the `__init__`, `__repr__` and `__eq__` methods will be generated for you.
|
|
158
158
|
|
|
159
|
+
> Note: Field classes will be frozen when running under pytest.
|
|
160
|
+
> They are not frozen normally for performance reasons.
|
|
161
|
+
|
|
159
162
|
```python
|
|
160
163
|
from ducktools.classbuilder import Field, SlotFields, fieldclass
|
|
161
164
|
|
|
@@ -184,102 +187,26 @@ along with a 'globals' dictionary of any names the code needs to refer
|
|
|
184
187
|
to, or an empty dictionary if none are needed. Many methods don't require
|
|
185
188
|
any globals values, but it is essential for some.
|
|
186
189
|
|
|
187
|
-
#### Iterable Classes ####
|
|
188
|
-
|
|
189
|
-
Say you want to make the class iterable, so you want to add `__iter__`.
|
|
190
|
-
|
|
191
|
-
```python
|
|
192
|
-
from ducktools.classbuilder import (
|
|
193
|
-
default_methods, get_fields, slotclass, MethodMaker, SlotFields
|
|
194
|
-
)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
def iter_maker(cls):
|
|
198
|
-
field_names = get_fields(cls).keys()
|
|
199
|
-
field_yield = "\n".join(f" yield self.{f}" for f in field_names)
|
|
200
|
-
code = (
|
|
201
|
-
f"def __iter__(self):\n"
|
|
202
|
-
f"{field_yield}"
|
|
203
|
-
)
|
|
204
|
-
globs = {}
|
|
205
|
-
return code, globs
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
iter_desc = MethodMaker("__iter__", iter_maker)
|
|
209
|
-
new_methods = frozenset(default_methods | {iter_desc})
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
def iterclass(cls=None, /):
|
|
213
|
-
return slotclass(cls, methods=new_methods)
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if __name__ == "__main__":
|
|
217
|
-
@iterclass
|
|
218
|
-
class IterDemo:
|
|
219
|
-
__slots__ = SlotFields(
|
|
220
|
-
a=1,
|
|
221
|
-
b=2,
|
|
222
|
-
c=3,
|
|
223
|
-
d=4,
|
|
224
|
-
e=5,
|
|
225
|
-
)
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
ex = IterDemo()
|
|
229
|
-
print([item for item in ex])
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
You could also choose to yield tuples of `name, value` pairs in your implementation.
|
|
233
|
-
|
|
234
190
|
#### Frozen Classes ####
|
|
235
191
|
|
|
236
|
-
|
|
237
|
-
|
|
192
|
+
In order to make frozen classes you need to replace `__setattr__` and `__delattr__`
|
|
193
|
+
|
|
194
|
+
The building blocks for this are actually already included as they're used to prevent
|
|
195
|
+
`fieldclass` instances from being mutated.
|
|
238
196
|
|
|
239
|
-
|
|
240
|
-
> when generated so the name remains correct even if the class name is changed.
|
|
197
|
+
These methods can be reused to make `slotclasses` 'frozen'.
|
|
241
198
|
|
|
242
199
|
```python
|
|
243
200
|
from ducktools.classbuilder import (
|
|
244
201
|
slotclass,
|
|
245
|
-
get_fields,
|
|
246
202
|
SlotFields,
|
|
247
|
-
MethodMaker,
|
|
248
203
|
default_methods,
|
|
204
|
+
frozen_setattr_desc,
|
|
205
|
+
frozen_delattr_desc,
|
|
249
206
|
)
|
|
250
207
|
|
|
251
208
|
|
|
252
|
-
|
|
253
|
-
globs = {
|
|
254
|
-
"object_setattr": object.__setattr__
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
field_names = set(get_fields(cls).keys())
|
|
258
|
-
|
|
259
|
-
code = (
|
|
260
|
-
f"def __setattr__(self, name, value):\n"
|
|
261
|
-
f" fields = {field_names!r}\n"
|
|
262
|
-
f" if name in fields and not hasattr(self, name):\n"
|
|
263
|
-
f" object_setattr(self, name, value)\n"
|
|
264
|
-
f" else:\n"
|
|
265
|
-
f' raise TypeError(f"{{type(self).__name__!r}} object does not support attribute assignment")'
|
|
266
|
-
)
|
|
267
|
-
return code, globs
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
def delattr_maker(cls):
|
|
271
|
-
code = (
|
|
272
|
-
f"def __delattr__(self, name):\n"
|
|
273
|
-
f' raise TypeError(f"{{type(self).__name__!r}} object does not support attribute deletion")'
|
|
274
|
-
)
|
|
275
|
-
globs = {}
|
|
276
|
-
return code, globs
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
setattr_desc = MethodMaker("__setattr__", setattr_maker)
|
|
280
|
-
delattr_desc = MethodMaker("__delattr__", delattr_maker)
|
|
281
|
-
|
|
282
|
-
new_methods = frozenset(default_methods | {setattr_desc, delattr_desc})
|
|
209
|
+
new_methods = default_methods | {frozen_setattr_desc, frozen_delattr_desc}
|
|
283
210
|
|
|
284
211
|
|
|
285
212
|
def frozen(cls, /):
|
|
@@ -315,6 +242,53 @@ if __name__ == "__main__":
|
|
|
315
242
|
print(e)
|
|
316
243
|
```
|
|
317
244
|
|
|
245
|
+
#### Iterable Classes ####
|
|
246
|
+
|
|
247
|
+
Say you want to make the class iterable, so you want to add `__iter__`.
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
from ducktools.classbuilder import (
|
|
251
|
+
default_methods, get_fields, slotclass, MethodMaker, SlotFields
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def iter_maker(cls):
|
|
256
|
+
field_names = get_fields(cls).keys()
|
|
257
|
+
field_yield = "\n".join(f" yield self.{f}" for f in field_names)
|
|
258
|
+
code = (
|
|
259
|
+
f"def __iter__(self):\n"
|
|
260
|
+
f"{field_yield}"
|
|
261
|
+
)
|
|
262
|
+
globs = {}
|
|
263
|
+
return code, globs
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
iter_desc = MethodMaker("__iter__", iter_maker)
|
|
267
|
+
new_methods = frozenset(default_methods | {iter_desc})
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def iterclass(cls=None, /):
|
|
271
|
+
return slotclass(cls, methods=new_methods)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
if __name__ == "__main__":
|
|
275
|
+
@iterclass
|
|
276
|
+
class IterDemo:
|
|
277
|
+
__slots__ = SlotFields(
|
|
278
|
+
a=1,
|
|
279
|
+
b=2,
|
|
280
|
+
c=3,
|
|
281
|
+
d=4,
|
|
282
|
+
e=5,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
ex = IterDemo()
|
|
287
|
+
print([item for item in ex])
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
You could also choose to yield tuples of `name, value` pairs in your implementation.
|
|
291
|
+
|
|
318
292
|
### Extending Field ###
|
|
319
293
|
|
|
320
294
|
#### Excluding Attributes ####
|
|
@@ -605,93 +579,15 @@ if __name__ == "__main__":
|
|
|
605
579
|
```
|
|
606
580
|
|
|
607
581
|
### Gatherers ###
|
|
608
|
-
#### Using type hints/annotations instead of slots? ####
|
|
609
|
-
|
|
610
|
-
Have you heard of [dataclasses](https://docs.python.org/3/library/dataclasses.html)?
|
|
611
|
-
|
|
612
|
-
But we can also do that. These classes will not be slotted, however,
|
|
613
|
-
due to the issues mentioned in the readme.
|
|
614
|
-
|
|
615
|
-
```python
|
|
616
|
-
import sys
|
|
617
|
-
from ducktools.classbuilder import builder, default_methods, Field, NOTHING
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
def _is_classvar(hint):
|
|
621
|
-
# Avoid importing typing if it's not already used
|
|
622
|
-
_typing = sys.modules.get("typing")
|
|
623
|
-
if _typing:
|
|
624
|
-
if (
|
|
625
|
-
hint is _typing.ClassVar
|
|
626
|
-
or getattr(hint, "__origin__", None) is _typing.ClassVar
|
|
627
|
-
):
|
|
628
|
-
return True
|
|
629
|
-
# String used as annotation
|
|
630
|
-
elif isinstance(hint, str) and "ClassVar" in hint:
|
|
631
|
-
return True
|
|
632
|
-
return False
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
def annotation_gatherer(cls):
|
|
636
|
-
cls_annotations = cls.__dict__.get("__annotations__", {})
|
|
637
|
-
cls_fields = {}
|
|
638
|
-
|
|
639
|
-
for k, v in cls_annotations.items():
|
|
640
|
-
# Ignore ClassVar
|
|
641
|
-
if _is_classvar(v):
|
|
642
|
-
continue
|
|
643
|
-
|
|
644
|
-
attrib = getattr(cls, k, NOTHING)
|
|
645
|
-
|
|
646
|
-
if attrib is not NOTHING:
|
|
647
|
-
if isinstance(attrib, Field):
|
|
648
|
-
attrib.type = v
|
|
649
|
-
else:
|
|
650
|
-
attrib = Field(default=attrib)
|
|
651
|
-
|
|
652
|
-
# Remove the class variable
|
|
653
|
-
delattr(cls, k)
|
|
654
|
-
|
|
655
|
-
else:
|
|
656
|
-
attrib = Field()
|
|
657
|
-
|
|
658
|
-
cls_fields[k] = attrib
|
|
659
|
-
|
|
660
|
-
return cls_fields
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
def annotation_class(cls=None, /, *, methods=default_methods):
|
|
664
|
-
return builder(cls, gatherer=annotation_gatherer, methods=methods)
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
if __name__ == "__main__":
|
|
668
|
-
import typing
|
|
669
|
-
|
|
670
|
-
@annotation_class
|
|
671
|
-
class H2G2:
|
|
672
|
-
the_answer: int = 42
|
|
673
|
-
the_question: str = Field(
|
|
674
|
-
default="What do you get if you multiply six by nine?",
|
|
675
|
-
)
|
|
676
|
-
the_book: typing.ClassVar[str] = "The Hitchhiker's Guide to the Galaxy"
|
|
677
|
-
the_author: "typing.ClassVar[str]" = "Douglas Adams"
|
|
678
|
-
|
|
679
|
-
ex = H2G2()
|
|
680
|
-
print(ex)
|
|
681
|
-
ex2 = H2G2(
|
|
682
|
-
the_question="What is the ultimate answer to the meaning of life, the universe, and everything?"
|
|
683
|
-
)
|
|
684
|
-
print(ex2)
|
|
685
|
-
|
|
686
|
-
print(H2G2.the_book)
|
|
687
|
-
print(H2G2.the_author)
|
|
688
|
-
```
|
|
689
|
-
|
|
690
582
|
#### What about using annotations instead of `Field(init=False, ...)` ####
|
|
691
583
|
|
|
692
584
|
This seems to be a feature people keep requesting for `dataclasses`.
|
|
693
585
|
This is also doable.
|
|
694
586
|
|
|
587
|
+
> Note: Field classes will be frozen when running under pytest.
|
|
588
|
+
> They should not be mutated by gatherers.
|
|
589
|
+
> If you need to change the value of a field use Field.from_field(...) to make a new instance.
|
|
590
|
+
|
|
695
591
|
```python
|
|
696
592
|
import inspect
|
|
697
593
|
from pprint import pp
|
|
@@ -63,6 +63,27 @@ ex = SlottedDC()
|
|
|
63
63
|
print(ex)
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
+
## Annotation Class Usage ##
|
|
67
|
+
|
|
68
|
+
> Annotation based classes via 'extras' are only supported on Python 3.10 or later!
|
|
69
|
+
|
|
70
|
+
Building classes based on annotations requires the `extras` submodule.
|
|
71
|
+
This is separate because it relies on `inspect` to simplify the implementation
|
|
72
|
+
but is a slow import.
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from ducktools.classbuilder import annotationclass
|
|
76
|
+
|
|
77
|
+
@annotationclass
|
|
78
|
+
class AnnotatedDC:
|
|
79
|
+
the_answer: int = 42
|
|
80
|
+
the_question: str = "What do you get if you multiply six by nine?"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
ex = AnnotatedDC()
|
|
84
|
+
print(ex)
|
|
85
|
+
```
|
|
86
|
+
|
|
66
87
|
## Indices and tables ##
|
|
67
88
|
|
|
68
89
|
* {ref}`genindex`
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Performance tests #
|
|
2
|
+
|
|
3
|
+
Rough specs: 2023 Windows 10 Desktop: Intel i5-13600KF, Crucial P3 1TB PCIe M.2 2280 SSD
|
|
4
|
+
|
|
5
|
+
## Import Times ##
|
|
6
|
+
|
|
7
|
+
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|
|
8
|
+
|:---|---:|---:|---:|---:|
|
|
9
|
+
| `python -c "pass"` | 23.2 ± 0.8 | 21.8 | 25.3 | 1.00 |
|
|
10
|
+
| `python -c "from ducktools.classbuilder import slotclass"` | 23.4 ± 1.8 | 22.3 | 32.8 | 1.01 ± 0.09 |
|
|
11
|
+
| `python -c "from ducktools.classbuilder.prefab import prefab"` | 23.7 ± 0.9 | 22.8 | 27.6 | 1.02 ± 0.05 |
|
|
12
|
+
| `python -c "from collections import namedtuple"` | 24.2 ± 0.8 | 23.3 | 27.5 | 1.04 ± 0.05 |
|
|
13
|
+
| `python -c "from typing import NamedTuple"` | 32.2 ± 0.8 | 30.7 | 35.7 | 1.39 ± 0.06 |
|
|
14
|
+
| `python -c "from dataclasses import dataclass"` | 39.6 ± 1.1 | 37.9 | 43.2 | 1.70 ± 0.08 |
|
|
15
|
+
| `python -c "from attrs import define"` | 54.0 ± 1.8 | 51.4 | 60.1 | 2.32 ± 0.11 |
|
|
16
|
+
| `python -c "from pydantic import BaseModel"` | 70.2 ± 1.2 | 68.3 | 72.7 | 3.02 ± 0.12 |
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
## Loading a module with 100 classes defined ##
|
|
21
|
+
|
|
22
|
+
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|
|
23
|
+
|:---|---:|---:|---:|---:|
|
|
24
|
+
| `python -c "pass"` | 22.9 ± 0.4 | 21.8 | 23.6 | 1.00 |
|
|
25
|
+
| `python hyperfine_importers/native_classes_timer.py` | 24.2 ± 0.7 | 22.8 | 26.6 | 1.06 ± 0.04 |
|
|
26
|
+
| `python hyperfine_importers/slotclasses_timer.py` | 24.9 ± 0.7 | 23.8 | 27.7 | 1.09 ± 0.04 |
|
|
27
|
+
| `python hyperfine_importers/prefab_timer.py` | 25.8 ± 0.5 | 24.8 | 27.2 | 1.13 ± 0.03 |
|
|
28
|
+
| `python hyperfine_importers/prefab_slots_timer.py` | 25.9 ± 0.6 | 24.9 | 27.5 | 1.13 ± 0.03 |
|
|
29
|
+
| `python hyperfine_importers/prefab_eval_timer.py` | 35.1 ± 0.6 | 34.0 | 36.4 | 1.53 ± 0.04 |
|
|
30
|
+
| `python hyperfine_importers/namedtuples_timer.py` | 28.3 ± 0.4 | 27.5 | 29.0 | 1.24 ± 0.03 |
|
|
31
|
+
| `python hyperfine_importers/typed_namedtuples_timer.py` | 39.1 ± 0.9 | 37.4 | 41.8 | 1.71 ± 0.05 |
|
|
32
|
+
| `python hyperfine_importers/dataclasses_timer.py` | 61.1 ± 2.1 | 58.1 | 69.1 | 2.67 ± 0.11 |
|
|
33
|
+
| `python hyperfine_importers/attrs_noslots_timer.py` | 88.9 ± 2.2 | 86.7 | 98.3 | 3.88 ± 0.12 |
|
|
34
|
+
| `python hyperfine_importers/attrs_slots_timer.py` | 90.9 ± 1.4 | 88.9 | 94.2 | 3.97 ± 0.10 |
|
|
35
|
+
| `python hyperfine_importers/pydantic_timer.py` | 172.4 ± 5.3 | 164.5 | 181.5 | 7.53 ± 0.27 |
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
## Class Generation time without imports ##
|
|
40
|
+
|
|
41
|
+
From `perf_profile.py`.
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
Python Version: 3.12.2 (tags/v3.12.2:6abddd9, Feb 6 2024, 21:26:36) [MSC v.1937 64 bit (AMD64)]
|
|
45
|
+
Classbuilder version: v0.4.0
|
|
46
|
+
Platform: Windows-10-10.0.19045-SP0
|
|
47
|
+
Time for 100 imports of 100 classes defined with 5 basic attributes
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
| Method | Total Time (seconds) |
|
|
51
|
+
| --- | --- |
|
|
52
|
+
| standard classes | 0.07 |
|
|
53
|
+
| namedtuple | 0.33 |
|
|
54
|
+
| NamedTuple | 0.50 |
|
|
55
|
+
| dataclasses | 2.05 |
|
|
56
|
+
| attrs 23.2.0 | 3.73 |
|
|
57
|
+
| pydantic 2.7.1 | 4.19 |
|
|
58
|
+
| dabeaz/cluegen | 0.10 |
|
|
59
|
+
| dabeaz/cluegen_eval | 0.91 |
|
|
60
|
+
| dabeaz/dataklasses | 0.10 |
|
|
61
|
+
| dabeaz/dataklasses_eval | 0.10 |
|
|
62
|
+
| slotclass v0.4.0 | 0.12 |
|
|
63
|
+
| prefab_slots v0.4.0 | 0.16 |
|
|
64
|
+
| prefab v0.4.0 | 0.18 |
|
|
65
|
+
| prefab_attributes v0.4.0 | 0.16 |
|
|
66
|
+
| prefab_eval v0.4.0 | 1.12 |
|
|
@@ -33,7 +33,7 @@ where = ["src"]
|
|
|
33
33
|
version = {attr = "ducktools.classbuilder.__version__"}
|
|
34
34
|
|
|
35
35
|
[project.optional-dependencies]
|
|
36
|
-
testing = ["pytest", "pytest-cov", "mypy"]
|
|
36
|
+
testing = ["pytest", "pytest-cov", "mypy", "typing_extensions; python_version < '3.10'"]
|
|
37
37
|
docs = ["sphinx", "myst-parser", "sphinx_rtd_theme"]
|
|
38
38
|
|
|
39
39
|
[project.urls]
|