0
# Plugin System
1
2
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.
3
4
## Capabilities
5
6
### BasePlugin Class
7
8
Abstract base class for creating APISpec plugins. Provides helper methods that are called during specification generation to process and transform components.
9
10
```python { .api }
11
class BasePlugin:
12
def init_spec(self, spec: APISpec) -> None:
13
"""
14
Initialize plugin with APISpec object.
15
16
Parameters:
17
- spec: APISpec object this plugin instance is attached to
18
19
Called once when plugin is registered with APISpec instance.
20
Use this to store references and initialize plugin state.
21
"""
22
```
23
24
### Schema Processing
25
26
Process schema components during registration, allowing plugins to transform or enhance schema definitions.
27
28
```python { .api }
29
def schema_helper(
30
self,
31
name: str,
32
definition: dict,
33
**kwargs: Any
34
) -> dict | None:
35
"""
36
Process schema component definition.
37
38
Parameters:
39
- name: Identifier by which schema may be referenced
40
- definition: Schema definition dictionary
41
- **kwargs: Additional keyword arguments sent to APISpec.components.schema()
42
43
Returns:
44
Modified schema definition dictionary or None
45
46
Raises:
47
- PluginMethodNotImplementedError: If method not implemented by plugin
48
"""
49
```
50
51
### Response Processing
52
53
Process response components during registration.
54
55
```python { .api }
56
def response_helper(self, response: dict, **kwargs: Any) -> dict | None:
57
"""
58
Process response component definition.
59
60
Parameters:
61
- response: Response definition dictionary
62
- **kwargs: Additional keyword arguments sent to APISpec.components.response()
63
64
Returns:
65
Modified response definition dictionary or None
66
67
Raises:
68
- PluginMethodNotImplementedError: If method not implemented by plugin
69
"""
70
```
71
72
### Parameter Processing
73
74
Process parameter components during registration.
75
76
```python { .api }
77
def parameter_helper(self, parameter: dict, **kwargs: Any) -> dict | None:
78
"""
79
Process parameter component definition.
80
81
Parameters:
82
- parameter: Parameter definition dictionary
83
- **kwargs: Additional keyword arguments sent to APISpec.components.parameter()
84
85
Returns:
86
Modified parameter definition dictionary or None
87
88
Raises:
89
- PluginMethodNotImplementedError: If method not implemented by plugin
90
"""
91
```
92
93
### Header Processing
94
95
Process header components during registration.
96
97
```python { .api }
98
def header_helper(self, header: dict, **kwargs: Any) -> dict | None:
99
"""
100
Process header component definition.
101
102
Parameters:
103
- header: Header definition dictionary
104
- **kwargs: Additional keyword arguments sent to APISpec.components.header()
105
106
Returns:
107
Modified header definition dictionary or None
108
109
Raises:
110
- PluginMethodNotImplementedError: If method not implemented by plugin
111
"""
112
```
113
114
### Path Processing
115
116
Process path definitions, allowing plugins to extract path information from framework-specific sources.
117
118
```python { .api }
119
def path_helper(
120
self,
121
path: str | None = None,
122
operations: dict | None = None,
123
parameters: list[dict] | None = None,
124
**kwargs: Any
125
) -> str | None:
126
"""
127
Process path definition and extract path string.
128
129
Parameters:
130
- path: Path to the resource
131
- operations: Dictionary mapping HTTP methods to operation objects
132
- parameters: List of parameters for the path
133
- **kwargs: Additional keyword arguments sent to APISpec.path()
134
135
Returns:
136
Path string or None. If string returned, it sets the path value.
137
138
Raises:
139
- PluginMethodNotImplementedError: If method not implemented by plugin
140
141
Note:
142
The last path helper returning a string sets the final path value.
143
Plugin registration order matters for path helpers.
144
"""
145
```
146
147
### Operation Processing
148
149
Process operation definitions within paths, allowing plugins to extract operation information from framework-specific sources.
150
151
```python { .api }
152
def operation_helper(
153
self,
154
path: str | None = None,
155
operations: dict | None = None,
156
**kwargs: Any
157
) -> None:
158
"""
159
Process operations dictionary, modifying operations in-place.
160
161
Parameters:
162
- path: Path to the resource
163
- operations: Dictionary mapping HTTP methods to operation objects
164
- **kwargs: Additional keyword arguments sent to APISpec.path()
165
166
Raises:
167
- PluginMethodNotImplementedError: If method not implemented by plugin
168
169
Note:
170
This method should modify the operations dictionary in-place.
171
"""
172
```
173
174
## Exception Handling
175
176
Plugin-specific exception for unimplemented methods.
177
178
```python { .api }
179
class PluginMethodNotImplementedError(APISpecError, NotImplementedError):
180
"""Raised when calling an unimplemented helper method in a plugin."""
181
```
182
183
## Usage Examples
184
185
### Creating a Custom Plugin
186
187
```python
188
from apispec import BasePlugin, APISpec
189
190
class CustomFrameworkPlugin(BasePlugin):
191
def init_spec(self, spec: APISpec) -> None:
192
"""Initialize plugin with spec reference."""
193
self.spec = spec
194
self.framework_routes = self.discover_routes()
195
196
def path_helper(self, path=None, operations=None, **kwargs):
197
"""Extract path from framework route object."""
198
route_obj = kwargs.get('route')
199
if route_obj:
200
return route_obj.get_path_template()
201
return path
202
203
def operation_helper(self, path=None, operations=None, **kwargs):
204
"""Extract operations from framework route handlers."""
205
route_obj = kwargs.get('route')
206
if route_obj and operations:
207
# Extract operation info from route handlers
208
for method, handler in route_obj.get_handlers().items():
209
if method.lower() in operations:
210
# Add framework-specific operation metadata
211
operations[method.lower()].update({
212
'operationId': handler.__name__,
213
'summary': getattr(handler, '__doc__', '').split('\n')[0]
214
})
215
216
def discover_routes(self):
217
"""Framework-specific route discovery logic."""
218
# Implementation depends on the framework
219
pass
220
```
221
222
### Using Plugins with APISpec
223
224
```python
225
from apispec import APISpec
226
from apispec.ext.marshmallow import MarshmallowPlugin
227
228
# Create spec with multiple plugins
229
spec = APISpec(
230
title="My API",
231
version="1.0.0",
232
openapi_version="3.0.2",
233
plugins=[
234
CustomFrameworkPlugin(),
235
MarshmallowPlugin()
236
]
237
)
238
239
# Plugins process components and paths during registration
240
spec.components.schema("User", schema=UserSchema) # MarshmallowPlugin processes this
241
spec.path("/users", route=user_route_obj) # CustomFrameworkPlugin processes this
242
```
243
244
### Framework Integration Plugin
245
246
```python
247
class FlaskPlugin(BasePlugin):
248
def path_helper(self, path=None, operations=None, **kwargs):
249
"""Extract path from Flask view function."""
250
view_function = kwargs.get('view_function')
251
if view_function:
252
# Get Flask route rules for this view
253
rules = [rule for rule in current_app.url_map.iter_rules()
254
if rule.endpoint == view_function.__name__]
255
if rules:
256
return rules[0].rule
257
return path
258
259
def operation_helper(self, path=None, operations=None, **kwargs):
260
"""Extract operations from Flask view function."""
261
view_function = kwargs.get('view_function')
262
if view_function and operations:
263
# Parse docstring for OpenAPI spec
264
from apispec.yaml_utils import load_operations_from_docstring
265
doc_operations = load_operations_from_docstring(view_function.__doc__ or '')
266
operations.update(doc_operations)
267
268
# Usage with Flask
269
@app.route('/users/<int:user_id>')
270
def get_user(user_id):
271
"""Get user by ID.
272
---
273
get:
274
parameters:
275
- name: user_id
276
in: path
277
schema:
278
type: integer
279
responses:
280
200:
281
description: User found
282
"""
283
pass
284
285
# Register with APISpec
286
spec.path(view_function=get_user)
287
```
288
289
### Schema Processing Plugin
290
291
```python
292
class SchemaValidationPlugin(BasePlugin):
293
def schema_helper(self, name, definition, **kwargs):
294
"""Add validation metadata to schema definitions."""
295
if definition and isinstance(definition, dict):
296
# Add schema validation rules
297
definition = definition.copy()
298
definition.setdefault('additionalProperties', False)
299
300
# Add pattern validation for string fields
301
if 'properties' in definition:
302
for prop_name, prop_def in definition['properties'].items():
303
if prop_def.get('type') == 'string' and 'email' in prop_name.lower():
304
prop_def['format'] = 'email'
305
306
return definition
307
return None
308
```