Automatic documentation from sources, for MkDocs.
—
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.
Abstract base class that defines the interface for language-specific documentation handlers.
class BaseHandler:
"""Base class for all documentation handlers."""
# Class attributes
name: ClassVar[str] = ""
"""Handler identifier name."""
domain: ClassVar[str] = ""
"""Handler domain for inventory organization."""
enable_inventory: ClassVar[bool] = False
"""Whether handler supports inventory generation."""
fallback_theme: ClassVar[str] = ""
"""Default theme when specified theme unavailable."""
extra_css: str = ""
"""Additional CSS content for handler."""
def __init__(self, *args: Any, **kwargs: Any) -> None:
"""Initialize handler."""
# Abstract methods (must be implemented by subclasses)
def collect(self, identifier: str, options: HandlerOptions) -> CollectorItem:
"""
Collect documentation data for the given identifier.
Args:
identifier: The object identifier to collect docs for
options: Handler-specific options
Returns:
Collected documentation data
Raises:
CollectionError: If collection fails
"""
def render(self, data: CollectorItem, options: HandlerOptions, *, locale: str | None = None) -> str:
"""
Render collected data into HTML.
Args:
data: Data returned by collect method
options: Handler-specific options
locale: Locale for rendering
Returns:
Rendered HTML string
"""
# Concrete methods
def get_inventory_urls(self) -> list[tuple[str, dict[str, Any]]]:
"""Get inventory URLs for this handler."""
@classmethod
def load_inventory(cls, in_file: BinaryIO, url: str, base_url: str | None = None, **kwargs: Any) -> Iterator[tuple[str, str]]:
"""Load external inventory file."""
def get_options(self, local_options: Mapping[str, Any]) -> HandlerOptions:
"""Merge global and local handler options."""
def render_backlinks(self, backlinks: Mapping[str, Iterable[Backlink]], *, locale: str | None = None) -> str:
"""Render cross-reference backlinks."""
def teardown(self) -> None:
"""Cleanup after processing."""
def get_templates_dir(self, handler: str | None = None) -> Path:
"""Get templates directory for this handler."""
def get_extended_templates_dirs(self, handler: str) -> list[Path]:
"""Get all template directories in search order."""
def get_aliases(self, identifier: str) -> tuple[str, ...]:
"""Get alternative identifiers for the given identifier."""
def do_convert_markdown(self, text: str, heading_level: int, html_id: str = "", *, strip_paragraph: bool = False, autoref_hook: AutorefsHookInterface | None = None) -> Markup:
"""Convert Markdown text to HTML."""
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:
"""Create a heading element."""
def get_headings(self) -> Sequence[Element]:
"""Get heading elements from last render."""
def update_env(self, *args: Any, **kwargs: Any) -> None:
"""Update template environment."""
@property
def md(self) -> Markdown:
"""The Markdown processor instance."""
@property
def outer_layer(self) -> bool:
"""Whether in outer Markdown conversion layer."""Custom Handler Example:
from mkdocstrings import BaseHandler, CollectionError
class MyLanguageHandler(BaseHandler):
name = "mylang"
domain = "mylang"
enable_inventory = True
def collect(self, identifier: str, options: HandlerOptions) -> CollectorItem:
try:
# Parse source code and extract documentation
# This would be language-specific logic
data = self._parse_source(identifier)
return data
except Exception as e:
raise CollectionError(f"Failed to collect {identifier}: {e}")
def render(self, data: CollectorItem, options: HandlerOptions, *, locale: str | None = None) -> str:
# Render data using templates
template = self.env.get_template("mylang.html")
return template.render(data=data, options=options)
def _parse_source(self, identifier: str):
# Language-specific parsing logic
passContainer and manager for handler instances, providing handler lifecycle management and configuration.
class Handlers:
"""Container and manager for handler instances."""
def __init__(
self,
*,
theme: str,
default: str,
inventory_project: str,
inventory_version: str = "0.0.0",
handlers_config: dict[str, HandlerConfig] | None = None,
custom_templates: str | None = None,
mdx: Sequence[str | Extension] | None = None,
mdx_config: Mapping[str, Any] | None = None,
locale: str = "en",
tool_config: Any
) -> None:
"""
Initialize handlers manager.
Args:
theme: Theme name for template selection
default: Default handler name
inventory_project: Project name for inventory
inventory_version: Project version for inventory
handlers_config: Configuration for each handler
custom_templates: Custom templates directory
mdx: Markdown extensions to load
mdx_config: Configuration for Markdown extensions
locale: Default locale
tool_config: Tool-specific configuration
"""
def get_handler_name(self, config: dict) -> str:
"""Determine handler name from configuration."""
def get_handler_config(self, name: str) -> dict:
"""Get configuration for specific handler."""
def get_handler(self, name: str, handler_config: dict | None = None) -> BaseHandler:
"""
Get or create handler instance.
Args:
name: Handler name
handler_config: Override configuration
Returns:
Handler instance
"""
def get_anchors(self, identifier: str) -> tuple[str, ...]:
"""Get anchors for identifier (deprecated)."""
def teardown(self) -> None:
"""Cleanup all handlers."""
@property
def inventory(self) -> Inventory:
"""The objects inventory for cross-referencing."""
@property
def seen_handlers(self) -> Iterable[BaseHandler]:
"""Handlers used during current build."""Usage Example:
from mkdocstrings import Handlers
# Create handlers manager
handlers = Handlers(
theme="material",
default="python",
inventory_project="my-project",
inventory_version="1.0.0",
handlers_config={
"python": {
"paths": ["src"],
"options": {
"docstring_style": "google",
"show_source": False
}
}
},
custom_templates="templates/",
locale="en"
)
# Get a handler
python_handler = handlers.get_handler("python")
# Use handler to collect and render documentation
data = python_handler.collect("my_module.MyClass", options)
html = python_handler.render(data, options)
# Cleanup when done
handlers.teardown()To create a custom handler:
class MyHandler(BaseHandler):
name = "mylang"
domain = "mylang"
enable_inventory = Truedef collect(self, identifier: str, options: HandlerOptions) -> CollectorItem:
# Parse source code and extract documentation
pass
def render(self, data: CollectorItem, options: HandlerOptions, *, locale: str | None = None) -> str:
# Render documentation using templates
passHandlers receive options through the HandlerOptions type:
# Options are passed from plugin configuration
options = {
"show_source": False,
"docstring_style": "google",
"show_signature_annotations": True,
"heading_level": 2
}
# Handler processes options in collect/render methods
def collect(self, identifier: str, options: HandlerOptions) -> CollectorItem:
show_source = options.get("show_source", True)
# Use options to customize behaviorHandlers use Jinja2 templates for rendering:
def render(self, data: CollectorItem, options: HandlerOptions, *, locale: str | None = None) -> str:
template = self.env.get_template("mylang/class.html")
return template.render(
data=data,
options=options,
locale=locale,
logger=self.get_template_logger()
)Template directories are resolved in this order:
Handlers should raise appropriate exceptions:
from mkdocstrings import CollectionError
def collect(self, identifier: str, options: HandlerOptions) -> CollectorItem:
try:
return self._do_collection(identifier)
except ImportError as e:
raise CollectionError(f"Could not import {identifier}: {e}")
except Exception as e:
raise CollectionError(f"Collection failed for {identifier}: {e}")Handlers that support inventory should:
enable_inventory = Truedomain valuedef render(self, data: CollectorItem, options: HandlerOptions, *, locale: str | None = None) -> str:
# Register in inventory
self.handlers.inventory.register(
name=data.name,
domain=self.domain,
role="class",
uri=f"#{data.anchor}",
dispname=data.display_name
)
# Render normally
return template.render(data=data)Install with Tessl CLI
npx tessl i tessl/pypi-mkdocstrings