ducktools-classbuilder 0.7.0__tar.gz → 0.7.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.7.0 → ducktools_classbuilder-0.7.1}/PKG-INFO +2 -1
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/docs/extension_examples.md +2 -2
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/docs/tutorial.md +10 -8
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/docs_code/docs_ex3_iterable.py +1 -1
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/docs_code/tutorial_code.py +4 -2
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/pyproject.toml +1 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/src/ducktools/classbuilder/__init__.py +0 -7
- ducktools_classbuilder-0.7.1/src/ducktools/classbuilder/_version.py +2 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/src/ducktools/classbuilder/annotations.py +106 -23
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/src/ducktools/classbuilder/annotations.pyi +6 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/src/ducktools_classbuilder.egg-info/PKG-INFO +2 -1
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/src/ducktools_classbuilder.egg-info/SOURCES.txt +3 -1
- ducktools_classbuilder-0.7.1/tests/annotations/test_future_annotations.py +45 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/conftest.py +2 -2
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/dynamic/test_slotted_class.py +0 -1
- ducktools_classbuilder-0.7.1/tests/py314_tests/test_forwardref_annotations.py +45 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/test_core.py +0 -1
- ducktools_classbuilder-0.7.0/src/ducktools/classbuilder/_version.py +0 -2
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/.github/dependabot.yml +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/.github/workflows/auto_test.yml +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/.github/workflows/publish_to_pypi.yml +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/.github/workflows/publish_to_testpypi.yml +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/.gitignore +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/.readthedocs.yaml +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/LICENSE.md +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/MANIFEST.in +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/README.md +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/docs/Makefile +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/docs/api.md +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/docs/approach_vs_tool.md +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/docs/conf.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/docs/generated_code.md +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/docs/index.md +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/docs/make.bat +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/docs/perf/performance_tests.md +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/docs/prefab/index.md +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/docs_code/docs_ex1_basic.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/docs_code/docs_ex2_register.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/docs_code/docs_ex5_frozen.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/docs_code/docs_ex7_posonly.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/docs_code/docs_ex8_converters.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/docs_code/docs_ex9_annotated.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/perf/cluegen.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/perf/dataklasses.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/perf/hyperfine_testmaker.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/perf/perf_profile.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/setup.cfg +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/src/ducktools/classbuilder/__init__.pyi +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/src/ducktools/classbuilder/prefab.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/src/ducktools/classbuilder/prefab.pyi +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/src/ducktools/classbuilder/py.typed +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/src/ducktools_classbuilder.egg-info/dependency_links.txt +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/src/ducktools_classbuilder.egg-info/requires.txt +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/src/ducktools_classbuilder.egg-info/top_level.txt +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/annotations/test_annotated.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/annotations/test_annotations_module.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/dynamic/test_compare_attrib.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/dynamic/test_construction.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/dynamic/test_frozen.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/dynamic/test_internals.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/dynamic/test_pre_post_init.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/dynamic/test_private.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/dynamic/test_slots_novalues.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/dynamic/test_subclass_implementation.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/conftest.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/creation.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/creation_empty.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/dunders.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/fails/creation_1.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/fails/creation_2.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/fails/creation_3.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/fails/creation_5.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/fails/inheritance_1.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/fails/inheritance_2.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/funcs_prefabs.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/hint_syntax.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/inheritance.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/init_ex.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/kw_only.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/examples/repr_func.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/test_creation.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/test_dunders.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/test_funcs.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/test_hint_syntax.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/test_inheritance.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/test_init.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/test_kw_only.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/test_repr.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/py312_tests/test_generic_annotations.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/test_field_flags.py +0 -0
- {ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/test_slotmakermeta.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ducktools-classbuilder
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: Toolkit for creating class boilerplate generators
|
|
5
5
|
Author: David C Ellis
|
|
6
6
|
License: MIT License
|
|
@@ -32,6 +32,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
32
32
|
Classifier: Programming Language :: Python :: 3.10
|
|
33
33
|
Classifier: Programming Language :: Python :: 3.11
|
|
34
34
|
Classifier: Programming Language :: Python :: 3.12
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
35
36
|
Classifier: Operating System :: OS Independent
|
|
36
37
|
Classifier: License :: OSI Approved :: MIT License
|
|
37
38
|
Requires-Python: >=3.8
|
|
@@ -84,7 +84,7 @@ def iter_generator(cls, funcname="__iter__"):
|
|
|
84
84
|
field_yield = "\n".join(f" yield self.{f}" for f in field_names)
|
|
85
85
|
if not field_yield:
|
|
86
86
|
field_yield = " yield from ()"
|
|
87
|
-
code = f"def {funcname}(self):\n
|
|
87
|
+
code = f"def {funcname}(self):\n{field_yield}"
|
|
88
88
|
globs = {}
|
|
89
89
|
return GeneratedCode(code, globs)
|
|
90
90
|
|
|
@@ -330,7 +330,7 @@ if __name__ == "__main__":
|
|
|
330
330
|
|
|
331
331
|
This seems to be a feature people keep requesting for `dataclasses`.
|
|
332
332
|
|
|
333
|
-
To implement this you
|
|
333
|
+
To implement this you need to create a new annotated_gatherer function.
|
|
334
334
|
|
|
335
335
|
> Note: Field classes will be frozen when running under pytest.
|
|
336
336
|
> They should not be mutated by gatherers.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Tutorial: Making a class boilerplate generator #
|
|
2
2
|
|
|
3
|
-
The core idea is that there are
|
|
3
|
+
The core idea is that there are 4 parts to the process of generating
|
|
4
4
|
the class boilerplate that need to be handled:
|
|
5
5
|
|
|
6
6
|
1. Create a new subclass of `Field` if you need to add any extra attributes to fields
|
|
@@ -162,7 +162,7 @@ print(report_generator(CodegenDemo).source_code)
|
|
|
162
162
|
Here we will make both a simple decorator based builder and then a subclass
|
|
163
163
|
based builder that can create `__slots__`.
|
|
164
164
|
|
|
165
|
-
### Decorator builder ###
|
|
165
|
+
### 4a: Decorator builder ###
|
|
166
166
|
```python
|
|
167
167
|
def reportclass(cls):
|
|
168
168
|
gatherer = fields_attribute_gatherer
|
|
@@ -177,21 +177,22 @@ def reportclass(cls):
|
|
|
177
177
|
flags = {"slotted": slotted}
|
|
178
178
|
|
|
179
179
|
return dtbuild.builder(cls, gatherer=gatherer, methods=methods, flags=flags)
|
|
180
|
+
```
|
|
180
181
|
|
|
181
|
-
|
|
182
|
+
### 4b: Base class Builder ###
|
|
183
|
+
```python
|
|
182
184
|
# Once slots have been made, slot_gatherer should be used.
|
|
183
185
|
slot_gatherer = dtbuild.make_slot_gatherer(CustomField)
|
|
184
|
-
```
|
|
185
186
|
|
|
186
|
-
|
|
187
|
-
```python
|
|
187
|
+
|
|
188
188
|
class ReportClass(metaclass=dtbuild.SlotMakerMeta):
|
|
189
189
|
__slots__ = {}
|
|
190
190
|
_meta_gatherer = fields_attribute_gatherer
|
|
191
191
|
|
|
192
192
|
def __init_subclass__(cls):
|
|
193
|
-
|
|
194
|
-
|
|
193
|
+
# Check if the metaclass has generated slots
|
|
194
|
+
meta_slotted = '__slots__' in vars(cls) and isinstance(cls.__slots__, dtbuild.SlotFields)
|
|
195
|
+
gatherer = slot_gatherer if meta_slotted else fields_attribute_gatherer
|
|
195
196
|
methods = {
|
|
196
197
|
dtbuild.eq_maker,
|
|
197
198
|
dtbuild.repr_maker,
|
|
@@ -199,6 +200,7 @@ class ReportClass(metaclass=dtbuild.SlotMakerMeta):
|
|
|
199
200
|
report_maker
|
|
200
201
|
}
|
|
201
202
|
|
|
203
|
+
# The class may still have slots unrelated to code generation
|
|
202
204
|
slotted = "__slots__" in vars(cls)
|
|
203
205
|
flags = {"slotted": slotted}
|
|
204
206
|
|
{ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/docs_code/docs_ex3_iterable.py
RENAMED
|
@@ -13,7 +13,7 @@ def iter_generator(cls, funcname="__iter__"):
|
|
|
13
13
|
field_yield = "\n".join(f" yield self.{f}" for f in field_names)
|
|
14
14
|
if not field_yield:
|
|
15
15
|
field_yield = " yield from ()"
|
|
16
|
-
code = f"def {funcname}(self):\n
|
|
16
|
+
code = f"def {funcname}(self):\n{field_yield}"
|
|
17
17
|
globs = {}
|
|
18
18
|
return GeneratedCode(code, globs)
|
|
19
19
|
|
|
@@ -117,8 +117,9 @@ class ReportClass(metaclass=dtbuild.SlotMakerMeta):
|
|
|
117
117
|
_meta_gatherer = fields_attribute_gatherer
|
|
118
118
|
|
|
119
119
|
def __init_subclass__(cls):
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
# Check if the metaclass has generated slots
|
|
121
|
+
meta_slotted = '__slots__' in vars(cls) and isinstance(cls.__slots__, dtbuild.SlotFields)
|
|
122
|
+
gatherer = slot_gatherer if meta_slotted else fields_attribute_gatherer
|
|
122
123
|
methods = {
|
|
123
124
|
dtbuild.eq_maker,
|
|
124
125
|
dtbuild.repr_maker,
|
|
@@ -126,6 +127,7 @@ class ReportClass(metaclass=dtbuild.SlotMakerMeta):
|
|
|
126
127
|
report_maker
|
|
127
128
|
}
|
|
128
129
|
|
|
130
|
+
# The class may still have slots unrelated to code generation
|
|
129
131
|
slotted = "__slots__" in vars(cls)
|
|
130
132
|
flags = {"slotted": slotted}
|
|
131
133
|
|
|
@@ -20,6 +20,7 @@ classifiers = [
|
|
|
20
20
|
"Programming Language :: Python :: 3.10",
|
|
21
21
|
"Programming Language :: Python :: 3.11",
|
|
22
22
|
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
23
24
|
"Operating System :: OS Independent",
|
|
24
25
|
"License :: OSI Approved :: MIT License",
|
|
25
26
|
]
|
{ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/src/ducktools/classbuilder/__init__.py
RENAMED
|
@@ -707,10 +707,6 @@ def make_slot_gatherer(field_type=Field):
|
|
|
707
707
|
"in order to generate a slotclass"
|
|
708
708
|
)
|
|
709
709
|
|
|
710
|
-
# Don't want to mutate original annotations so make a copy if it exists
|
|
711
|
-
# Looking at the dict is a Python3.9 or earlier requirement
|
|
712
|
-
cls_annotations = get_ns_annotations(cls_dict)
|
|
713
|
-
|
|
714
710
|
cls_fields = {}
|
|
715
711
|
slot_replacement = {}
|
|
716
712
|
|
|
@@ -724,8 +720,6 @@ def make_slot_gatherer(field_type=Field):
|
|
|
724
720
|
|
|
725
721
|
if isinstance(v, field_type):
|
|
726
722
|
attrib = v
|
|
727
|
-
if attrib.type is not NOTHING:
|
|
728
|
-
cls_annotations[k] = attrib.type
|
|
729
723
|
else:
|
|
730
724
|
# Plain values treated as defaults
|
|
731
725
|
attrib = field_type(default=v)
|
|
@@ -738,7 +732,6 @@ def make_slot_gatherer(field_type=Field):
|
|
|
738
732
|
# In this case, slots with documentation and new annotations.
|
|
739
733
|
modifications = {
|
|
740
734
|
"__slots__": slot_replacement,
|
|
741
|
-
"__annotations__": cls_annotations,
|
|
742
735
|
}
|
|
743
736
|
|
|
744
737
|
return cls_fields, modifications
|
|
@@ -24,6 +24,19 @@ import sys
|
|
|
24
24
|
import builtins
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
# Evil stuff from types.py
|
|
28
|
+
def _cell_factory():
|
|
29
|
+
a = 1
|
|
30
|
+
|
|
31
|
+
def f():
|
|
32
|
+
nonlocal a
|
|
33
|
+
return f.__closure__[0]
|
|
34
|
+
_FunctionType = type(_cell_factory)
|
|
35
|
+
_CellType = type(_cell_factory())
|
|
36
|
+
del _cell_factory
|
|
37
|
+
# End evil stuff from types.py
|
|
38
|
+
|
|
39
|
+
|
|
27
40
|
class _StringGlobs(dict):
|
|
28
41
|
"""
|
|
29
42
|
Based on the fake globals dictionary used for annotations
|
|
@@ -99,6 +112,62 @@ def eval_hint(hint, context=None, *, recursion_limit=2):
|
|
|
99
112
|
return hint
|
|
100
113
|
|
|
101
114
|
|
|
115
|
+
def call_annotate_func(annotate):
|
|
116
|
+
# Python 3.14 breaks the old methods of getting annotations
|
|
117
|
+
# The new annotationlib currently relies on 'ast' and 'functools'
|
|
118
|
+
# that this project tries to avoid importing.
|
|
119
|
+
|
|
120
|
+
# The basic logic is copied from there, however, replacing ForwardRef
|
|
121
|
+
# with a more basic class.
|
|
122
|
+
# While `annotationlib` is trying to return ForwardRef objects that can
|
|
123
|
+
# be evaluated later, this module only cares about annotations that can
|
|
124
|
+
# be evaluated at the point this function is called.
|
|
125
|
+
# As such we throw away the other information and just return strings
|
|
126
|
+
# instead of forwardrefs.
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
raw_annotations = annotate(1)
|
|
130
|
+
except NameError:
|
|
131
|
+
pass
|
|
132
|
+
else:
|
|
133
|
+
return raw_annotations
|
|
134
|
+
|
|
135
|
+
# The annotate func may support forwardref natively
|
|
136
|
+
try:
|
|
137
|
+
raw_annotations = annotate(2)
|
|
138
|
+
except NotImplementedError:
|
|
139
|
+
pass
|
|
140
|
+
else:
|
|
141
|
+
return raw_annotations
|
|
142
|
+
|
|
143
|
+
# Not supported so we have to implement our own deferred handling
|
|
144
|
+
# Some modified logic from annotationlib
|
|
145
|
+
namespace = {**annotate.__builtins__, **annotate.__globals__}
|
|
146
|
+
globs = _StringGlobs(namespace)
|
|
147
|
+
|
|
148
|
+
# This handles closures where the variable is defined after get annotations is called.
|
|
149
|
+
if annotate.__closure__:
|
|
150
|
+
freevars = annotate.__code__.co_freevars
|
|
151
|
+
new_closure = []
|
|
152
|
+
for i, cell in enumerate(annotate.__closure__):
|
|
153
|
+
try:
|
|
154
|
+
cell.cell_contents
|
|
155
|
+
except ValueError:
|
|
156
|
+
if i < len(freevars):
|
|
157
|
+
name = freevars[i]
|
|
158
|
+
else:
|
|
159
|
+
name = "__cell__"
|
|
160
|
+
new_closure.append(_CellType(name))
|
|
161
|
+
else:
|
|
162
|
+
new_closure.append(cell)
|
|
163
|
+
closure = tuple(new_closure)
|
|
164
|
+
else:
|
|
165
|
+
closure = None
|
|
166
|
+
|
|
167
|
+
new_annotate = _FunctionType(annotate.__code__, globs, closure=closure)
|
|
168
|
+
return new_annotate(1)
|
|
169
|
+
|
|
170
|
+
|
|
102
171
|
def get_ns_annotations(ns, eval_str=True):
|
|
103
172
|
"""
|
|
104
173
|
Given a class namespace, attempt to retrieve the
|
|
@@ -112,36 +181,50 @@ def get_ns_annotations(ns, eval_str=True):
|
|
|
112
181
|
:param eval_str: Attempt to evaluate string annotations (default to True)
|
|
113
182
|
:return: dictionary of evaluated annotations
|
|
114
183
|
"""
|
|
115
|
-
raw_annotations = ns.get("__annotations__", {})
|
|
116
184
|
|
|
117
|
-
|
|
118
|
-
|
|
185
|
+
# In 3.14 the 'canonical' method of getting annotations is to use __annotate__
|
|
186
|
+
# If this doesn't exist, check __annotations__ and treat as 3.13 or earlier.
|
|
187
|
+
annotate = ns.get("__annotate__")
|
|
119
188
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
except KeyError:
|
|
123
|
-
obj_module = None
|
|
189
|
+
if annotate is not None:
|
|
190
|
+
raw_annotations = call_annotate_func(annotate)
|
|
124
191
|
else:
|
|
125
|
-
|
|
192
|
+
raw_annotations = ns.get("__annotations__", {})
|
|
126
193
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
194
|
+
# Unlike annotationlib we still try to evaluate string annotations
|
|
195
|
+
# This will catch cases where someone has used a literal string for a
|
|
196
|
+
# single attribute.
|
|
197
|
+
if eval_str:
|
|
198
|
+
try:
|
|
199
|
+
obj_modulename = ns["__module__"]
|
|
200
|
+
except KeyError:
|
|
201
|
+
obj_module = None
|
|
202
|
+
else:
|
|
203
|
+
obj_module = sys.modules.get(obj_modulename, None)
|
|
204
|
+
|
|
205
|
+
if obj_module:
|
|
206
|
+
obj_globals = vars(obj_module)
|
|
207
|
+
else:
|
|
208
|
+
obj_globals = {}
|
|
131
209
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
210
|
+
# Type parameters should be usable in hints without breaking
|
|
211
|
+
# This is for Python 3.12+
|
|
212
|
+
type_params = {
|
|
213
|
+
repr(param): param
|
|
214
|
+
for param in ns.get("__type_params__", ())
|
|
215
|
+
}
|
|
138
216
|
|
|
139
|
-
|
|
217
|
+
context = {**vars(builtins), **obj_globals, **type_params, **ns}
|
|
218
|
+
|
|
219
|
+
annotations = {
|
|
220
|
+
k: eval_hint(v, context)
|
|
221
|
+
for k, v in raw_annotations.items()
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
else:
|
|
225
|
+
annotations = raw_annotations.copy()
|
|
140
226
|
|
|
141
|
-
return
|
|
142
|
-
k: eval_hint(v, context)
|
|
143
|
-
for k, v in raw_annotations.items()
|
|
144
|
-
}
|
|
227
|
+
return annotations
|
|
145
228
|
|
|
146
229
|
|
|
147
230
|
def is_classvar(hint):
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
1
2
|
import typing
|
|
2
3
|
import types
|
|
3
4
|
|
|
@@ -8,6 +9,11 @@ class _StringGlobs(dict):
|
|
|
8
9
|
def __missing__(self, key: _T) -> _T: ...
|
|
9
10
|
|
|
10
11
|
|
|
12
|
+
def call_annotate_func(
|
|
13
|
+
annotate: Callable[[int], dict[str, type | typing.ForwardRef]]
|
|
14
|
+
) -> dict[str, type | str]: ...
|
|
15
|
+
|
|
16
|
+
|
|
11
17
|
def eval_hint(
|
|
12
18
|
hint: type | str,
|
|
13
19
|
context: None | dict[str, typing.Any] = None,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ducktools-classbuilder
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: Toolkit for creating class boilerplate generators
|
|
5
5
|
Author: David C Ellis
|
|
6
6
|
License: MIT License
|
|
@@ -32,6 +32,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
32
32
|
Classifier: Programming Language :: Python :: 3.10
|
|
33
33
|
Classifier: Programming Language :: Python :: 3.11
|
|
34
34
|
Classifier: Programming Language :: Python :: 3.12
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
35
36
|
Classifier: Operating System :: OS Independent
|
|
36
37
|
Classifier: License :: OSI Approved :: MIT License
|
|
37
38
|
Requires-Python: >=3.8
|
|
@@ -50,6 +50,7 @@ tests/test_field_flags.py
|
|
|
50
50
|
tests/test_slotmakermeta.py
|
|
51
51
|
tests/annotations/test_annotated.py
|
|
52
52
|
tests/annotations/test_annotations_module.py
|
|
53
|
+
tests/annotations/test_future_annotations.py
|
|
53
54
|
tests/prefab/dynamic/test_compare_attrib.py
|
|
54
55
|
tests/prefab/dynamic/test_construction.py
|
|
55
56
|
tests/prefab/dynamic/test_frozen.py
|
|
@@ -83,4 +84,5 @@ tests/prefab/shared/examples/fails/creation_3.py
|
|
|
83
84
|
tests/prefab/shared/examples/fails/creation_5.py
|
|
84
85
|
tests/prefab/shared/examples/fails/inheritance_1.py
|
|
85
86
|
tests/prefab/shared/examples/fails/inheritance_2.py
|
|
86
|
-
tests/py312_tests/test_generic_annotations.py
|
|
87
|
+
tests/py312_tests/test_generic_annotations.py
|
|
88
|
+
tests/py314_tests/test_forwardref_annotations.py
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from ducktools.classbuilder.annotations import get_ns_annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
global_type = int
|
|
8
|
+
|
|
9
|
+
def test_bare_forwardref():
|
|
10
|
+
class Ex:
|
|
11
|
+
a: str
|
|
12
|
+
b: Path
|
|
13
|
+
c: plain_forwardref
|
|
14
|
+
|
|
15
|
+
annos = get_ns_annotations(Ex.__dict__)
|
|
16
|
+
|
|
17
|
+
assert annos == {'a': str, 'b': Path, 'c': "plain_forwardref"}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_inner_outer_ref():
|
|
21
|
+
|
|
22
|
+
def make_func():
|
|
23
|
+
inner_type = str
|
|
24
|
+
|
|
25
|
+
class Inner:
|
|
26
|
+
a_val: inner_type = "hello"
|
|
27
|
+
b_val: global_type = 42
|
|
28
|
+
c_val: hyper_type = 3.14
|
|
29
|
+
|
|
30
|
+
# Try to get annotations before hyper_type exists
|
|
31
|
+
annos = get_ns_annotations(Inner.__dict__)
|
|
32
|
+
|
|
33
|
+
hyper_type = float
|
|
34
|
+
|
|
35
|
+
return Inner, annos
|
|
36
|
+
|
|
37
|
+
cls, annos = make_func()
|
|
38
|
+
|
|
39
|
+
# Only global types can be evaluated
|
|
40
|
+
assert annos == {"a_val": "inner_type", "b_val": int, "c_val": "hyper_type"}
|
|
41
|
+
|
|
42
|
+
# No extra evaluation
|
|
43
|
+
assert get_ns_annotations(cls.__dict__) == {
|
|
44
|
+
"a_val": "inner_type", "b_val": int, "c_val": "hyper_type"
|
|
45
|
+
}
|
|
@@ -2,9 +2,9 @@ import sys
|
|
|
2
2
|
|
|
3
3
|
collect_ignore = []
|
|
4
4
|
|
|
5
|
-
if sys.version_info < (3,
|
|
5
|
+
if sys.version_info < (3, 15):
|
|
6
6
|
minor_ver = sys.version_info.minor
|
|
7
7
|
|
|
8
8
|
collect_ignore.extend(
|
|
9
|
-
f"py3{i+1}_tests" for i in range(minor_ver,
|
|
9
|
+
f"py3{i+1}_tests" for i in range(minor_ver, 15)
|
|
10
10
|
)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Bare forwardrefs only work in 3.14 or later
|
|
2
|
+
|
|
3
|
+
from ducktools.classbuilder.annotations import get_ns_annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
global_type = int
|
|
8
|
+
|
|
9
|
+
def test_bare_forwardref():
|
|
10
|
+
class Ex:
|
|
11
|
+
a: str
|
|
12
|
+
b: Path
|
|
13
|
+
c: plain_forwardref
|
|
14
|
+
|
|
15
|
+
annos = get_ns_annotations(Ex.__dict__)
|
|
16
|
+
|
|
17
|
+
assert annos == {'a': str, 'b': Path, 'c': "plain_forwardref"}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_inner_outer_ref():
|
|
21
|
+
|
|
22
|
+
def make_func():
|
|
23
|
+
inner_type = str
|
|
24
|
+
|
|
25
|
+
class Inner:
|
|
26
|
+
a_val: inner_type = "hello"
|
|
27
|
+
b_val: global_type = 42
|
|
28
|
+
c_val: hyper_type = 3.14
|
|
29
|
+
|
|
30
|
+
# Try to get annotations before hyper_type exists
|
|
31
|
+
annos = get_ns_annotations(Inner.__dict__)
|
|
32
|
+
|
|
33
|
+
hyper_type = float
|
|
34
|
+
|
|
35
|
+
return Inner, annos
|
|
36
|
+
|
|
37
|
+
cls, annos = make_func()
|
|
38
|
+
|
|
39
|
+
# Forwardref given as string if used before it can be evaluated
|
|
40
|
+
assert annos == {"a_val": str, "b_val": int, "c_val": "hyper_type"}
|
|
41
|
+
|
|
42
|
+
# Correctly evaluated if it exists
|
|
43
|
+
assert get_ns_annotations(cls.__dict__) == {
|
|
44
|
+
"a_val": str, "b_val": int, "c_val": float
|
|
45
|
+
}
|
|
@@ -188,7 +188,6 @@ def test_slot_gatherer_success():
|
|
|
188
188
|
|
|
189
189
|
assert slots == fields
|
|
190
190
|
assert modifications["__slots__"] == {"a": None, "b": None, "c": "a list", "d": None}
|
|
191
|
-
assert modifications["__annotations__"] == {"a": int, "d": str}
|
|
192
191
|
assert get_ns_annotations(SlotsExample.__dict__) == {"a": int} # Original annotations dict unmodified
|
|
193
192
|
|
|
194
193
|
|
|
File without changes
|
{ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/.github/workflows/auto_test.yml
RENAMED
|
File without changes
|
{ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/.github/workflows/publish_to_pypi.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/docs/perf/performance_tests.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/docs_code/docs_ex2_register.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/docs_code/docs_ex8_converters.py
RENAMED
|
File without changes
|
{ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/docs_code/docs_ex9_annotated.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/src/ducktools/classbuilder/prefab.py
RENAMED
|
File without changes
|
{ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/src/ducktools/classbuilder/prefab.pyi
RENAMED
|
File without changes
|
{ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/src/ducktools/classbuilder/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/annotations/test_annotated.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/dynamic/test_frozen.py
RENAMED
|
File without changes
|
{ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/dynamic/test_internals.py
RENAMED
|
File without changes
|
|
File without changes
|
{ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/dynamic/test_private.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/conftest.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/test_creation.py
RENAMED
|
File without changes
|
{ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/test_dunders.py
RENAMED
|
File without changes
|
{ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/test_funcs.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/test_init.py
RENAMED
|
File without changes
|
{ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/test_kw_only.py
RENAMED
|
File without changes
|
{ducktools_classbuilder-0.7.0 → ducktools_classbuilder-0.7.1}/tests/prefab/shared/test_repr.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|