Description of the bug
With merge_init_into_class: true, the source block in
templates/material/_base/class.html.jinja unconditionally prefers
all_members["__init__"] over the class itself:
{% if config.merge_init_into_class %}
{% if "__init__" in all_members and all_members["__init__"].source %}
all_members includes inherited members, so when a class does not
define __init__ itself (e.g. any pydantic BaseModel subclass, with
preload_modules: [pydantic] so griffe can resolve the base), the
rendered source block is pydantic.BaseModel.__init__ with a summary
like:
Source code in .venv/lib/python3.10/site-packages/pydantic/main.py
instead of the documented class's own file. Two problems:
- The environment-specific
.venv/... path leaks into published docs.
- The source shown is the base class's
__init__, which is not the
documented object's code at all.
Additionally, when the merged __init__ has no source, the block
renders nothing rather than falling back to the class source.
To Reproduce
Verified with mkdocs 1.6.1, mkdocstrings 1.0.4, mkdocstrings-python
2.0.5, griffe 2.1.0, pydantic 2.13.4 on Python 3.10.
mkdir -p repro/src/pkg repro/docs && cd repro
python -m venv .venv && . .venv/bin/activate
pip install mkdocs mkdocs-material "mkdocstrings[python]" pydantic
src/pkg/__init__.py:
from pydantic import BaseModel
class Model(BaseModel):
"""A pydantic model without its own `__init__`."""
x: int = 0
docs/index.md:
mkdocs.yml:
site_name: repro
theme:
name: material
plugins:
- mkdocstrings:
handlers:
python:
paths: [src]
options:
merge_init_into_class: true
preload_modules: [pydantic]
show_source: true
Build and inspect the rendered source block:
$ mkdocs build
$ grep -o "Source code in <code>[^<]*</code>" site/index.html
Source code in <code>.venv/lib/python3.10/site-packages/pydantic/main.py</code>
Expected: src/pkg/__init__.py.
The mechanism, shown at the griffe level (run from the same directory):
import sysconfig
import griffe
loader = griffe.GriffeLoader(
search_paths=["src", sysconfig.get_paths()["purelib"]]
)
loader.load("pydantic")
pkg = loader.load("pkg")
loader.resolve_aliases()
cls = pkg["Model"]
init = cls.all_members["__init__"]
print(cls.relative_filepath) # src/pkg/__init__.py
print(init.inherited) # True
print(init.relative_filepath) # .venv/lib/python3.10/site-packages/pydantic/main.py
Expected behavior
Only use __init__'s source when the class defines it itself
(griffe exposes inherited on the member); otherwise fall back to the
class's own source.
Environment information
python -m mkdocstrings_handlers.python._internal.debug # | xclip -selection clipboard
- System: macOS-26.5.1-arm64-arm-64bit
- Python: cpython 3.10.20 (/.venv/bin/python3)
- Environment variables:
- Installed packages:
mkdocstrings-python v2.0.5
Additional context
I'm using a template override in
docs/templates/python/materialx/class.html.jinja, wired
up via the custom_templates option in mkdocs.yaml:
{% extends "_base/class.html.jinja" %}
{% block source %}
{% import "language.html.jinja" as lang with context %}
{% if config.show_source %}
{% if config.merge_init_into_class
and "__init__" in all_members
and not all_members["__init__"].inherited
and all_members["__init__"].source %}
{% with init = all_members["__init__"] %}
<details class="mkdocstrings-source">
<summary>{{ lang.t("Source code in") }} <code>
{%- if init.relative_filepath.is_absolute() -%}
{{ init.relative_package_filepath }}
{%- else -%}
{{ init.relative_filepath }}
{%- endif -%}
</code></summary>
{{ init.source|highlight(language="python", linestart=init.lineno or 0, linenums=True) }}
</details>
{% endwith %}
{% elif class.source %}
<details class="mkdocstrings-source">
<summary>{{ lang.t("Source code in") }} <code>
{%- if class.relative_filepath.is_absolute() -%}
{{ class.relative_package_filepath }}
{%- else -%}
{{ class.relative_filepath }}
{%- endif -%}
</code></summary>
{{ class.source|highlight(language="python", linestart=class.lineno or 0, linenums=True) }}
</details>
{% endif %}
{% endif %}
{% endblock source %}
Description of the bug
With
merge_init_into_class: true, thesourceblock intemplates/material/_base/class.html.jinjaunconditionally prefersall_members["__init__"]over the class itself:all_membersincludes inherited members, so when a class does notdefine
__init__itself (e.g. any pydanticBaseModelsubclass, withpreload_modules: [pydantic]so griffe can resolve the base), therendered source block is
pydantic.BaseModel.__init__with a summarylike:
instead of the documented class's own file. Two problems:
.venv/...path leaks into published docs.__init__, which is not thedocumented object's code at all.
Additionally, when the merged
__init__has no source, the blockrenders nothing rather than falling back to the class source.
To Reproduce
Verified with mkdocs 1.6.1, mkdocstrings 1.0.4, mkdocstrings-python
2.0.5, griffe 2.1.0, pydantic 2.13.4 on Python 3.10.
src/pkg/__init__.py:docs/index.md:mkdocs.yml:Build and inspect the rendered source block:
Expected:
src/pkg/__init__.py.The mechanism, shown at the griffe level (run from the same directory):
Expected behavior
Only use
__init__'s source when the class defines it itself(griffe exposes
inheritedon the member); otherwise fall back to theclass's own source.
Environment information
python -m mkdocstrings_handlers.python._internal.debug # | xclip -selection clipboardmkdocstrings-pythonv2.0.5Additional context
I'm using a template override in
docs/templates/python/materialx/class.html.jinja, wiredup via the
custom_templatesoption inmkdocs.yaml: