mansenfranzen / autodoc_pydantic

Seamlessly integrate pydantic models in your Sphinx documentation.
MIT License
159 stars 27 forks source link

Autodoc_pydantic 2.x doesn't handle autodoc_type_aliases in conf.py correctly #304

Closed caseyzak24 closed 1 month ago

caseyzak24 commented 2 months ago

Build Versions

sphinx 7.4.7 pydantic 2.9.2 autodoc-pydantic 2.2.0

Problem

We recently migrated to v2 for pydantic/autodoc_pydantic and the html build now fails with

File "/Users/caseyzak/repos/generation/.venv/lib/python3.10/site-packages/sphinxcontrib/autodoc_pydantic/inspection.py", line 152, in _get_meta_items
    return meta_class.__dict__
AttributeError: 'str' object has no attribute '__dict__'

The full traceback wasn't much more help, so I modified the _get_meta_items method in inspection.py as follows:

    def _get_meta_items(meta_class: Any) -> dict[str, str]:  # noqa: ANN401
        """Helper method to extract constraint names and values from different
        pydantic Metadata objects such as `pydantic.types.Strict`.

        """
        print(meta_class)
        try:
            return meta_class.__dataclass_fields__
        except AttributeError:
            try:
                return meta_class.__dict__
            except AttributeError:
                raise AttributeError(f"{meta_class} is type {type(meta_class).__name__}")

which revealed that the failure occurs on the field type constraint, which is coming up as string instead of a metaclass instance. This seems to be because we use Annotated types for fields as clean way to document field units in our code and docs. To demonstrate:

unit_types.py

from __future__ import annotations
from typing_extensions import Annotated

# note the pattern here, we define a type to be used in the code (for devs benefit). The 2 strings are always
#  1) the type variable name and 2) the path to the variable
unit_types = [
    kW := Annotated[float, "kW", "unit_types.kW"],
    kWh := Annotated[float, "kWh", "unit_types.kWh"],
    V := Annotated[float, "V", "unit_types.V"],
    Wm2 := Annotated[float, "Wm2", "unit_types.Wm2"],
    Whm2 := Annotated[float, "Whm2", "unit_types.Whm2"],
    m2 := Annotated[float, "m2", "unit_types.m2"],
    deg := Annotated[float, "deg", "unit_types.deg"],
    degC := Annotated[float, "degC", "unit_types.degC"],
    dec := Annotated[float, "dec", "unit_types.dec"],
]

conf.py

from generation_models.generation_models.unit_types import unit_types  # noqa: E402
from typing import get_args  # noqa: E402

autodoc_member_order = "bysource"
add_module_names = False
autodoc_type_aliases = {get_args(k)[1]: get_args(k)[2] for k in unit_types}

models.py

from __future__ import annotations
import typing as t
from pydantic import BaseModel
from .unit_types import kW, V, dec, Wm2, deg, degC, kWh, Whm2, m2

class PVTimeSeries(BaseModel):
    r"""-"""

    ghi: t.List[Wm2]

    tracker_rotation_angle: t.Optional[t.List[deg]] = None

    front_poa_nominal: t.Optional[t.List[Wm2]] = None

    front_poa_shaded: t.Optional[t.List[Wm2]] = None

Using pydantic/autodoc-pydantic v1, this would generate docs like this:

image

Given that autodoc_type_aliases is a sphinx autodoc feature and this worked for v1, I assume previously autodoc-pydantic either handed this to sphinx to resolve or just ignored it and let autodoc handle it after (I'm a little unclear on the handling order).

Happy to work on fixing this if I'm pointed in the right direction, or perhaps our approach is just hacky and there is a more canonical way to achieve the same result in v2.

caseyzak24 commented 1 month ago

This seems to have been solved with the following package upgrades/versions:

sphinx 8.1.3 pydantic 2.9.2 autodoc-pydantic 2.2.0 sphinx-rtd-theme 3.0.1 sphinx-autodoc-typehints 2.5.0

Unfortunately I'm not exactly sure which of these upgrades or additional package installs resolved the issue. I performed them trying to solve a different problem and after doing so checked to see if they resolved this problem and they did. Hopefully this will help others facing a similar issue.