diff --git a/CHANGELOG.md b/CHANGELOG.md
index 826873b0..ed973133 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,22 @@ 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).
+## [0.29.1](https://github.com/mkdocstrings/mkdocstrings/releases/tag/0.29.1) - 2025-03-31
+
+[Compare with 0.29.0](https://github.com/mkdocstrings/mkdocstrings/compare/0.29.0...0.29.1)
+
+### Dependencies
+
+- Remove unused typing-extensions dependency ([ba98661](https://github.com/mkdocstrings/mkdocstrings/commit/ba98661b50e2cde19d8696d6c8ceecdbb49ce83f) by Timothée Mazzucotelli).
+
+### Bug Fixes
+
+- Ignore invalid inventory lines ([81caff5](https://github.com/mkdocstrings/mkdocstrings/commit/81caff5ff76f1a6606da9d2980e81ae9d2e02246) by Josh Mitchell). [PR-748](https://github.com/mkdocstrings/mkdocstrings/pull/748)
+
+### Code Refactoring
+
+- Rename loggers to "mkdocstrings" ([1a98040](https://github.com/mkdocstrings/mkdocstrings/commit/1a980402c39728ce265d8998b396c34bf76a113d) by Timothée Mazzucotelli).
+
## [0.29.0](https://github.com/mkdocstrings/mkdocstrings/releases/tag/0.29.0) - 2025-03-10
[Compare with 0.28.3](https://github.com/mkdocstrings/mkdocstrings/compare/0.28.3...0.29.0)
diff --git a/config/ruff.toml b/config/ruff.toml
index 655a158c..65416253 100644
--- a/config/ruff.toml
+++ b/config/ruff.toml
@@ -43,6 +43,7 @@ ignore = [
"PLR0913", # Too many arguments to function call
"PLR0915", # Too many statements
"SLF001", # Private member accessed
+ "S704", # Unsafe use of `markupsafe.Markup`
"TRY003", # Avoid specifying long messages outside the exception class
]
diff --git a/docs/usage/handlers.md b/docs/usage/handlers.md
index b9a01f68..0d375a95 100644
--- a/docs/usage/handlers.md
+++ b/docs/usage/handlers.md
@@ -4,12 +4,12 @@ A handler is what makes it possible to collect and render documentation for a pa
## Available handlers
-- [C](https://mkdocstrings.github.io/c/){ .external } [:octicons-heart-fill-24:{ .heart .pulse title="Sponsors only" }](../insiders/index.md)
+- [C](https://mkdocstrings.github.io/c/){ .external }
- [Crystal](https://mkdocstrings.github.io/crystal/){ .external }
- [Python](https://mkdocstrings.github.io/python/){ .external }
- [Python (Legacy)](https://mkdocstrings.github.io/python-legacy/){ .external }
- [Shell](https://mkdocstrings.github.io/shell/){ .external }
-- [TypeScript](https://mkdocstrings.github.io/typescript/){ .external } [:octicons-heart-fill-24:{ .heart .pulse title="Sponsors only" }](../insiders/index.md)
+- [TypeScript](https://mkdocstrings.github.io/typescript/){ .external }
- [VBA](https://pypi.org/project/mkdocstrings-vba/){ .external }
## About the Python handlers
diff --git a/pyproject.toml b/pyproject.toml
index c3087f61..29aff0d9 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -31,14 +31,15 @@ classifiers = [
"Typing :: Typed",
]
dependencies = [
+ # YORE: Bump 1: Replace `2.11.1` with `3.1` within line.
"Jinja2>=2.11.1",
"Markdown>=3.6",
"MarkupSafe>=1.1",
"mkdocs>=1.6",
"mkdocs-autorefs>=1.4",
"pymdown-extensions>=6.3",
+ # YORE: EOL 3.9: Remove line.
"importlib-metadata>=4.6; python_version < '3.10'",
- "typing-extensions>=4.1; python_version < '3.10'",
]
[project.optional-dependencies]
diff --git a/scripts/insiders.py b/scripts/insiders.py
index 6535a31e..4cd438d4 100644
--- a/scripts/insiders.py
+++ b/scripts/insiders.py
@@ -168,6 +168,6 @@ def load_json(url: str) -> str | list | dict:
ongoing_goals = [goal for goal in goals.values() if not goal.complete]
unreleased_features = sorted(
(ft for ft in feature_list(ongoing_goals) if ft.since),
- key=lambda ft: cast(date, ft.since),
+ key=lambda ft: cast("date", ft.since),
reverse=True,
)
diff --git a/src/mkdocstrings/_internal/download.py b/src/mkdocstrings/_internal/download.py
index 2beb053a..ffe25e6b 100644
--- a/src/mkdocstrings/_internal/download.py
+++ b/src/mkdocstrings/_internal/download.py
@@ -9,7 +9,7 @@
from mkdocstrings._internal.loggers import get_logger
-_logger = get_logger(__name__)
+_logger = get_logger("mkdocstrings")
# Regex pattern for an environment variable in the form ${ENV_VAR}.
_ENV_VAR_PATTERN = re.compile(r"\$\{([A-Za-z_][A-Za-z0-9_]*)\}")
diff --git a/src/mkdocstrings/_internal/extension.py b/src/mkdocstrings/_internal/extension.py
index 182fc563..83421ff8 100644
--- a/src/mkdocstrings/_internal/extension.py
+++ b/src/mkdocstrings/_internal/extension.py
@@ -44,7 +44,7 @@
from mkdocs_autorefs import AutorefsPlugin
-_logger = get_logger(__name__)
+_logger = get_logger("mkdocstrings")
class AutoDocProcessor(BlockProcessor):
diff --git a/src/mkdocstrings/_internal/handlers/base.py b/src/mkdocstrings/_internal/handlers/base.py
index 7784f007..3d5852c5 100644
--- a/src/mkdocstrings/_internal/handlers/base.py
+++ b/src/mkdocstrings/_internal/handlers/base.py
@@ -17,7 +17,6 @@
from jinja2 import Environment, FileSystemLoader
from markdown import Markdown
-from markdown.extensions.toc import TocTreeprocessor
from markupsafe import Markup
from mkdocs.utils.cache import download_and_cache_url
from mkdocs_autorefs import AutorefsInlineProcessor, BacklinksTreeProcessor
@@ -33,7 +32,7 @@
from mkdocstrings._internal.inventory import Inventory
from mkdocstrings._internal.loggers import get_logger, get_template_logger
-# TODO: remove once support for Python 3.9 is dropped
+# YORE: EOL 3.9: Replace block with line 4.
if sys.version_info < (3, 10):
from importlib_metadata import entry_points
else:
@@ -43,9 +42,10 @@
from collections.abc import Iterable, Iterator, Mapping, Sequence
from markdown import Extension
+ from markdown.extensions.toc import TocTreeprocessor
from mkdocs_autorefs import AutorefsHookInterface, Backlink
-_logger = get_logger(__name__)
+_logger = get_logger("mkdocstrings")
CollectorItem = Any
"""The type of the item returned by the `collect` method of a handler."""
@@ -493,7 +493,7 @@ def do_heading(
el = Element(f"h{heading_level}", attributes)
el.append(Element("mkdocstrings-placeholder"))
# Tell the inner 'toc' extension to make its additions if configured so.
- toc = cast(TocTreeprocessor, self.md.treeprocessors["toc"])
+ toc = cast("TocTreeprocessor", self.md.treeprocessors["toc"])
if toc.use_anchors:
toc.add_anchor(el, attributes["id"])
if toc.use_permalinks:
diff --git a/src/mkdocstrings/_internal/inventory.py b/src/mkdocstrings/_internal/inventory.py
index 471e3633..241bbb12 100644
--- a/src/mkdocstrings/_internal/inventory.py
+++ b/src/mkdocstrings/_internal/inventory.py
@@ -8,7 +8,7 @@
import re
import zlib
from textwrap import dedent
-from typing import TYPE_CHECKING, BinaryIO
+from typing import TYPE_CHECKING, BinaryIO, Literal, overload
if TYPE_CHECKING:
from collections.abc import Collection
@@ -66,11 +66,21 @@ def format_sphinx(self) -> str:
sphinx_item_regex = re.compile(r"^(.+?)\s+(\S+):(\S+)\s+(-?\d+)\s+(\S+)\s*(.*)$")
"""Regex to parse a Sphinx v2 inventory line."""
+ @overload
@classmethod
- def parse_sphinx(cls, line: str) -> InventoryItem:
+ def parse_sphinx(cls, line: str, *, return_none: Literal[False]) -> InventoryItem: ...
+
+ @overload
+ @classmethod
+ def parse_sphinx(cls, line: str, *, return_none: Literal[True]) -> InventoryItem | None: ...
+
+ @classmethod
+ def parse_sphinx(cls, line: str, *, return_none: bool = False) -> InventoryItem | None:
"""Parse a line from a Sphinx v2 inventory file and return an `InventoryItem` from it."""
match = cls.sphinx_item_regex.search(line)
if not match:
+ if return_none:
+ return None
raise ValueError(line)
name, domain, role, priority, uri, dispname = match.groups()
if uri.endswith("$"):
@@ -167,7 +177,9 @@ def parse_sphinx(cls, in_file: BinaryIO, *, domain_filter: Collection[str] = ())
for _ in range(4):
in_file.readline()
lines = zlib.decompress(in_file.read()).splitlines()
- items = [InventoryItem.parse_sphinx(line.decode("utf8")) for line in lines]
+ items: list[InventoryItem] = [
+ item for line in lines if (item := InventoryItem.parse_sphinx(line.decode("utf8"), return_none=True))
+ ]
if domain_filter:
items = [item for item in items if item.domain in domain_filter]
return cls(items)
diff --git a/src/mkdocstrings/_internal/loggers.py b/src/mkdocstrings/_internal/loggers.py
index d56d09c3..6c6304c3 100644
--- a/src/mkdocstrings/_internal/loggers.py
+++ b/src/mkdocstrings/_internal/loggers.py
@@ -7,9 +7,10 @@
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable
+# YORE: Bump 1: Replace block with line 2.
try:
from jinja2 import pass_context
-except ImportError: # TODO: remove once Jinja2 < 3.1 is dropped
+except ImportError:
from jinja2 import contextfunction as pass_context # type: ignore[attr-defined,no-redef]
if TYPE_CHECKING:
diff --git a/src/mkdocstrings/_internal/plugin.py b/src/mkdocstrings/_internal/plugin.py
index d7adf1c6..afc94490 100644
--- a/src/mkdocstrings/_internal/plugin.py
+++ b/src/mkdocstrings/_internal/plugin.py
@@ -35,7 +35,7 @@
from mkdocs.structure.files import Files
-_logger = get_logger(__name__)
+_logger = get_logger("mkdocstrings")
class PluginConfig(Config):
diff --git a/src/mkdocstrings/extension.py b/src/mkdocstrings/extension.py
index 15a84cc8..c7943652 100644
--- a/src/mkdocstrings/extension.py
+++ b/src/mkdocstrings/extension.py
@@ -1,5 +1,7 @@
"""Deprecated. Import from `mkdocstrings` directly."""
+# YORE: Bump 1: Remove file.
+
import warnings
from typing import Any
diff --git a/src/mkdocstrings/handlers/__init__.py b/src/mkdocstrings/handlers/__init__.py
index af032e98..b684324a 100644
--- a/src/mkdocstrings/handlers/__init__.py
+++ b/src/mkdocstrings/handlers/__init__.py
@@ -1 +1,3 @@
"""Deprecated. Import from `mkdocstrings` directly."""
+
+# YORE: Bump 1: Remove file.
diff --git a/src/mkdocstrings/handlers/base.py b/src/mkdocstrings/handlers/base.py
index 82ee3edb..c55a50ba 100644
--- a/src/mkdocstrings/handlers/base.py
+++ b/src/mkdocstrings/handlers/base.py
@@ -1,5 +1,7 @@
"""Deprecated. Import from `mkdocstrings` directly."""
+# YORE: Bump 1: Remove file.
+
import warnings
from typing import Any
diff --git a/src/mkdocstrings/handlers/rendering.py b/src/mkdocstrings/handlers/rendering.py
index 940f3a9c..f3f04eea 100644
--- a/src/mkdocstrings/handlers/rendering.py
+++ b/src/mkdocstrings/handlers/rendering.py
@@ -1,5 +1,7 @@
"""Deprecated. Import from `mkdocstrings` directly."""
+# YORE: Bump 1: Remove file.
+
import warnings
from typing import Any
diff --git a/src/mkdocstrings/inventory.py b/src/mkdocstrings/inventory.py
index b5c8adea..7192acff 100644
--- a/src/mkdocstrings/inventory.py
+++ b/src/mkdocstrings/inventory.py
@@ -1,5 +1,7 @@
"""Deprecated. Import from `mkdocstrings` directly."""
+# YORE: Bump 1: Remove file.
+
import warnings
from typing import Any
diff --git a/src/mkdocstrings/loggers.py b/src/mkdocstrings/loggers.py
index ce805362..25545ca5 100644
--- a/src/mkdocstrings/loggers.py
+++ b/src/mkdocstrings/loggers.py
@@ -1,5 +1,7 @@
"""Deprecated. Import from `mkdocstrings` directly."""
+# YORE: Bump 1: Remove file.
+
import warnings
from typing import Any
diff --git a/src/mkdocstrings/plugin.py b/src/mkdocstrings/plugin.py
index b4edf945..dbb6abf9 100644
--- a/src/mkdocstrings/plugin.py
+++ b/src/mkdocstrings/plugin.py
@@ -1,5 +1,7 @@
"""Deprecated. Import from `mkdocstrings` directly."""
+# YORE: Bump 1: Remove file.
+
import warnings
from typing import Any
diff --git a/tests/test_extension.py b/tests/test_extension.py
index b7c1c742..dd3d7028 100644
--- a/tests/test_extension.py
+++ b/tests/test_extension.py
@@ -3,7 +3,6 @@
from __future__ import annotations
import re
-import sys
from textwrap import dedent
from typing import TYPE_CHECKING
@@ -60,7 +59,6 @@ def test_reference_inside_autodoc(ext_markdown: Markdown) -> None:
assert re.search(r"Link to <.*something\.Else.*>something\.Else<.*>\.", output)
-@pytest.mark.skipif(sys.version_info < (3, 8), reason="typing.Literal requires Python 3.8")
def test_quote_inside_annotation(ext_markdown: Markdown) -> None:
"""Assert that inline highlighting doesn't double-escape HTML."""
output = ext_markdown.convert("::: tests.fixtures.string_annotation.Foo")
diff --git a/tests/test_inventory.py b/tests/test_inventory.py
index eb008661..ab61e599 100644
--- a/tests/test_inventory.py
+++ b/tests/test_inventory.py
@@ -2,7 +2,6 @@
from __future__ import annotations
-import sys
from io import BytesIO
from os.path import join
@@ -12,8 +11,6 @@
from mkdocstrings import Inventory, InventoryItem
-sphinx = pytest.importorskip("sphinx.util.inventory", reason="Sphinx is not installed")
-
@pytest.mark.parametrize(
"our_inv",
@@ -22,10 +19,13 @@
Inventory([InventoryItem(name="object_path", domain="py", role="obj", uri="page_url")]),
Inventory([InventoryItem(name="object_path", domain="py", role="obj", uri="page_url#object_path")]),
Inventory([InventoryItem(name="object_path", domain="py", role="obj", uri="page_url#other_anchor")]),
+ Inventory([InventoryItem(name="o", domain="py", role="obj", uri="u#o", dispname="first line\nsecond line")]),
],
)
def test_sphinx_load_inventory_file(our_inv: Inventory) -> None:
"""Perform the 'live' inventory load test."""
+ sphinx = pytest.importorskip("sphinx.util.inventory", reason="Sphinx is not installed")
+
buffer = BytesIO(our_inv.format_sphinx())
sphinx_inv = sphinx.InventoryFile.load(buffer, "", join)
@@ -36,9 +36,10 @@ def test_sphinx_load_inventory_file(our_inv: Inventory) -> None:
assert item.name in sphinx_inv[f"{item.domain}:{item.role}"]
-@pytest.mark.skipif(sys.version_info < (3, 7), reason="using plugins that require Python 3.7")
def test_sphinx_load_mkdocstrings_inventory_file() -> None:
"""Perform the 'live' inventory load test on mkdocstrings own inventory."""
+ sphinx = pytest.importorskip("sphinx.util.inventory", reason="Sphinx is not installed")
+
mkdocs_config = load_config()
mkdocs_config["plugins"].run_event("startup", command="build", dirty=False)
try:
@@ -55,3 +56,29 @@ def test_sphinx_load_mkdocstrings_inventory_file() -> None:
for item in own_inv.values():
assert item.name in sphinx_inv[f"{item.domain}:{item.role}"]
+
+
+@pytest.mark.parametrize(
+ "our_inv",
+ [
+ Inventory(),
+ Inventory([InventoryItem(name="object_path", domain="py", role="obj", uri="page_url")]),
+ Inventory([InventoryItem(name="object_path", domain="py", role="obj", uri="page_url#object_path")]),
+ Inventory([InventoryItem(name="object_path", domain="py", role="obj", uri="page_url#other_anchor")]),
+ Inventory([InventoryItem(name="o", domain="py", role="obj", uri="u#o", dispname="first line\nsecond line")]),
+ ],
+)
+def test_mkdocstrings_roundtrip_inventory_file(our_inv: Inventory) -> None:
+ """Save some inventory files, then load them in again."""
+ buffer = BytesIO(our_inv.format_sphinx())
+ round_tripped = Inventory.parse_sphinx(buffer)
+
+ assert our_inv.keys() == round_tripped.keys()
+ for key, value in our_inv.items():
+ round_tripped_item = round_tripped[key]
+ assert round_tripped_item.name == value.name
+ assert round_tripped_item.domain == value.domain
+ assert round_tripped_item.role == value.role
+ assert round_tripped_item.uri == value.uri
+ assert round_tripped_item.priority == value.priority
+ assert round_tripped_item.dispname == value.dispname.splitlines()[0]