Skip to content

Commit 946a5b2

Browse files
committed
refactor: Prepare backlinks support
Issue-153: #153
1 parent a0e888c commit 946a5b2

27 files changed

+228
-56
lines changed

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ plugins:
160160
- https://mkdocstrings.github.io/griffe/objects.inv
161161
- https://python-markdown.github.io/objects.inv
162162
options:
163+
backlinks: flat
163164
docstring_options:
164165
ignore_init_summary: true
165166
docstring_section_style: list

src/mkdocstrings_handlers/python/config.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,14 @@ class PythonInputOptions:
386386
),
387387
] = "brief"
388388

389+
backlinks: Annotated[
390+
Literal["flat", "tree", False],
391+
Field(
392+
group="general",
393+
description="Whether to render backlinks, and how.",
394+
),
395+
] = False
396+
389397
docstring_options: Annotated[
390398
GoogleStyleOptions | NumpyStyleOptions | SphinxStyleOptions | AutoStyleOptions | None,
391399
Field(

src/mkdocstrings_handlers/python/handler.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ def update_env(self, config: Any) -> None: # noqa: ARG002
301301
self.env.filters["as_functions_section"] = rendering.do_as_functions_section
302302
self.env.filters["as_classes_section"] = rendering.do_as_classes_section
303303
self.env.filters["as_modules_section"] = rendering.do_as_modules_section
304+
self.env.filters["backlink_tree"] = rendering.do_backlink_tree
304305
self.env.globals["AutorefsHook"] = rendering.AutorefsHook
305306
self.env.tests["existing_template"] = lambda template_name: template_name in self.env.list_templates()
306307

src/mkdocstrings_handlers/python/rendering.py

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@
88
import subprocess
99
import sys
1010
import warnings
11+
from collections import defaultdict
12+
from collections.abc import Iterable, Sequence
1113
from dataclasses import replace
1214
from functools import lru_cache
1315
from pathlib import Path
1416
from re import Match, Pattern
15-
from typing import TYPE_CHECKING, Any, Callable, ClassVar, Literal
17+
from typing import TYPE_CHECKING, Any, Callable, ClassVar, Literal, TypeVar
1618

1719
from griffe import (
1820
Alias,
@@ -28,7 +30,7 @@
2830
)
2931
from jinja2 import TemplateNotFound, pass_context, pass_environment
3032
from markupsafe import Markup
31-
from mkdocs_autorefs import AutorefsHookInterface
33+
from mkdocs_autorefs import AutorefsHookInterface, Backlink, BacklinkCrumb
3234
from mkdocstrings import get_logger
3335

3436
if TYPE_CHECKING:
@@ -210,10 +212,15 @@ def do_format_attribute(
210212

211213
signature = str(attribute_path).strip()
212214
if annotations and attribute.annotation:
213-
annotation = template.render(context.parent, expression=attribute.annotation, signature=True)
215+
annotation = template.render(
216+
context.parent,
217+
expression=attribute.annotation,
218+
signature=True,
219+
backlink_type="returned-by",
220+
)
214221
signature += f": {annotation}"
215222
if attribute.value:
216-
value = template.render(context.parent, expression=attribute.value, signature=True)
223+
value = template.render(context.parent, expression=attribute.value, signature=True, backlink_type="used-by")
217224
signature += f" = {value}"
218225

219226
signature = do_format_code(signature, line_length)
@@ -725,3 +732,52 @@ def get_context(self) -> AutorefsHookInterface.Context:
725732
filepath=str(filepath),
726733
lineno=lineno,
727734
)
735+
736+
737+
T = TypeVar("T")
738+
Tree = dict[T, "Tree"]
739+
CompactTree = dict[tuple[T, ...], "CompactTree"]
740+
_rtree = lambda: defaultdict(_rtree)
741+
742+
743+
def _tree(data: Iterable[tuple[T, ...]]) -> Tree:
744+
new_tree = _rtree()
745+
for nav in data:
746+
*path, leaf = nav
747+
node = new_tree
748+
for key in path:
749+
node = node[key]
750+
node[leaf] = _rtree()
751+
return new_tree
752+
753+
754+
def print_tree(tree: Tree, level: int = 0) -> None:
755+
for key, value in tree.items():
756+
print(" " * level + str(key))
757+
if value:
758+
print_tree(value, level + 1)
759+
760+
761+
def _compact_tree(tree: Tree) -> CompactTree:
762+
new_tree = _rtree()
763+
for key, value in tree.items():
764+
child = _compact_tree(value)
765+
if len(child) == 1:
766+
child_key, child_value = next(iter(child.items()))
767+
new_key = (key, *child_key)
768+
new_tree[new_key] = child_value
769+
else:
770+
new_tree[(key,)] = child
771+
return new_tree
772+
773+
774+
def do_backlink_tree(backlinks: list[Backlink]) -> CompactTree[BacklinkCrumb]:
775+
"""Build a tree of backlinks.
776+
777+
Parameters:
778+
backlinks: The list of backlinks.
779+
780+
Returns:
781+
A tree of backlinks.
782+
"""
783+
return _compact_tree(_tree(backlink.crumbs for backlink in backlinks))

src/mkdocstrings_handlers/python/templates/material/_base/attribute.html.jinja

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ Context:
113113
{% include "docstring"|get_template with context %}
114114
{% endwith %}
115115
{% endblock docstring %}
116+
117+
<backlinks identifier="{{ html_id }}" handler="python" />
116118
{% endblock contents %}
117119
</div>
118120

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{#- Template for backlinks.
2+
3+
This template renders backlinks.
4+
5+
Context:
6+
backlinks (Mapping[str, Iterable[str]]): The backlinks to render.
7+
config (dict): The configuration options.
8+
verbose_type (Mapping[str, str]): The verbose backlink types.
9+
default_crumb (BacklinkCrumb): A default, empty crumb.
10+
-#}
11+
12+
{% block logs scoped %}
13+
{#- Logging block.
14+
15+
This block can be used to log debug messages, deprecation messages, warnings, etc.
16+
-#}
17+
{% endblock logs %}

src/mkdocstrings_handlers/python/templates/material/_base/class.html.jinja

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,11 @@ Context:
130130
{% if config.show_bases and class.bases %}
131131
<p class="doc doc-class-bases">
132132
Bases: {% for expression in class.bases -%}
133-
<code>{% include "expression"|get_template with context %}</code>{% if not loop.last %}, {% endif %}
133+
<code>
134+
{%- with backlink_type = "subclassed-by" -%}
135+
{%- include "expression"|get_template with context -%}
136+
{%- endwith -%}
137+
</code>{% if not loop.last %}, {% endif %}
134138
{% endfor -%}
135139
</p>
136140
{% endif %}
@@ -159,6 +163,8 @@ Context:
159163
{% endif %}
160164
{% endblock docstring %}
161165

166+
<backlinks identifier="{{ html_id }}" handler="python" />
167+
162168
{% block summary scoped %}
163169
{#- Summary block.
164170

src/mkdocstrings_handlers/python/templates/material/_base/docstring/other_parameters.html.jinja

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Context:
3636
<td><code>{{ parameter.name }}</code></td>
3737
<td>
3838
{% if parameter.annotation %}
39-
{% with expression = parameter.annotation %}
39+
{% with expression = parameter.annotation, backlink_type = "used-by" %}
4040
<code>{% include "expression"|get_template with context %}</code>
4141
{% endwith %}
4242
{% endif %}
@@ -60,7 +60,7 @@ Context:
6060
<li class="doc-section-item field-body">
6161
<b><code>{{ parameter.name }}</code></b>
6262
{% if parameter.annotation %}
63-
{% with expression = parameter.annotation %}
63+
{% with expression = parameter.annotation, backlink_type = "used-by" %}
6464
(<code>{% include "expression"|get_template with context %}</code>)
6565
{% endwith %}
6666
{% endif %}
@@ -94,7 +94,7 @@ Context:
9494
{% if parameter.annotation %}
9595
<span class="doc-param-annotation">
9696
<b>{{ lang.t("TYPE:") }}</b>
97-
{% with expression = parameter.annotation %}
97+
{% with expression = parameter.annotation, backlink_type = "used-by" %}
9898
<code>{% include "expression"|get_template with context %}</code>
9999
{% endwith %}
100100
</span>

src/mkdocstrings_handlers/python/templates/material/_base/docstring/parameters.html.jinja

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ Context:
5151
</td>
5252
<td>
5353
{% if parameter.annotation %}
54-
{% with expression = parameter.annotation %}
54+
{% with expression = parameter.annotation, backlink_type = "used-by" %}
5555
<code>{% include "expression"|get_template with context %}</code>
5656
{% endwith %}
5757
{% endif %}
@@ -63,7 +63,7 @@ Context:
6363
</td>
6464
<td>
6565
{% if parameter.default %}
66-
{% with expression = parameter.default %}
66+
{% with expression = parameter.default, backlink_type = "used-by" %}
6767
<code>{% include "expression"|get_template with context %}</code>
6868
{% endwith %}
6969
{% else %}
@@ -96,10 +96,10 @@ Context:
9696
<b><code>{{ parameter.name }}</code></b>
9797
{% endif %}
9898
{% if parameter.annotation %}
99-
{% with expression = parameter.annotation %}
99+
{% with expression = parameter.annotation, backlink_type = "used-by" %}
100100
(<code>{% include "expression"|get_template with context %}</code>
101101
{%- if parameter.default %}, {{ lang.t("default:") }}
102-
{% with expression = parameter.default %}
102+
{% with expression = parameter.default, backlink_type = "used-by" %}
103103
<code>{% include "expression"|get_template with context %}</code>
104104
{% endwith %}
105105
{% endif %})
@@ -149,15 +149,15 @@ Context:
149149
{% if parameter.annotation %}
150150
<span class="doc-param-annotation">
151151
<b>{{ lang.t("TYPE:") }}</b>
152-
{% with expression = parameter.annotation %}
152+
{% with expression = parameter.annotation, backlink_type = "used-by" %}
153153
<code>{% include "expression"|get_template with context %}</code>
154154
{% endwith %}
155155
</span>
156156
{% endif %}
157157
{% if parameter.default %}
158158
<span class="doc-param-default">
159159
<b>{{ lang.t("DEFAULT:") }}</b>
160-
{% with expression = parameter.default %}
160+
{% with expression = parameter.default, backlink_type = "used-by" %}
161161
<code>{% include "expression"|get_template with context %}</code>
162162
{% endwith %}
163163
</span>

src/mkdocstrings_handlers/python/templates/material/_base/docstring/raises.html.jinja

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Context:
3434
<tr class="doc-section-item">
3535
<td>
3636
{% if raises.annotation %}
37-
{% with expression = raises.annotation %}
37+
{% with expression = raises.annotation, backlink_type = "raised-by" %}
3838
<code>{% include "expression"|get_template with context %}</code>
3939
{% endwith %}
4040
{% endif %}
@@ -57,7 +57,7 @@ Context:
5757
{% for raises in section.value %}
5858
<li class="doc-section-item field-body">
5959
{% if raises.annotation %}
60-
{% with expression = raises.annotation %}
60+
{% with expression = raises.annotation, backlink_type = "raised-by" %}
6161
<code>{% include "expression"|get_template with context %}</code>
6262
{% endwith %}
6363
@@ -84,7 +84,7 @@ Context:
8484
<tr class="doc-section-item">
8585
<td>
8686
<span class="doc-raises-annotation">
87-
{% with expression = raises.annotation %}
87+
{% with expression = raises.annotation, backlink_type = "raised-by" %}
8888
<code>{% include "expression"|get_template with context %}</code>
8989
{% endwith %}
9090
</span>

0 commit comments

Comments
 (0)