ducktools-classbuilder 0.8.1__tar.gz → 0.8.3__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.8.1 → ducktools_classbuilder-0.8.3}/.github/workflows/auto_test.yml +1 -1
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/PKG-INFO +1 -1
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/docs/extension_examples.md +19 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/src/ducktools/classbuilder/__init__.py +15 -5
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/src/ducktools/classbuilder/__init__.pyi +3 -0
- ducktools_classbuilder-0.8.3/src/ducktools/classbuilder/_version.py +2 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/src/ducktools/classbuilder/annotations.py +18 -14
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/src/ducktools/classbuilder/prefab.py +6 -1
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/src/ducktools/classbuilder/prefab.pyi +5 -2
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/src/ducktools_classbuilder.egg-info/PKG-INFO +1 -1
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/src/ducktools_classbuilder.egg-info/SOURCES.txt +1 -0
- ducktools_classbuilder-0.8.3/tests/py314_tests/_test_support.py +42 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/py314_tests/test_forwardref_annotations.py +5 -3
- ducktools_classbuilder-0.8.1/src/ducktools/classbuilder/_version.py +0 -2
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/.github/dependabot.yml +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/.github/workflows/publish_to_pypi.yml +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/.github/workflows/publish_to_testpypi.yml +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/.gitignore +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/.readthedocs.yaml +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/LICENSE +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/MANIFEST.in +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/README.md +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/docs/Makefile +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/docs/api.md +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/docs/approach_vs_tool.md +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/docs/conf.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/docs/generated_code.md +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/docs/index.md +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/docs/make.bat +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/docs/perf/performance_tests.md +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/docs/prefab/index.md +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/docs/tutorial.md +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/docs_code/docs_ex10_frozen_attributes.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/docs_code/docs_ex1_basic.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/docs_code/docs_ex2_register.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/docs_code/docs_ex3_iterable.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/docs_code/docs_ex5_frozen.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/docs_code/docs_ex7_posonly.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/docs_code/docs_ex8_converters.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/docs_code/docs_ex9_annotated.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/docs_code/index_example.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/docs_code/tutorial_code.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/pyproject.toml +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/setup.cfg +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/src/ducktools/classbuilder/annotations.pyi +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/src/ducktools/classbuilder/py.typed +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/src/ducktools_classbuilder.egg-info/dependency_links.txt +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/src/ducktools_classbuilder.egg-info/requires.txt +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/src/ducktools_classbuilder.egg-info/top_level.txt +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/annotations/test_annotated.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/annotations/test_annotations_module.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/annotations/test_future_annotations.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/conftest.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/dynamic/test_compare_attrib.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/dynamic/test_construction.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/dynamic/test_frozen.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/dynamic/test_internals.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/dynamic/test_pre_post_init.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/dynamic/test_private.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/dynamic/test_slots_novalues.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/dynamic/test_slotted_class.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/dynamic/test_subclass_implementation.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/shared/conftest.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/shared/examples/creation.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/shared/examples/creation_empty.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/shared/examples/dunders.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/shared/examples/fails/creation_2.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/shared/examples/fails/creation_3.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/shared/examples/fails/creation_5.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/shared/examples/fails/inheritance_1.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/shared/examples/fails/inheritance_2.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/shared/examples/funcs_prefabs.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/shared/examples/hint_syntax.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/shared/examples/inheritance.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/shared/examples/init_ex.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/shared/examples/kw_only.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/shared/examples/repr_func.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/shared/test_creation.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/shared/test_dunders.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/shared/test_funcs.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/shared/test_hint_syntax.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/shared/test_inheritance.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/shared/test_init.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/shared/test_kw_only.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/shared/test_repr.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/py312_tests/test_generic_annotations.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/test_core.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/test_field_flags.py +0 -0
- {ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/test_slotmakermeta.py +0 -0
{ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/.github/workflows/auto_test.yml
RENAMED
|
@@ -14,7 +14,7 @@ jobs:
|
|
|
14
14
|
fail-fast: false
|
|
15
15
|
matrix:
|
|
16
16
|
os: [ubuntu-latest]
|
|
17
|
-
python-version: ["3.13", "3.12", "3.11", "3.10", "pypy-3.10", "3.9", "3.8"]
|
|
17
|
+
python-version: ["3.14-dev", "3.13", "3.12", "3.11", "3.10", "pypy-3.10", "3.9", "3.8"]
|
|
18
18
|
|
|
19
19
|
steps:
|
|
20
20
|
- uses: actions/checkout@v4
|
|
@@ -116,6 +116,25 @@ You could also choose to yield tuples of `name, value` pairs in your implementat
|
|
|
116
116
|
|
|
117
117
|
### Extending Field ###
|
|
118
118
|
|
|
119
|
+
The `Field` class can also be extended as if it is a slotclass, with annotations or
|
|
120
|
+
with `Field` declarations.
|
|
121
|
+
|
|
122
|
+
One notable caveat - if you want to use a `default_factory` in extending `Field` you
|
|
123
|
+
need to declare `default=FIELD_NOTHING` also in order for default to be ignored. This
|
|
124
|
+
is a special case for `Field` and is not needed in general.
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from ducktools.classbuilder import Field, FIELD_NOTHING
|
|
128
|
+
|
|
129
|
+
class MetadataField(Field):
|
|
130
|
+
metadata: dict = Field(default=FIELD_NOTHING, default_factory=dict)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
In regular classes the `__init__` function generator considers `NOTHING` to be an
|
|
134
|
+
ignored value, but for `Field` subclasses it is a valid value so `FIELD_NOTHING` is
|
|
135
|
+
the ignored term. This is all because `None` *is* a valid value and can't be used
|
|
136
|
+
as a sentinel for Fields (otherwise `Field(default=None)` couldn't work).
|
|
137
|
+
|
|
119
138
|
#### Positional Only Arguments? ####
|
|
120
139
|
|
|
121
140
|
This is possible, but a little longer as we also need to modify multiple methods
|
{ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/src/ducktools/classbuilder/__init__.py
RENAMED
|
@@ -99,11 +99,16 @@ def _get_inst_fields(inst):
|
|
|
99
99
|
# As 'None' can be a meaningful value we need a sentinel value
|
|
100
100
|
# to use to show no value has been provided.
|
|
101
101
|
class _NothingType:
|
|
102
|
+
def __init__(self, custom=None):
|
|
103
|
+
self.custom = custom
|
|
102
104
|
def __repr__(self):
|
|
105
|
+
if self.custom:
|
|
106
|
+
return f"<{self.custom} NOTHING OBJECT>"
|
|
103
107
|
return "<NOTHING OBJECT>"
|
|
104
108
|
|
|
105
109
|
|
|
106
110
|
NOTHING = _NothingType()
|
|
111
|
+
FIELD_NOTHING = _NothingType("FIELD")
|
|
107
112
|
|
|
108
113
|
|
|
109
114
|
# KW_ONLY sentinel 'type' to use to indicate all subsequent attributes are
|
|
@@ -432,11 +437,11 @@ frozen_setattr_maker = MethodMaker("__setattr__", frozen_setattr_generator)
|
|
|
432
437
|
frozen_delattr_maker = MethodMaker("__delattr__", frozen_delattr_generator)
|
|
433
438
|
default_methods = frozenset({init_maker, repr_maker, eq_maker})
|
|
434
439
|
|
|
435
|
-
# Special `__init__` maker for 'Field' subclasses
|
|
440
|
+
# Special `__init__` maker for 'Field' subclasses - needs its own NOTHING option
|
|
436
441
|
_field_init_maker = MethodMaker(
|
|
437
442
|
funcname="__init__",
|
|
438
443
|
code_generator=get_init_generator(
|
|
439
|
-
null=
|
|
444
|
+
null=FIELD_NOTHING,
|
|
440
445
|
extra_code=["self.validate_field()"],
|
|
441
446
|
)
|
|
442
447
|
)
|
|
@@ -649,7 +654,7 @@ class Field(metaclass=SlotMakerMeta):
|
|
|
649
654
|
|
|
650
655
|
def validate_field(self):
|
|
651
656
|
cls_name = self.__class__.__name__
|
|
652
|
-
if self.default is not
|
|
657
|
+
if type(self.default) is not _NothingType and type(self.default_factory) is not _NothingType:
|
|
653
658
|
raise AttributeError(
|
|
654
659
|
f"{cls_name} cannot define both a default value and a default factory."
|
|
655
660
|
)
|
|
@@ -807,6 +812,7 @@ def make_annotation_gatherer(
|
|
|
807
812
|
def make_field_gatherer(
|
|
808
813
|
field_type=Field,
|
|
809
814
|
leave_default_values=False,
|
|
815
|
+
assign_types=True,
|
|
810
816
|
):
|
|
811
817
|
def field_attribute_gatherer(cls_or_ns):
|
|
812
818
|
if isinstance(cls_or_ns, (_MappingProxyType, dict)):
|
|
@@ -819,7 +825,11 @@ def make_field_gatherer(
|
|
|
819
825
|
for k, v in cls_dict.items()
|
|
820
826
|
if isinstance(v, field_type)
|
|
821
827
|
}
|
|
822
|
-
|
|
828
|
+
|
|
829
|
+
if assign_types:
|
|
830
|
+
cls_annotations = get_ns_annotations(cls_dict)
|
|
831
|
+
else:
|
|
832
|
+
cls_annotations = {}
|
|
823
833
|
|
|
824
834
|
cls_modifications = {}
|
|
825
835
|
|
|
@@ -830,7 +840,7 @@ def make_field_gatherer(
|
|
|
830
840
|
else:
|
|
831
841
|
cls_modifications[name] = NOTHING
|
|
832
842
|
|
|
833
|
-
if (anno := cls_annotations.get(name, NOTHING)) is not NOTHING:
|
|
843
|
+
if assign_types and (anno := cls_annotations.get(name, NOTHING)) is not NOTHING:
|
|
834
844
|
cls_attributes[name] = field_type.from_field(attrib, type=anno)
|
|
835
845
|
|
|
836
846
|
return cls_attributes, cls_modifications
|
|
@@ -24,8 +24,10 @@ def get_methods(cls: type) -> types.MappingProxyType[str, MethodMaker]: ...
|
|
|
24
24
|
def _get_inst_fields(inst: typing.Any) -> dict[str, typing.Any]: ...
|
|
25
25
|
|
|
26
26
|
class _NothingType:
|
|
27
|
+
def __init__(self, custom: str | None = ...) -> None: ...
|
|
27
28
|
def __repr__(self) -> str: ...
|
|
28
29
|
NOTHING: _NothingType
|
|
30
|
+
FIELD_NOTHING: _NothingType
|
|
29
31
|
|
|
30
32
|
# noinspection PyPep8Naming
|
|
31
33
|
class _KW_ONLY_TYPE:
|
|
@@ -191,6 +193,7 @@ def make_annotation_gatherer(
|
|
|
191
193
|
def make_field_gatherer(
|
|
192
194
|
field_type: type[_FieldType],
|
|
193
195
|
leave_default_values: bool = False,
|
|
196
|
+
assign_types: bool = True,
|
|
194
197
|
) -> Callable[[type | _CopiableMappings], tuple[dict[str, _FieldType], dict[str, typing.Any]]]: ...
|
|
195
198
|
|
|
196
199
|
@typing.overload
|
|
@@ -22,6 +22,17 @@
|
|
|
22
22
|
import sys
|
|
23
23
|
|
|
24
24
|
|
|
25
|
+
class _LazyAnnotationLib:
|
|
26
|
+
def __getattr__(self, item):
|
|
27
|
+
global _lazyannotationlib
|
|
28
|
+
import annotationlib
|
|
29
|
+
_lazyannotationlib = annotationlib
|
|
30
|
+
return getattr(annotationlib, item)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
_lazy_annotationlib = _LazyAnnotationLib()
|
|
34
|
+
|
|
35
|
+
|
|
25
36
|
def get_ns_annotations(ns):
|
|
26
37
|
"""
|
|
27
38
|
Given a class namespace, attempt to retrieve the
|
|
@@ -34,20 +45,14 @@ def get_ns_annotations(ns):
|
|
|
34
45
|
annotations = ns.get("__annotations__")
|
|
35
46
|
if annotations is not None:
|
|
36
47
|
annotations = annotations.copy()
|
|
37
|
-
|
|
48
|
+
elif sys.version_info >= (3, 14):
|
|
38
49
|
# See if we're using PEP-649 annotations
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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)
|
|
50
|
+
annotate = _lazy_annotationlib.get_annotate_from_class_namespace(ns)
|
|
51
|
+
if annotate:
|
|
52
|
+
annotations = _lazy_annotationlib.call_annotate_function(
|
|
53
|
+
annotate,
|
|
54
|
+
format=_lazy_annotationlib.Format.FORWARDREF
|
|
55
|
+
)
|
|
51
56
|
|
|
52
57
|
if annotations is None:
|
|
53
58
|
annotations = {}
|
|
@@ -88,4 +93,3 @@ def is_classvar(hint):
|
|
|
88
93
|
):
|
|
89
94
|
return True
|
|
90
95
|
return False
|
|
91
|
-
|
{ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/src/ducktools/classbuilder/prefab.py
RENAMED
|
@@ -26,7 +26,7 @@ A 'prebuilt' implementation of class generation.
|
|
|
26
26
|
Includes pre and post init functions along with other methods.
|
|
27
27
|
"""
|
|
28
28
|
from . import (
|
|
29
|
-
INTERNALS_DICT, NOTHING,
|
|
29
|
+
INTERNALS_DICT, NOTHING, FIELD_NOTHING,
|
|
30
30
|
Field, MethodMaker, GatheredFields, GeneratedCode, SlotMakerMeta,
|
|
31
31
|
builder, get_flags, get_fields,
|
|
32
32
|
make_unified_gatherer,
|
|
@@ -289,10 +289,12 @@ class Attribute(Field):
|
|
|
289
289
|
:param kw_only: Make this argument keyword only in init
|
|
290
290
|
:param serialize: Include this attribute in methods that serialize to dict
|
|
291
291
|
:param doc: Parameter documentation for slotted classes
|
|
292
|
+
:param metadata: Additional non-construction related metadata
|
|
292
293
|
:param type: Type of this attribute (for slotted classes)
|
|
293
294
|
"""
|
|
294
295
|
iter: bool = True
|
|
295
296
|
serialize: bool = True
|
|
297
|
+
metadata: dict = Field(default=FIELD_NOTHING, default_factory=dict)
|
|
296
298
|
|
|
297
299
|
|
|
298
300
|
# noinspection PyShadowingBuiltins
|
|
@@ -309,6 +311,7 @@ def attribute(
|
|
|
309
311
|
exclude_field=False,
|
|
310
312
|
private=False,
|
|
311
313
|
doc=None,
|
|
314
|
+
metadata=None,
|
|
312
315
|
type=NOTHING,
|
|
313
316
|
):
|
|
314
317
|
"""
|
|
@@ -326,6 +329,7 @@ def attribute(
|
|
|
326
329
|
:param exclude_field: Shorthand for setting repr, compare, iter and serialize to False
|
|
327
330
|
:param private: Short for init, repr, compare, iter, serialize = False, must have default or factory
|
|
328
331
|
:param doc: Parameter documentation for slotted classes
|
|
332
|
+
:param metadata: Dictionary for additional non-construction metadata
|
|
329
333
|
:param type: Type of this attribute (for slotted classes)
|
|
330
334
|
|
|
331
335
|
:return: Attribute generated with these parameters.
|
|
@@ -356,6 +360,7 @@ def attribute(
|
|
|
356
360
|
serialize=serialize,
|
|
357
361
|
doc=doc,
|
|
358
362
|
type=type,
|
|
363
|
+
metadata=metadata,
|
|
359
364
|
)
|
|
360
365
|
|
|
361
366
|
|
{ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/src/ducktools/classbuilder/prefab.pyi
RENAMED
|
@@ -50,6 +50,7 @@ class Attribute(Field):
|
|
|
50
50
|
|
|
51
51
|
iter: bool
|
|
52
52
|
serialize: bool
|
|
53
|
+
metadata: dict
|
|
53
54
|
|
|
54
55
|
def __init__(
|
|
55
56
|
self,
|
|
@@ -64,6 +65,7 @@ class Attribute(Field):
|
|
|
64
65
|
iter: bool = True,
|
|
65
66
|
kw_only: bool = False,
|
|
66
67
|
serialize: bool = True,
|
|
68
|
+
metadata: dict | None = None,
|
|
67
69
|
) -> None: ...
|
|
68
70
|
|
|
69
71
|
def __repr__(self) -> str: ...
|
|
@@ -74,8 +76,6 @@ def attribute(
|
|
|
74
76
|
*,
|
|
75
77
|
default: typing.Any | _NothingType = NOTHING,
|
|
76
78
|
default_factory: typing.Any | _NothingType = NOTHING,
|
|
77
|
-
type: type | _NothingType = NOTHING,
|
|
78
|
-
doc: str | None = None,
|
|
79
79
|
init: bool = True,
|
|
80
80
|
repr: bool = True,
|
|
81
81
|
compare: bool = True,
|
|
@@ -84,6 +84,9 @@ def attribute(
|
|
|
84
84
|
serialize: bool = True,
|
|
85
85
|
exclude_field: bool = False,
|
|
86
86
|
private: bool = False,
|
|
87
|
+
doc: str | None = None,
|
|
88
|
+
metadata: dict | None = None,
|
|
89
|
+
type: type | _NothingType = NOTHING,
|
|
87
90
|
) -> Attribute: ...
|
|
88
91
|
|
|
89
92
|
def prefab_gatherer(cls_or_ns: type | MappingProxyType) -> tuple[dict[str, Attribute], dict[str, typing.Any]]: ...
|
|
@@ -82,4 +82,5 @@ tests/prefab/shared/examples/fails/creation_5.py
|
|
|
82
82
|
tests/prefab/shared/examples/fails/inheritance_1.py
|
|
83
83
|
tests/prefab/shared/examples/fails/inheritance_2.py
|
|
84
84
|
tests/py312_tests/test_generic_annotations.py
|
|
85
|
+
tests/py314_tests/_test_support.py
|
|
85
86
|
tests/py314_tests/test_forwardref_annotations.py
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from annotationlib import ForwardRef
|
|
2
|
+
|
|
3
|
+
# From test/support/__init__.py
|
|
4
|
+
class EqualToForwardRef:
|
|
5
|
+
"""Helper to ease use of annotationlib.ForwardRef in tests.
|
|
6
|
+
|
|
7
|
+
This checks only attributes that can be set using the constructor.
|
|
8
|
+
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
arg,
|
|
14
|
+
*,
|
|
15
|
+
module=None,
|
|
16
|
+
owner=None,
|
|
17
|
+
is_class=False,
|
|
18
|
+
):
|
|
19
|
+
self.__forward_arg__ = arg
|
|
20
|
+
self.__forward_is_class__ = is_class
|
|
21
|
+
self.__forward_module__ = module
|
|
22
|
+
self.__owner__ = owner
|
|
23
|
+
|
|
24
|
+
def __eq__(self, other):
|
|
25
|
+
if not isinstance(other, (EqualToForwardRef, ForwardRef)):
|
|
26
|
+
return NotImplemented
|
|
27
|
+
return (
|
|
28
|
+
self.__forward_arg__ == other.__forward_arg__
|
|
29
|
+
and self.__forward_module__ == other.__forward_module__
|
|
30
|
+
and self.__forward_is_class__ == other.__forward_is_class__
|
|
31
|
+
and self.__owner__ == other.__owner__
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def __repr__(self):
|
|
35
|
+
extra = []
|
|
36
|
+
if self.__forward_module__ is not None:
|
|
37
|
+
extra.append(f", module={self.__forward_module__!r}")
|
|
38
|
+
if self.__forward_is_class__:
|
|
39
|
+
extra.append(", is_class=True")
|
|
40
|
+
if self.__owner__ is not None:
|
|
41
|
+
extra.append(f", owner={self.__owner__!r}")
|
|
42
|
+
return f"EqualToForwardRef({self.__forward_arg__!r}{''.join(extra)})"
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# Bare forwardrefs only work in 3.14 or later
|
|
2
2
|
|
|
3
3
|
from ducktools.classbuilder.annotations import get_ns_annotations
|
|
4
|
-
from annotationlib import ForwardRef
|
|
5
4
|
|
|
6
5
|
from pathlib import Path
|
|
7
6
|
|
|
7
|
+
from _test_support import EqualToForwardRef
|
|
8
|
+
|
|
8
9
|
global_type = int
|
|
9
10
|
|
|
11
|
+
|
|
10
12
|
def test_bare_forwardref():
|
|
11
13
|
class Ex:
|
|
12
14
|
a: str
|
|
@@ -15,7 +17,7 @@ def test_bare_forwardref():
|
|
|
15
17
|
|
|
16
18
|
annos = get_ns_annotations(Ex.__dict__)
|
|
17
19
|
|
|
18
|
-
assert annos == {'a': str, 'b': Path, 'c':
|
|
20
|
+
assert annos == {'a': str, 'b': Path, 'c': EqualToForwardRef("plain_forwardref")}
|
|
19
21
|
|
|
20
22
|
|
|
21
23
|
def test_inner_outer_ref():
|
|
@@ -38,7 +40,7 @@ def test_inner_outer_ref():
|
|
|
38
40
|
cls, annos = make_func()
|
|
39
41
|
|
|
40
42
|
# Forwardref given as string if used before it can be evaluated
|
|
41
|
-
assert annos == {"a_val": str, "b_val": int, "c_val":
|
|
43
|
+
assert annos == {"a_val": str, "b_val": int, "c_val": EqualToForwardRef("hyper_type")}
|
|
42
44
|
|
|
43
45
|
# Correctly evaluated if it exists
|
|
44
46
|
assert get_ns_annotations(cls.__dict__) == {
|
|
File without changes
|
{ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/.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.8.1 → ducktools_classbuilder-0.8.3}/docs/perf/performance_tests.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/docs_code/docs_ex2_register.py
RENAMED
|
File without changes
|
{ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/docs_code/docs_ex3_iterable.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/docs_code/docs_ex8_converters.py
RENAMED
|
File without changes
|
{ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/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
|
{ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/src/ducktools/classbuilder/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/annotations/test_annotated.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/dynamic/test_frozen.py
RENAMED
|
File without changes
|
{ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/dynamic/test_internals.py
RENAMED
|
File without changes
|
|
File without changes
|
{ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/dynamic/test_private.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/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
|
{ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/shared/test_creation.py
RENAMED
|
File without changes
|
{ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/shared/test_dunders.py
RENAMED
|
File without changes
|
{ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/shared/test_funcs.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/shared/test_init.py
RENAMED
|
File without changes
|
{ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/shared/test_kw_only.py
RENAMED
|
File without changes
|
{ducktools_classbuilder-0.8.1 → ducktools_classbuilder-0.8.3}/tests/prefab/shared/test_repr.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|