0
# Handler System
1
2
Extensible handler system for processing different programming languages. Handlers are responsible for collecting documentation data from source code and rendering it into HTML. The system provides base classes for creating custom handlers and a manager for handler lifecycle.
3
4
## Capabilities
5
6
### Base Handler
7
8
Abstract base class that defines the interface for language-specific documentation handlers.
9
10
```python { .api }
11
class BaseHandler:
12
"""Base class for all documentation handlers."""
13
14
# Class attributes
15
name: ClassVar[str] = ""
16
"""Handler identifier name."""
17
18
domain: ClassVar[str] = ""
19
"""Handler domain for inventory organization."""
20
21
enable_inventory: ClassVar[bool] = False
22
"""Whether handler supports inventory generation."""
23
24
fallback_theme: ClassVar[str] = ""
25
"""Default theme when specified theme unavailable."""
26
27
extra_css: str = ""
28
"""Additional CSS content for handler."""
29
30
def __init__(self, *args: Any, **kwargs: Any) -> None:
31
"""Initialize handler."""
32
33
# Abstract methods (must be implemented by subclasses)
34
def collect(self, identifier: str, options: HandlerOptions) -> CollectorItem:
35
"""
36
Collect documentation data for the given identifier.
37
38
Args:
39
identifier: The object identifier to collect docs for
40
options: Handler-specific options
41
42
Returns:
43
Collected documentation data
44
45
Raises:
46
CollectionError: If collection fails
47
"""
48
49
def render(self, data: CollectorItem, options: HandlerOptions, *, locale: str | None = None) -> str:
50
"""
51
Render collected data into HTML.
52
53
Args:
54
data: Data returned by collect method
55
options: Handler-specific options
56
locale: Locale for rendering
57
58
Returns:
59
Rendered HTML string
60
"""
61
62
# Concrete methods
63
def get_inventory_urls(self) -> list[tuple[str, dict[str, Any]]]:
64
"""Get inventory URLs for this handler."""
65
66
@classmethod
67
def load_inventory(cls, in_file: BinaryIO, url: str, base_url: str | None = None, **kwargs: Any) -> Iterator[tuple[str, str]]:
68
"""Load external inventory file."""
69
70
def get_options(self, local_options: Mapping[str, Any]) -> HandlerOptions:
71
"""Merge global and local handler options."""
72
73
def render_backlinks(self, backlinks: Mapping[str, Iterable[Backlink]], *, locale: str | None = None) -> str:
74
"""Render cross-reference backlinks."""
75
76
def teardown(self) -> None:
77
"""Cleanup after processing."""
78
79
def get_templates_dir(self, handler: str | None = None) -> Path:
80
"""Get templates directory for this handler."""
81
82
def get_extended_templates_dirs(self, handler: str) -> list[Path]:
83
"""Get all template directories in search order."""
84
85
def get_aliases(self, identifier: str) -> tuple[str, ...]:
86
"""Get alternative identifiers for the given identifier."""
87
88
def do_convert_markdown(self, text: str, heading_level: int, html_id: str = "", *, strip_paragraph: bool = False, autoref_hook: AutorefsHookInterface | None = None) -> Markup:
89
"""Convert Markdown text to HTML."""
90
91
def do_heading(self, content: Markup, heading_level: int, *, role: str | None = None, hidden: bool = False, toc_label: str | None = None, skip_inventory: bool = False, **attributes: str) -> Markup:
92
"""Create a heading element."""
93
94
def get_headings(self) -> Sequence[Element]:
95
"""Get heading elements from last render."""
96
97
def update_env(self, *args: Any, **kwargs: Any) -> None:
98
"""Update template environment."""
99
100
@property
101
def md(self) -> Markdown:
102
"""The Markdown processor instance."""
103
104
@property
105
def outer_layer(self) -> bool:
106
"""Whether in outer Markdown conversion layer."""
107
```
108
109
**Custom Handler Example:**
110
111
```python
112
from mkdocstrings import BaseHandler, CollectionError
113
114
class MyLanguageHandler(BaseHandler):
115
name = "mylang"
116
domain = "mylang"
117
enable_inventory = True
118
119
def collect(self, identifier: str, options: HandlerOptions) -> CollectorItem:
120
try:
121
# Parse source code and extract documentation
122
# This would be language-specific logic
123
data = self._parse_source(identifier)
124
return data
125
except Exception as e:
126
raise CollectionError(f"Failed to collect {identifier}: {e}")
127
128
def render(self, data: CollectorItem, options: HandlerOptions, *, locale: str | None = None) -> str:
129
# Render data using templates
130
template = self.env.get_template("mylang.html")
131
return template.render(data=data, options=options)
132
133
def _parse_source(self, identifier: str):
134
# Language-specific parsing logic
135
pass
136
```
137
138
### Handler Manager
139
140
Container and manager for handler instances, providing handler lifecycle management and configuration.
141
142
```python { .api }
143
class Handlers:
144
"""Container and manager for handler instances."""
145
146
def __init__(
147
self,
148
*,
149
theme: str,
150
default: str,
151
inventory_project: str,
152
inventory_version: str = "0.0.0",
153
handlers_config: dict[str, HandlerConfig] | None = None,
154
custom_templates: str | None = None,
155
mdx: Sequence[str | Extension] | None = None,
156
mdx_config: Mapping[str, Any] | None = None,
157
locale: str = "en",
158
tool_config: Any
159
) -> None:
160
"""
161
Initialize handlers manager.
162
163
Args:
164
theme: Theme name for template selection
165
default: Default handler name
166
inventory_project: Project name for inventory
167
inventory_version: Project version for inventory
168
handlers_config: Configuration for each handler
169
custom_templates: Custom templates directory
170
mdx: Markdown extensions to load
171
mdx_config: Configuration for Markdown extensions
172
locale: Default locale
173
tool_config: Tool-specific configuration
174
"""
175
176
def get_handler_name(self, config: dict) -> str:
177
"""Determine handler name from configuration."""
178
179
def get_handler_config(self, name: str) -> dict:
180
"""Get configuration for specific handler."""
181
182
def get_handler(self, name: str, handler_config: dict | None = None) -> BaseHandler:
183
"""
184
Get or create handler instance.
185
186
Args:
187
name: Handler name
188
handler_config: Override configuration
189
190
Returns:
191
Handler instance
192
"""
193
194
def get_anchors(self, identifier: str) -> tuple[str, ...]:
195
"""Get anchors for identifier (deprecated)."""
196
197
def teardown(self) -> None:
198
"""Cleanup all handlers."""
199
200
@property
201
def inventory(self) -> Inventory:
202
"""The objects inventory for cross-referencing."""
203
204
@property
205
def seen_handlers(self) -> Iterable[BaseHandler]:
206
"""Handlers used during current build."""
207
```
208
209
**Usage Example:**
210
211
```python
212
from mkdocstrings import Handlers
213
214
# Create handlers manager
215
handlers = Handlers(
216
theme="material",
217
default="python",
218
inventory_project="my-project",
219
inventory_version="1.0.0",
220
handlers_config={
221
"python": {
222
"paths": ["src"],
223
"options": {
224
"docstring_style": "google",
225
"show_source": False
226
}
227
}
228
},
229
custom_templates="templates/",
230
locale="en"
231
)
232
233
# Get a handler
234
python_handler = handlers.get_handler("python")
235
236
# Use handler to collect and render documentation
237
data = python_handler.collect("my_module.MyClass", options)
238
html = python_handler.render(data, options)
239
240
# Cleanup when done
241
handlers.teardown()
242
```
243
244
## Handler Development
245
246
### Creating Custom Handlers
247
248
To create a custom handler:
249
250
1. **Inherit from BaseHandler**:
251
```python
252
class MyHandler(BaseHandler):
253
name = "mylang"
254
domain = "mylang"
255
enable_inventory = True
256
```
257
258
2. **Implement Abstract Methods**:
259
```python
260
def collect(self, identifier: str, options: HandlerOptions) -> CollectorItem:
261
# Parse source code and extract documentation
262
pass
263
264
def render(self, data: CollectorItem, options: HandlerOptions, *, locale: str | None = None) -> str:
265
# Render documentation using templates
266
pass
267
```
268
269
3. **Register Handler**:
270
Handler registration happens automatically when the handler module is imported and the handler class is available in the module's namespace.
271
272
### Handler Options
273
274
Handlers receive options through the `HandlerOptions` type:
275
276
```python
277
# Options are passed from plugin configuration
278
options = {
279
"show_source": False,
280
"docstring_style": "google",
281
"show_signature_annotations": True,
282
"heading_level": 2
283
}
284
285
# Handler processes options in collect/render methods
286
def collect(self, identifier: str, options: HandlerOptions) -> CollectorItem:
287
show_source = options.get("show_source", True)
288
# Use options to customize behavior
289
```
290
291
### Template Integration
292
293
Handlers use Jinja2 templates for rendering:
294
295
```python
296
def render(self, data: CollectorItem, options: HandlerOptions, *, locale: str | None = None) -> str:
297
template = self.env.get_template("mylang/class.html")
298
return template.render(
299
data=data,
300
options=options,
301
locale=locale,
302
logger=self.get_template_logger()
303
)
304
```
305
306
Template directories are resolved in this order:
307
1. Custom templates directory (if specified)
308
2. Handler-specific template directory
309
3. Default template directory
310
4. Fallback theme templates
311
312
### Error Handling
313
314
Handlers should raise appropriate exceptions:
315
316
```python
317
from mkdocstrings import CollectionError
318
319
def collect(self, identifier: str, options: HandlerOptions) -> CollectorItem:
320
try:
321
return self._do_collection(identifier)
322
except ImportError as e:
323
raise CollectionError(f"Could not import {identifier}: {e}")
324
except Exception as e:
325
raise CollectionError(f"Collection failed for {identifier}: {e}")
326
```
327
328
### Inventory Integration
329
330
Handlers that support inventory should:
331
332
1. Set `enable_inventory = True`
333
2. Set appropriate `domain` value
334
3. Register items during rendering:
335
336
```python
337
def render(self, data: CollectorItem, options: HandlerOptions, *, locale: str | None = None) -> str:
338
# Register in inventory
339
self.handlers.inventory.register(
340
name=data.name,
341
domain=self.domain,
342
role="class",
343
uri=f"#{data.anchor}",
344
dispname=data.display_name
345
)
346
347
# Render normally
348
return template.render(data=data)
349
```