A pluggable API specification generator for OpenAPI/Swagger specifications
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
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.
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.
"""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
"""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
"""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
"""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
"""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.
"""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.
"""Plugin-specific exception for unimplemented methods.
class PluginMethodNotImplementedError(APISpecError, NotImplementedError):
"""Raised when calling an unimplemented helper method in a 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
passfrom 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 thisclass 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)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 NoneInstall with Tessl CLI
npx tessl i tessl/pypi-apispec