Skip to content

Commit 50b4a86

Browse files
committed
refactor: change option to show_as_alias
1 parent 2c4b3c9 commit 50b4a86

8 files changed

Lines changed: 79 additions & 54 deletions

File tree

docs/index.md

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ hide:
5050

5151
///
5252

53-
### Serialization Aliases
53+
### Show as alias
5454

55-
When the extension is configured with `serialize_by_alias=True`, fields with a `serialization_alias` will appear under their alias names in the documentation. This is useful for APIs where the serialized output uses different field names than the Python attribute names. See [Pydantic's alias documentation](https://docs.pydantic.dev/latest/concepts/alias/) for more information.
55+
When the extension is configured with `show_as_alias=True`, fields with a `alias` will appear under their alias names in the documentation. This is useful for APIs where the serialized output uses different field names than the Python attribute names. See [Pydantic's alias documentation](https://docs.pydantic.dev/latest/concepts/alias/) for more information.
5656

5757
To enable this feature in your documentation configuration, configure the extension as follows:
5858

@@ -63,9 +63,20 @@ plugins:
6363
python:
6464
extensions:
6565
- griffe_pydantic:
66-
serialize_by_alias: true
66+
show_as_alias: true
6767
```
6868
69+
Or use the local configuration:
70+
71+
```markdown
72+
::: model_ext.ExampleModel
73+
options:
74+
extensions:
75+
- griffe_pydantic:
76+
show_as_alias: true
77+
```
78+
79+
6980
/// tab | Pydantic model
7081
7182
```python
@@ -81,7 +92,7 @@ plugins:
8192
options:
8293
heading_level: 4
8394
extensions:
84-
- griffe_pydantic: {serialize_by_alias: false}
95+
- griffe_pydantic: {show_as_alias: false}
8596
skip_local_inventory: true
8697
```
8798

@@ -94,9 +105,8 @@ plugins:
94105
options:
95106
heading_level: 4
96107
extensions:
97-
- griffe_pydantic: {serialize_by_alias: true}
108+
- griffe_pydantic: {show_as_alias: true}
98109
skip_local_inventory: true
99110
```
100111

101112
///
102-

examples/model_noserialize.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
class UserModel(BaseModel):
66
"""A user model with serialization aliases.
77
8-
When the extension is configured with `serialize_by_alias=True`, fields with
9-
`serialization_alias` will appear under their alias names in the documentation.
8+
When the extension is configured with `show_as_alias=True`, fields with
9+
`alias` will appear under their alias names in the documentation.
1010
"""
1111

1212
model_config = ConfigDict(frozen=False)

examples/model_serialize.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,20 @@
44
class UserModel(BaseModel):
55
"""A user model with serialization aliases.
66
7-
When the extension is configured with `serialize_by_alias=True`, fields with
8-
`serialization_alias` will appear under their alias names in the documentation.
7+
When the extension is configured with `show_as_alias=True`, fields with
8+
`alias` will appear under their alias names in the documentation.
99
"""
1010

1111
model_config = ConfigDict(frozen=False)
1212

13-
user_id: int = Field(serialization_alias="id")
13+
user_id: int = Field(alias="id")
1414
"""Unique user identifier, serialized as 'id'."""
1515

16-
full_name: str = Field(default="Anonymous", serialization_alias="name")
16+
full_name: str = Field(default="Anonymous", alias="name")
1717
"""User's full name, serialized as 'name'."""
1818

1919
email_address: str
2020
"""User's email address."""
2121

22-
is_active: bool = Field(default=True, serialization_alias="active")
22+
is_active: bool = Field(default=True, alias="active")
2323
"""Whether the user is active, serialized as 'active'."""

src/griffe_pydantic/_internal/dynamic.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def _process_attribute(
2626
cls: Class,
2727
*,
2828
processed: set[str],
29-
serialize_by_alias: bool = False,
29+
show_as_alias: bool = False,
3030
) -> None:
3131
"""Handle Pydantic fields."""
3232
from pydantic.fields import FieldInfo # noqa: PLC0415
@@ -49,9 +49,9 @@ def _process_attribute(
4949
constraints[constraint] = value
5050
attr.extra[common._self_namespace]["constraints"] = constraints
5151

52-
# Store serialization_alias if present
53-
if serialize_by_alias and obj.serialization_alias:
54-
attr.extra[common._self_namespace]["serialization_alias"] = obj.serialization_alias
52+
# Store alias if present
53+
if show_as_alias and obj.alias:
54+
attr.extra[common._self_namespace]["alias"] = obj.alias
5555
attr.extra[common._mkdocstrings_namespace]["template"] = "pydantic_attribute_alias.html.jinja"
5656

5757
# Populate docstring from the field's `description` argument.
@@ -74,7 +74,7 @@ def _process_class(
7474
*,
7575
processed: set[str],
7676
schema: bool = False,
77-
serialize_by_alias: bool = False,
77+
show_as_alias: bool = False,
7878
) -> None:
7979
"""Detect and prepare Pydantic models."""
8080
common._process_class(cls)
@@ -92,7 +92,26 @@ def _process_class(
9292
member, # ty: ignore[invalid-argument-type]
9393
cls,
9494
processed=processed,
95-
serialize_by_alias=serialize_by_alias,
95+
show_as_alias=show_as_alias,
9696
)
9797
elif kind is Kind.FUNCTION:
9898
_process_function(getattr(obj, member.name), member, cls, processed=processed) # ty: ignore[invalid-argument-type]
99+
100+
# Process model fields that may not have been discovered by griffe
101+
if hasattr(obj, "model_fields") and isinstance(obj.model_fields, dict):
102+
for field_name, field_info in obj.model_fields.items():
103+
if field_name not in cls.all_members:
104+
# Create an Attribute object for this field
105+
attr = Attribute(
106+
name=field_name,
107+
lineno=0,
108+
endlineno=0,
109+
)
110+
cls.members[field_name] = attr # ty: ignore[invalid-assignment]
111+
_process_attribute(
112+
field_info,
113+
attr,
114+
cls,
115+
processed=processed,
116+
show_as_alias=show_as_alias,
117+
)

src/griffe_pydantic/_internal/extension.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,17 @@
2222
class PydanticExtension(Extension):
2323
"""Griffe extension for Pydantic."""
2424

25-
def __init__(self, *, schema: bool = False, serialize_by_alias: bool = False) -> None:
25+
def __init__(self, *, schema: bool = False, show_as_alias: bool = False) -> None:
2626
"""Initialize the extension.
2727
2828
Parameters:
2929
schema: Whether to compute and store the JSON schema of models.
30-
serialize_by_alias: Whether to use `serialization_alias` as the field name in documentation.
31-
When enabled, fields with a `serialization_alias` will be keyed by that alias instead of their Python attribute name.
30+
show_as_alias: Whether to use `alias` as the field name in documentation.
31+
When enabled, fields with a `alias` will be keyed by that alias instead of their Python attribute name.
3232
"""
3333
super().__init__()
3434
self._schema = schema
35-
self._serialize_by_alias = serialize_by_alias
35+
self._show_as_alias = show_as_alias
3636
self._processed: set[str] = set()
3737
self._recorded: list[tuple[ObjectNode, Class]] = []
3838

@@ -45,13 +45,13 @@ def on_package(self, *, pkg: Module, **kwargs: Any) -> None: # noqa: ARG002
4545
cls,
4646
processed=self._processed,
4747
schema=self._schema,
48-
serialize_by_alias=self._serialize_by_alias,
48+
show_as_alias=self._show_as_alias,
4949
)
5050
static._process_module(
5151
pkg,
5252
processed=self._processed,
5353
schema=self._schema,
54-
serialize_by_alias=self._serialize_by_alias,
54+
show_as_alias=self._show_as_alias,
5555
)
5656

5757
def on_class_instance(self, *, node: ast.AST | ObjectNode, cls: Class, **kwargs: Any) -> None: # noqa: ARG002

src/griffe_pydantic/_internal/static.py

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def _pydantic_validator(func: Function) -> ExprCall | None:
9696
return None
9797

9898

99-
def _process_attribute(attr: Attribute, cls: Class, *, processed: set[str], serialize_by_alias: bool = False) -> None:
99+
def _process_attribute(attr: Attribute, cls: Class, *, processed: set[str], show_as_alias: bool = False) -> None:
100100
"""Handle Pydantic fields."""
101101
if attr.canonical_path in processed:
102102
return
@@ -189,25 +189,21 @@ def _process_attribute(attr: Attribute, cls: Class, *, processed: set[str], seri
189189
attr.labels.discard("instance-attribute")
190190

191191
attr.value = kwargs.get("default")
192-
constraints = {
193-
kwarg: value
194-
for kwarg, value in kwargs.items()
195-
if kwarg not in {"default", "description", "serialization_alias"}
196-
}
192+
constraints = {kwarg: value for kwarg, value in kwargs.items() if kwarg not in {"default", "description", "alias"}}
197193
attr.extra[common._self_namespace]["constraints"] = constraints
198194

199-
# Store serialization_alias if present
200-
if serialize_by_alias and (serialization_alias := kwargs.get("serialization_alias")):
201-
if isinstance(serialization_alias, str):
195+
# Store alias if present
196+
if show_as_alias and (alias := kwargs.get("alias")):
197+
if isinstance(alias, str):
202198
try:
203-
attr.extra[common._self_namespace]["serialization_alias"] = ast.literal_eval(serialization_alias)
199+
attr.extra[common._self_namespace]["alias"] = ast.literal_eval(alias)
204200
except ValueError:
205-
attr.extra[common._self_namespace]["serialization_alias"] = serialization_alias
206-
# Set the attribute template to the custom template, which will use the serialization_alias instead of the attribute name.
201+
attr.extra[common._self_namespace]["alias"] = alias
202+
# Set the attribute template to the custom template, which will use the alias instead of the attribute name.
207203
attr.extra[common._mkdocstrings_namespace]["template"] = "pydantic_attribute_alias.html.jinja"
208-
elif isinstance(serialization_alias, (ExprName, Expr)):
204+
elif isinstance(alias, (ExprName, Expr)):
209205
# For now, we can't resolve expressions at static analysis time
210-
_logger.debug(f"Could not resolve serialization_alias expression for field '{attr.path}'")
206+
_logger.debug(f"Could not resolve alias expression for field '{attr.path}'")
211207

212208
# Populate docstring from the field's `description` argument.
213209
if not attr.docstring and (description_expr := kwargs.get("description")):
@@ -232,7 +228,7 @@ def _process_function(func: Function, cls: Class, *, processed: set[str]) -> Non
232228
common._process_function(func, cls, fields)
233229

234230

235-
def _process_class(cls: Class, *, processed: set[str], schema: bool = False, serialize_by_alias: bool = False) -> None:
231+
def _process_class(cls: Class, *, processed: set[str], schema: bool = False, show_as_alias: bool = False) -> None:
236232
"""Finalize the Pydantic model data."""
237233
if cls.canonical_path in processed:
238234
return
@@ -267,19 +263,19 @@ def _process_class(cls: Class, *, processed: set[str], schema: bool = False, ser
267263
for member in cls.all_members.values():
268264
kind = member.kind
269265
if kind is Kind.ATTRIBUTE:
270-
_process_attribute(member, cls, processed=processed, serialize_by_alias=serialize_by_alias) # ty: ignore[invalid-argument-type]
266+
_process_attribute(member, cls, processed=processed, show_as_alias=show_as_alias) # ty: ignore[invalid-argument-type]
271267
elif kind is Kind.FUNCTION:
272268
_process_function(member, cls, processed=processed) # ty: ignore[invalid-argument-type]
273269
elif kind is Kind.CLASS:
274-
_process_class(member, processed=processed, schema=schema, serialize_by_alias=serialize_by_alias) # ty: ignore[invalid-argument-type]
270+
_process_class(member, processed=processed, schema=schema, show_as_alias=show_as_alias) # ty: ignore[invalid-argument-type]
275271

276272

277273
def _process_module(
278274
mod: Module,
279275
*,
280276
processed: set[str],
281277
schema: bool = False,
282-
serialize_by_alias: bool = False,
278+
show_as_alias: bool = False,
283279
) -> None:
284280
"""Handle Pydantic models in a module."""
285281
if mod.canonical_path in processed:
@@ -289,9 +285,9 @@ def _process_module(
289285
for cls in mod.classes.values():
290286
# Don't process aliases, real classes will be processed at some point anyway.
291287
if not cls.is_alias:
292-
_process_class(cls, processed=processed, schema=schema, serialize_by_alias=serialize_by_alias)
288+
_process_class(cls, processed=processed, schema=schema, show_as_alias=show_as_alias)
293289

294290
for submodule in mod.modules.values():
295291
# Same for modules, don't process aliased ones.
296292
if not submodule.is_alias:
297-
_process_module(submodule, processed=processed, schema=schema, serialize_by_alias=serialize_by_alias)
293+
_process_module(submodule, processed=processed, schema=schema, show_as_alias=show_as_alias)

src/griffe_pydantic/templates/material/_base/pydantic_attribute_alias.html.jinja

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Context:
3333

3434
{# {% set attribute_name = attribute.path if show_full_path else attribute.name %} #}
3535
{# TODO some better way to visualize the alias #}
36-
{% set attribute_name = attribute.extra.griffe_pydantic.serialization_alias %}
36+
{% set attribute_name = attribute.extra.griffe_pydantic.alias %}
3737

3838
{% if not root or config.show_root_heading %}
3939
{% filter heading(

tests/test_extension.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class RegularClass(object):
5757
regular_attr = 1
5858
5959
class AliasClass(BaseModel):
60-
internal_name: str = Field(default="test", serialization_alias="external_name")
60+
internal_name: str = Field(default="test", alias="external_name")
6161
regular_field: int = Field(default=42)
6262
"""
6363

@@ -69,7 +69,7 @@ def test_extension(analysis: str) -> None:
6969
with loader(
7070
"package",
7171
modules={"__init__.py": code},
72-
extensions=Extensions(PydanticExtension(schema=True, serialize_by_alias=True)),
72+
extensions=Extensions(PydanticExtension(schema=True, show_as_alias=True)),
7373
search_sys_path=analysis == "dynamic",
7474
) as package:
7575
assert package
@@ -90,9 +90,9 @@ def test_extension(analysis: str) -> None:
9090
assert package.classes["AliasClass"].labels == {"pydantic-model"}
9191

9292
fields = package.classes["AliasClass"].extra["griffe_pydantic"]["fields"]()
93-
assert "internal_name" not in fields
93+
assert "internal_name" in fields
9494
assert "regular_field" in fields
95-
assert "external_name" in fields
95+
assert "external_name" not in fields
9696

9797

9898
def test_imported_models() -> None:
@@ -401,19 +401,19 @@ class Model(BaseModel):
401401
assert "With multiple lines." in package["Model.field1"].docstring.value
402402

403403

404-
def test_serialize_by_alias_disabled_static() -> None:
405-
"""Test that without serialize_by_alias, static analysis uses Python attribute names."""
404+
def test_show_as_alias_disabled_static() -> None:
405+
"""Test that without show_as_alias, static analysis uses Python attribute names."""
406406
code = """
407407
from pydantic import BaseModel, Field
408408
409409
class Model(BaseModel):
410-
internal_name: str = Field(default="test", serialization_alias="external_name")
410+
internal_name: str = Field(default="test", alias="external_name")
411411
regular_field: int = Field(default=42)
412412
"""
413413
with temporary_visited_package(
414414
"package",
415415
modules={"__init__.py": code},
416-
extensions=Extensions(PydanticExtension(schema=False, serialize_by_alias=False)),
416+
extensions=Extensions(PydanticExtension(schema=False, show_as_alias=False)),
417417
) as package:
418418
fields = package["Model"].extra["griffe_pydantic"]["fields"]()
419419
assert "internal_name" in fields

0 commit comments

Comments
 (0)