Skip to content

bug: merge_init_into_class renders inherited __init__ source (leaks site-packages paths) #333

Description

@cdwilson

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:

  1. The environment-specific .venv/... path leaks into published docs.
  2. 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:

::: pkg.Model

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 %}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

featureNew feature or request

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions