Skip to content

Commit a2317df

Browse files
committed
build: Depend on mkdocstrings 0.28.3
1 parent 7df38fc commit a2317df

File tree

6 files changed

+89
-90
lines changed

6 files changed

+89
-90
lines changed

pyproject.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ classifiers = [
3030
"Typing :: Typed",
3131
]
3232
dependencies = [
33-
"mkdocstrings>=0.19",
33+
"mkdocstrings>=0.28.3",
3434
"mkdocs-autorefs>=1.1",
3535
"pytkdocs>=0.14",
3636
]
@@ -81,6 +81,7 @@ maintain = [
8181
]
8282
ci = [
8383
"duty>=1.6",
84+
"griffe>=1.6",
8485
"ruff>=0.4",
8586
"pytest>=8.2",
8687
"pytest-cov>=5.0",
@@ -95,9 +96,7 @@ ci = [
9596
"markdown-exec>=1.8",
9697
"mkdocs>=1.6",
9798
"mkdocs-coverage>=1.0",
98-
"mkdocs-gen-files>=0.5",
9999
"mkdocs-git-revision-date-localized-plugin>=1.2",
100-
"mkdocs-literate-nav>=0.6",
101100
"mkdocs-llmstxt>=0.1",
102101
"mkdocs-material>=9.5",
103102
"mkdocs-minify-plugin>=0.8",

src/mkdocstrings_handlers/python/handler.py

Lines changed: 73 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,15 @@
88
import posixpath
99
import sys
1010
import traceback
11-
from collections import ChainMap
1211
from collections.abc import Iterator, Mapping, MutableMapping
12+
from copy import deepcopy
1313
from pathlib import Path
1414
from subprocess import PIPE, Popen
1515
from typing import Any, BinaryIO, ClassVar, Optional
1616

17-
from markdown import Markdown
18-
from mkdocstrings.extension import PluginError
19-
from mkdocstrings.handlers.base import BaseHandler, CollectionError, CollectorItem
20-
from mkdocstrings.inventory import Inventory
21-
from mkdocstrings.loggers import get_logger
17+
from mkdocs.config.defaults import MkDocsConfig
18+
from mkdocs.exceptions import PluginError
19+
from mkdocstrings import BaseHandler, CollectionError, CollectorItem, Inventory, get_logger
2220

2321
from mkdocstrings_handlers.python.rendering import (
2422
do_brief_xref,
@@ -34,22 +32,21 @@
3432

3533

3634
class PythonHandler(BaseHandler):
37-
"""The Python handler class.
35+
"""The Python handler class."""
3836

39-
Attributes:
40-
domain: The cross-documentation domain/language for this handler.
41-
enable_inventory: Whether this handler is interested in enabling the creation
42-
of the `objects.inv` Sphinx inventory file.
43-
"""
44-
45-
domain: str = "py" # to match Sphinx's default domain
46-
enable_inventory: bool = True
37+
name: ClassVar[str] = "python"
38+
"""The handler name."""
39+
domain: ClassVar[str] = "py" # to match Sphinx's default domain
40+
"""The domain of the handler."""
41+
enable_inventory: ClassVar[bool] = True
42+
"""Whether the handler supports inventory files."""
4743

48-
fallback_theme = "material"
44+
fallback_theme: ClassVar[str] = "material"
45+
"""The fallback theme to use when the user-selected theme is not supported."""
4946
fallback_config: ClassVar[dict] = {"docstring_style": "markdown", "filters": ["!.*"]}
5047
"""The configuration used when falling back to re-collecting an object to get its anchor.
5148
52-
This configuration is used in [`Handlers.get_anchors`][mkdocstrings.handlers.base.Handlers.get_anchors].
49+
This configuration is used in [`Handlers.get_anchors`][mkdocstrings.Handlers.get_anchors].
5350
5451
When trying to fix (optional) cross-references, the autorefs plugin will try to collect
5552
an object with every configured handler until one succeeds. It will then try to get
@@ -118,14 +115,7 @@ class PythonHandler(BaseHandler):
118115
- `show_source` (`bool`): Show the source code of this object. Default: `True`.
119116
"""
120117

121-
def __init__(
122-
self,
123-
*args: Any,
124-
setup_commands: Optional[List[str]] = None,
125-
config_file_path: Optional[str] = None,
126-
paths: Optional[List[str]] = None,
127-
**kwargs: Any,
128-
) -> None:
118+
def __init__(self, config: dict[str, Any], base_dir: Path, **kwargs: Any) -> None:
129119
"""Initialize the handler.
130120
131121
When instantiating a Python handler, we open a `pytkdocs` subprocess in the background with `subprocess.Popen`.
@@ -134,24 +124,27 @@ def __init__(
134124
too resource intensive, and would slow down `mkdocstrings` a lot.
135125
136126
Parameters:
137-
*args: Handler name, theme and custom templates.
138-
setup_commands: A list of python commands as strings to be executed in the subprocess before `pytkdocs`.
139-
config_file_path: The MkDocs configuration file path.
140-
paths: A list of paths to use as search paths.
141-
**kwargs: Same thing, but with keyword arguments.
127+
config: The handler configuration.
128+
base_dir: The base directory of the project.
129+
**kwargs: Arguments passed to the parent constructor.
142130
"""
131+
super().__init__(**kwargs)
132+
133+
self.base_dir = base_dir
134+
self.config = config
135+
self.global_options = config.get("options", {})
136+
143137
logger.debug("Opening 'pytkdocs' subprocess")
144138
env = os.environ.copy()
145139
env["PYTHONUNBUFFERED"] = "1"
146140

147-
self._config_file_path = config_file_path
148-
paths = paths or []
149-
if not paths and config_file_path:
150-
paths.append(os.path.dirname(config_file_path))
141+
paths = config.get("paths") or []
142+
if not paths and self.base_dir:
143+
paths.append(self.base_dir)
151144
search_paths = []
152145
for path in paths:
153-
if not os.path.isabs(path) and config_file_path:
154-
path = os.path.abspath(os.path.join(os.path.dirname(config_file_path), path)) # noqa: PLW2901
146+
if not os.path.isabs(path) and self.base_dir:
147+
path = os.path.abspath(os.path.join(self.base_dir, path)) # noqa: PLW2901
155148
if path not in search_paths:
156149
search_paths.append(path)
157150
self._paths = search_paths
@@ -161,7 +154,7 @@ def __init__(
161154
if search_paths:
162155
commands.extend([f"sys.path.insert(0, {path!r})" for path in reversed(search_paths)])
163156

164-
if setup_commands:
157+
if setup_commands := config.get("setup_commands"):
165158
# prevent the Python interpreter or the setup commands
166159
# from writing to stdout as it would break pytkdocs output
167160
commands.extend(
@@ -193,7 +186,13 @@ def __init__(
193186
bufsize=-1,
194187
env=env,
195188
)
196-
super().__init__(*args, **kwargs)
189+
190+
def get_inventory_urls(self) -> list[tuple[str, dict[str, Any]]]:
191+
"""Return the URLs of the inventory files to download."""
192+
return [
193+
(inv.pop("url"), inv) if isinstance(inv, dict) else (inv, {})
194+
for inv in deepcopy(self.config.get("import", []))
195+
]
197196

198197
@classmethod
199198
def load_inventory(
@@ -222,7 +221,20 @@ def load_inventory(
222221
for item in Inventory.parse_sphinx(in_file, domain_filter=("py",)).values():
223222
yield item.name, posixpath.join(base_url, item.uri)
224223

225-
def collect(self, identifier: str, config: MutableMapping[str, Any]) -> CollectorItem:
224+
def get_options(self, local_options: Mapping[str, Any]) -> MutableMapping[str, Any]:
225+
"""Return the options to use to collect an object.
226+
227+
We merge the global options with the options specific to the object being collected.
228+
229+
Arguments:
230+
local_options: The selection options.
231+
232+
Returns:
233+
The options to use to collect an object.
234+
"""
235+
return {**self.default_config, **self.global_options, **local_options}
236+
237+
def collect(self, identifier: str, options: MutableMapping[str, Any]) -> CollectorItem:
226238
"""Collect the documentation tree given an identifier and selection options.
227239
228240
In this method, we feed one line of JSON to the standard input of the subprocess that was opened
@@ -244,23 +256,21 @@ def collect(self, identifier: str, config: MutableMapping[str, Any]) -> Collecto
244256
245257
Arguments:
246258
identifier: The dotted-path of a Python object available in the Python path.
247-
config: Selection options, used to alter the data collection done by `pytkdocs`.
259+
options: Selection options, used to alter the data collection done by `pytkdocs`.
248260
249261
Raises:
250262
CollectionError: When there was a problem collecting the object documentation.
251263
252264
Returns:
253265
The collected object-tree.
254266
"""
255-
final_config = {}
267+
pytkdocs_options = {}
256268
for option in ("filters", "members", "docstring_style", "docstring_options"):
257-
if option in config:
258-
final_config[option] = config[option]
259-
elif option in self.default_config:
260-
final_config[option] = self.default_config[option]
269+
if option in options:
270+
pytkdocs_options[option] = options[option]
261271

262272
logger.debug("Preparing input")
263-
json_input = json.dumps({"objects": [{"path": identifier, **final_config}]})
273+
json_input = json.dumps({"objects": [{"path": identifier, **pytkdocs_options}]})
264274

265275
logger.debug("Writing to process' stdin")
266276
self.process.stdin.write(json_input + "\n") # type: ignore[union-attr]
@@ -302,17 +312,16 @@ def teardown(self) -> None:
302312
logger.debug("Tearing process down")
303313
self.process.terminate()
304314

305-
def render(self, data: CollectorItem, config: Mapping[str, Any]) -> str: # noqa: D102 (ignore missing docstring)
306-
final_config = ChainMap(config, self.default_config) # type: ignore[arg-type]
307-
315+
def render(self, data: CollectorItem, options: MutableMapping[str, Any]) -> str:
316+
"""Render the collected data into HTML."""
308317
template = self.env.get_template(f"{data['category']}.html")
309318

310319
# Heading level is a "state" variable, that will change at each step
311320
# of the rendering recursion. Therefore, it's easier to use it as a plain value
312321
# than as an item in a dictionary.
313-
heading_level = final_config["heading_level"]
314-
members_order = final_config["members_order"]
322+
heading_level = options["heading_level"]
315323

324+
members_order = options["members_order"]
316325
if members_order == "alphabetical":
317326
sort_function = sort_key_alphabetical
318327
elif members_order == "source":
@@ -323,49 +332,37 @@ def render(self, data: CollectorItem, config: Mapping[str, Any]) -> str: # noqa
323332
sort_object(data, sort_function=sort_function)
324333

325334
return template.render(
326-
**{"config": final_config, data["category"]: data, "heading_level": heading_level, "root": True},
335+
**{"config": options, data["category"]: data, "heading_level": heading_level, "root": True},
327336
)
328337

329-
def get_anchors(self, data: CollectorItem) -> tuple[str, ...]: # noqa: D102 (ignore missing docstring)
338+
def get_aliases(self, identifier: str) -> tuple[str, ...]:
339+
"""Return the aliases of an identifier."""
330340
try:
341+
data = self.collect(identifier, self.fallback_config)
331342
return (data["path"],)
332-
except KeyError:
343+
except (CollectionError, KeyError):
333344
return ()
334345

335-
def update_env(self, md: Markdown, config: dict) -> None: # noqa: D102 (ignore missing docstring)
336-
super().update_env(md, config)
346+
def update_env(self, config: dict) -> None: # noqa: ARG002,D102
337347
self.env.trim_blocks = True
338348
self.env.lstrip_blocks = True
339349
self.env.keep_trailing_newline = False
340350
self.env.filters["brief_xref"] = do_brief_xref
341351

342352

343353
def get_handler(
344-
theme: str,
345-
custom_templates: Optional[str] = None,
346-
setup_commands: Optional[List[str]] = None,
347-
config_file_path: Optional[str] = None,
348-
paths: Optional[List[str]] = None,
349-
**config: Any, # noqa: ARG001
354+
handler_config: MutableMapping[str, Any],
355+
tool_config: MkDocsConfig,
356+
**kwargs: Any,
350357
) -> PythonHandler:
351358
"""Simply return an instance of `PythonHandler`.
352359
353360
Arguments:
354-
theme: The theme to use when rendering contents.
355-
custom_templates: Directory containing custom templates.
356-
setup_commands: A list of commands as strings to be executed in the subprocess before `pytkdocs`.
357-
config_file_path: The MkDocs configuration file path.
358-
paths: A list of paths to use as search paths.
359-
config: Configuration passed to the handler.
361+
handler_config: The handler configuration.
362+
tool_config: The tool (SSG) configuration.
360363
361364
Returns:
362365
An instance of `PythonHandler`.
363366
"""
364-
return PythonHandler(
365-
handler="python",
366-
theme=theme,
367-
custom_templates=custom_templates,
368-
setup_commands=setup_commands,
369-
config_file_path=config_file_path,
370-
paths=paths,
371-
)
367+
base_dir = Path(tool_config.config_file_path or "./mkdocs.yml").parent
368+
return PythonHandler(config=dict(handler_config), base_dir=base_dir, **kwargs)

src/mkdocstrings_handlers/python/rendering.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
from typing import Any, Callable
55

66
from markupsafe import Markup
7-
from mkdocstrings.handlers.base import CollectorItem
8-
from mkdocstrings.loggers import get_logger
7+
from mkdocstrings import CollectorItem, get_logger
98

109
log = get_logger(__name__)
1110

tests/conftest.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@
1313
from collections.abc import Iterator
1414
from pathlib import Path
1515

16-
from mkdocstrings.extension import MkdocstringsExtension
17-
from mkdocstrings.plugin import MkdocstringsPlugin
16+
from mkdocstrings import MkdocstringsExtension, MkdocstringsPlugin
1817

1918

2019
@pytest.fixture(name="mkdocs_conf")
@@ -79,4 +78,4 @@ def fixture_ext_markdown(plugin: MkdocstringsPlugin) -> MkdocstringsExtension:
7978
Returns:
8079
The plugin Markdown instance.
8180
"""
82-
return plugin.md
81+
return plugin.md # type: ignore[attr-defined]

tests/test_collector.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33
from unittest import mock
44

55
import pytest
6-
from mkdocstrings.handlers.base import CollectionError
6+
from mkdocstrings import CollectionError
77

88
from mkdocstrings_handlers.python import get_handler
99

1010

11+
class _FakeMkDocsConfig:
12+
config_file_path = "mkdocs.yml"
13+
14+
1115
@pytest.mark.parametrize(
1216
("retval", "exp_res"),
1317
[
@@ -26,6 +30,6 @@ def test_collect_result_error(retval: dict, exp_res: str) -> None:
2630
with mock.patch("mkdocstrings_handlers.python.handler.json.loads") as m_loads: # noqa: SIM117
2731
with pytest.raises(CollectionError) as excinfo: # noqa: PT012
2832
m_loads.return_value = retval
29-
handler = get_handler("material")
33+
handler = get_handler({}, _FakeMkDocsConfig, theme="material") # type: ignore[arg-type]
3034
assert handler.collect("", {})
3135
assert str(excinfo.value) == exp_res

tests/test_themes.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import pytest
88

99
if TYPE_CHECKING:
10-
from mkdocstrings.plugin import MkdocstringsPlugin
10+
from mkdocstrings import MkdocstringsPlugin
1111

1212

1313
@pytest.mark.parametrize(
@@ -39,6 +39,7 @@ def test_render_themes_templates(module: str, plugin: MkdocstringsPlugin) -> Non
3939
plugin: The plugin instance (parametrized fixture).
4040
"""
4141
handler = plugin.handlers.get_handler("python")
42-
handler._update_env(plugin.md, plugin.handlers._config)
43-
data = handler.collect(module, {})
44-
handler.render(data, {})
42+
handler._update_env(plugin.md, config=plugin.handlers._tool_config) # type: ignore[attr-defined]
43+
options = handler.get_options({})
44+
data = handler.collect(module, options)
45+
handler.render(data, options)

0 commit comments

Comments
 (0)