CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-griffe

Extract Python API signatures and detect breaking changes for documentation generation.

Pending
Overview
Eval results
Files

extensions.mddocs/

Extensions

Extensible plugin system for customizing and enhancing Griffe's analysis capabilities. Extensions allow developers to add custom processing during code analysis, handle special decorators, implement domain-specific analysis, and extend Griffe's understanding of Python constructs.

Capabilities

Extension Loading

Functions for loading and managing extensions from various sources.

def load_extensions(
    extensions: list[LoadableExtensionType] | None = None
) -> Extensions:
    """
    Load configured extensions from various sources.
    
    Supports loading built-in extensions, installed packages, and custom
    extension classes. Extensions are executed during different phases
    of the analysis process.
    
    Args:
        extensions: List of extension specifications including:
            - String names for built-in extensions ("dataclasses")
            - Import paths for installed extensions ("package.module:ExtensionClass")
            - Extension class instances
            - Extension class types
            
    Returns:
        Extensions: Container with loaded and configured extensions
        
    Raises:
        ExtensionNotLoadedError: If extension cannot be loaded
        
    Examples:
        Load built-in extensions:
        >>> extensions = griffe.load_extensions(["dataclasses"])
        
        Load custom extensions:
        >>> extensions = griffe.load_extensions([
        ...     "dataclasses",  # built-in
        ...     "mypackage.extensions:CustomAnalyzer",  # installed  
        ...     MyExtension(),  # instance
        ... ])
    """

# Type alias for extension specifications
LoadableExtensionType = (
    str |                    # Extension name or import path
    type[Extension] |        # Extension class  
    Extension |              # Extension instance
    dict[str, Any]          # Extension configuration
)

# Built-in extensions registry
builtin_extensions: dict[str, type[Extension]]
"""Dictionary of built-in extensions available in Griffe."""

Base Extension Class

Foundation class for creating custom extensions with lifecycle hooks.

class Extension:
    """
    Base class for Griffe extensions.
    
    Allows custom processing during code analysis by providing hooks
    at different stages of the loading and analysis process.
    """
    
    def __init__(self, **options: Any) -> None:
        """
        Initialize the extension.
        
        Args:
            **options: Extension-specific configuration options
        """
    
    def on_package_loaded(self, *, pkg: Module) -> None:
        """
        Hook called when a package is loaded.
        
        Called after a top-level package (module) has been fully loaded
        and all its contents analyzed. Use this for package-level processing.
        
        Args:
            pkg: The loaded package module
        """
    
    def on_module_loaded(self, *, mod: Module) -> None:
        """
        Hook called when a module is loaded.
        
        Called after a module has been loaded and its contents analyzed.
        Includes both top-level packages and submodules.
        
        Args:
            mod: The loaded module
        """
    
    def on_class_loaded(self, *, cls: Class) -> None:
        """
        Hook called when a class is loaded.
        
        Called after a class definition has been analyzed and all its
        methods, attributes, and nested classes have been processed.
        
        Args:
            cls: The loaded class
        """
    
    def on_function_loaded(self, *, func: Function) -> None:
        """
        Hook called when a function is loaded.
        
        Called after a function or method has been analyzed, including
        parameter extraction and signature processing.
        
        Args:
            func: The loaded function or method
        """
    
    def on_attribute_loaded(self, *, attr: Attribute) -> None:
        """
        Hook called when an attribute is loaded.
        
        Called after an attribute (variable, class attribute, etc.)
        has been analyzed and its type and value determined.
        
        Args:
            attr: The loaded attribute
        """
    
    def on_alias_loaded(self, *, alias: Alias) -> None:
        """
        Hook called when an alias is loaded.
        
        Called after an import alias has been processed and its
        target path determined.
        
        Args:
            alias: The loaded alias
        """

Extensions Container

Container class for managing multiple extensions and their execution.

class Extensions:
    """
    Container class for managing multiple extensions.
    
    Manages a collection of extensions and provides methods to execute
    their hooks at appropriate times during analysis.
    """
    
    def __init__(self, extensions: list[Extension] | None = None) -> None:
        """
        Initialize extensions container.
        
        Args:
            extensions: List of extension instances
        """
    
    def add(self, extension: Extension) -> None:
        """
        Add an extension to the container.
        
        Args:
            extension: Extension instance to add
        """
    
    def call(self, method: str, **kwargs: Any) -> None:
        """
        Call a method on all extensions.
        
        Args:
            method: Method name to call (e.g., "on_class_loaded")
            **kwargs: Arguments to pass to the method
        """
    
    def __iter__(self) -> Iterator[Extension]:
        """Iterate over extensions."""
    
    def __len__(self) -> int:
        """Number of extensions."""

Built-in Extensions

Dataclasses Extension

Built-in extension for enhanced analysis of Python dataclasses.

class DataclassesExtension(Extension):
    """
    Built-in extension for handling Python dataclasses.
    
    Provides enhanced analysis of dataclass decorators, field definitions,
    and generated methods. Automatically detects dataclass usage and
    extracts field information with types and defaults.
    """
    
    def __init__(self, **options: Any) -> None:
        """
        Initialize dataclasses extension.
        
        Args:
            **options: Configuration options for dataclass analysis
        """
    
    def on_class_loaded(self, *, cls: Class) -> None:
        """
        Process dataclass when class is loaded.
        
        Analyzes @dataclass decorators and extracts field information,
        adding synthetic attributes for dataclass fields.
        """

Usage Examples

Using Built-in Extensions

import griffe

# Load with dataclasses extension
extensions = griffe.load_extensions(["dataclasses"])
loader = griffe.GriffeLoader(extensions=extensions)

# Load a package that uses dataclasses
package = loader.load("mypackage")

# The dataclasses extension will have processed any @dataclass decorators
for class_name, cls in package.classes.items():
    if any("dataclass" in str(dec.value) for dec in cls.decorators):
        print(f"Dataclass: {class_name}")
        print(f"  Fields: {list(cls.attributes.keys())}")

Creating Custom Extensions

import griffe
from griffe import Extension, Class, Function

class LoggingExtension(Extension):
    """Extension that logs all loaded objects."""
    
    def __init__(self, log_level="INFO", **options):
        super().__init__(**options)
        self.log_level = log_level
        self.stats = {"modules": 0, "classes": 0, "functions": 0}
    
    def on_module_loaded(self, *, mod):
        self.stats["modules"] += 1
        print(f"[{self.log_level}] Loaded module: {mod.path}")
    
    def on_class_loaded(self, *, cls):
        self.stats["classes"] += 1
        print(f"[{self.log_level}] Loaded class: {cls.path}")
        
        # Log inheritance information
        if cls.bases:
            base_names = [str(base) for base in cls.bases]
            print(f"  Inherits from: {', '.join(base_names)}")
    
    def on_function_loaded(self, *, func):
        self.stats["functions"] += 1
        print(f"[{self.log_level}] Loaded function: {func.path}")
        
        # Log parameter information
        param_count = len(func.parameters)
        print(f"  Parameters: {param_count}")

# Use the custom extension
logging_ext = LoggingExtension(log_level="DEBUG")
extensions = griffe.Extensions([logging_ext])

loader = griffe.GriffeLoader(extensions=extensions)
package = loader.load("requests")

print("Final stats:", logging_ext.stats)

Domain-Specific Analysis Extension

import griffe
from griffe import Extension, Function, Class

class FastAPIExtension(Extension):
    """Extension for analyzing FastAPI applications."""
    
    def __init__(self, **options):
        super().__init__(**options)
        self.routes = []
        self.dependencies = []
        self.middleware = []
    
    def on_function_loaded(self, *, func: Function):
        """Analyze FastAPI route decorators."""
        for decorator in func.decorators:
            decorator_str = str(decorator.value)
            
            # Check for route decorators
            if any(method in decorator_str for method in ["get", "post", "put", "delete", "patch"]):
                route_info = {
                    "function": func.path,
                    "decorator": decorator_str,
                    "parameters": [p.name for p in func.parameters],
                    "returns": str(func.returns) if func.returns else None
                }
                self.routes.append(route_info)
                print(f"Found route: {func.name} -> {decorator_str}")
            
            # Check for dependency injection
            elif "Depends" in decorator_str:
                self.dependencies.append({
                    "function": func.path,
                    "dependency": decorator_str
                })
    
    def on_class_loaded(self, *, cls: Class):
        """Analyze FastAPI middleware classes."""
        for decorator in cls.decorators:
            if "middleware" in str(decorator.value).lower():
                self.middleware.append({
                    "class": cls.path,
                    "decorator": str(decorator.value)
                })
    
    def get_api_summary(self):
        """Get summary of FastAPI application structure."""
        return {
            "routes": len(self.routes),
            "dependencies": len(self.dependencies),
            "middleware": len(self.middleware),
            "route_details": self.routes
        }

# Use FastAPI extension
fastapi_ext = FastAPIExtension()
extensions = griffe.Extensions([fastapi_ext])

loader = griffe.GriffeLoader(extensions=extensions)
app_module = loader.load("myapp")

# Get API analysis results
summary = fastapi_ext.get_api_summary()
print(f"FastAPI Analysis: {summary['routes']} routes, {summary['dependencies']} dependencies")

Type Annotation Extension

import griffe
from griffe import Extension, Function, Attribute
import ast

class TypeAnnotationExtension(Extension):
    """Extension for enhanced type annotation analysis."""
    
    def __init__(self, **options):
        super().__init__(**options)
        self.type_stats = {
            "annotated_functions": 0,
            "unannotated_functions": 0,
            "annotated_attributes": 0,
            "complex_types": 0
        }
    
    def on_function_loaded(self, *, func: Function):
        """Analyze function type annotations."""
        if func.returns or any(p.annotation for p in func.parameters):
            self.type_stats["annotated_functions"] += 1
            
            # Check for complex return types
            if func.returns:
                return_str = str(func.returns)
                if any(keyword in return_str for keyword in ["Union", "Optional", "Generic", "TypeVar"]):
                    self.type_stats["complex_types"] += 1
                    print(f"Complex return type in {func.name}: {return_str}")
        else:
            self.type_stats["unannotated_functions"] += 1
    
    def on_attribute_loaded(self, *, attr: Attribute):
        """Analyze attribute type annotations."""
        if attr.annotation:
            self.type_stats["annotated_attributes"] += 1
            
            # Check for complex attribute types
            attr_str = str(attr.annotation)
            if any(keyword in attr_str for keyword in ["Union", "Optional", "List", "Dict"]):
                print(f"Complex attribute type: {attr.path}: {attr_str}")
    
    def get_type_coverage(self):
        """Calculate type annotation coverage."""
        total_functions = self.type_stats["annotated_functions"] + self.type_stats["unannotated_functions"]
        if total_functions > 0:
            coverage = (self.type_stats["annotated_functions"] / total_functions) * 100
            return {
                "function_coverage": round(coverage, 2),
                "annotated_functions": self.type_stats["annotated_functions"],
                "total_functions": total_functions,
                "complex_types": self.type_stats["complex_types"],
                "annotated_attributes": self.type_stats["annotated_attributes"]
            }
        return {"function_coverage": 0, "total_functions": 0}

# Use type annotation extension
type_ext = TypeAnnotationExtension()
extensions = griffe.Extensions([type_ext])

loader = griffe.GriffeLoader(extensions=extensions)
package = loader.load("mypackage")

coverage = type_ext.get_type_coverage()
print(f"Type annotation coverage: {coverage['function_coverage']}%")
print(f"Complex types found: {coverage['complex_types']}")

Extension with Configuration

import griffe
from griffe import Extension

class ConfigurableExtension(Extension):
    """Extension with comprehensive configuration options."""
    
    def __init__(
        self,
        track_private=False,
        ignore_patterns=None,
        output_file=None,
        **options
    ):
        super().__init__(**options)
        self.track_private = track_private
        self.ignore_patterns = ignore_patterns or []
        self.output_file = output_file
        self.data = []
    
    def should_process(self, obj_path: str) -> bool:
        """Check if object should be processed based on configuration."""
        # Skip private objects if not tracking them
        if not self.track_private and any(part.startswith("_") for part in obj_path.split(".")):
            return False
        
        # Skip ignored patterns
        if any(pattern in obj_path for pattern in self.ignore_patterns):
            return False
        
        return True
    
    def on_function_loaded(self, *, func):
        if self.should_process(func.path):
            self.data.append({
                "type": "function",
                "path": func.path,
                "parameters": len(func.parameters),
                "has_docstring": func.docstring is not None
            })
    
    def on_class_loaded(self, *, cls):
        if self.should_process(cls.path):
            self.data.append({
                "type": "class", 
                "path": cls.path,
                "methods": len(cls.methods),
                "attributes": len(cls.attributes),
                "bases": len(cls.bases)
            })
    
    def finalize(self):
        """Called after all loading is complete."""
        if self.output_file:
            import json
            with open(self.output_file, "w") as f:
                json.dump(self.data, f, indent=2)
            print(f"Extension data written to {self.output_file}")

# Configure and use extension
ext = ConfigurableExtension(
    track_private=False,
    ignore_patterns=["test_", "_internal"],
    output_file="analysis_results.json"
)

extensions = griffe.Extensions([ext])
loader = griffe.GriffeLoader(extensions=extensions)
package = loader.load("mypackage")

# Finalize extension processing
ext.finalize()

Advanced Extension Patterns

Multi-Phase Extension

import griffe
from griffe import Extension

class MultiPhaseExtension(Extension):
    """Extension that processes objects in multiple phases."""
    
    def __init__(self, **options):
        super().__init__(**options)
        self.phase = "collection"
        self.collected_objects = {"classes": [], "functions": []}
        self.relationships = []
    
    def on_class_loaded(self, *, cls):
        if self.phase == "collection":
            self.collected_objects["classes"].append(cls)
    
    def on_function_loaded(self, *, func):
        if self.phase == "collection":
            self.collected_objects["functions"].append(func)
    
    def on_package_loaded(self, *, pkg):
        """Switch to relationship analysis phase."""
        if self.phase == "collection":
            self.phase = "analysis"
            self._analyze_relationships()
    
    def _analyze_relationships(self):
        """Analyze relationships between collected objects."""
        for cls in self.collected_objects["classes"]:
            # Check inheritance relationships
            for base in cls.bases:
                base_name = str(base)
                matching_classes = [
                    c for c in self.collected_objects["classes"]
                    if c.name == base_name.split(".")[-1]
                ]
                if matching_classes:
                    self.relationships.append({
                        "type": "inheritance",
                        "child": cls.path,
                        "parent": matching_classes[0].path
                    })
        
        print(f"Found {len(self.relationships)} relationships")

# Use multi-phase extension
multi_ext = MultiPhaseExtension()
extensions = griffe.Extensions([multi_ext])

loader = griffe.GriffeLoader(extensions=extensions)
package = loader.load("mypackage")

print("Relationships:", multi_ext.relationships)

Types

from typing import Any, Iterator, Union

# Extension type specifications
LoadableExtensionType = Union[
    str,                    # Extension name or import path
    type[Extension],        # Extension class
    Extension,              # Extension instance  
    dict[str, Any]         # Extension configuration
]

# Core extension classes
class Extension: ...
class Extensions: ...

# Built-in extensions registry
builtin_extensions: dict[str, type[Extension]]

# Built-in extension classes
class DataclassesExtension(Extension): ...

# Core object types from models
from griffe import Module, Class, Function, Attribute, Alias

Install with Tessl CLI

npx tessl i tessl/pypi-griffe

docs

agents.md

breaking-changes.md

cli.md

docstrings.md

extensions.md

index.md

loaders.md

models.md

serialization.md

tile.json