Skip to content

Commit dbb580a

Browse files
committed
feat: Implement handler and add templates
1 parent c94142e commit dbb580a

Some content is hidden

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

57 files changed

+1623
-8
lines changed

config/flake8.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ ignore =
1010
D105
1111
# multi-line docstring summary should start at the first line
1212
D212
13+
# does not support Parameters sections
14+
D417
1315
# whitespace before ':' (incompatible with Black)
1416
E203
1517
# redundant with E0602 (undefined variable)

config/mypy.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@ exclude = tests/fixtures/
44

55
[mypy-toml.*]
66
ignore_missing_imports = true
7+
8+
[mypy-markdown.*]
9+
ignore_missing_imports = true

docs/gen_ref_nav.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,27 @@
77
nav = mkdocs_gen_files.Nav()
88

99
for path in sorted(Path("src").glob("**/*.py")):
10+
# if str(path) in exclude:
11+
# continue
1012
module_path = path.relative_to("src").with_suffix("")
11-
doc_path = path.relative_to("src", "mkdocstrings").with_suffix(".md")
13+
doc_path = path.relative_to("src").with_suffix(".md")
1214
full_doc_path = Path("reference", doc_path)
1315

1416
parts = list(module_path.parts)
15-
parts[-1] = f"{parts[-1]}.py"
16-
nav[parts] = doc_path
17+
if parts[-1] == "__init__":
18+
parts = parts[:-1]
19+
doc_path = doc_path.with_name("index.md")
20+
full_doc_path = full_doc_path.with_name("index.md")
21+
elif parts[-1] == "__main__":
22+
continue
23+
nav_parts = list(parts)
24+
nav[nav_parts] = doc_path
1725

1826
with mkdocs_gen_files.open(full_doc_path, "w") as fd:
19-
ident = ".".join(module_path.parts)
27+
ident = ".".join(parts)
2028
print("::: " + ident, file=fd)
2129

2230
mkdocs_gen_files.set_edit_path(full_doc_path, path)
2331

24-
# add pages manually:
25-
# nav["package", "module"] = "path/to/file.md"
26-
2732
with mkdocs_gen_files.open("reference/SUMMARY.md", "w") as nav_file:
2833
nav_file.writelines(nav.build_literate_nav())

docs/reference/cli.md

Lines changed: 0 additions & 1 deletion
This file was deleted.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ classifiers = [
1919
]
2020
dependencies = [
2121
"griffe~=0.3",
22+
"-e ../griffe",
2223
]
2324

2425
[project.urls]
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""This module implements a handler for the Python language."""
2+
3+
import posixpath
4+
from typing import Any, BinaryIO, Iterator, Optional, Tuple
5+
6+
from mkdocstrings.handlers.base import BaseHandler
7+
from mkdocstrings.handlers.python.collector import PythonCollector
8+
from mkdocstrings.handlers.python.renderer import PythonRenderer
9+
from mkdocstrings.inventory import Inventory
10+
from mkdocstrings.loggers import get_logger
11+
12+
log = get_logger(__name__)
13+
14+
15+
class PythonHandler(BaseHandler):
16+
"""The Python handler class.
17+
18+
Attributes:
19+
domain: The cross-documentation domain/language for this handler.
20+
enable_inventory: Whether this handler is interested in enabling the creation
21+
of the `objects.inv` Sphinx inventory file.
22+
"""
23+
24+
domain: str = "py" # to match Sphinx's default domain
25+
enable_inventory: bool = True
26+
27+
@classmethod
28+
def load_inventory(
29+
cls, in_file: BinaryIO, url: str, base_url: Optional[str] = None, **kwargs
30+
) -> Iterator[Tuple[str, str]]:
31+
"""Yield items and their URLs from an inventory file streamed from `in_file`.
32+
33+
This implements mkdocstrings' `load_inventory` "protocol" (see plugin.py).
34+
35+
Arguments:
36+
in_file: The binary file-like object to read the inventory from.
37+
url: The URL that this file is being streamed from (used to guess `base_url`).
38+
base_url: The URL that this inventory's sub-paths are relative to.
39+
**kwargs: Ignore additional arguments passed from the config.
40+
41+
Yields:
42+
Tuples of (item identifier, item URL).
43+
"""
44+
if base_url is None:
45+
base_url = posixpath.dirname(url)
46+
47+
for item in Inventory.parse_sphinx(in_file, domain_filter=("py",)).values(): # noqa: WPS526
48+
yield item.name, posixpath.join(base_url, item.uri)
49+
50+
51+
def get_handler(
52+
theme: str, # noqa: W0613 (unused argument config)
53+
custom_templates: Optional[str] = None,
54+
**config: Any,
55+
) -> PythonHandler:
56+
"""Simply return an instance of `PythonHandler`.
57+
58+
Arguments:
59+
theme: The theme to use when rendering contents.
60+
custom_templates: Directory containing custom templates.
61+
config: Configuration passed to the handler.
62+
63+
Returns:
64+
An instance of `PythonHandler`.
65+
"""
66+
return PythonHandler(
67+
collector=PythonCollector(),
68+
renderer=PythonRenderer("python", theme, custom_templates),
69+
)
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
"""This module implements a collector for the Python language.
2+
3+
It collects data with [Griffe](https://github.com/pawamoy/griffe).
4+
"""
5+
6+
from collections import ChainMap
7+
8+
from griffe import logger as griffe_logger
9+
10+
from mkdocstrings.handlers.base import BaseCollector, CollectionError, CollectorItem
11+
from mkdocstrings.loggers import get_logger
12+
13+
griffe_logger.get_logger = get_logger # patch logger to blend in MkDocs logs
14+
from griffe.collections import LinesCollection, ModulesCollection # noqa: E402
15+
from griffe.docstrings.parsers import Parser # noqa: E402
16+
from griffe.extensions import load_extensions # noqa: E402
17+
from griffe.loader import GriffeLoader # noqa: E402
18+
19+
logger = get_logger(__name__)
20+
21+
22+
class PythonCollector(BaseCollector):
23+
"""The class responsible for loading Jinja templates and rendering them.
24+
25+
It defines some configuration options, implements the `render` method,
26+
and overrides the `update_env` method of the [`BaseRenderer` class][mkdocstrings.handlers.base.BaseRenderer].
27+
"""
28+
29+
default_config: dict = {"docstring_style": "google", "docstring_options": {}}
30+
"""The default selection options.
31+
32+
Option | Type | Description | Default
33+
------ | ---- | ----------- | -------
34+
**`docstring_style`** | `"google" | "numpy" | "rst" | None` | The docstring style to use. | `"google"`
35+
**`docstring_options`** | dict[str, Any] | The options for the docstring parser. | `{}`
36+
"""
37+
38+
def __init__(self) -> None:
39+
"""Initialize the object."""
40+
self._modules_collection: ModulesCollection = ModulesCollection()
41+
self._lines_collection: LinesCollection = LinesCollection()
42+
43+
def collect(self, identifier: str, config: dict) -> CollectorItem: # noqa: WPS231
44+
"""Collect the documentation tree given an identifier and selection options.
45+
46+
Arguments:
47+
identifier: The dotted-path of a Python object available in the Python path.
48+
config: Selection options, used to alter the data collection done by `pytkdocs`.
49+
50+
Raises:
51+
CollectionError: When there was a problem collecting the object documentation.
52+
53+
Returns:
54+
The collected object-tree.
55+
"""
56+
final_config = ChainMap(config, self.default_config)
57+
58+
module_name = identifier.split(".", 1)[0]
59+
if module_name not in self._modules_collection:
60+
loader = GriffeLoader(
61+
extensions=load_extensions(final_config.get("extensions", [])),
62+
docstring_parser=Parser(final_config["docstring_style"]),
63+
docstring_options=final_config["docstring_options"],
64+
modules_collection=self._modules_collection,
65+
lines_collection=self._lines_collection,
66+
)
67+
try:
68+
module = loader.load_module(module_name)
69+
except ModuleNotFoundError as error:
70+
raise CollectionError from error
71+
72+
for _ in range(5):
73+
if loader.follow_aliases(module):
74+
break
75+
else:
76+
logger.warning("some aliases could not be resolved")
77+
78+
try:
79+
return self._modules_collection[identifier]
80+
except KeyError as error: # noqa: WPS440
81+
raise CollectionError from error

0 commit comments

Comments
 (0)