Skip to content

Commit 5035e92

Browse files
waylanpawamoy
andauthored
fix: Make extension paths relative to config file
PR #112: #112 Co-authored-by: Timothée Mazzucotelli <[email protected]>
1 parent f747ecb commit 5035e92

File tree

2 files changed

+72
-2
lines changed

2 files changed

+72
-2
lines changed

src/mkdocstrings_handlers/python/handler.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import sys
1010
from collections import ChainMap
1111
from contextlib import suppress
12-
from typing import TYPE_CHECKING, Any, BinaryIO, ClassVar, Iterator, Mapping
12+
from typing import TYPE_CHECKING, Any, BinaryIO, ClassVar, Iterator, Mapping, Sequence
1313

1414
from griffe.collections import LinesCollection, ModulesCollection
1515
from griffe.docstrings.parsers import Parser
@@ -265,8 +265,9 @@ def collect(self, identifier: str, config: Mapping[str, Any]) -> CollectorItem:
265265
parser = parser_name and Parser(parser_name)
266266

267267
if unknown_module:
268+
extensions = self.normalize_extension_paths(final_config.get("extensions", []))
268269
loader = GriffeLoader(
269-
extensions=load_extensions(final_config.get("extensions", [])),
270+
extensions=load_extensions(extensions),
270271
search_paths=self._paths,
271272
docstring_parser=parser,
272273
docstring_options=parser_options,
@@ -369,6 +370,35 @@ def get_anchors(self, data: CollectorItem) -> tuple[str, ...]: # noqa: D102 (ig
369370
return tuple(anchors)
370371
return tuple(anchors)
371372

373+
def normalize_extension_paths(self, extensions: Sequence) -> Sequence:
374+
"""Resolve extension paths relative to config file."""
375+
if self._config_file_path is None:
376+
return extensions
377+
378+
base_path = os.path.dirname(self._config_file_path)
379+
normalized = []
380+
381+
for ext in extensions:
382+
if isinstance(ext, dict):
383+
pth, options = next(iter(ext.items()))
384+
pth = str(pth)
385+
else:
386+
pth = str(ext)
387+
options = None
388+
389+
if pth.endswith(".py") or ".py:" in pth or "/" in pth or "\\" in pth: # noqa: SIM102
390+
# This is a sytem path. Normalize it.
391+
if not os.path.isabs(pth):
392+
# Make path absolute relative to config file path.
393+
pth = os.path.normpath(os.path.join(base_path, pth))
394+
395+
if options is not None:
396+
normalized.append({pth: options})
397+
else:
398+
normalized.append(pth)
399+
400+
return normalized
401+
372402

373403
def get_handler(
374404
*,

tests/test_handler.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,43 @@ def test_expand_globs_without_changing_directory() -> None:
105105
)
106106
for path in list(glob(os.path.abspath(".") + "/*.md")):
107107
assert path in handler._paths
108+
109+
110+
@pytest.mark.parametrize(
111+
("expect_change", "extension"),
112+
[
113+
(True, "extension.py"),
114+
(True, "extension.py:SomeExtension"),
115+
(True, "path/to/extension.py"),
116+
(True, "path/to/extension.py:SomeExtension"),
117+
(True, {"extension.py": {"option": "value"}}),
118+
(True, {"extension.py:SomeExtension": {"option": "value"}}),
119+
(True, {"path/to/extension.py": {"option": "value"}}),
120+
(True, {"path/to/extension.py:SomeExtension": {"option": "value"}}),
121+
(False, "/absolute/path/to/extension.py"),
122+
(False, "/absolute/path/to/extension.py:SomeExtension"),
123+
(False, {"/absolute/path/to/extension.py": {"option": "value"}}),
124+
(False, {"/absolute/path/to/extension.py:SomeExtension": {"option": "value"}}),
125+
(False, "dot.notation.path.to.extension"),
126+
(False, "dot.notation.path.to.pyextension"),
127+
(False, {"dot.notation.path.to.extension": {"option": "value"}}),
128+
(False, {"dot.notation.path.to.pyextension": {"option": "value"}}),
129+
],
130+
)
131+
def test_extension_paths(tmp_path: Path, expect_change: bool, extension: str | dict) -> None:
132+
"""Assert extension paths are resolved relative to config file."""
133+
handler = get_handler(
134+
theme="material",
135+
config_file_path=str(tmp_path.joinpath("mkdocs.yml")),
136+
)
137+
normalized = handler.normalize_extension_paths([extension])[0]
138+
if expect_change:
139+
if isinstance(normalized, str) and isinstance(extension, str):
140+
assert normalized == str(tmp_path.joinpath(extension))
141+
elif isinstance(normalized, dict) and isinstance(extension, dict):
142+
pth, options = next(iter(extension.items()))
143+
assert normalized == {str(tmp_path.joinpath(pth)): options}
144+
else:
145+
raise ValueError("Normalization must not change extension items type")
146+
else:
147+
assert normalized == extension

0 commit comments

Comments
 (0)