Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions connexion/apis/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ class AbstractAPI(object):
def __init__(self, specification, base_path=None, arguments=None,
validate_responses=False, strict_validation=False, resolver=None,
auth_all_paths=False, debug=False, resolver_error_handler=None,
validator_map=None, pythonic_params=False, options=None, pass_context_arg_name=None,
validator_map=None, pythonic_params=False, options=None,
pass_context_arg_name=None, plugins=None,
**old_style_options):
"""
:type specification: pathlib.Path | dict
Expand All @@ -65,6 +66,8 @@ def __init__(self, specification, base_path=None, arguments=None,
:param pass_context_arg_name: If not None URL request handling functions with an argument matching this name
will be passed the framework's request context.
:type pass_context_arg_name: str | None
:param plugins: list of plugin classes
:type plugins: list

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
:type plugins: list
:type plugins: list | None

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
:type plugins: list
:type plugins: list | None

:param old_style_options: Old style options support for backward compatibility. Preference is
what is defined in `options` parameter.
"""
Expand Down Expand Up @@ -137,6 +140,9 @@ def __init__(self, specification, base_path=None, arguments=None,
logger.debug('pass_context_arg_name: %s', pass_context_arg_name)
self.pass_context_arg_name = pass_context_arg_name

logger.debug('plugins: %s', plugins)
self.plugins = plugins

if self.options.openapi_spec_available:
self.add_swagger_json()

Expand Down Expand Up @@ -212,7 +218,8 @@ def add_operation(self, method, path, swagger_operation, path_parameters):
resolver=self.resolver,
pythonic_params=self.pythonic_params,
uri_parser_class=self.options.uri_parser_class,
pass_context_arg_name=self.pass_context_arg_name)
pass_context_arg_name=self.pass_context_arg_name,
plugins=self.plugins)
self._add_operation_internal(method, path, operation)

@abc.abstractmethod
Expand Down
62 changes: 62 additions & 0 deletions connexion/decorators/plugins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import functools
import logging

logger = logging.getLogger('connexion.decorators.plugins')


class BasePlugin(object):
"""Define the standard plugin interface.

To implement a new plugin, inherit from the base class
and override the hook functions.
"""

def __init__(self, api):
"""Initialize the plugin."""
self.api = api

def before(self, request):
"""Hook method - called before the request is handled"""
pass # pragma: no cover

def after(self, request, response):
"""Hook method - called after the request is handled"""
pass # pragma: no cover


class PluginsDecorator(object):
"""Plugins decorator composed of multiple plugins."""

def __init__(self, api, plugin_classes):
"""Initialize the plugins decorator.

:param plugin_classes: list of plugin classes
:type plugin_classes: list
"""
self.api = api
self.plugins = [plugin_class(api=api)
for plugin_class in plugin_classes]

def __call__(self, function):
"""
:type function: types.FunctionType
:rtype: types.FunctionType
"""

@functools.wraps(function)
def wrapper(request):
"""Wrap operation with plugins logic."""
response = None

logger.debug("%s running plugins 'before' ...", request.url)
for plugin in self.plugins:
plugin.before(request)
try:
response = function(request)
return response
finally:
logger.debug("%s running plugins 'after' ...", request.url)
for plugin in self.plugins:
plugin.after(request, response)

return wrapper
21 changes: 20 additions & 1 deletion connexion/operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
EndOfRequestLifecycleDecorator)
from .decorators.metrics import UWSGIMetricsCollector
from .decorators.parameter import parameter_to_arg
from .decorators.plugins import PluginsDecorator
from .decorators.produces import BaseSerializer, Produces
from .decorators.response import ResponseValidator
from .decorators.security import (get_tokeninfo_func, get_tokeninfo_url,
Expand Down Expand Up @@ -139,7 +140,7 @@ def __init__(self, api, method, path, operation, resolver, app_produces, app_con
definitions=None, parameter_definitions=None, response_definitions=None,
validate_responses=False, strict_validation=False, randomize_endpoint=None,
validator_map=None, pythonic_params=False, uri_parser_class=None,
pass_context_arg_name=None):
pass_context_arg_name=None, plugins=None):
"""
This class uses the OperationID identify the module and function that will handle the operation

Expand Down Expand Up @@ -191,6 +192,8 @@ def __init__(self, api, method, path, operation, resolver, app_produces, app_con
:param pass_context_arg_name: If not None will try to inject the request context to the function using this
name.
:type pass_context_arg_name: str|None
:param plugins: list of plugin classes
:type plugins: list
"""

self.api = api
Expand Down Expand Up @@ -229,6 +232,8 @@ def __init__(self, api, method, path, operation, resolver, app_produces, app_con
self.operation_id = resolution.operation_id
self.__undecorated_function = resolution.function

self.plugins = plugins or []

def resolve_reference(self, schema):
schema = deepcopy(schema) # avoid changing the original schema
self.check_references(schema)
Expand Down Expand Up @@ -387,6 +392,10 @@ def function(self):
logger.debug('... Adding uri parsing decorator (%r)', uri_parsing_decorator)
function = uri_parsing_decorator(function)

for plugins_decorator in self.__plugins_decorator:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the for loop?
self.__plugins_decorator yields once.

logger.debug('... Adding plugins decorator (%r)', plugins_decorator)
function = plugins_decorator(function)

# NOTE: the security decorator should be applied last to check auth before anything else :-)
security_decorator = self.security_decorator
logger.debug('... Adding security decorator (%r)', security_decorator)
Expand Down Expand Up @@ -458,6 +467,16 @@ def __validation_decorators(self):
yield RequestBodyValidator(self.body_schema, self.consumes, self.api,
is_nullable(self.body_definition))

@property
def __plugins_decorator(self):
"""
Get plugins decorator.

:yields: types.FunctionType
"""
if self.plugins:
yield PluginsDecorator(api=self.api, plugin_classes=self.plugins)

@property
def __response_validation_decorator(self):
"""
Expand Down