ducktools-classbuilder 0.7.5__tar.gz → 0.8.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ducktools-classbuilder might be problematic. Click here for more details.

Files changed (95) hide show
  1. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/MANIFEST.in +1 -0
  2. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/PKG-INFO +16 -38
  3. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/README.md +14 -14
  4. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/docs/api.md +0 -4
  5. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/docs/extension_examples.md +2 -1
  6. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/docs/generated_code.md +16 -10
  7. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/docs/index.md +35 -7
  8. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/docs/tutorial.md +8 -2
  9. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/docs_code/docs_ex9_annotated.py +0 -2
  10. ducktools_classbuilder-0.8.1/docs_code/index_example.py +34 -0
  11. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/docs_code/tutorial_code.py +7 -4
  12. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/pyproject.toml +0 -1
  13. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/src/ducktools/classbuilder/__init__.py +2 -2
  14. ducktools_classbuilder-0.8.1/src/ducktools/classbuilder/_version.py +2 -0
  15. ducktools_classbuilder-0.8.1/src/ducktools/classbuilder/annotations.py +91 -0
  16. ducktools_classbuilder-0.8.1/src/ducktools/classbuilder/annotations.pyi +12 -0
  17. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/src/ducktools_classbuilder.egg-info/PKG-INFO +16 -38
  18. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/src/ducktools_classbuilder.egg-info/SOURCES.txt +2 -5
  19. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/annotations/test_annotated.py +1 -1
  20. ducktools_classbuilder-0.8.1/tests/annotations/test_annotations_module.py +58 -0
  21. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/annotations/test_future_annotations.py +17 -3
  22. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/py312_tests/test_generic_annotations.py +1 -1
  23. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/test_slotmakermeta.py +1 -1
  24. ducktools_classbuilder-0.7.5/perf/cluegen.py +0 -127
  25. ducktools_classbuilder-0.7.5/perf/dataklasses.py +0 -102
  26. ducktools_classbuilder-0.7.5/perf/hyperfine_testmaker.py +0 -311
  27. ducktools_classbuilder-0.7.5/perf/perf_profile.py +0 -291
  28. ducktools_classbuilder-0.7.5/src/ducktools/classbuilder/_version.py +0 -2
  29. ducktools_classbuilder-0.7.5/src/ducktools/classbuilder/annotations.py +0 -223
  30. ducktools_classbuilder-0.7.5/src/ducktools/classbuilder/annotations.pyi +0 -27
  31. ducktools_classbuilder-0.7.5/tests/annotations/test_annotations_module.py +0 -118
  32. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/.github/dependabot.yml +0 -0
  33. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/.github/workflows/auto_test.yml +0 -0
  34. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/.github/workflows/publish_to_pypi.yml +0 -0
  35. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/.github/workflows/publish_to_testpypi.yml +0 -0
  36. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/.gitignore +0 -0
  37. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/.readthedocs.yaml +0 -0
  38. /ducktools_classbuilder-0.7.5/LICENSE.md → /ducktools_classbuilder-0.8.1/LICENSE +0 -0
  39. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/docs/Makefile +0 -0
  40. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/docs/approach_vs_tool.md +0 -0
  41. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/docs/conf.py +0 -0
  42. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/docs/make.bat +0 -0
  43. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/docs/perf/performance_tests.md +0 -0
  44. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/docs/prefab/index.md +0 -0
  45. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/docs_code/docs_ex10_frozen_attributes.py +0 -0
  46. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/docs_code/docs_ex1_basic.py +0 -0
  47. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/docs_code/docs_ex2_register.py +0 -0
  48. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/docs_code/docs_ex3_iterable.py +0 -0
  49. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/docs_code/docs_ex5_frozen.py +0 -0
  50. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/docs_code/docs_ex7_posonly.py +0 -0
  51. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/docs_code/docs_ex8_converters.py +0 -0
  52. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/setup.cfg +0 -0
  53. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/src/ducktools/classbuilder/__init__.pyi +0 -0
  54. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/src/ducktools/classbuilder/prefab.py +0 -0
  55. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/src/ducktools/classbuilder/prefab.pyi +0 -0
  56. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/src/ducktools/classbuilder/py.typed +0 -0
  57. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/src/ducktools_classbuilder.egg-info/dependency_links.txt +0 -0
  58. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/src/ducktools_classbuilder.egg-info/requires.txt +0 -0
  59. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/src/ducktools_classbuilder.egg-info/top_level.txt +0 -0
  60. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/conftest.py +0 -0
  61. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/prefab/dynamic/test_compare_attrib.py +0 -0
  62. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/prefab/dynamic/test_construction.py +0 -0
  63. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/prefab/dynamic/test_frozen.py +0 -0
  64. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/prefab/dynamic/test_internals.py +0 -0
  65. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/prefab/dynamic/test_pre_post_init.py +0 -0
  66. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/prefab/dynamic/test_private.py +0 -0
  67. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/prefab/dynamic/test_slots_novalues.py +0 -0
  68. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/prefab/dynamic/test_slotted_class.py +0 -0
  69. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/prefab/dynamic/test_subclass_implementation.py +0 -0
  70. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/prefab/shared/conftest.py +0 -0
  71. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/prefab/shared/examples/creation.py +0 -0
  72. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/prefab/shared/examples/creation_empty.py +0 -0
  73. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/prefab/shared/examples/dunders.py +0 -0
  74. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/prefab/shared/examples/fails/creation_2.py +0 -0
  75. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/prefab/shared/examples/fails/creation_3.py +0 -0
  76. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/prefab/shared/examples/fails/creation_5.py +0 -0
  77. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/prefab/shared/examples/fails/inheritance_1.py +0 -0
  78. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/prefab/shared/examples/fails/inheritance_2.py +0 -0
  79. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/prefab/shared/examples/funcs_prefabs.py +0 -0
  80. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/prefab/shared/examples/hint_syntax.py +0 -0
  81. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/prefab/shared/examples/inheritance.py +0 -0
  82. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/prefab/shared/examples/init_ex.py +0 -0
  83. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/prefab/shared/examples/kw_only.py +0 -0
  84. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/prefab/shared/examples/repr_func.py +0 -0
  85. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/prefab/shared/test_creation.py +0 -0
  86. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/prefab/shared/test_dunders.py +0 -0
  87. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/prefab/shared/test_funcs.py +0 -0
  88. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/prefab/shared/test_hint_syntax.py +0 -0
  89. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/prefab/shared/test_inheritance.py +0 -0
  90. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/prefab/shared/test_init.py +0 -0
  91. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/prefab/shared/test_kw_only.py +0 -0
  92. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/prefab/shared/test_repr.py +0 -0
  93. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/py314_tests/test_forwardref_annotations.py +0 -0
  94. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/test_core.py +0 -0
  95. {ducktools_classbuilder-0.7.5 → ducktools_classbuilder-0.8.1}/tests/test_field_flags.py +0 -0
@@ -5,5 +5,6 @@ graft docs
5
5
  prune */build
6
6
  prune */dist
7
7
  prune */.pytest_cache
8
+ prune perf
8
9
 
9
10
  global-exclude *~ *.py[cod] *.so
@@ -1,30 +1,8 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ducktools-classbuilder
3
- Version: 0.7.5
3
+ Version: 0.8.1
4
4
  Summary: Toolkit for creating class boilerplate generators
5
5
  Author: David C Ellis
6
- License: MIT License
7
-
8
- Copyright (c) 2024 David C Ellis
9
-
10
- Permission is hereby granted, free of charge, to any person obtaining a copy
11
- of this software and associated documentation files (the "Software"), to deal
12
- in the Software without restriction, including without limitation the rights
13
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
- copies of the Software, and to permit persons to whom the Software is
15
- furnished to do so, subject to the following conditions:
16
-
17
- The above copyright notice and this permission notice shall be included in all
18
- copies or substantial portions of the Software.
19
-
20
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
- SOFTWARE.
27
-
28
6
  Project-URL: Homepage, https://github.com/davidcellis/ducktools-classbuilder
29
7
  Classifier: Development Status :: 4 - Beta
30
8
  Classifier: Programming Language :: Python :: 3.8
@@ -37,7 +15,7 @@ Classifier: Operating System :: OS Independent
37
15
  Classifier: License :: OSI Approved :: MIT License
38
16
  Requires-Python: >=3.8
39
17
  Description-Content-Type: text/markdown
40
- License-File: LICENSE.md
18
+ License-File: LICENSE
41
19
  Provides-Extra: testing
42
20
  Requires-Dist: pytest>=8.2; extra == "testing"
43
21
  Requires-Dist: pytest-cov; extra == "testing"
@@ -86,10 +64,12 @@ These tools are available from the main `ducktools.classbuilder` module.
86
64
  * `@slotclass`
87
65
  * A decorator based implementation that uses a special dict subclass assigned
88
66
  to `__slots__` to describe the fields for method generation.
89
- * `AnnotationClass`
90
- * A subclass based implementation that works with `__slots__`, type annotations
91
- or `Field(...)` attributes to describe the fields for method generation.
92
- * If `__slots__` isn't used to declare fields, it will be generated by a metaclass.
67
+ * `SlotMakerMeta`
68
+ * A metaclass for creating other implementations using annotations, fields or slots.
69
+ * This metaclass will allow for creating `__slots__` correctly in subclasses.
70
+ * `builder`
71
+ * This is the main tool used for constructing decorators and base classes to provide
72
+ generated methods.
93
73
 
94
74
  Each of these forms of class generation will result in the same methods being
95
75
  attached to the class after the field information has been obtained.
@@ -119,8 +99,9 @@ This includes more customization including `__prefab_pre_init__` and `__prefab_p
119
99
  functions for subclass customization.
120
100
 
121
101
  A `@prefab` decorator and `Prefab` base class are provided.
122
- Similar to `AnnotationClass`, `Prefab` will generate `__slots__` by default.
123
- However decorated classes with `@prefab` that do not declare fields using `__slots__`
102
+
103
+ `Prefab` will generate `__slots__` by default.
104
+ decorated classes with `@prefab` that do not declare fields using `__slots__`
124
105
  will **not** be slotted and there is no `slots` argument to apply this.
125
106
 
126
107
  Here is an example of applying a conversion in `__post_init__`:
@@ -164,7 +145,7 @@ the fields can be set *before* the class is constructed, so the class
164
145
  will work correctly without needing to be rebuilt.
165
146
 
166
147
  For example these two classes would be roughly equivalent, except that
167
- `@dataclass` has had to recreate the class from scratch while `AnnotationClass`
148
+ `@dataclass` has had to recreate the class from scratch while `Prefab`
168
149
  has created `__slots__` and added the methods on to the original class.
169
150
  This means that any references stored to the original class *before*
170
151
  `@dataclass` has rebuilt the class will not be pointing towards the
@@ -179,7 +160,7 @@ functions.
179
160
  ```python
180
161
  import json
181
162
  from dataclasses import dataclass
182
- from ducktools.classbuilder import AnnotationClass, Field
163
+ from ducktools.classbuilder.prefab import Prefab, attribute
183
164
 
184
165
 
185
166
  class _RegisterDescriptor:
@@ -222,10 +203,10 @@ class DataCoords:
222
203
  return {"x": self.x, "y": self.y}
223
204
 
224
205
 
225
- # slots=True is the default for AnnotationClass
226
- class BuilderCoords(AnnotationClass, slots=True):
206
+ # slots=True is the default for Prefab
207
+ class BuilderCoords(Prefab):
227
208
  x: float = 0.0
228
- y: float = Field(default=0.0, doc="y coordinate")
209
+ y: float = attribute(default=0.0, doc="y coordinate")
229
210
 
230
211
  @register.register_method
231
212
  def to_json(self):
@@ -289,9 +270,6 @@ It will copy values provided as the `type` to `Field` into the
289
270
  Values provided to `doc` will be placed in the final `__slots__`
290
271
  field so they are present on the class if `help(...)` is called.
291
272
 
292
- `AnnotationClass` offers the same features with additional methods of gathering
293
- fields.
294
-
295
273
  If you want something with more features you can look at the `prefab`
296
274
  submodule which provides more specific features that differ further from the
297
275
  behaviour of `dataclasses`.
@@ -32,10 +32,12 @@ These tools are available from the main `ducktools.classbuilder` module.
32
32
  * `@slotclass`
33
33
  * A decorator based implementation that uses a special dict subclass assigned
34
34
  to `__slots__` to describe the fields for method generation.
35
- * `AnnotationClass`
36
- * A subclass based implementation that works with `__slots__`, type annotations
37
- or `Field(...)` attributes to describe the fields for method generation.
38
- * If `__slots__` isn't used to declare fields, it will be generated by a metaclass.
35
+ * `SlotMakerMeta`
36
+ * A metaclass for creating other implementations using annotations, fields or slots.
37
+ * This metaclass will allow for creating `__slots__` correctly in subclasses.
38
+ * `builder`
39
+ * This is the main tool used for constructing decorators and base classes to provide
40
+ generated methods.
39
41
 
40
42
  Each of these forms of class generation will result in the same methods being
41
43
  attached to the class after the field information has been obtained.
@@ -65,8 +67,9 @@ This includes more customization including `__prefab_pre_init__` and `__prefab_p
65
67
  functions for subclass customization.
66
68
 
67
69
  A `@prefab` decorator and `Prefab` base class are provided.
68
- Similar to `AnnotationClass`, `Prefab` will generate `__slots__` by default.
69
- However decorated classes with `@prefab` that do not declare fields using `__slots__`
70
+
71
+ `Prefab` will generate `__slots__` by default.
72
+ decorated classes with `@prefab` that do not declare fields using `__slots__`
70
73
  will **not** be slotted and there is no `slots` argument to apply this.
71
74
 
72
75
  Here is an example of applying a conversion in `__post_init__`:
@@ -110,7 +113,7 @@ the fields can be set *before* the class is constructed, so the class
110
113
  will work correctly without needing to be rebuilt.
111
114
 
112
115
  For example these two classes would be roughly equivalent, except that
113
- `@dataclass` has had to recreate the class from scratch while `AnnotationClass`
116
+ `@dataclass` has had to recreate the class from scratch while `Prefab`
114
117
  has created `__slots__` and added the methods on to the original class.
115
118
  This means that any references stored to the original class *before*
116
119
  `@dataclass` has rebuilt the class will not be pointing towards the
@@ -125,7 +128,7 @@ functions.
125
128
  ```python
126
129
  import json
127
130
  from dataclasses import dataclass
128
- from ducktools.classbuilder import AnnotationClass, Field
131
+ from ducktools.classbuilder.prefab import Prefab, attribute
129
132
 
130
133
 
131
134
  class _RegisterDescriptor:
@@ -168,10 +171,10 @@ class DataCoords:
168
171
  return {"x": self.x, "y": self.y}
169
172
 
170
173
 
171
- # slots=True is the default for AnnotationClass
172
- class BuilderCoords(AnnotationClass, slots=True):
174
+ # slots=True is the default for Prefab
175
+ class BuilderCoords(Prefab):
173
176
  x: float = 0.0
174
- y: float = Field(default=0.0, doc="y coordinate")
177
+ y: float = attribute(default=0.0, doc="y coordinate")
175
178
 
176
179
  @register.register_method
177
180
  def to_json(self):
@@ -235,9 +238,6 @@ It will copy values provided as the `type` to `Field` into the
235
238
  Values provided to `doc` will be placed in the final `__slots__`
236
239
  field so they are present on the class if `help(...)` is called.
237
240
 
238
- `AnnotationClass` offers the same features with additional methods of gathering
239
- fields.
240
-
241
241
  If you want something with more features you can look at the `prefab`
242
242
  submodule which provides more specific features that differ further from the
243
243
  behaviour of `dataclasses`.
@@ -40,7 +40,3 @@
40
40
  ```{eval-rst}
41
41
  .. autofunction:: ducktools.classbuilder.annotations::make_annotation_gatherer
42
42
  ```
43
-
44
- ```{eval-rst}
45
- .. autoclass:: ducktools.classbuilder.annotations::AnnotationClass
46
- ```
@@ -469,7 +469,8 @@ To implement this you need to create a new annotated_gatherer function.
469
469
  > If you need to change the value of a field use Field.from_field(...) to make a new instance.
470
470
 
471
471
  ```python
472
- from __future__ import annotations
472
+ # Don't use __future__ annotations with get_ns_annotations in this case
473
+ # as it doesn't evaluate string annotations.
473
474
 
474
475
  import types
475
476
  from typing import Annotated, Any, ClassVar, get_origin
@@ -7,11 +7,14 @@ There is a helper function `get_methods` that can be used to obtain the names an
7
7
  methods that have been attached to the class by the builder.
8
8
 
9
9
  ```python
10
- from ducktools.classbuilder import AnnotationClass, get_methods
10
+ from ducktools.classbuilder import SlotFields, get_methods, slotclass
11
11
 
12
- class Example(AnnotationClass):
13
- a: str = "a"
14
- b: str = "b"
12
+ @slotclass
13
+ class Example:
14
+ __slots__ = SlotFields(
15
+ a='a',
16
+ b='b'
17
+ )
15
18
 
16
19
  print(get_methods(Example))
17
20
  ```
@@ -28,12 +31,15 @@ These can then be used to examine the generated source code and global variables
28
31
  to the `exec` function. This can be useful for debugging code generators.
29
32
 
30
33
  ```python
31
- from ducktools.classbuilder import AnnotationClass, get_methods
32
-
33
- class Example(AnnotationClass):
34
- a: str = "a"
35
- b: str = "b"
36
-
34
+ from ducktools.classbuilder import SlotFields, get_methods, slotclass
35
+
36
+ @slotclass
37
+ class Example:
38
+ __slots__ = SlotFields(
39
+ a='a',
40
+ b='b',
41
+ )
42
+
37
43
  methods = get_methods(Example)
38
44
 
39
45
  for method in methods.values():
@@ -65,21 +65,49 @@ ex = SlottedDC()
65
65
  print(ex)
66
66
  ```
67
67
 
68
- ## Annotation Class Usage ##
68
+ ## Using Annotations ##
69
69
 
70
- There is an additional AnnotationClass base class that allows creating slotted classes
71
- using annotations. This has to be a base class with a specific metaclass in order to
72
- create the `__slots__` field *before* the class has been generated in order to work
73
- correctly.
70
+ It is possible to create slotted classes using Annotations.
71
+ There is a `Prefab` base class in the `prefab` submodule that does this,
72
+ but it also easy to implement using the provided tools.
73
+
74
+ In order to correctly implement `__slots__` this needs to be done
75
+ using a metaclass as `__slots__` must be defined before the **class**
76
+ is created.
74
77
 
75
78
  ```python
76
- from ducktools.classbuilder import AnnotationClass
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
+
77
105
 
78
106
  class AnnotatedDC(AnnotationClass):
79
107
  the_answer: int = 42
80
108
  the_question: str = "What do you get if you multiply six by nine?"
81
109
 
82
-
110
+
83
111
  ex = AnnotatedDC()
84
112
  print(ex)
85
113
  ```
@@ -146,9 +146,15 @@ def report_generator(cls, funcname="report"):
146
146
  report_maker = dtbuild.MethodMaker("report", report_generator)
147
147
  ```
148
148
 
149
- We can take a quick look at what this generates by applying it to an `AnnotationClass`:
149
+ We can take a quick look at what this generates by applying it to a `slotclass`:
150
150
  ```python
151
- class CodegenDemo(dtbuild.AnnotationClass):
151
+ @dtbuild.slotclass
152
+ class CodegenDemo:
153
+ __slots__ = dtbuild.SlotFields(
154
+ field_1="Field one",
155
+ field_2="Field two",
156
+ field_3="Field three",
157
+ )
152
158
  field_1: str = "Field one"
153
159
  field_2: str = "Field two"
154
160
  field_3: str = "Field three"
@@ -1,5 +1,3 @@
1
- from __future__ import annotations
2
-
3
1
  import types
4
2
  from typing import Annotated, Any, ClassVar, get_origin
5
3
 
@@ -0,0 +1,34 @@
1
+ from ducktools.classbuilder import (
2
+ SlotMakerMeta,
3
+ builder,
4
+ check_argument_order,
5
+ default_methods,
6
+ unified_gatherer,
7
+ )
8
+
9
+
10
+ class AnnotationClass(metaclass=SlotMakerMeta):
11
+ __slots__ = {}
12
+
13
+ def __init_subclass__(
14
+ cls,
15
+ methods=default_methods,
16
+ gatherer=unified_gatherer,
17
+ **kwargs
18
+ ):
19
+ # Check class dict otherwise this will always be True as this base
20
+ # class uses slots.
21
+ slots = "__slots__" in cls.__dict__
22
+
23
+ builder(cls, gatherer=gatherer, methods=methods, flags={"slotted": slots})
24
+ check_argument_order(cls)
25
+ super().__init_subclass__(**kwargs)
26
+
27
+
28
+ class AnnotatedDC(AnnotationClass):
29
+ the_answer: int = 42
30
+ the_question: str = "What do you get if you multiply six by nine?"
31
+
32
+
33
+ ex = AnnotatedDC()
34
+ print(ex)
@@ -81,10 +81,13 @@ report_maker = dtbuild.MethodMaker("report", report_generator)
81
81
 
82
82
 
83
83
  # View the generated code by testing on a demo class
84
- class CodegenDemo(dtbuild.AnnotationClass):
85
- field_1: str = "Field one"
86
- field_2: str = "Field two"
87
- field_3: str = "Field three"
84
+ @dtbuild.slotclass
85
+ class CodegenDemo:
86
+ __slots__ = dtbuild.SlotFields(
87
+ field_1="Field one",
88
+ field_2="Field two",
89
+ field_3="Field three",
90
+ )
88
91
 
89
92
 
90
93
  print(report_generator(CodegenDemo).source_code)
@@ -25,7 +25,6 @@ classifiers = [
25
25
  "License :: OSI Approved :: MIT License",
26
26
  ]
27
27
  dynamic = ['version']
28
- license = {file = "LICENSE.md"}
29
28
 
30
29
  [project.optional-dependencies]
31
30
  testing = ["pytest>=8.2", "pytest-cov", "typing_extensions"]
@@ -769,7 +769,7 @@ def make_annotation_gatherer(
769
769
  if is_classvar(v):
770
770
  continue
771
771
 
772
- if v is KW_ONLY:
772
+ if v is KW_ONLY or (isinstance(v, str) and v == "KW_ONLY"):
773
773
  if kw_flag:
774
774
  raise SyntaxError("KW_ONLY sentinel may only appear once.")
775
775
  kw_flag = True
@@ -868,7 +868,7 @@ def make_unified_gatherer(
868
868
  # To choose between annotation and attribute gatherers
869
869
  # compare sets of names.
870
870
  # Don't bother evaluating string annotations, as we only need names
871
- cls_annotations = get_ns_annotations(cls_dict, eval_str=False)
871
+ cls_annotations = get_ns_annotations(cls_dict)
872
872
  cls_attributes = {
873
873
  k: v for k, v in cls_dict.items() if isinstance(v, field_type)
874
874
  }
@@ -0,0 +1,2 @@
1
+ __version__ = "0.8.1"
2
+ __version_tuple__ = (0, 8, 1)
@@ -0,0 +1,91 @@
1
+ # MIT License
2
+ #
3
+ # Copyright (c) 2024 David C Ellis
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+ import sys
23
+
24
+
25
+ def get_ns_annotations(ns):
26
+ """
27
+ Given a class namespace, attempt to retrieve the
28
+ annotations dictionary.
29
+
30
+ :param ns: Class namespace (eg cls.__dict__)
31
+ :return: dictionary of annotations
32
+ """
33
+
34
+ annotations = ns.get("__annotations__")
35
+ if annotations is not None:
36
+ annotations = annotations.copy()
37
+ else:
38
+ # See if we're using PEP-649 annotations
39
+ # Guarding this with a try/except instead of a version check
40
+ # In case there's a change and PEP-649 somehow doesn't make 3.14
41
+ try:
42
+ from annotationlib import Format, call_annotate_function, get_annotate_function
43
+ except ImportError:
44
+ pass
45
+ else:
46
+ annotate = ns.get("__annotate__") # Works in the alphas, but may break
47
+ if not annotate:
48
+ annotate = get_annotate_function(ns)
49
+ if annotate:
50
+ annotations = call_annotate_function(annotate, format=Format.FORWARDREF)
51
+
52
+ if annotations is None:
53
+ annotations = {}
54
+
55
+ return annotations
56
+
57
+
58
+ def is_classvar(hint):
59
+ if isinstance(hint, str):
60
+ # String annotations, just check if the string 'ClassVar' is in there
61
+ # This is overly broad and could be smarter.
62
+ return "ClassVar" in hint
63
+ elif (annotationlib := sys.modules.get("annotationlib")) and isinstance(hint, annotationlib.ForwardRef):
64
+ return "ClassVar" in hint.__arg__
65
+ else:
66
+ _typing = sys.modules.get("typing")
67
+ if _typing:
68
+ # Annotated is a nightmare I'm never waking up from
69
+ # 3.8 and 3.9 need Annotated from typing_extensions
70
+ # 3.8 also needs get_origin from typing_extensions
71
+ if sys.version_info < (3, 10):
72
+ _typing_extensions = sys.modules.get("typing_extensions")
73
+ if _typing_extensions:
74
+ _Annotated = _typing_extensions.Annotated
75
+ _get_origin = _typing_extensions.get_origin
76
+ else:
77
+ _Annotated, _get_origin = None, None
78
+ else:
79
+ _Annotated = _typing.Annotated
80
+ _get_origin = _typing.get_origin
81
+
82
+ if _Annotated and _get_origin(hint) is _Annotated:
83
+ hint = getattr(hint, "__origin__", None)
84
+
85
+ if (
86
+ hint is _typing.ClassVar
87
+ or getattr(hint, "__origin__", None) is _typing.ClassVar
88
+ ):
89
+ return True
90
+ return False
91
+
@@ -0,0 +1,12 @@
1
+ import typing
2
+ import types
3
+
4
+ _CopiableMappings = dict[str, typing.Any] | types.MappingProxyType[str, typing.Any]
5
+
6
+ def get_ns_annotations(
7
+ ns: _CopiableMappings,
8
+ ) -> dict[str, typing.Any]: ...
9
+
10
+ def is_classvar(
11
+ hint: object,
12
+ ) -> bool: ...
@@ -1,30 +1,8 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ducktools-classbuilder
3
- Version: 0.7.5
3
+ Version: 0.8.1
4
4
  Summary: Toolkit for creating class boilerplate generators
5
5
  Author: David C Ellis
6
- License: MIT License
7
-
8
- Copyright (c) 2024 David C Ellis
9
-
10
- Permission is hereby granted, free of charge, to any person obtaining a copy
11
- of this software and associated documentation files (the "Software"), to deal
12
- in the Software without restriction, including without limitation the rights
13
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
- copies of the Software, and to permit persons to whom the Software is
15
- furnished to do so, subject to the following conditions:
16
-
17
- The above copyright notice and this permission notice shall be included in all
18
- copies or substantial portions of the Software.
19
-
20
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
- SOFTWARE.
27
-
28
6
  Project-URL: Homepage, https://github.com/davidcellis/ducktools-classbuilder
29
7
  Classifier: Development Status :: 4 - Beta
30
8
  Classifier: Programming Language :: Python :: 3.8
@@ -37,7 +15,7 @@ Classifier: Operating System :: OS Independent
37
15
  Classifier: License :: OSI Approved :: MIT License
38
16
  Requires-Python: >=3.8
39
17
  Description-Content-Type: text/markdown
40
- License-File: LICENSE.md
18
+ License-File: LICENSE
41
19
  Provides-Extra: testing
42
20
  Requires-Dist: pytest>=8.2; extra == "testing"
43
21
  Requires-Dist: pytest-cov; extra == "testing"
@@ -86,10 +64,12 @@ These tools are available from the main `ducktools.classbuilder` module.
86
64
  * `@slotclass`
87
65
  * A decorator based implementation that uses a special dict subclass assigned
88
66
  to `__slots__` to describe the fields for method generation.
89
- * `AnnotationClass`
90
- * A subclass based implementation that works with `__slots__`, type annotations
91
- or `Field(...)` attributes to describe the fields for method generation.
92
- * If `__slots__` isn't used to declare fields, it will be generated by a metaclass.
67
+ * `SlotMakerMeta`
68
+ * A metaclass for creating other implementations using annotations, fields or slots.
69
+ * This metaclass will allow for creating `__slots__` correctly in subclasses.
70
+ * `builder`
71
+ * This is the main tool used for constructing decorators and base classes to provide
72
+ generated methods.
93
73
 
94
74
  Each of these forms of class generation will result in the same methods being
95
75
  attached to the class after the field information has been obtained.
@@ -119,8 +99,9 @@ This includes more customization including `__prefab_pre_init__` and `__prefab_p
119
99
  functions for subclass customization.
120
100
 
121
101
  A `@prefab` decorator and `Prefab` base class are provided.
122
- Similar to `AnnotationClass`, `Prefab` will generate `__slots__` by default.
123
- However decorated classes with `@prefab` that do not declare fields using `__slots__`
102
+
103
+ `Prefab` will generate `__slots__` by default.
104
+ decorated classes with `@prefab` that do not declare fields using `__slots__`
124
105
  will **not** be slotted and there is no `slots` argument to apply this.
125
106
 
126
107
  Here is an example of applying a conversion in `__post_init__`:
@@ -164,7 +145,7 @@ the fields can be set *before* the class is constructed, so the class
164
145
  will work correctly without needing to be rebuilt.
165
146
 
166
147
  For example these two classes would be roughly equivalent, except that
167
- `@dataclass` has had to recreate the class from scratch while `AnnotationClass`
148
+ `@dataclass` has had to recreate the class from scratch while `Prefab`
168
149
  has created `__slots__` and added the methods on to the original class.
169
150
  This means that any references stored to the original class *before*
170
151
  `@dataclass` has rebuilt the class will not be pointing towards the
@@ -179,7 +160,7 @@ functions.
179
160
  ```python
180
161
  import json
181
162
  from dataclasses import dataclass
182
- from ducktools.classbuilder import AnnotationClass, Field
163
+ from ducktools.classbuilder.prefab import Prefab, attribute
183
164
 
184
165
 
185
166
  class _RegisterDescriptor:
@@ -222,10 +203,10 @@ class DataCoords:
222
203
  return {"x": self.x, "y": self.y}
223
204
 
224
205
 
225
- # slots=True is the default for AnnotationClass
226
- class BuilderCoords(AnnotationClass, slots=True):
206
+ # slots=True is the default for Prefab
207
+ class BuilderCoords(Prefab):
227
208
  x: float = 0.0
228
- y: float = Field(default=0.0, doc="y coordinate")
209
+ y: float = attribute(default=0.0, doc="y coordinate")
229
210
 
230
211
  @register.register_method
231
212
  def to_json(self):
@@ -289,9 +270,6 @@ It will copy values provided as the `type` to `Field` into the
289
270
  Values provided to `doc` will be placed in the final `__slots__`
290
271
  field so they are present on the class if `help(...)` is called.
291
272
 
292
- `AnnotationClass` offers the same features with additional methods of gathering
293
- fields.
294
-
295
273
  If you want something with more features you can look at the `prefab`
296
274
  submodule which provides more specific features that differ further from the
297
275
  behaviour of `dataclasses`.
@@ -1,6 +1,6 @@
1
1
  .gitignore
2
2
  .readthedocs.yaml
3
- LICENSE.md
3
+ LICENSE
4
4
  MANIFEST.in
5
5
  README.md
6
6
  pyproject.toml
@@ -27,11 +27,8 @@ docs_code/docs_ex5_frozen.py
27
27
  docs_code/docs_ex7_posonly.py
28
28
  docs_code/docs_ex8_converters.py
29
29
  docs_code/docs_ex9_annotated.py
30
+ docs_code/index_example.py
30
31
  docs_code/tutorial_code.py
31
- perf/cluegen.py
32
- perf/dataklasses.py
33
- perf/hyperfine_testmaker.py
34
- perf/perf_profile.py
35
32
  src/ducktools/classbuilder/__init__.py
36
33
  src/ducktools/classbuilder/__init__.pyi
37
34
  src/ducktools/classbuilder/_version.py