Skip to content

Commit 5021332

Browse files
authored
feat: Supports Pydantic Field defined via Annotated
Issue-37: #37 PR-46: #46
1 parent b36c951 commit 5021332

2 files changed

Lines changed: 37 additions & 4 deletions

File tree

src/griffe_pydantic/_internal/static.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
ExprCall,
1414
ExprKeyword,
1515
ExprName,
16+
ExprSubscript,
17+
ExprTuple,
1618
Function,
1719
Kind,
1820
Module,
@@ -80,8 +82,37 @@ def _process_attribute(attr: Attribute, cls: Class, *, processed: set[str]) -> N
8082
if "class-attribute" in attr.labels and "instance-attribute" not in attr.labels:
8183
return
8284

85+
# Check if the annotation is Annotated[type, Field(...)]
86+
field_call = None
87+
if (
88+
isinstance(attr.annotation, ExprSubscript)
89+
and attr.annotation.canonical_path == "typing.Annotated"
90+
and isinstance(attr.annotation.slice, ExprTuple)
91+
):
92+
# Extract Field from Annotated's slice elements
93+
slice_elements = attr.annotation.slice.elements
94+
# Replace annotation with the actual type (first element)
95+
if len(slice_elements) > 0:
96+
attr.annotation = slice_elements[0]
97+
98+
for element in slice_elements:
99+
if isinstance(element, ExprCall) and element.function.canonical_path == "pydantic.Field":
100+
field_call = element
101+
break
102+
83103
kwargs = {}
84-
if isinstance(attr.value, ExprCall):
104+
if field_call is not None:
105+
# Extract kwargs from Field in Annotated
106+
kwargs = {
107+
argument.name: argument.value for argument in field_call.arguments if isinstance(argument, ExprKeyword)
108+
}
109+
if (
110+
len(field_call.arguments) >= 1
111+
and not isinstance(field_call.arguments[0], ExprKeyword)
112+
and field_call.arguments[0] != "..." # handle Field(...), i.e. no default
113+
):
114+
kwargs["default"] = field_call.arguments[0]
115+
elif isinstance(attr.value, ExprCall):
85116
kwargs = {
86117
argument.name: argument.value for argument in attr.value.arguments if isinstance(argument, ExprKeyword)
87118
}

tests/test_extension.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,6 @@ class B(BaseModel, A):
235235
assert "pydantic-field" in package["B.a"].labels
236236

237237

238-
@pytest.mark.skip(reason="Currently not supported.")
239238
def test_annotated_fields() -> None:
240239
"""Test the extension with annotated fields."""
241240
code = """
@@ -255,5 +254,8 @@ class Model(BaseModel):
255254
assert package["Model.b"].is_attribute
256255
assert "pydantic-field" in package["Model.a"].labels
257256
assert "pydantic-field" in package["Model.b"].labels
258-
assert package["Model.a"].docstring == "Some description."
259-
assert package["Model.b"].docstring == "Another description."
257+
assert package["Model.a"].docstring.value == "Some description."
258+
assert package["Model.b"].docstring.value == "Another description."
259+
# Check that annotation is the actual type, not Annotated[...]
260+
assert str(package["Model.a"].annotation) == "int"
261+
assert str(package["Model.b"].annotation) == "int"

0 commit comments

Comments
 (0)