11"""This module implements a handler for the Python language."""
22
3+ from __future__ import annotations
4+
35import posixpath
6+ from collections import ChainMap
7+ from contextlib import suppress
48from typing import Any , BinaryIO , Iterator , Optional , Tuple
59
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
615from 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
819from mkdocstrings .inventory import Inventory
920from mkdocstrings .loggers import get_logger
1021
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__ )
1325
1426patch_loggers (get_logger )
1527
@@ -21,10 +33,82 @@ class PythonHandler(BaseHandler):
2133 domain: The cross-documentation domain/language for this handler.
2234 enable_inventory: Whether this handler is interested in enabling the creation
2335 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].
2442 """
2543
2644 domain : str = "py" # to match Sphinx's default domain
2745 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 ()
28112
29113 @classmethod
30114 def load_inventory (
@@ -53,6 +137,95 @@ def load_inventory(
53137 for item in Inventory .parse_sphinx (in_file , domain_filter = ("py" ,)).values (): # noqa: WPS526
54138 yield item .name , posixpath .join (base_url , item .uri )
55139
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+
56229
57230def get_handler (
58231 theme : str , # noqa: W0613 (unused argument config)
@@ -69,7 +242,4 @@ def get_handler(
69242 Returns:
70243 An instance of `PythonHandler`.
71244 """
72- return PythonHandler (
73- collector = PythonCollector (),
74- renderer = PythonRenderer ("python" , theme , custom_templates ),
75- )
245+ return PythonHandler ("python" , theme , custom_templates )
0 commit comments