-
-
Notifications
You must be signed in to change notification settings - Fork 779
Add plugins support #728
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Add plugins support #728
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -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 | ||||||
|
|
@@ -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 | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| :param old_style_options: Old style options support for backward compatibility. Preference is | ||||||
| what is defined in `options` parameter. | ||||||
| """ | ||||||
|
|
@@ -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() | ||||||
|
|
||||||
|
|
@@ -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 | ||||||
|
|
||||||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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, | ||
|
|
@@ -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 | ||
|
|
||
|
|
@@ -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 | ||
|
|
@@ -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) | ||
|
|
@@ -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: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why the for loop? |
||
| 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) | ||
|
|
@@ -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): | ||
| """ | ||
|
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.