Skip to content

Commit 34eaa79

Browse files
committed
Bugfix - prevent Prefab and get_attributes from converting subclasses downwards to Attributes
1 parent 93b1621 commit 34eaa79

File tree

3 files changed

+48
-5
lines changed

3 files changed

+48
-5
lines changed

src/ducktools/classbuilder/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1139,13 +1139,16 @@ def field_annotation_gatherer(cls_or_ns, *, cls_annotations=None):
11391139
if isinstance(attrib, field_type):
11401140
kw_only = attrib.kw_only or kw_flag
11411141

1142-
attrib = field_type.from_field(attrib, type=v, kw_only=kw_only)
1142+
# Don't try to down convert subclass instances
1143+
attrib_type = type(attrib)
1144+
attrib = attrib_type.from_field(attrib, type=v, kw_only=kw_only)
11431145

11441146
if attrib.default is not NOTHING and leave_default_values:
11451147
modifications[k] = attrib.default
11461148
else:
11471149
# NOTHING sentinel indicates a value should be removed
11481150
modifications[k] = NOTHING
1151+
11491152
elif not isinstance(attrib, _MemberDescriptorType):
11501153
attrib = field_type(default=attrib, type=v, kw_only=kw_flag)
11511154
if not leave_default_values:

src/ducktools/classbuilder/prefab.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,10 @@ def get_attributes(cls, *, local=False):
7373
"""
7474
attributes = get_fields(cls, local=local)
7575

76-
if any(type(obj) is not Attribute for obj in attributes.values()):
76+
if any(type(obj) is Field for obj in attributes.values()):
7777
attributes = {
78-
k: Attribute.from_field(v) for k, v in attributes.items()
78+
k: Attribute.from_field(v) if type(v) is Field else v
79+
for k, v in attributes.items()
7980
}
8081

8182
return attributes

tests/prefab/test_get_methods.py

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,54 @@
22
from textwrap import indent
33
from unittest.mock import patch
44

5-
from ducktools.classbuilder import get_methods, get_generated_code, print_generated_code, INTERNALS_DICT
6-
from ducktools.classbuilder.prefab import Prefab
5+
from ducktools.classbuilder import (
6+
get_methods,
7+
get_generated_code,
8+
print_generated_code,
9+
slotclass,
10+
SlotFields,
11+
INTERNALS_DICT,
12+
)
13+
from ducktools.classbuilder.prefab import attribute, get_attributes, Attribute, Prefab
714

815

916
class Example(Prefab):
1017
a: int = 42
1118
b: str = "Life the Universe and Everything"
1219

1320

21+
def test_get_attributes():
22+
expected = {
23+
"a": attribute(default=42, type=int),
24+
"b": attribute(default="Life the Universe and Everything", type=str),
25+
}
26+
assert get_attributes(Example) == expected
27+
28+
def test_get_attributes_converts_upward():
29+
expected = {
30+
"a": attribute(default=42),
31+
}
32+
@slotclass
33+
class HasFields:
34+
__slots__ = SlotFields(a=42)
35+
36+
assert get_attributes(HasFields) == expected
37+
38+
39+
def test_get_attributes_not_converting_downward():
40+
class NewAttribute(Attribute):
41+
new_arg: int = 42
42+
43+
class HasNewAttribute(Prefab):
44+
a: str = NewAttribute(default="Oh dear not again")
45+
46+
expected = {
47+
"a": NewAttribute(default="Oh dear not again", type=str)
48+
}
49+
50+
assert get_attributes(HasNewAttribute) == expected
51+
52+
1453
def test_get_generated_code_keys():
1554
assert get_methods(Example).keys() == get_generated_code(Example).keys()
1655

0 commit comments

Comments
 (0)