CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-mkdocstrings

Automatic documentation from sources, for MkDocs.

Pending
Overview
Eval results
Files

handler-system.mddocs/

Handler System

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.

Capabilities

Base Handler

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
        pass

Handler Manager

Container 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()

Handler Development

Creating Custom Handlers

To create a custom handler:

  1. Inherit from BaseHandler:
class MyHandler(BaseHandler):
    name = "mylang"
    domain = "mylang"
    enable_inventory = True
  1. Implement Abstract Methods:
def 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
    pass
  1. Register Handler: Handler registration happens automatically when the handler module is imported and the handler class is available in the module's namespace.

Handler Options

Handlers 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 behavior

Template Integration

Handlers 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:

  1. Custom templates directory (if specified)
  2. Handler-specific template directory
  3. Default template directory
  4. Fallback theme templates

Error Handling

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}")

Inventory Integration

Handlers that support inventory should:

  1. Set enable_inventory = True
  2. Set appropriate domain value
  3. Register items during rendering:
def 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

docs

handler-system.md

index.md

inventory-system.md

logging-system.md

markdown-processing.md

plugin-integration.md

rendering-system.md

tile.json