CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-apispec

A pluggable API specification generator for OpenAPI/Swagger specifications

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

plugin-system.mddocs/

Plugin System

Extensible plugin architecture for integrating APISpec with web frameworks and schema libraries. Plugins provide lifecycle hooks for processing paths, operations, and components during specification generation.

Capabilities

BasePlugin Class

Abstract base class for creating APISpec plugins. Provides helper methods that are called during specification generation to process and transform components.

class BasePlugin:
    def init_spec(self, spec: APISpec) -> None:
        """
        Initialize plugin with APISpec object.
        
        Parameters:
        - spec: APISpec object this plugin instance is attached to
        
        Called once when plugin is registered with APISpec instance.
        Use this to store references and initialize plugin state.
        """

Schema Processing

Process schema components during registration, allowing plugins to transform or enhance schema definitions.

def schema_helper(
    self, 
    name: str, 
    definition: dict, 
    **kwargs: Any
) -> dict | None:
    """
    Process schema component definition.
    
    Parameters:
    - name: Identifier by which schema may be referenced
    - definition: Schema definition dictionary
    - **kwargs: Additional keyword arguments sent to APISpec.components.schema()
    
    Returns:
    Modified schema definition dictionary or None
    
    Raises:
    - PluginMethodNotImplementedError: If method not implemented by plugin
    """

Response Processing

Process response components during registration.

def response_helper(self, response: dict, **kwargs: Any) -> dict | None:
    """
    Process response component definition.
    
    Parameters:
    - response: Response definition dictionary
    - **kwargs: Additional keyword arguments sent to APISpec.components.response()
    
    Returns:
    Modified response definition dictionary or None
    
    Raises:
    - PluginMethodNotImplementedError: If method not implemented by plugin
    """

Parameter Processing

Process parameter components during registration.

def parameter_helper(self, parameter: dict, **kwargs: Any) -> dict | None:
    """
    Process parameter component definition.
    
    Parameters:
    - parameter: Parameter definition dictionary  
    - **kwargs: Additional keyword arguments sent to APISpec.components.parameter()
    
    Returns:
    Modified parameter definition dictionary or None
    
    Raises:
    - PluginMethodNotImplementedError: If method not implemented by plugin
    """

Header Processing

Process header components during registration.

def header_helper(self, header: dict, **kwargs: Any) -> dict | None:
    """
    Process header component definition.
    
    Parameters:
    - header: Header definition dictionary
    - **kwargs: Additional keyword arguments sent to APISpec.components.header()
    
    Returns:
    Modified header definition dictionary or None
    
    Raises:
    - PluginMethodNotImplementedError: If method not implemented by plugin
    """

Path Processing

Process path definitions, allowing plugins to extract path information from framework-specific sources.

def path_helper(
    self,
    path: str | None = None,
    operations: dict | None = None,
    parameters: list[dict] | None = None,
    **kwargs: Any
) -> str | None:
    """
    Process path definition and extract path string.
    
    Parameters:
    - path: Path to the resource  
    - operations: Dictionary mapping HTTP methods to operation objects
    - parameters: List of parameters for the path
    - **kwargs: Additional keyword arguments sent to APISpec.path()
    
    Returns:
    Path string or None. If string returned, it sets the path value.
    
    Raises:
    - PluginMethodNotImplementedError: If method not implemented by plugin
    
    Note:
    The last path helper returning a string sets the final path value.
    Plugin registration order matters for path helpers.
    """

Operation Processing

Process operation definitions within paths, allowing plugins to extract operation information from framework-specific sources.

def operation_helper(
    self,
    path: str | None = None,
    operations: dict | None = None,
    **kwargs: Any
) -> None:
    """
    Process operations dictionary, modifying operations in-place.
    
    Parameters:
    - path: Path to the resource
    - operations: Dictionary mapping HTTP methods to operation objects
    - **kwargs: Additional keyword arguments sent to APISpec.path()
    
    Raises:
    - PluginMethodNotImplementedError: If method not implemented by plugin
    
    Note:
    This method should modify the operations dictionary in-place.
    """

Exception Handling

Plugin-specific exception for unimplemented methods.

class PluginMethodNotImplementedError(APISpecError, NotImplementedError):
    """Raised when calling an unimplemented helper method in a plugin."""

Usage Examples

Creating a Custom Plugin

from apispec import BasePlugin, APISpec

class CustomFrameworkPlugin(BasePlugin):
    def init_spec(self, spec: APISpec) -> None:
        """Initialize plugin with spec reference."""
        self.spec = spec
        self.framework_routes = self.discover_routes()
    
    def path_helper(self, path=None, operations=None, **kwargs):
        """Extract path from framework route object."""
        route_obj = kwargs.get('route')
        if route_obj:
            return route_obj.get_path_template()
        return path
    
    def operation_helper(self, path=None, operations=None, **kwargs):
        """Extract operations from framework route handlers."""
        route_obj = kwargs.get('route')
        if route_obj and operations:
            # Extract operation info from route handlers
            for method, handler in route_obj.get_handlers().items():
                if method.lower() in operations:
                    # Add framework-specific operation metadata
                    operations[method.lower()].update({
                        'operationId': handler.__name__,
                        'summary': getattr(handler, '__doc__', '').split('\n')[0]
                    })
    
    def discover_routes(self):
        """Framework-specific route discovery logic."""
        # Implementation depends on the framework
        pass

Using Plugins with APISpec

from apispec import APISpec
from apispec.ext.marshmallow import MarshmallowPlugin

# Create spec with multiple plugins
spec = APISpec(
    title="My API",
    version="1.0.0", 
    openapi_version="3.0.2",
    plugins=[
        CustomFrameworkPlugin(),
        MarshmallowPlugin()
    ]
)

# Plugins process components and paths during registration
spec.components.schema("User", schema=UserSchema)  # MarshmallowPlugin processes this
spec.path("/users", route=user_route_obj)  # CustomFrameworkPlugin processes this

Framework Integration Plugin

class FlaskPlugin(BasePlugin):
    def path_helper(self, path=None, operations=None, **kwargs):
        """Extract path from Flask view function."""
        view_function = kwargs.get('view_function')
        if view_function:
            # Get Flask route rules for this view
            rules = [rule for rule in current_app.url_map.iter_rules() 
                    if rule.endpoint == view_function.__name__]
            if rules:
                return rules[0].rule
        return path
    
    def operation_helper(self, path=None, operations=None, **kwargs):
        """Extract operations from Flask view function."""
        view_function = kwargs.get('view_function')
        if view_function and operations:
            # Parse docstring for OpenAPI spec
            from apispec.yaml_utils import load_operations_from_docstring
            doc_operations = load_operations_from_docstring(view_function.__doc__ or '')
            operations.update(doc_operations)

# Usage with Flask
@app.route('/users/<int:user_id>')
def get_user(user_id):
    """Get user by ID.
    ---
    get:
      parameters:
        - name: user_id
          in: path
          schema:
            type: integer
      responses:
        200:
          description: User found
    """
    pass

# Register with APISpec
spec.path(view_function=get_user)

Schema Processing Plugin

class SchemaValidationPlugin(BasePlugin):
    def schema_helper(self, name, definition, **kwargs):
        """Add validation metadata to schema definitions."""
        if definition and isinstance(definition, dict):
            # Add schema validation rules
            definition = definition.copy()
            definition.setdefault('additionalProperties', False)
            
            # Add pattern validation for string fields
            if 'properties' in definition:
                for prop_name, prop_def in definition['properties'].items():
                    if prop_def.get('type') == 'string' and 'email' in prop_name.lower():
                        prop_def['format'] = 'email'
            
            return definition
        return None

Install with Tessl CLI

npx tessl i tessl/pypi-apispec

docs

components.md

core-spec.md

index.md

marshmallow.md

plugin-system.md

utils.md

tile.json