diff --git a/CHANGELOG.md b/CHANGELOG.md index 854109c2..1458ef18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ## Unreleased: pdoc next +- Support Pydantic [`computed_field`](https://docs.pydantic.dev/2.0/usage/computed_fields/) descriptions ([#855](https://github.com/mitmproxy/pdoc/pull/855), @avhz) ## 2025-10-27: pdoc 16.0.0 diff --git a/pdoc/__init__.py b/pdoc/__init__.py index f6ccdc3e..de74da8d 100644 --- a/pdoc/__init__.py +++ b/pdoc/__init__.py @@ -265,11 +265,11 @@ class GoldenRetriever(Dog): For [Pydantic models](https://docs.pydantic.dev/latest/concepts/models/), pdoc will extract [field](https://docs.pydantic.dev/latest/concepts/fields/) descriptions and treat them just like [documented -variables](#document-variables). For example, the following two Pydantic models +variables](#document-variables). For example, the following Pydantic models would have identical pdoc-rendered documentation: ```python -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, computed_field class Foo(BaseModel): a: int = Field(description="Docs for field a.") @@ -278,6 +278,11 @@ class OtherFoo(BaseModel): a: int """Docs for field a.""" +class ComputedFoo(BaseModel): + @computed_field(description="Docs for field a.") + @property + def a(self) -> int: + ... ``` ## ...render math formulas? diff --git a/pdoc/_pydantic.py b/pdoc/_pydantic.py index 656c9895..945537a5 100644 --- a/pdoc/_pydantic.py +++ b/pdoc/_pydantic.py @@ -51,4 +51,6 @@ def get_field_docstring(parent: ClassOrModule, field_name: str) -> str | None: if is_pydantic_model(parent): if field := parent.__pydantic_fields__.get(field_name, None): return field.description + if computed := parent.__pydantic_computed_fields__.get(field_name, None): + return computed.description return None diff --git a/test/test__pydantic.py b/test/test__pydantic.py index 40112529..34aa0e74 100644 --- a/test/test__pydantic.py +++ b/test/test__pydantic.py @@ -1,3 +1,5 @@ +from functools import cached_property + import pydantic from pdoc import _pydantic @@ -16,10 +18,22 @@ class ExampleModel(pydantic.BaseModel): id: int name: str = pydantic.Field(description="desc", default="Jane Doe") + @pydantic.computed_field(description="computed") # type: ignore[misc] + @property + def computed(self) -> str: + return "computed_value" + + @pydantic.computed_field(description="cached") # type: ignore[misc] + @cached_property + def cached(self) -> str: + return "computed_value" + def test_with_pydantic(monkeypatch): assert _pydantic.is_pydantic_model(ExampleModel) assert _pydantic.get_field_docstring(ExampleModel, "name") == "desc" + assert _pydantic.get_field_docstring(ExampleModel, "computed") == "computed" + assert _pydantic.get_field_docstring(ExampleModel, "cached") == "cached" assert _pydantic.default_value(ExampleModel, "name", None) == "Jane Doe" assert not _pydantic.is_pydantic_model(pdoc.doc.Module)