1
1
"""This module implements a handler for the Python language."""
2
2
3
+ from __future__ import annotations
4
+
3
5
import posixpath
6
+ from collections import ChainMap
7
+ from contextlib import suppress
4
8
from typing import Any , BinaryIO , Iterator , Optional , Tuple
5
9
10
+ from griffe .agents .extensions import load_extensions
11
+ from griffe .collections import LinesCollection , ModulesCollection
12
+ from griffe .docstrings .parsers import Parser
13
+ from griffe .exceptions import AliasResolutionError
14
+ from griffe .loader import GriffeLoader
6
15
from griffe .logger import patch_loggers
7
- from mkdocstrings .handlers .base import BaseHandler
16
+ from markdown import Markdown
17
+ from mkdocstrings .extension import PluginError
18
+ from mkdocstrings .handlers .base import BaseHandler , CollectionError , CollectorItem
8
19
from mkdocstrings .inventory import Inventory
9
20
from mkdocstrings .loggers import get_logger
10
21
11
- from mkdocstrings_handlers .python .collector import PythonCollector
12
- from mkdocstrings_handlers .python .renderer import PythonRenderer
22
+ from mkdocstrings_handlers .python import rendering
23
+
24
+ logger = get_logger (__name__ )
13
25
14
26
patch_loggers (get_logger )
15
27
@@ -21,10 +33,82 @@ class PythonHandler(BaseHandler):
21
33
domain: The cross-documentation domain/language for this handler.
22
34
enable_inventory: Whether this handler is interested in enabling the creation
23
35
of the `objects.inv` Sphinx inventory file.
36
+ fallback_theme: The fallback theme.
37
+ fallback_config: The configuration used to collect item during autorefs fallback.
38
+ default_collection_config: The default rendering options,
39
+ see [`default_collection_config`][mkdocstrings_handlers.python.handler.PythonHandler.default_collection_config].
40
+ default_rendering_config: The default rendering options,
41
+ see [`default_rendering_config`][mkdocstrings_handlers.python.handler.PythonHandler.default_rendering_config].
24
42
"""
25
43
26
44
domain : str = "py" # to match Sphinx's default domain
27
45
enable_inventory : bool = True
46
+ fallback_theme = "material"
47
+ fallback_config : dict = {"fallback" : True }
48
+ default_collection_config : dict = {"docstring_style" : "google" , "docstring_options" : {}}
49
+ """The default collection options.
50
+
51
+ Option | Type | Description | Default
52
+ ------ | ---- | ----------- | -------
53
+ **`docstring_style`** | `"google" | "numpy" | "sphinx" | None` | The docstring style to use. | `"google"`
54
+ **`docstring_options`** | `dict[str, Any]` | The options for the docstring parser. | `{}`
55
+ """
56
+ default_rendering_config : dict = {
57
+ "show_root_heading" : False ,
58
+ "show_root_toc_entry" : True ,
59
+ "show_root_full_path" : True ,
60
+ "show_root_members_full_path" : False ,
61
+ "show_object_full_path" : False ,
62
+ "show_category_heading" : False ,
63
+ "show_if_no_docstring" : False ,
64
+ "show_signature" : True ,
65
+ "show_signature_annotations" : False ,
66
+ "separate_signature" : False ,
67
+ "line_length" : 60 ,
68
+ "merge_init_into_class" : False ,
69
+ "show_source" : True ,
70
+ "show_bases" : True ,
71
+ "show_submodules" : True ,
72
+ "group_by_category" : True ,
73
+ "heading_level" : 2 ,
74
+ "members_order" : rendering .Order .alphabetical .value ,
75
+ "docstring_section_style" : "table" ,
76
+ }
77
+ """The default rendering options.
78
+
79
+ Option | Type | Description | Default
80
+ ------ | ---- | ----------- | -------
81
+ **`show_root_heading`** | `bool` | Show the heading of the object at the root of the documentation tree. | `False`
82
+ **`show_root_toc_entry`** | `bool` | If the root heading is not shown, at least add a ToC entry for it. | `True`
83
+ **`show_root_full_path`** | `bool` | Show the full Python path for the root object heading. | `True`
84
+ **`show_object_full_path`** | `bool` | Show the full Python path of every object. | `False`
85
+ **`show_root_members_full_path`** | `bool` | Show the full Python path of objects that are children of the root object (for example, classes in a module). When False, `show_object_full_path` overrides. | `False`
86
+ **`show_category_heading`** | `bool` | When grouped by categories, show a heading for each category. | `False`
87
+ **`show_if_no_docstring`** | `bool` | Show the object heading even if it has no docstring or children with docstrings. | `False`
88
+ **`show_signature`** | `bool` | Show method and function signatures. | `True`
89
+ **`show_signature_annotations`** | `bool` | Show the type annotations in method and function signatures. | `False`
90
+ **`separate_signature`** | `bool` | Whether to put the whole signature in a code block below the heading. | `False`
91
+ **`line_length`** | `int` | Maximum line length when formatting code. | `60`
92
+ **`merge_init_into_class`** | `bool` | Whether to merge the `__init__` method into the class' signature and docstring. | `False`
93
+ **`show_source`** | `bool` | Show the source code of this object. | `True`
94
+ **`show_bases`** | `bool` | Show the base classes of a class. | `True`
95
+ **`show_submodules`** | `bool` | When rendering a module, show its submodules recursively. | `True`
96
+ **`group_by_category`** | `bool` | Group the object's children by categories: attributes, classes, functions, methods, and modules. | `True`
97
+ **`heading_level`** | `int` | The initial heading level to use. | `2`
98
+ **`members_order`** | `str` | The members ordering to use. Options: `alphabetical` - order by the members names, `source` - order members as they appear in the source file. | `alphabetical`
99
+ **`docstring_section_style`** | `str` | The style used to render docstring sections. Options: `table`, `list`, `spacy`. | `table`
100
+ """ # noqa: E501
101
+
102
+ def __init__ (self , * args , ** kwargs ) -> None :
103
+ """Initialize the handler.
104
+
105
+ Parameters:
106
+ *args: Handler name, theme and custom templates.
107
+ **kwargs: Same thing, but with keyword arguments.
108
+ """
109
+ super ().__init__ (* args , ** kwargs )
110
+ self ._modules_collection : ModulesCollection = ModulesCollection ()
111
+ self ._lines_collection : LinesCollection = LinesCollection ()
28
112
29
113
@classmethod
30
114
def load_inventory (
@@ -53,6 +137,95 @@ def load_inventory(
53
137
for item in Inventory .parse_sphinx (in_file , domain_filter = ("py" ,)).values (): # noqa: WPS526
54
138
yield item .name , posixpath .join (base_url , item .uri )
55
139
140
+ def collect (self , identifier : str , config : dict ) -> CollectorItem : # noqa: WPS231
141
+ """Collect the documentation tree given an identifier and selection options.
142
+
143
+ Arguments:
144
+ identifier: The dotted-path of a Python object available in the Python path.
145
+ config: Selection options, used to alter the data collection done by `pytkdocs`.
146
+
147
+ Raises:
148
+ CollectionError: When there was a problem collecting the object documentation.
149
+
150
+ Returns:
151
+ The collected object-tree.
152
+ """
153
+ module_name = identifier .split ("." , 1 )[0 ]
154
+ unknown_module = module_name not in self ._modules_collection
155
+ if config .get ("fallback" , False ) and unknown_module :
156
+ raise CollectionError ("Not loading additional modules during fallback" )
157
+
158
+ final_config = ChainMap (config , self .default_collection_config )
159
+ parser_name = final_config ["docstring_style" ]
160
+ parser_options = final_config ["docstring_options" ]
161
+ parser = parser_name and Parser (parser_name )
162
+
163
+ if unknown_module :
164
+ loader = GriffeLoader (
165
+ extensions = load_extensions (final_config .get ("extensions" , [])),
166
+ docstring_parser = parser ,
167
+ docstring_options = parser_options ,
168
+ modules_collection = self ._modules_collection ,
169
+ lines_collection = self ._lines_collection ,
170
+ )
171
+ try :
172
+ loader .load_module (module_name )
173
+ except ImportError as error :
174
+ raise CollectionError (str (error )) from error
175
+
176
+ unresolved , iterations = loader .resolve_aliases (only_exported = True , only_known_modules = True )
177
+ if unresolved :
178
+ logger .warning (f"{ len (unresolved )} aliases were still unresolved after { iterations } iterations" )
179
+
180
+ try :
181
+ doc_object = self ._modules_collection [identifier ]
182
+ except KeyError as error : # noqa: WPS440
183
+ raise CollectionError (f"{ identifier } could not be found" ) from error
184
+
185
+ if not unknown_module :
186
+ with suppress (AliasResolutionError ):
187
+ if doc_object .docstring is not None :
188
+ doc_object .docstring .parser = parser
189
+ doc_object .docstring .parser_options = parser_options
190
+
191
+ return doc_object
192
+
193
+ def render (self , data : CollectorItem , config : dict ) -> str : # noqa: D102 (ignore missing docstring)
194
+ final_config = ChainMap (config , self .default_rendering_config )
195
+
196
+ template = self .env .get_template (f"{ data .kind .value } .html" )
197
+
198
+ # Heading level is a "state" variable, that will change at each step
199
+ # of the rendering recursion. Therefore, it's easier to use it as a plain value
200
+ # than as an item in a dictionary.
201
+ heading_level = final_config ["heading_level" ]
202
+ try :
203
+ final_config ["members_order" ] = rendering .Order (final_config ["members_order" ])
204
+ except ValueError :
205
+ choices = "', '" .join (item .value for item in rendering .Order )
206
+ raise PluginError (f"Unknown members_order '{ final_config ['members_order' ]} ', choose between '{ choices } '." )
207
+
208
+ return template .render (
209
+ ** {"config" : final_config , data .kind .value : data , "heading_level" : heading_level , "root" : True },
210
+ )
211
+
212
+ def update_env (self , md : Markdown , config : dict ) -> None : # noqa: D102 (ignore missing docstring)
213
+ super ().update_env (md , config )
214
+ self .env .trim_blocks = True
215
+ self .env .lstrip_blocks = True
216
+ self .env .keep_trailing_newline = False
217
+ self .env .filters ["crossref" ] = rendering .do_crossref
218
+ self .env .filters ["multi_crossref" ] = rendering .do_multi_crossref
219
+ self .env .filters ["order_members" ] = rendering .do_order_members
220
+ self .env .filters ["format_code" ] = rendering .do_format_code
221
+ self .env .filters ["format_signature" ] = rendering .do_format_signature
222
+
223
+ def get_anchors (self , data : CollectorItem ) -> list [str ]: # noqa: D102 (ignore missing docstring)
224
+ try :
225
+ return list ({data .path , data .canonical_path , * data .aliases })
226
+ except AliasResolutionError :
227
+ return [data .path ]
228
+
56
229
57
230
def get_handler (
58
231
theme : str , # noqa: W0613 (unused argument config)
@@ -69,7 +242,4 @@ def get_handler(
69
242
Returns:
70
243
An instance of `PythonHandler`.
71
244
"""
72
- return PythonHandler (
73
- collector = PythonCollector (),
74
- renderer = PythonRenderer ("python" , theme , custom_templates ),
75
- )
245
+ return PythonHandler ("python" , theme , custom_templates )
0 commit comments