Skip to content

Commit 9491921

Browse files
authored
feat: Support Pydantic's PrivateAttr
Issue-43: #43 PR-49: #49
1 parent 078fcd3 commit 9491921

File tree

2 files changed

+54
-3
lines changed

2 files changed

+54
-3
lines changed

src/griffe_pydantic/_internal/static.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,13 @@ def _process_attribute(attr: Attribute, cls: Class, *, processed: set[str]) -> N
8282
if "class-attribute" in attr.labels and "instance-attribute" not in attr.labels:
8383
return
8484

85+
# PrivateAttr values are not public fields.
86+
if isinstance(attr.value, ExprCall) and attr.value.function.canonical_path in {
87+
"pydantic.PrivateAttr",
88+
"pydantic.fields.PrivateAttr",
89+
}:
90+
return
91+
8592
# Check if the annotation is Annotated[type, Field(...)]
8693
field_call = None
8794
if (
@@ -96,9 +103,16 @@ def _process_attribute(attr: Attribute, cls: Class, *, processed: set[str]) -> N
96103
attr.annotation = slice_elements[0]
97104

98105
for element in slice_elements:
99-
if isinstance(element, ExprCall) and element.function.canonical_path == "pydantic.Field":
100-
field_call = element
101-
break
106+
if isinstance(element, ExprCall):
107+
# PrivateAttr values are not public fields.
108+
if element.function.canonical_path in {
109+
"pydantic.PrivateAttr",
110+
"pydantic.fields.PrivateAttr",
111+
}:
112+
return
113+
if element.function.canonical_path == "pydantic.Field":
114+
field_call = element
115+
break
102116

103117
kwargs = {}
104118
if field_call is not None:

tests/test_extension.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,3 +259,40 @@ class Model(BaseModel):
259259
# Check that annotation is the actual type, not Annotated[...]
260260
assert str(package["Model.a"].annotation) == "int"
261261
assert str(package["Model.b"].annotation) == "int"
262+
263+
264+
def test_ignore_private_attrs() -> None:
265+
"""Test the extension ignores private attributes."""
266+
code = """
267+
from pydantic import BaseModel, PrivateAttr
268+
269+
class Model(BaseModel):
270+
field: str
271+
_private: str = PrivateAttr(default="secret")
272+
"""
273+
with temporary_visited_package(
274+
"package",
275+
modules={"__init__.py": code},
276+
extensions=Extensions(PydanticExtension(schema=False)),
277+
) as package:
278+
assert "pydantic-field" in package["Model.field"].labels
279+
assert "pydantic-field" not in package["Model._private"].labels
280+
281+
282+
def test_ignore_private_attrs_annotated() -> None:
283+
"""Test the extension ignores private attributes with Annotated syntax."""
284+
code = """
285+
from pydantic import BaseModel, PrivateAttr
286+
from typing import Annotated
287+
288+
class Model(BaseModel):
289+
field: str
290+
_private: Annotated[str, PrivateAttr(default="secret")]
291+
"""
292+
with temporary_visited_package(
293+
"package",
294+
modules={"__init__.py": code},
295+
extensions=Extensions(PydanticExtension(schema=False)),
296+
) as package:
297+
assert "pydantic-field" in package["Model.field"].labels
298+
assert "pydantic-field" not in package["Model._private"].labels

0 commit comments

Comments
 (0)