diff --git a/src/mkdocstrings_handlers/python/handler.py b/src/mkdocstrings_handlers/python/handler.py
index 0051be0..8146c19 100644
--- a/src/mkdocstrings_handlers/python/handler.py
+++ b/src/mkdocstrings_handlers/python/handler.py
@@ -6,6 +6,7 @@
import os
import posixpath
import sys
+from collections.abc import Iterable
from contextlib import suppress
from dataclasses import asdict
from pathlib import Path
@@ -25,6 +26,7 @@
from mkdocstrings.handlers.base import BaseHandler, CollectionError, CollectorItem, HandlerOptions
from mkdocstrings.inventory import Inventory
from mkdocstrings.loggers import get_logger
+from mkdocs_autorefs.plugin import BacklinkCrumb
from mkdocstrings_handlers.python import rendering
from mkdocstrings_handlers.python.config import PythonConfig, PythonOptions
@@ -33,6 +35,7 @@
from collections.abc import Iterator, Mapping, MutableMapping, Sequence
from mkdocs.config.defaults import MkDocsConfig
+ from mkdocs_autorefs.plugin import Backlink
if sys.version_info >= (3, 11):
@@ -280,6 +283,16 @@ def render(self, data: CollectorItem, options: PythonOptions) -> str: # noqa: D
},
)
+ def render_backlinks(self, backlinks: Mapping[str, Iterable[Backlink]]) -> str: # noqa: D102 (ignore missing docstring)
+ template = self.env.get_template("backlinks.html.jinja")
+ verbose_type = {key: key.capitalize().replace("-by", " by") for key in backlinks.keys()}
+ return template.render(
+ backlinks=backlinks,
+ config=self.get_options({}),
+ verbose_type=verbose_type,
+ default_crumb=BacklinkCrumb(title="", url=""),
+ )
+
def update_env(self, config: Any) -> None: # noqa: ARG002
"""Update the Jinja environment with custom filters and tests.
@@ -303,6 +316,7 @@ def update_env(self, config: Any) -> None: # noqa: ARG002
self.env.filters["as_functions_section"] = rendering.do_as_functions_section
self.env.filters["as_classes_section"] = rendering.do_as_classes_section
self.env.filters["as_modules_section"] = rendering.do_as_modules_section
+ self.env.filters["backlink_tree"] = rendering.do_backlink_tree
self.env.globals["AutorefsHook"] = rendering.AutorefsHook
self.env.tests["existing_template"] = lambda template_name: template_name in self.env.list_templates()
diff --git a/src/mkdocstrings_handlers/python/rendering.py b/src/mkdocstrings_handlers/python/rendering.py
index d284567..a99f78a 100644
--- a/src/mkdocstrings_handlers/python/rendering.py
+++ b/src/mkdocstrings_handlers/python/rendering.py
@@ -2,6 +2,7 @@
from __future__ import annotations
+from collections.abc import Iterable
import random
import re
import string
@@ -12,7 +13,7 @@
from functools import lru_cache
from pathlib import Path
from re import Match, Pattern
-from typing import TYPE_CHECKING, Any, Callable, ClassVar, Literal
+from typing import TYPE_CHECKING, Any, Callable, ClassVar, Literal, TypeVar
from griffe import (
Alias,
@@ -26,9 +27,12 @@
DocstringSectionModules,
Object,
)
+from collections import defaultdict
+from typing import Optional, Sequence, Union
+
from jinja2 import TemplateNotFound, pass_context, pass_environment
from markupsafe import Markup
-from mkdocs_autorefs import AutorefsHookInterface
+from mkdocs_autorefs import AutorefsHookInterface, Backlink, BacklinkCrumb
from mkdocstrings.loggers import get_logger
if TYPE_CHECKING:
@@ -210,10 +214,15 @@ def do_format_attribute(
signature = str(attribute_path).strip()
if annotations and attribute.annotation:
- annotation = template.render(context.parent, expression=attribute.annotation, signature=True)
+ annotation = template.render(
+ context.parent,
+ expression=attribute.annotation,
+ signature=True,
+ backlink_type="returned-by",
+ )
signature += f": {annotation}"
if attribute.value:
- value = template.render(context.parent, expression=attribute.value, signature=True)
+ value = template.render(context.parent, expression=attribute.value, signature=True, backlink_type="used-by")
signature += f" = {value}"
signature = do_format_code(signature, line_length)
@@ -725,3 +734,52 @@ def get_context(self) -> AutorefsHookInterface.Context:
filepath=str(filepath),
lineno=lineno,
)
+
+
+T = TypeVar("T")
+Tree = dict[T, "Tree"]
+CompactTree = dict[tuple[T, ...], "CompactTree"]
+_rtree = lambda: defaultdict(_rtree)
+
+
+def _tree(data: Iterable[tuple[T, ...]]) -> Tree:
+ new_tree = _rtree()
+ for nav in data:
+ *path, leaf = nav
+ node = new_tree
+ for key in path:
+ node = node[key]
+ node[leaf] = _rtree()
+ return new_tree
+
+
+def print_tree(tree: Tree, level: int = 0) -> None:
+ for key, value in tree.items():
+ print(" " * level + str(key))
+ if value:
+ print_tree(value, level + 1)
+
+
+def _compact_tree(tree: Tree) -> CompactTree:
+ new_tree = _rtree()
+ for key, value in tree.items():
+ child = _compact_tree(value)
+ if len(child) == 1:
+ child_key, child_value = next(iter(child.items()))
+ new_key = (key, *child_key)
+ new_tree[new_key] = child_value
+ else:
+ new_tree[(key,)] = child
+ return new_tree
+
+
+def do_backlink_tree(backlinks: list[Backlink]) -> CompactTree[BacklinkCrumb]:
+ """Build a tree of backlinks.
+
+ Parameters:
+ backlinks: The list of backlinks.
+
+ Returns:
+ A tree of backlinks.
+ """
+ return _compact_tree(_tree((backlink.crumbs for backlink in backlinks)))
diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/attribute.html.jinja b/src/mkdocstrings_handlers/python/templates/material/_base/attribute.html.jinja
index c13bb64..17a86f5 100644
--- a/src/mkdocstrings_handlers/python/templates/material/_base/attribute.html.jinja
+++ b/src/mkdocstrings_handlers/python/templates/material/_base/attribute.html.jinja
@@ -113,6 +113,8 @@ Context:
{% include "docstring"|get_template with context %}
{% endwith %}
{% endblock docstring %}
+
+
Bases: {% for expression in class.bases -%}
- {% include "expression"|get_template with context %}
{% if not loop.last %}, {% endif %}
+
+ {%- with backlink_type = "subclassed-by" -%}
+ {%- include "expression"|get_template with context -%}
+ {%- endwith -%}
+
{% if not loop.last %}, {% endif %}
{% endfor -%}
{{ parameter.name }}
{% include "expression"|get_template with context %}
{% endwith %}
{% endif %}
@@ -60,7 +60,7 @@ Context:
{{ parameter.name }}
{% if parameter.annotation %}
- {% with expression = parameter.annotation %}
+ {% with expression = parameter.annotation, backlink_type = "used-by" %}
({% include "expression"|get_template with context %}
)
{% endwith %}
{% endif %}
@@ -94,7 +94,7 @@ Context:
{% if parameter.annotation %}
{{ lang.t("TYPE:") }}
- {% with expression = parameter.annotation %}
+ {% with expression = parameter.annotation, backlink_type = "used-by" %}
{% include "expression"|get_template with context %}
{% endwith %}
diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/docstring/parameters.html.jinja b/src/mkdocstrings_handlers/python/templates/material/_base/docstring/parameters.html.jinja
index d4e9acb..ae8fb7a 100644
--- a/src/mkdocstrings_handlers/python/templates/material/_base/docstring/parameters.html.jinja
+++ b/src/mkdocstrings_handlers/python/templates/material/_base/docstring/parameters.html.jinja
@@ -51,7 +51,7 @@ Context:
{% include "expression"|get_template with context %}
{% endwith %}
{% endif %}
@@ -63,7 +63,7 @@ Context:
{% include "expression"|get_template with context %}
{% endwith %}
{% else %}
@@ -96,10 +96,10 @@ Context:
{{ parameter.name }}
{% endif %}
{% if parameter.annotation %}
- {% with expression = parameter.annotation %}
+ {% with expression = parameter.annotation, backlink_type = "used-by" %}
({% include "expression"|get_template with context %}
{%- if parameter.default %}, {{ lang.t("default:") }}
- {% with expression = parameter.default %}
+ {% with expression = parameter.default, backlink_type = "used-by" %}
{% include "expression"|get_template with context %}
{% endwith %}
{% endif %})
@@ -149,7 +149,7 @@ Context:
{% if parameter.annotation %}
{{ lang.t("TYPE:") }}
- {% with expression = parameter.annotation %}
+ {% with expression = parameter.annotation, backlink_type = "used-by" %}
{% include "expression"|get_template with context %}
{% endwith %}
@@ -157,7 +157,7 @@ Context:
{% if parameter.default %}
{{ lang.t("DEFAULT:") }}
- {% with expression = parameter.default %}
+ {% with expression = parameter.default, backlink_type = "used-by" %}
{% include "expression"|get_template with context %}
{% endwith %}
diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/docstring/raises.html.jinja b/src/mkdocstrings_handlers/python/templates/material/_base/docstring/raises.html.jinja
index d734e94..1e1d500 100644
--- a/src/mkdocstrings_handlers/python/templates/material/_base/docstring/raises.html.jinja
+++ b/src/mkdocstrings_handlers/python/templates/material/_base/docstring/raises.html.jinja
@@ -34,7 +34,7 @@ Context:
{% include "expression"|get_template with context %}
{% endwith %}
{% endif %}
@@ -57,7 +57,7 @@ Context:
{% for raises in section.value %}
{% include "expression"|get_template with context %}
{% endwith %}
–
@@ -84,7 +84,7 @@ Context:
{% include "expression"|get_template with context %}
{% endwith %}
diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/docstring/receives.html.jinja b/src/mkdocstrings_handlers/python/templates/material/_base/docstring/receives.html.jinja
index 3b618c6..a447b21 100644
--- a/src/mkdocstrings_handlers/python/templates/material/_base/docstring/receives.html.jinja
+++ b/src/mkdocstrings_handlers/python/templates/material/_base/docstring/receives.html.jinja
@@ -37,7 +37,7 @@ Context:
{% if name_column %}{{ receives.name }}
{% endif %}{% include "expression"|get_template with context %}
{% endwith %}
{% endif %}
@@ -61,7 +61,7 @@ Context:
{{ receives.name }}
{% endif %}
{% if receives.annotation %}
- {% with expression = receives.annotation %}
+ {% with expression = receives.annotation, backlink_type = "received-by" %}
{% if receives.name %} ({% endif %}
{% include "expression"|get_template with context %}
{% if receives.name %}){% endif %}
@@ -93,7 +93,7 @@ Context:
{{ receives.name }}
{% elif receives.annotation %}
- {% with expression = receives.annotation %}
+ {% with expression = receives.annotation, backlink_type = "received-by" %}
{% include "expression"|get_template with context %}
{% endwith %}
@@ -107,7 +107,7 @@ Context:
{{ lang.t("TYPE:") }}
- {% with expression = receives.annotation %}
+ {% with expression = receives.annotation, backlink_type = "received-by" %}
{% include "expression"|get_template with context %}
{% endwith %}
diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/docstring/returns.html.jinja b/src/mkdocstrings_handlers/python/templates/material/_base/docstring/returns.html.jinja
index af61fce..30540e6 100644
--- a/src/mkdocstrings_handlers/python/templates/material/_base/docstring/returns.html.jinja
+++ b/src/mkdocstrings_handlers/python/templates/material/_base/docstring/returns.html.jinja
@@ -37,7 +37,7 @@ Context:
{% if name_column %}
{{ returns.name }}
{% endif %}{% include "expression"|get_template with context %}
{% endwith %}
{% endif %}
@@ -61,7 +61,7 @@ Context:
{{ returns.name }}
{% endif %}
{% if returns.annotation %}
- {% with expression = returns.annotation %}
+ {% with expression = returns.annotation, backlink_type = "returned-by" %}
{% if returns.name %} ({% endif %}
{% include "expression"|get_template with context %}
{% if returns.name %}){% endif %}
@@ -93,7 +93,7 @@ Context:
{{ returns.name }}
{% elif returns.annotation %}
- {% with expression = returns.annotation %}
+ {% with expression = returns.annotation, backlink_type = "returned-by" %}
{% include "expression"|get_template with context %}
{% endwith %}
@@ -107,7 +107,7 @@ Context:
{{ lang.t("TYPE:") }}
- {% with expression = returns.annotation %}
+ {% with expression = returns.annotation, backlink_type = "returned-by" %}
{% include "expression"|get_template with context %}
{% endwith %}
diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/docstring/warns.html.jinja b/src/mkdocstrings_handlers/python/templates/material/_base/docstring/warns.html.jinja
index 782c0cd..814e302 100644
--- a/src/mkdocstrings_handlers/python/templates/material/_base/docstring/warns.html.jinja
+++ b/src/mkdocstrings_handlers/python/templates/material/_base/docstring/warns.html.jinja
@@ -34,7 +34,7 @@ Context:
{% include "expression"|get_template with context %}
{% endwith %}
{% endif %}
@@ -57,7 +57,7 @@ Context:
{% for warns in section.value %}
{% include "expression"|get_template with context %}
{% endwith %}
–
@@ -84,7 +84,7 @@ Context:
{% include "expression"|get_template with context %}
{% endwith %}
diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/docstring/yields.html.jinja b/src/mkdocstrings_handlers/python/templates/material/_base/docstring/yields.html.jinja
index 6d3cfa1..01b8b9e 100644
--- a/src/mkdocstrings_handlers/python/templates/material/_base/docstring/yields.html.jinja
+++ b/src/mkdocstrings_handlers/python/templates/material/_base/docstring/yields.html.jinja
@@ -37,7 +37,7 @@ Context:
{% if name_column %}{{ yields.name }}
{% endif %}{% include "expression"|get_template with context %}
{% endwith %}
{% endif %}
@@ -61,7 +61,7 @@ Context:
{{ yields.name }}
{% endif %}
{% if yields.annotation %}
- {% with expression = yields.annotation %}
+ {% with expression = yields.annotation, backlink_type = "yielded-by" %}
{% if yields.name %} ({% endif %}
{% include "expression"|get_template with context %}
{% if yields.name %}){% endif %}
@@ -93,7 +93,7 @@ Context:
{{ yields.name }}
{% elif yields.annotation %}
- {% with expression = yields.annotation %}
+ {% with expression = yields.annotation, backlink_type = "yielded-by" %}
{% include "expression"|get_template with context %}
{% endwith %}
@@ -107,7 +107,7 @@ Context:
{{ lang.t("TYPE:") }}:
- {% with expression = yields.annotation %}
+ {% with expression = yields.annotation, backlink_type = "yielded-by" %}
{% include "expression"|get_template with context %}
{% endwith %}
diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/expression.html.jinja b/src/mkdocstrings_handlers/python/templates/material/_base/expression.html.jinja
index 781d46c..d49e43b 100644
--- a/src/mkdocstrings_handlers/python/templates/material/_base/expression.html.jinja
+++ b/src/mkdocstrings_handlers/python/templates/material/_base/expression.html.jinja
@@ -11,7 +11,7 @@ which is a tree-like structure representing a Python expression.
-#}
{% endblock logs %}
-{%- macro crossref(name, annotation_path) -%}
+{%- macro crossref(name, annotation_path, backlink_type="") -%}
{#- Output a cross-reference.
This macro outputs a cross-reference to the given name.
@@ -35,11 +35,11 @@ which is a tree-like structure representing a Python expression.
{{ prefix }}
{%- if not signature -%}
{#- Always render cross-references outside of signatures. We don't need to stash them. -#}
-
{{ parameter.name }}
{% if parameter.annotation %}
- {% with expression = parameter.annotation %}
+ {% with expression = parameter.annotation, backlink_type = "used-by" %}
({% include "expression"|get_template with context %}
)
{% endwith %}
{% endif %}
diff --git a/src/mkdocstrings_handlers/python/templates/readthedocs/_base/docstring/parameters.html.jinja b/src/mkdocstrings_handlers/python/templates/readthedocs/_base/docstring/parameters.html.jinja
index 295ab08..6017a8c 100644
--- a/src/mkdocstrings_handlers/python/templates/readthedocs/_base/docstring/parameters.html.jinja
+++ b/src/mkdocstrings_handlers/python/templates/readthedocs/_base/docstring/parameters.html.jinja
@@ -32,10 +32,10 @@ Context:
{{ parameter.name }}
{% if parameter.annotation %}
- {% with expression = parameter.annotation %}
+ {% with expression = parameter.annotation, backlink_type = "used-by" %}
({% include "expression"|get_template with context %}
{%- if parameter.default %}, {{ lang.t("default:") }}
- {% with expression = parameter.default %}
+ {% with expression = parameter.default, backlink_type = "used-by" %}
{% include "expression"|get_template with context %}
{% endwith %}
{% endif %})
diff --git a/src/mkdocstrings_handlers/python/templates/readthedocs/_base/docstring/raises.html.jinja b/src/mkdocstrings_handlers/python/templates/readthedocs/_base/docstring/raises.html.jinja
index 7fa8cd8..11f695e 100644
--- a/src/mkdocstrings_handlers/python/templates/readthedocs/_base/docstring/raises.html.jinja
+++ b/src/mkdocstrings_handlers/python/templates/readthedocs/_base/docstring/raises.html.jinja
@@ -31,7 +31,7 @@ Context:
{% for raises in section.value %}
{% include "expression"|get_template with context %}
{% endwith %}
{% endif %}
diff --git a/src/mkdocstrings_handlers/python/templates/readthedocs/_base/docstring/receives.html.jinja b/src/mkdocstrings_handlers/python/templates/readthedocs/_base/docstring/receives.html.jinja
index 9ee189b..aec9a85 100644
--- a/src/mkdocstrings_handlers/python/templates/readthedocs/_base/docstring/receives.html.jinja
+++ b/src/mkdocstrings_handlers/python/templates/readthedocs/_base/docstring/receives.html.jinja
@@ -32,7 +32,7 @@ Context:
{{ receives.name }}
{% endif %}
{% if receives.annotation %}
- {% with expression = receives.annotation %}
+ {% with expression = receives.annotation, backlink_type = "received-by" %}
{% if receives.name %}({% endif %}
{% include "expression"|get_template with context %}
{% if receives.name %}){% endif %}
diff --git a/src/mkdocstrings_handlers/python/templates/readthedocs/_base/docstring/returns.html.jinja b/src/mkdocstrings_handlers/python/templates/readthedocs/_base/docstring/returns.html.jinja
index 2dbd21a..3f8bbba 100644
--- a/src/mkdocstrings_handlers/python/templates/readthedocs/_base/docstring/returns.html.jinja
+++ b/src/mkdocstrings_handlers/python/templates/readthedocs/_base/docstring/returns.html.jinja
@@ -32,7 +32,7 @@ Context:
{{ returns.name }}
{% endif %}
{% if returns.annotation %}
- {% with expression = returns.annotation %}
+ {% with expression = returns.annotation, backlink_type = "returned-by" %}
{% if returns.name %}({% endif %}
{% include "expression"|get_template with context %}
{% if returns.name %}){% endif %}
diff --git a/src/mkdocstrings_handlers/python/templates/readthedocs/_base/docstring/warns.html.jinja b/src/mkdocstrings_handlers/python/templates/readthedocs/_base/docstring/warns.html.jinja
index 61f3c83..1d465ec 100644
--- a/src/mkdocstrings_handlers/python/templates/readthedocs/_base/docstring/warns.html.jinja
+++ b/src/mkdocstrings_handlers/python/templates/readthedocs/_base/docstring/warns.html.jinja
@@ -31,7 +31,7 @@ Context:
{% for warns in section.value %}
{% include "expression"|get_template with context %}
{% endwith %}
{% endif %}
diff --git a/src/mkdocstrings_handlers/python/templates/readthedocs/_base/docstring/yields.html.jinja b/src/mkdocstrings_handlers/python/templates/readthedocs/_base/docstring/yields.html.jinja
index 0fa6fcb..70c782b 100644
--- a/src/mkdocstrings_handlers/python/templates/readthedocs/_base/docstring/yields.html.jinja
+++ b/src/mkdocstrings_handlers/python/templates/readthedocs/_base/docstring/yields.html.jinja
@@ -32,7 +32,7 @@ Context:
{% include "expression"|get_template with context %}
{% if yields.name %}){% endif %}