Repository: mkdocstrings/python

Issue:
bug: Inventory config 'base_url' no longer works
### Description of the bug
When specifying an inventory with a `base_url` that is different from the `url`, the `base_url` is ignored after the `0.28.0` rework. The rendered cross-reference URL uses the `url` value, not the `base_url` value.

The issue is that the new `Inventory` dataclass in https://github.com/mkdocstrings/python/blob/main/src/mkdocstrings_handlers/python/_internal/config.py#L963-L992 has a `base` attribute, which is also returned in the `_config` property as `base`, and not `base_url`. However, the `load_inventory` method still expects a `base_url` keyword, but now gets `base=...` which is put into `**kwargs` and ignored.

### Environment information
```bash
python -m mkdocstrings_handlers.python._internal.debug  # | xclip -selection clipboard
```

- __System__: Linux-6.11.0-21-generic-x86_64-with-glibc2.39
- __Python__: cpython 3.12.3 (/home/stefan/salling/ml-utils-pipeline-new/.venv/bin/python)
- __Environment variables__:
- __Installed packages__:
  - `mkdocstrings-python` v1.16.8

### Additional context
I'll be happy to make a PR fixing this, but I could use some help in figuring out how to add test(s) 🙂

Hints from issue comments:
@pawamoy: Good catch @connesy, thanks for the report!

A PR would be great, thank you 🙏 I believe we just need to rename `base` to `base_url` then, in the `Inventory` dataclass, do you agree?

For tests, we could add them to the `test_handler.py` module. They would receive the `handler` fixture, onto which we would update `config.inventories` to add an `Inventory` with both URL and base URL (might need to `dataclasses.replace()` it if fields are frozen). Then we would create a `BytesIO` that mocks the bytes of a small inventory (one item), get the URL and config with `url, config = handler.get_inventory_urls()`, and call `handler.load_inventory(mocked_bytes, url, **config)`, finally asserting that this yields an URL based on the provided base URL.

To mock the inventory bytes, we can create a `mkdocstrings.Inventory`, `.register()` one item, and get the bytes with `.format_sphinx()`.@connesy: > I believe we just need to rename base to base_url then, in the Inventory dataclass, do you agree?

Yes, I do. I've already tested this, and it works just fine 👍 

I'll make a PR and try adding a test case 🙂 

Solution patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cf5d374..bdd5c26 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
 and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
 
 <!-- insertion marker -->
+## [1.16.9](https://github.com/mkdocstrings/python/releases/tag/1.16.9) - 2025-04-03
+
+<small>[Compare with 1.16.8](https://github.com/mkdocstrings/python/compare/1.16.8...1.16.9)</small>
+
+### Bug Fixes
+
+- Use `toc_label` option in a few missing places ([337b46b](https://github.com/mkdocstrings/python/commit/337b46be912ff69e70b398bb252c8217c917db0a) by Timothée Mazzucotelli). [Issue-267](https://github.com/mkdocstrings/python/discussions/267)
+
 ## [1.16.8](https://github.com/mkdocstrings/python/releases/tag/1.16.8) - 2025-03-24
 
 <small>[Compare with 1.16.7](https://github.com/mkdocstrings/python/compare/1.16.7...1.16.8)</small>
diff --git a/src/mkdocstrings_handlers/python/_internal/config.py b/src/mkdocstrings_handlers/python/_internal/config.py
index 11c77da..210f8fe 100644
--- a/src/mkdocstrings_handlers/python/_internal/config.py
+++ b/src/mkdocstrings_handlers/python/_internal/config.py
@@ -971,7 +971,7 @@ class Inventory:
         ),
     ]
 
-    base: Annotated[
+    base_url: Annotated[
         str | None,
         _Field(
             parent="inventories",
@@ -989,7 +989,7 @@ class Inventory:
 
     @property
     def _config(self) -> dict[str, Any]:
-        return {"base": self.base, "domains": self.domains}
+        return {"base_url": self.base_url, "domains": self.domains}
 
 
 # YORE: EOL 3.9: Replace `**_dataclass_options` with `frozen=True, kw_only=True` within line.
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 e69b9b8..7894c54 100644
--- a/src/mkdocstrings_handlers/python/templates/material/_base/attribute.html.jinja
+++ b/src/mkdocstrings_handlers/python/templates/material/_base/attribute.html.jinja
@@ -39,7 +39,7 @@ Context:
           role="data" if attribute.parent.kind.value == "module" else "attr",
           id=html_id,
           class="doc doc-heading",
-          toc_label=('<code class="doc-symbol doc-symbol-toc doc-symbol-attribute"></code>&nbsp;'|safe if config.show_symbol_type_toc else '') + attribute.name,
+          toc_label=('<code class="doc-symbol doc-symbol-toc doc-symbol-attribute"></code>&nbsp;'|safe if config.show_symbol_type_toc else '') + (config.toc_label if config.toc_label and root else attribute_name),
         ) %}
 
         {% block heading scoped %}
diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/class.html.jinja b/src/mkdocstrings_handlers/python/templates/material/_base/class.html.jinja
index e0aab3a..ae2f8de 100644
--- a/src/mkdocstrings_handlers/python/templates/material/_base/class.html.jinja
+++ b/src/mkdocstrings_handlers/python/templates/material/_base/class.html.jinja
@@ -38,7 +38,7 @@ Context:
           role="class",
           id=html_id,
           class="doc doc-heading",
-          toc_label=('<code class="doc-symbol doc-symbol-toc doc-symbol-class"></code>&nbsp;'|safe if config.show_symbol_type_toc else '') + class.name,
+          toc_label=('<code class="doc-symbol doc-symbol-toc doc-symbol-class"></code>&nbsp;'|safe if config.show_symbol_type_toc else '') + (config.toc_label if config.toc_label and root else class.name),
         ) %}
 
         {% block heading scoped %}
diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/function.html.jinja b/src/mkdocstrings_handlers/python/templates/material/_base/function.html.jinja
index 50ed584..e56bc7e 100644
--- a/src/mkdocstrings_handlers/python/templates/material/_base/function.html.jinja
+++ b/src/mkdocstrings_handlers/python/templates/material/_base/function.html.jinja
@@ -45,7 +45,7 @@ Context:
           role="function",
           id=html_id,
           class="doc doc-heading",
-          toc_label=(('<code class="doc-symbol doc-symbol-toc doc-symbol-' + symbol_type + '"></code>&nbsp;')|safe if config.show_symbol_type_toc else '') + function.name,
+          toc_label=(('<code class="doc-symbol doc-symbol-toc doc-symbol-' + symbol_type + '"></code>&nbsp;')|safe if config.show_symbol_type_toc else '') + (config.toc_label if config.toc_label and root else function.name),
         ) %}
 
         {% block heading scoped %}


Test patch:
diff --git a/tests/test_handler.py b/tests/test_handler.py
index 5940af5..f98ce54 100644
--- a/tests/test_handler.py
+++ b/tests/test_handler.py
@@ -4,11 +4,14 @@ from __future__ import annotations
 
 import os
 import sys
+from dataclasses import replace
 from glob import glob
+from io import BytesIO
 from pathlib import Path
 from textwrap import dedent
 from typing import TYPE_CHECKING
 
+import mkdocstrings
 import pytest
 from griffe import (
     Docstring,
@@ -20,7 +23,7 @@ from griffe import (
 )
 from mkdocstrings import CollectionError
 
-from mkdocstrings_handlers.python import PythonConfig, PythonHandler, PythonOptions
+from mkdocstrings_handlers.python import Inventory, PythonConfig, PythonHandler, PythonOptions
 
 if TYPE_CHECKING:
     from mkdocstrings import MkdocstringsPlugin
@@ -298,3 +301,33 @@ def test_inheriting_self_from_parent_class(handler: PythonHandler) -> None:
             module,
             handler.get_options({"inherited_members": True}),
         )
+
+
+def test_specifying_inventory_base_url(handler: PythonHandler) -> None:
+    """Assert that the handler renders inventory URLs using the specified base_url."""
+    # Update handler config to include an inventory with a base URL
+    base_url = "https://docs.com/my_library"
+    inventory = Inventory(url="https://example.com/objects.inv", base_url=base_url)
+    handler.config = replace(handler.config, inventories=[inventory])
+
+    # Mock inventory bytes
+    item_name = "my_library.my_module.MyClass"
+    mocked_inventory = mkdocstrings.Inventory()
+    mocked_inventory.register(
+        name=item_name,
+        domain="py",
+        role="class",
+        uri=f"api-reference/#{item_name}",
+        dispname=item_name,
+    )
+    mocked_bytes = BytesIO(mocked_inventory.format_sphinx())
+
+    # Get inventory URL and config
+    url, config = handler.get_inventory_urls()[0]
+
+    # Load the mocked inventory
+    _, item_url = next(handler.load_inventory(mocked_bytes, url, **config))
+
+    # Assert the URL is based on the provided base URL
+    msg = "Expected inventory URL to start with base_url"
+    assert item_url.startswith(base_url), msg