ducktools-classbuilder 0.3.0__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.

Files changed (60) hide show
  1. {ducktools_classbuilder-0.3.0/src/ducktools_classbuilder.egg-info → ducktools_classbuilder-0.4.0}/PKG-INFO +26 -2
  2. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/README.md +24 -1
  3. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/docs/extension_examples.md +62 -166
  4. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/docs/index.md +21 -0
  5. ducktools_classbuilder-0.4.0/docs/perf/performance_tests.md +66 -0
  6. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/pyproject.toml +1 -1
  7. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/src/ducktools/classbuilder/__init__.py +264 -85
  8. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/src/ducktools/classbuilder/__init__.pyi +44 -5
  9. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/src/ducktools/classbuilder/prefab.py +23 -88
  10. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/src/ducktools/classbuilder/prefab.pyi +0 -8
  11. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0/src/ducktools_classbuilder.egg-info}/PKG-INFO +26 -2
  12. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/src/ducktools_classbuilder.egg-info/SOURCES.txt +1 -0
  13. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/src/ducktools_classbuilder.egg-info/requires.txt +3 -0
  14. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/examples/creation.py +22 -0
  15. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/test_creation.py +37 -16
  16. ducktools_classbuilder-0.4.0/tests/test_annotated.py +140 -0
  17. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/tests/test_core.py +54 -0
  18. ducktools_classbuilder-0.3.0/docs/perf/performance_tests.md +0 -63
  19. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/LICENSE.md +0 -0
  20. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/MANIFEST.in +0 -0
  21. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/docs/Makefile +0 -0
  22. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/docs/api.md +0 -0
  23. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/docs/approach_vs_tool.md +0 -0
  24. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/docs/conf.py +0 -0
  25. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/docs/make.bat +0 -0
  26. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/docs/prefab/index.md +0 -0
  27. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/setup.cfg +0 -0
  28. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/src/ducktools/classbuilder/py.typed +0 -0
  29. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/src/ducktools_classbuilder.egg-info/dependency_links.txt +0 -0
  30. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/src/ducktools_classbuilder.egg-info/top_level.txt +0 -0
  31. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/tests/prefab/dynamic/test_compare_attrib.py +0 -0
  32. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/tests/prefab/dynamic/test_construction.py +0 -0
  33. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/tests/prefab/dynamic/test_internals.py +0 -0
  34. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/tests/prefab/dynamic/test_pre_post_init.py +0 -0
  35. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/tests/prefab/dynamic/test_slots_novalues.py +0 -0
  36. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/tests/prefab/dynamic/test_slotted_class.py +0 -0
  37. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/conftest.py +0 -0
  38. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/examples/creation_empty.py +0 -0
  39. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/examples/dunders.py +0 -0
  40. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/examples/fails/creation_1.py +0 -0
  41. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/examples/fails/creation_2.py +0 -0
  42. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/examples/fails/creation_3.py +0 -0
  43. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/examples/fails/creation_5.py +0 -0
  44. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/examples/fails/inheritance_1.py +0 -0
  45. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/examples/fails/inheritance_2.py +0 -0
  46. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/examples/frozen_prefabs.py +0 -0
  47. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/examples/funcs_prefabs.py +0 -0
  48. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/examples/hint_syntax.py +0 -0
  49. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/examples/inheritance.py +0 -0
  50. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/examples/init_ex.py +0 -0
  51. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/examples/kw_only.py +0 -0
  52. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/examples/repr_func.py +0 -0
  53. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/test_dunders.py +0 -0
  54. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/test_frozen.py +0 -0
  55. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/test_funcs.py +0 -0
  56. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/test_hint_syntax.py +0 -0
  57. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/test_inheritance.py +0 -0
  58. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/test_init.py +0 -0
  59. {ducktools_classbuilder-0.3.0 → ducktools_classbuilder-0.4.0}/tests/prefab/shared/test_kw_only.py +0 -0
  60. {ducktools_classbuilder-0.3.0 → 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.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
- Here's an example of frozen slotted classes that only allow assignment once
237
- (which happens in the `__init__` method generated).
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
- > Note that these methods use `type(self).__name__` instead of `cls.__name__`
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
- def setattr_maker(cls):
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]