8000 refactor: Prepare backlinks support · jhominal/mkdocstrings-python@56bf627 · GitHub
[go: up one dir, main page]

Skip to content

Commit 56bf627

Browse files
authored
refactor: Prepare backlinks support
Issue-153: mkdocstrings#153 PR-252: mkdocstrings#252
1 parent a0e888c commit 56bf627

File tree

62 files changed

+342
-178
lines changed
  • readthedocs/_base/docstring
  • tests
  • Some content is hidden

    Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

    62 files changed

    +342
    -178
    lines changed

    docs/css/mkdocstrings.css

    Lines changed: 45 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -25,3 +25,48 @@ a.external:hover::after,
    2525
    a.autorefs-external:hover::after {
    2626
    background-color: var(--md-accent-fg-color);
    2727
    }
    28+
    29+
    /* Tree-like output for backlinks. */
    30+
    .doc-backlink-list {
    31+
    --tree-clr: var(--md-default-fg-color);
    32+
    --tree-font-size: 1rem;
    33+
    --tree-item-height: 1;
    34+
    --tree-offset: 1rem;
    35+
    --tree-thickness: 1px;
    36+
    --tree-style: solid;
    37+
    display: grid;
    38+
    list-style: none !important;
    39+
    }
    40+
    41+
    .doc-backlink-list li > span:first-child {
    42+
    text-indent: .3rem;
    43+
    }
    44+
    .doc-backlink-list li {
    45+
    padding-inline-start: var(--tree-offset);
    46+
    border-left: var(--tree-thickness) var(--tree-style) var(--tree-clr);
    47+
    position: relative;
    48+
    margin-left: 0 !important;
    49+
    50+
    &:last-child {
    51+
    border-color: transparent;
    52+
    }
    53+
    &::before{
    54+
    content: '';
    55+
    position: absolute;
    56+
    top: calc(var(--tree-item-height) / 2 * -1 * var(--tree-font-size) + var(--tree-thickness));
    57+
    left: calc(var(--tree-thickness) * -1);
    58+
    width: calc(var(--tree-offset) + var(--tree-thickness) * 2);
    59+
    height: calc(var(--tree-item-height) * var(--tree-font-size));
    60+
    border-left: var(--tree-thickness) var(--tree-style) var(--tree-clr);
    61+
    border-bottom: var(--tree-thickness) var(--tree-style) var(--tree-clr);
    62+
    }
    63+
    &::after{
    64+
    content: '';
    65+
    position: absolute;
    66+
    border-radius: 50%;
    67+
    background-color: var(--tree-clr);
    68+
    top: calc(var(--tree-item-height) / 2 * 1rem);
    69+
    left: var(--tree-offset) ;
    70+
    translate: calc(var(--tree-thickness) * -1) calc(var(--tree-thickness) * -1);
    71+
    }
    72+
    }

    docs/insiders/changelog.md

    Lines changed: 4 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -2,6 +2,10 @@
    22

    33
    ## mkdocstrings-python Insiders
    44

    5+
    ### 1.10.0 <small>March 10, 2025</small> { id="1.10.0" }
    6+
    7+
    - [Backlinks][backlinks]
    8+
    59
    ### 1.9.0 <small>September 03, 2024</small> { id="1.9.0" }
    610

    711
    - [Relative cross-references][relative_crossrefs]

    docs/insiders/goals.yml

    Lines changed: 3 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -42,3 +42,6 @@ goals:
    4242
    - name: Scoped cross-references
    4343
    ref: /usage/configuration/docstrings/#scoped_crossrefs
    4444
    since: 2024/09/03
    45+
    - name: Backlinks
    46+
    ref: /usage/configuration/general/#backlinks
    47+
    since: 2025/03/10

    docs/usage/configuration/general.md

    Lines changed: 32 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -60,6 +60,38 @@ plugins:
    6060
    ////
    6161
    ///
    6262
    63+
    [](){#option-backlinks}
    64+
    ## `backlinks`
    65+
    66+
    [:octicons-heart-fill-24:{ .pulse } Sponsors only](../../insiders/index.md){ .insiders } &mdash;
    67+
    [:octicons-tag-24: Insiders 1.10.0](../../insiders/changelog.md#1.10.0)
    68+
    69+
    - **:octicons-package-24: Type <code><autoref identifier="typing.Literal" optional>Literal</autoref>["flat", "tree", False]</code> :material-equal: `False`{ title="default value" }**
    70+
    71+
    The `backlinks` option enables rendering of backlinks within your API documentation.
    72+
    73+
    When an arbitrary section of your documentation links to an API symbol, this link will be collected as a backlink, and rendered below your API symbol. In short, the API symbol will link back to the section that links to it. Such backlinks will help your users navigate the documentation, as they will immediately which functions return a specific symbol, or where a specific symbol is accepted as parameter, etc..
    74+
    75+
    Each backlink is a list of breadcrumbs that represent the navigation, from the root page down to the given section.
    76+
    77+
    The available styles for rendering backlinks are **`flat`** and **`tree`**.
    78+
    79+
    - **`flat`** will render backlinks as a single-layer list. This can lead to repetition of breadcrumbs.
    80+
    - **`tree`** will combine backlinks into a tree, to remove repetition of breadcrumbs.
    81+
    82+
    WARNING: **Global-only option.** For now, the option only works when set globally in `mkdocs.yml`.
    83+
    84+
    ```yaml title="in mkdocs.yml (global configuration)"
    85+
    plugins:
    86+
    - mkdocstrings:
    87+
    handlers:
    88+
    python:
    89+
    options:
    90+
    backlinks: tree
    91+
    ```
    92+
    93+
    <!-- TODO: Add screenshots! -->
    94+
    6395
    [](){#option-extensions}
    6496
    ## `extensions`
    6597

    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: tree
    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: 53 additions & 5 deletions
    Original file line numberDiff line numberDiff line change
    @@ -8,11 +8,12 @@
    88
    import subprocess
    99
    import sys
    1010
    import warnings
    11+
    from collections import defaultdict
    1112
    from dataclasses import replace
    1213
    from functools import lru_cache
    1314
    from pathlib import Path
    1415
    from re import Match, Pattern
    15-
    from typing import TYPE_CHECKING, Any, Callable, ClassVar, Literal
    16+
    from typing import TYPE_CHECKING, Any, Callable, ClassVar, Literal, TypeVar
    1617

    1718
    from griffe import (
    1819
    Alias,
    @@ -28,11 +29,11 @@
    2829
    )
    2930
    from jinja2 import TemplateNotFound, pass_context, pass_environment
    3031
    from markupsafe import Markup
    31-
    from mkdocs_autorefs import AutorefsHookInterface
    32+
    from mkdocs_autorefs import AutorefsHookInterface, Backlink, BacklinkCrumb
    3233
    from mkdocstrings import get_logger
    3334

    3435
    if TYPE_CHECKING:
    35-
    from collections.abc import Iterator, Sequence
    36+
    from collections.abc import Iterable, Iterator, Sequence
    3637

    3738
    from griffe import Attribute, Class, Function, Module
    3839
    from jinja2 import Environment, Template
    @@ -210,10 +211,15 @@ def do_format_attribute(
    210211

    211212
    signature = str(attribute_path).strip()
    212213
    if annotations and attribute.annotation:
    213-
    annotation = template.render(context.parent, expression=attribute.annotation, signature=True)
    214+
    annotation = template.render(
    215+
    context.parent,
    216+
    expression=attribute.annotation,
    217+
    signature=True,
    218+
    backlink_type="returned-by",
    219+
    )
    214220
    signature += f": {annotation}"
    215221
    if attribute.value:
    216-
    value = template.render(context.parent, expression=attribute.value, signature=True)
    222+
    value = template.render(context.parent, expression=attribute.value, signature=True, backlink_type="used-by")
    217223
    signature += f" = {value}"
    218224

    219225
    signature = do_format_code(signature, line_length)
    @@ -725,3 +731,45 @@ def get_context(self) -> AutorefsHookInterface.Context:
    725731
    filepath=str(filepath),
    726732
    lineno=lineno,
    727733
    )
    734+
    735+
    736+
    T = TypeVar("T")
    737+
    Tree = dict[T, "Tree"]
    738+
    CompactTree = dict[tuple[T, ...], "CompactTree"]
    739+
    _rtree = lambda: defaultdict(_rtree) # type: ignore[has-type,var-annotated] # noqa: E731
    740+
    741+
    742+
    def _tree(data: Iterable[tuple[T, ...]]) -> Tree:
    743+
    new_tree = _rtree()
    744+
    for nav in data:
    745+
    *path, leaf = nav
    746+
    node = new_tree
    747+
    for key in path:
    748+
    node = node[key]
    749+
    node[leaf] = _rtree()
    750+
    return new_tree
    751+
    752+
    753+
    def _compact_tree(tree: Tree) -> CompactTree:
    754+
    new_tree = _rtree()
    755+
    for key, value in tree.items():
    756+
    child = _compact_tree(value)
    757+
    if len(child) == 1:
    758+
    child_key, child_value = next(iter(child.items()))
    759+
    new_key = (key, *child_key)
    760+
    new_tree[new_key] = child_value
    761+
    else:
    762+
    new_tree[(key,)] = child
    763+
    return new_tree
    764+
    765+
    766+
    def do_backlink_tree(backlinks: list[Backlink]) -> CompactTree[BacklinkCrumb]:
    767+
    """Build a tree of backlinks.
    768+
    769+
    Parameters:
    770+
    backlinks: The list of backlinks.
    771+
    772+
    Returns:
    773+
    A tree of backlinks.
    774+
    """
    775+
    return _compact_tree(_tree(backlink.crumbs for backlink in backlinks))

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

    Lines changed: 4 C2EE additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -113,6 +113,10 @@ Context:
    113113
    {% include "docstring"|get_template with context %}
    114114
    {% endwith %}
    115115
    {% endblock docstring %}
    116+
    117+
    {% if config.backlinks %}
    118+
    <backlinks identifier="{{ html_id }}" handler="python" />
    119+
    {% endif %}
    116120
    {% endblock contents %}
    117121
    </div>
    118122

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

    0 commit comments

    306F
    Comments
     (0)
    0