mirror of
https://github.com/LukeHagar/connexion.git
synced 2025-12-09 20:37:46 +00:00
New style of passing options to Connexion (#436)
* Order classes by relevance in module * Order definitions by relevance within module * Swagger UI options extracted * New style options * Use new-style options * Reuse code * Sort imports * Ignore typing imports * Warn users about parameter name change * Add back isort check * Fix isort check
This commit is contained in:
committed by
Henning Jacobs
parent
19e0b37194
commit
93c06711ed
@@ -3,6 +3,7 @@ import copy
|
|||||||
import logging
|
import logging
|
||||||
import pathlib
|
import pathlib
|
||||||
import sys
|
import sys
|
||||||
|
from typing import AnyStr, List # NOQA
|
||||||
|
|
||||||
import jinja2
|
import jinja2
|
||||||
import six
|
import six
|
||||||
@@ -11,6 +12,7 @@ from swagger_spec_validator.validator20 import validate_spec
|
|||||||
|
|
||||||
from ..exceptions import ResolverError
|
from ..exceptions import ResolverError
|
||||||
from ..operation import Operation
|
from ..operation import Operation
|
||||||
|
from ..options import ConnexionOptions
|
||||||
from ..resolver import Resolver
|
from ..resolver import Resolver
|
||||||
|
|
||||||
MODULE_PATH = pathlib.Path(__file__).absolute().parent.parent
|
MODULE_PATH = pathlib.Path(__file__).absolute().parent.parent
|
||||||
@@ -19,35 +21,7 @@ SWAGGER_UI_URL = 'ui'
|
|||||||
|
|
||||||
RESOLVER_ERROR_ENDPOINT_RANDOM_DIGITS = 6
|
RESOLVER_ERROR_ENDPOINT_RANDOM_DIGITS = 6
|
||||||
|
|
||||||
logger = logging.getLogger('connexion.apis')
|
logger = logging.getLogger('connexion.apis.abstract')
|
||||||
|
|
||||||
|
|
||||||
def canonical_base_url(base_path):
|
|
||||||
"""
|
|
||||||
Make given "basePath" a canonical base URL which can be prepended to paths starting with "/".
|
|
||||||
"""
|
|
||||||
return base_path.rstrip('/')
|
|
||||||
|
|
||||||
|
|
||||||
def compatibility_layer(spec):
|
|
||||||
"""Make specs compatible with older versions of Connexion."""
|
|
||||||
if not isinstance(spec, dict):
|
|
||||||
return spec
|
|
||||||
|
|
||||||
# Make all response codes be string
|
|
||||||
for path_name, methods_available in spec.get('paths', {}).items():
|
|
||||||
for method_name, method_def in methods_available.items():
|
|
||||||
if (method_name == 'parameters' or not isinstance(
|
|
||||||
method_def, dict)):
|
|
||||||
continue
|
|
||||||
|
|
||||||
response_definitions = {}
|
|
||||||
for response_code, response_def in method_def.get(
|
|
||||||
'responses', {}).items():
|
|
||||||
response_definitions[str(response_code)] = response_def
|
|
||||||
|
|
||||||
method_def['responses'] = response_definitions
|
|
||||||
return spec
|
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
@@ -56,19 +30,14 @@ class AbstractAPI(object):
|
|||||||
Defines an abstract interface for a Swagger API
|
Defines an abstract interface for a Swagger API
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, specification, jsonifier, base_url=None, arguments=None,
|
def __init__(self, specification, base_path=None, arguments=None,
|
||||||
swagger_json=None, swagger_ui=None, swagger_path=None, swagger_url=None,
|
|
||||||
validate_responses=False, strict_validation=False, resolver=None,
|
validate_responses=False, strict_validation=False, resolver=None,
|
||||||
auth_all_paths=False, debug=False, resolver_error_handler=None,
|
auth_all_paths=False, debug=False, resolver_error_handler=None,
|
||||||
validator_map=None, pythonic_params=False):
|
validator_map=None, pythonic_params=False, options=None, **old_style_options):
|
||||||
"""
|
"""
|
||||||
:type specification: pathlib.Path | dict
|
:type specification: pathlib.Path | dict
|
||||||
:type base_url: str | None
|
:type base_path: str | None
|
||||||
:type arguments: dict | None
|
:type arguments: dict | None
|
||||||
:type swagger_json: bool
|
|
||||||
:type swagger_ui: bool
|
|
||||||
:type swagger_path: string | None
|
|
||||||
:type swagger_url: string | None
|
|
||||||
:type validate_responses: bool
|
:type validate_responses: bool
|
||||||
:type strict_validation: bool
|
:type strict_validation: bool
|
||||||
:type auth_all_paths: bool
|
:type auth_all_paths: bool
|
||||||
@@ -82,17 +51,31 @@ class AbstractAPI(object):
|
|||||||
:param pythonic_params: When True CamelCase parameters are converted to snake_case and an underscore is appended
|
:param pythonic_params: When True CamelCase parameters are converted to snake_case and an underscore is appended
|
||||||
to any shadowed built-ins
|
to any shadowed built-ins
|
||||||
:type pythonic_params: bool
|
:type pythonic_params: bool
|
||||||
|
:param options: New style options dictionary.
|
||||||
|
:type options: dict | None
|
||||||
|
:param old_style_options: Old style options support for backward compatibility. Preference is
|
||||||
|
what is defined in `options` parameter.
|
||||||
"""
|
"""
|
||||||
self.debug = debug
|
self.debug = debug
|
||||||
self.validator_map = validator_map
|
self.validator_map = validator_map
|
||||||
self.resolver_error_handler = resolver_error_handler
|
self.resolver_error_handler = resolver_error_handler
|
||||||
|
|
||||||
|
self.options = ConnexionOptions(old_style_options)
|
||||||
|
# options is added last to preserve the highest priority
|
||||||
|
self.options = self.options.extend(options)
|
||||||
|
|
||||||
|
# TODO: Remove this in later versions (Current version is 1.1.9)
|
||||||
|
if base_path is None and 'base_url' in old_style_options:
|
||||||
|
base_path = old_style_options['base_url']
|
||||||
|
logger.warning("Parameter base_url should be no longer used. Use base_path instead.")
|
||||||
|
|
||||||
logger.debug('Loading specification: %s', specification,
|
logger.debug('Loading specification: %s', specification,
|
||||||
extra={'swagger_yaml': specification,
|
extra={'swagger_yaml': specification,
|
||||||
'base_url': base_url,
|
'base_path': base_path,
|
||||||
'arguments': arguments,
|
'arguments': arguments,
|
||||||
'swagger_ui': swagger_ui,
|
'swagger_ui': self.options.openapi_console_ui_available,
|
||||||
'swagger_path': swagger_path,
|
'swagger_path': self.options.openapi_console_ui_from_dir,
|
||||||
'swagger_url': swagger_url,
|
'swagger_url': self.options.openapi_console_ui_path,
|
||||||
'auth_all_paths': auth_all_paths})
|
'auth_all_paths': auth_all_paths})
|
||||||
|
|
||||||
if isinstance(specification, dict):
|
if isinstance(specification, dict):
|
||||||
@@ -108,12 +91,9 @@ class AbstractAPI(object):
|
|||||||
spec = copy.deepcopy(self.specification)
|
spec = copy.deepcopy(self.specification)
|
||||||
validate_spec(spec)
|
validate_spec(spec)
|
||||||
|
|
||||||
self.swagger_path = swagger_path or SWAGGER_UI_PATH
|
|
||||||
self.swagger_url = swagger_url or SWAGGER_UI_URL
|
|
||||||
|
|
||||||
# https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#fixed-fields
|
# https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#fixed-fields
|
||||||
# If base_url is not on provided then we try to read it from the swagger.yaml or use / by default
|
# If base_path is not on provided then we try to read it from the swagger.yaml or use / by default
|
||||||
self._set_base_url(base_url)
|
self._set_base_path(base_path)
|
||||||
|
|
||||||
# A list of MIME types the APIs can produce. This is global to all APIs but can be overridden on specific
|
# A list of MIME types the APIs can produce. This is global to all APIs but can be overridden on specific
|
||||||
# API calls.
|
# API calls.
|
||||||
@@ -142,11 +122,10 @@ class AbstractAPI(object):
|
|||||||
logger.debug('Pythonic params: %s', str(pythonic_params))
|
logger.debug('Pythonic params: %s', str(pythonic_params))
|
||||||
self.pythonic_params = pythonic_params
|
self.pythonic_params = pythonic_params
|
||||||
|
|
||||||
self.jsonifier = jsonifier
|
if self.options.openapi_spec_available:
|
||||||
|
|
||||||
if swagger_json:
|
|
||||||
self.add_swagger_json()
|
self.add_swagger_json()
|
||||||
if swagger_ui:
|
|
||||||
|
if self.options.openapi_console_ui_available:
|
||||||
self.add_swagger_ui()
|
self.add_swagger_ui()
|
||||||
|
|
||||||
self.add_paths()
|
self.add_paths()
|
||||||
@@ -154,23 +133,24 @@ class AbstractAPI(object):
|
|||||||
if auth_all_paths:
|
if auth_all_paths:
|
||||||
self.add_auth_on_not_found(self.security, self.security_definitions)
|
self.add_auth_on_not_found(self.security, self.security_definitions)
|
||||||
|
|
||||||
def _set_base_url(self, base_url):
|
def _set_base_path(self, base_path):
|
||||||
if base_url is None:
|
# type: (AnyStr) -> None
|
||||||
self.base_url = canonical_base_url(self.specification.get('basePath', ''))
|
if base_path is None:
|
||||||
|
self.base_path = canonical_base_path(self.specification.get('basePath', ''))
|
||||||
else:
|
else:
|
||||||
self.base_url = canonical_base_url(base_url)
|
self.base_path = canonical_base_path(base_path)
|
||||||
self.specification['basePath'] = base_url
|
self.specification['basePath'] = base_path
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def add_swagger_json(self):
|
def add_swagger_json(self):
|
||||||
"""
|
"""
|
||||||
Adds swagger json to {base_url}/swagger.json
|
Adds swagger json to {base_path}/swagger.json
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def add_swagger_ui(self):
|
def add_swagger_ui(self):
|
||||||
"""
|
"""
|
||||||
Adds swagger ui to {base_url}/ui/
|
Adds swagger ui to {base_path}/ui/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -249,7 +229,7 @@ class AbstractAPI(object):
|
|||||||
"""
|
"""
|
||||||
paths = paths or self.specification.get('paths', dict())
|
paths = paths or self.specification.get('paths', dict())
|
||||||
for path, methods in paths.items():
|
for path, methods in paths.items():
|
||||||
logger.debug('Adding %s%s...', self.base_url, path)
|
logger.debug('Adding %s%s...', self.base_path, path)
|
||||||
|
|
||||||
# search for parameters definitions in the path level
|
# search for parameters definitions in the path level
|
||||||
# http://swagger.io/specification/#pathItemObject
|
# http://swagger.io/specification/#pathItemObject
|
||||||
@@ -275,7 +255,7 @@ class AbstractAPI(object):
|
|||||||
self._handle_add_operation_error(path, method, sys.exc_info())
|
self._handle_add_operation_error(path, method, sys.exc_info())
|
||||||
|
|
||||||
def _handle_add_operation_error(self, path, method, exc_info):
|
def _handle_add_operation_error(self, path, method, exc_info):
|
||||||
url = '{base_url}{path}'.format(base_url=self.base_url, path=path)
|
url = '{base_path}{path}'.format(base_path=self.base_path, path=path)
|
||||||
error_msg = 'Failed to add operation for {method} {url}'.format(
|
error_msg = 'Failed to add operation for {method} {url}'.format(
|
||||||
method=method.upper(),
|
method=method.upper(),
|
||||||
url=url)
|
url=url)
|
||||||
@@ -317,3 +297,41 @@ class AbstractAPI(object):
|
|||||||
:type response: ConnexionResponse
|
:type response: ConnexionResponse
|
||||||
:type mimetype: str
|
:type mimetype: str
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@abc.abstractmethod
|
||||||
|
def json_loads(self, data):
|
||||||
|
"""
|
||||||
|
API specific JSON loader.
|
||||||
|
|
||||||
|
:param data:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def canonical_base_path(base_path):
|
||||||
|
"""
|
||||||
|
Make given "basePath" a canonical base URL which can be prepended to paths starting with "/".
|
||||||
|
"""
|
||||||
|
return base_path.rstrip('/')
|
||||||
|
|
||||||
|
|
||||||
|
def compatibility_layer(spec):
|
||||||
|
"""Make specs compatible with older versions of Connexion."""
|
||||||
|
if not isinstance(spec, dict):
|
||||||
|
return spec
|
||||||
|
|
||||||
|
# Make all response codes be string
|
||||||
|
for path_name, methods_available in spec.get('paths', {}).items():
|
||||||
|
for method_name, method_def in methods_available.items():
|
||||||
|
if (method_name == 'parameters' or not isinstance(
|
||||||
|
method_def, dict)):
|
||||||
|
continue
|
||||||
|
|
||||||
|
response_definitions = {}
|
||||||
|
for response_code, response_def in method_def.get(
|
||||||
|
'responses', {}).items():
|
||||||
|
response_definitions[str(response_code)] = response_def
|
||||||
|
|
||||||
|
method_def['responses'] = response_definitions
|
||||||
|
return spec
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import werkzeug.exceptions
|
|||||||
|
|
||||||
from connexion.apis import flask_utils
|
from connexion.apis import flask_utils
|
||||||
from connexion.apis.abstract import AbstractAPI
|
from connexion.apis.abstract import AbstractAPI
|
||||||
from connexion.decorators.produces import BaseSerializer, NoContent
|
from connexion.decorators.produces import NoContent
|
||||||
from connexion.handlers import AuthErrorHandler
|
from connexion.handlers import AuthErrorHandler
|
||||||
from connexion.lifecycle import ConnexionRequest, ConnexionResponse
|
from connexion.lifecycle import ConnexionRequest, ConnexionResponse
|
||||||
from connexion.utils import is_json_mimetype
|
from connexion.utils import is_json_mimetype
|
||||||
@@ -14,68 +14,28 @@ from connexion.utils import is_json_mimetype
|
|||||||
logger = logging.getLogger('connexion.apis.flask_api')
|
logger = logging.getLogger('connexion.apis.flask_api')
|
||||||
|
|
||||||
|
|
||||||
class Jsonifier(BaseSerializer):
|
|
||||||
@staticmethod
|
|
||||||
def dumps(data):
|
|
||||||
""" Central point where JSON serialization happens inside
|
|
||||||
Connexion.
|
|
||||||
"""
|
|
||||||
return "{}\n".format(flask.json.dumps(data, indent=2))
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def loads(data):
|
|
||||||
""" Central point where JSON serialization happens inside
|
|
||||||
Connexion.
|
|
||||||
"""
|
|
||||||
if isinstance(data, six.binary_type):
|
|
||||||
data = data.decode()
|
|
||||||
|
|
||||||
try:
|
|
||||||
return flask.json.loads(data)
|
|
||||||
except Exception as error:
|
|
||||||
if isinstance(data, six.string_types):
|
|
||||||
return data
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
"""
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
return '<Jsonifier: {}>'.format(self.mimetype)
|
|
||||||
|
|
||||||
|
|
||||||
class FlaskApi(AbstractAPI):
|
class FlaskApi(AbstractAPI):
|
||||||
jsonifier = Jsonifier
|
def _set_base_path(self, base_path):
|
||||||
|
super(FlaskApi, self)._set_base_path(base_path)
|
||||||
def __init__(self, specification, base_url=None, arguments=None,
|
|
||||||
swagger_json=None, swagger_ui=None, swagger_path=None, swagger_url=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):
|
|
||||||
super(FlaskApi, self).__init__(
|
|
||||||
specification, FlaskApi.jsonifier, base_url=base_url, arguments=arguments,
|
|
||||||
swagger_json=swagger_json, swagger_ui=swagger_ui,
|
|
||||||
swagger_path=swagger_path, swagger_url=swagger_url,
|
|
||||||
validate_responses=validate_responses, strict_validation=strict_validation,
|
|
||||||
resolver=resolver, auth_all_paths=auth_all_paths, debug=debug,
|
|
||||||
resolver_error_handler=resolver_error_handler, validator_map=validator_map,
|
|
||||||
pythonic_params=pythonic_params
|
|
||||||
)
|
|
||||||
|
|
||||||
def _set_base_url(self, base_url):
|
|
||||||
super(FlaskApi, self)._set_base_url(base_url)
|
|
||||||
self._set_blueprint()
|
self._set_blueprint()
|
||||||
|
|
||||||
def _set_blueprint(self):
|
def _set_blueprint(self):
|
||||||
logger.debug('Creating API blueprint: %s', self.base_url)
|
logger.debug('Creating API blueprint: %s', self.base_path)
|
||||||
endpoint = flask_utils.flaskify_endpoint(self.base_url)
|
endpoint = flask_utils.flaskify_endpoint(self.base_path)
|
||||||
self.blueprint = flask.Blueprint(endpoint, __name__, url_prefix=self.base_url,
|
self.blueprint = flask.Blueprint(endpoint, __name__, url_prefix=self.base_path,
|
||||||
template_folder=str(self.swagger_path))
|
template_folder=str(self.options.openapi_console_ui_from_dir))
|
||||||
|
|
||||||
|
def json_loads(self, data):
|
||||||
|
"""
|
||||||
|
Use Flask specific JSON loader
|
||||||
|
"""
|
||||||
|
return Jsonifier.loads(data)
|
||||||
|
|
||||||
def add_swagger_json(self):
|
def add_swagger_json(self):
|
||||||
"""
|
"""
|
||||||
Adds swagger json to {base_url}/swagger.json
|
Adds swagger json to {base_path}/swagger.json
|
||||||
"""
|
"""
|
||||||
logger.debug('Adding swagger.json: %s/swagger.json', self.base_url)
|
logger.debug('Adding swagger.json: %s/swagger.json', self.base_path)
|
||||||
endpoint_name = "{name}_swagger_json".format(name=self.blueprint.name)
|
endpoint_name = "{name}_swagger_json".format(name=self.blueprint.name)
|
||||||
self.blueprint.add_url_rule('/swagger.json',
|
self.blueprint.add_url_rule('/swagger.json',
|
||||||
endpoint_name,
|
endpoint_name,
|
||||||
@@ -83,24 +43,28 @@ class FlaskApi(AbstractAPI):
|
|||||||
|
|
||||||
def add_swagger_ui(self):
|
def add_swagger_ui(self):
|
||||||
"""
|
"""
|
||||||
Adds swagger ui to {base_url}/ui/
|
Adds swagger ui to {base_path}/ui/
|
||||||
"""
|
"""
|
||||||
logger.debug('Adding swagger-ui: %s/%s/', self.base_url, self.swagger_url)
|
console_ui_path = self.options.openapi_console_ui_path.strip('/')
|
||||||
|
logger.debug('Adding swagger-ui: %s/%s/',
|
||||||
|
self.base_path,
|
||||||
|
console_ui_path)
|
||||||
|
|
||||||
static_endpoint_name = "{name}_swagger_ui_static".format(name=self.blueprint.name)
|
static_endpoint_name = "{name}_swagger_ui_static".format(name=self.blueprint.name)
|
||||||
self.blueprint.add_url_rule('/{swagger_url}/<path:filename>'.format(swagger_url=self.swagger_url),
|
static_files_url = '/{console_ui_path}/<path:filename>'.format(
|
||||||
static_endpoint_name, self.swagger_ui_static)
|
console_ui_path=console_ui_path)
|
||||||
|
|
||||||
|
self.blueprint.add_url_rule(static_files_url,
|
||||||
|
static_endpoint_name,
|
||||||
|
self._handlers.console_ui_static_files)
|
||||||
|
|
||||||
index_endpoint_name = "{name}_swagger_ui_index".format(name=self.blueprint.name)
|
index_endpoint_name = "{name}_swagger_ui_index".format(name=self.blueprint.name)
|
||||||
self.blueprint.add_url_rule('/{swagger_url}/'.format(swagger_url=self.swagger_url),
|
console_ui_url = '/{swagger_url}/'.format(
|
||||||
index_endpoint_name, self.swagger_ui_index)
|
swagger_url=self.options.openapi_console_ui_path.strip('/'))
|
||||||
|
|
||||||
def swagger_ui_index(self):
|
self.blueprint.add_url_rule(console_ui_url,
|
||||||
return flask.render_template('index.html', api_url=self.base_url)
|
index_endpoint_name,
|
||||||
|
self._handlers.console_ui_home)
|
||||||
def swagger_ui_static(self, filename):
|
|
||||||
"""
|
|
||||||
:type filename: str
|
|
||||||
"""
|
|
||||||
return flask.send_from_directory(str(self.swagger_path), filename)
|
|
||||||
|
|
||||||
def add_auth_on_not_found(self, security, security_definitions):
|
def add_auth_on_not_found(self, security, security_definitions):
|
||||||
"""
|
"""
|
||||||
@@ -123,6 +87,13 @@ class FlaskApi(AbstractAPI):
|
|||||||
function = operation.function
|
function = operation.function
|
||||||
self.blueprint.add_url_rule(flask_path, endpoint_name, function, methods=[method])
|
self.blueprint.add_url_rule(flask_path, endpoint_name, function, methods=[method])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _handlers(self):
|
||||||
|
# type: () -> InternalHandlers
|
||||||
|
if not hasattr(self, '_internal_handlers'):
|
||||||
|
self._internal_handlers = InternalHandlers(self.base_path, self.options)
|
||||||
|
return self._internal_handlers
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_response(cls, response, mimetype=None, request=None):
|
def get_response(cls, response, mimetype=None, request=None):
|
||||||
"""Gets ConnexionResponse instance for the operation handler
|
"""Gets ConnexionResponse instance for the operation handler
|
||||||
@@ -190,7 +161,7 @@ class FlaskApi(AbstractAPI):
|
|||||||
flask_response.set_data(data)
|
flask_response.set_data(data)
|
||||||
|
|
||||||
elif data is NoContent:
|
elif data is NoContent:
|
||||||
flask_response.set_data('')
|
flask_response.set_data('')
|
||||||
|
|
||||||
return flask_response
|
return flask_response
|
||||||
|
|
||||||
@@ -198,7 +169,7 @@ class FlaskApi(AbstractAPI):
|
|||||||
def _jsonify_data(cls, data, mimetype):
|
def _jsonify_data(cls, data, mimetype):
|
||||||
if (isinstance(mimetype, six.string_types) and is_json_mimetype(mimetype)) \
|
if (isinstance(mimetype, six.string_types) and is_json_mimetype(mimetype)) \
|
||||||
or not (isinstance(data, six.binary_type) or isinstance(data, six.text_type)):
|
or not (isinstance(data, six.binary_type) or isinstance(data, six.text_type)):
|
||||||
return cls.jsonifier.dumps(data)
|
return Jsonifier.dumps(data)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@@ -262,6 +233,7 @@ class FlaskRequestContextProxy(object):
|
|||||||
""""Proxy assignments from `ConnexionRequest.context`
|
""""Proxy assignments from `ConnexionRequest.context`
|
||||||
to `flask.request` instance.
|
to `flask.request` instance.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.values = {}
|
self.values = {}
|
||||||
|
|
||||||
@@ -274,3 +246,55 @@ class FlaskRequestContextProxy(object):
|
|||||||
def items(self):
|
def items(self):
|
||||||
# type: () -> list
|
# type: () -> list
|
||||||
return self.values.items()
|
return self.values.items()
|
||||||
|
|
||||||
|
|
||||||
|
class Jsonifier(object):
|
||||||
|
@staticmethod
|
||||||
|
def dumps(data):
|
||||||
|
""" Central point where JSON serialization happens inside
|
||||||
|
Connexion.
|
||||||
|
"""
|
||||||
|
return "{}\n".format(flask.json.dumps(data, indent=2))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def loads(data):
|
||||||
|
""" Central point where JSON serialization happens inside
|
||||||
|
Connexion.
|
||||||
|
"""
|
||||||
|
if isinstance(data, six.binary_type):
|
||||||
|
data = data.decode()
|
||||||
|
|
||||||
|
try:
|
||||||
|
return flask.json.loads(data)
|
||||||
|
except Exception as error:
|
||||||
|
if isinstance(data, six.string_types):
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class InternalHandlers(object):
|
||||||
|
"""
|
||||||
|
Flask handlers for internally registered endpoints.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, base_path, options):
|
||||||
|
self.base_path = base_path
|
||||||
|
self.options = options
|
||||||
|
|
||||||
|
def console_ui_home(self):
|
||||||
|
"""
|
||||||
|
Home page of the OpenAPI Console UI.
|
||||||
|
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return flask.render_template('index.html', api_url=self.base_path)
|
||||||
|
|
||||||
|
def console_ui_static_files(self, filename):
|
||||||
|
"""
|
||||||
|
Servers the static files for the OpenAPI Console UI.
|
||||||
|
|
||||||
|
:param filename: Requested file contents.
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
# convert PosixPath to str
|
||||||
|
static_dir = str(self.options.openapi_console_ui_from_dir)
|
||||||
|
return flask.send_from_directory(static_dir, filename)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import pathlib
|
|||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
from ..options import ConnexionOptions
|
||||||
from ..resolver import Resolver
|
from ..resolver import Resolver
|
||||||
|
|
||||||
logger = logging.getLogger('connexion.app')
|
logger = logging.getLogger('connexion.app')
|
||||||
@@ -12,9 +13,8 @@ logger = logging.getLogger('connexion.app')
|
|||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class AbstractApp(object):
|
class AbstractApp(object):
|
||||||
def __init__(self, import_name, api_cls, port=None, specification_dir='',
|
def __init__(self, import_name, api_cls, port=None, specification_dir='',
|
||||||
server=None, arguments=None, auth_all_paths=False,
|
host=None, server=None, arguments=None, auth_all_paths=False, debug=False,
|
||||||
debug=False, swagger_json=True, swagger_ui=True, swagger_path=None,
|
validator_map=None, options=None, **old_style_options):
|
||||||
swagger_url=None, host=None, validator_map=None):
|
|
||||||
"""
|
"""
|
||||||
:param import_name: the name of the application package
|
:param import_name: the name of the application package
|
||||||
:type import_name: str
|
:type import_name: str
|
||||||
@@ -32,14 +32,6 @@ class AbstractApp(object):
|
|||||||
:type auth_all_paths: bool
|
:type auth_all_paths: bool
|
||||||
:param debug: include debugging information
|
:param debug: include debugging information
|
||||||
:type debug: bool
|
:type debug: bool
|
||||||
:param swagger_json: whether to include swagger json or not
|
|
||||||
:type swagger_json: bool
|
|
||||||
:param swagger_ui: whether to include swagger ui or not
|
|
||||||
:type swagger_ui: bool
|
|
||||||
:param swagger_path: path to swagger-ui directory
|
|
||||||
:type swagger_path: string | None
|
|
||||||
:param swagger_url: URL to access swagger-ui documentation
|
|
||||||
:type swagger_url: string | None
|
|
||||||
:param validator_map: map of validators
|
:param validator_map: map of validators
|
||||||
:type validator_map: dict
|
:type validator_map: dict
|
||||||
"""
|
"""
|
||||||
@@ -48,14 +40,16 @@ class AbstractApp(object):
|
|||||||
self.debug = debug
|
self.debug = debug
|
||||||
self.import_name = import_name
|
self.import_name = import_name
|
||||||
self.arguments = arguments or {}
|
self.arguments = arguments or {}
|
||||||
self.swagger_json = swagger_json
|
|
||||||
self.swagger_ui = swagger_ui
|
|
||||||
self.swagger_path = swagger_path
|
|
||||||
self.swagger_url = swagger_url
|
|
||||||
self.auth_all_paths = auth_all_paths
|
|
||||||
self.resolver_error = None
|
|
||||||
self.validator_map = validator_map
|
|
||||||
self.api_cls = api_cls
|
self.api_cls = api_cls
|
||||||
|
self.resolver_error = None
|
||||||
|
|
||||||
|
# Options
|
||||||
|
self.auth_all_paths = auth_all_paths
|
||||||
|
self.validator_map = validator_map
|
||||||
|
|
||||||
|
self.options = ConnexionOptions(old_style_options)
|
||||||
|
# options is added last to preserve the highest priority
|
||||||
|
self.options = self.options.extend(options) # type: ConnexionOptions
|
||||||
|
|
||||||
self.app = self.create_app()
|
self.app = self.create_app()
|
||||||
self.server = server
|
self.server = server
|
||||||
@@ -94,10 +88,9 @@ class AbstractApp(object):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def add_api(self, specification, base_path=None, arguments=None,
|
def add_api(self, specification, base_path=None, arguments=None,
|
||||||
auth_all_paths=None, swagger_json=None, swagger_ui=None,
|
auth_all_paths=None, validate_responses=False,
|
||||||
swagger_path=None, swagger_url=None, validate_responses=False,
|
|
||||||
strict_validation=False, resolver=Resolver(), resolver_error=None,
|
strict_validation=False, resolver=Resolver(), resolver_error=None,
|
||||||
pythonic_params=False):
|
pythonic_params=False, options=None, **old_style_options):
|
||||||
"""
|
"""
|
||||||
Adds an API to the application based on a swagger file or API dict
|
Adds an API to the application based on a swagger file or API dict
|
||||||
|
|
||||||
@@ -109,14 +102,6 @@ class AbstractApp(object):
|
|||||||
:type arguments: dict | None
|
:type arguments: dict | None
|
||||||
:param auth_all_paths: whether to authenticate not defined paths
|
:param auth_all_paths: whether to authenticate not defined paths
|
||||||
:type auth_all_paths: bool
|
:type auth_all_paths: bool
|
||||||
:param swagger_json: whether to include swagger json or not
|
|
||||||
:type swagger_json: bool
|
|
||||||
:param swagger_ui: whether to include swagger ui or not
|
|
||||||
:type swagger_ui: bool
|
|
||||||
:param swagger_path: path to swagger-ui directory
|
|
||||||
:type swagger_path: string | None
|
|
||||||
:param swagger_url: URL to access swagger-ui documentation
|
|
||||||
:type swagger_url: string | None
|
|
||||||
:param validate_responses: True enables validation. Validation errors generate HTTP 500 responses.
|
:param validate_responses: True enables validation. Validation errors generate HTTP 500 responses.
|
||||||
:type validate_responses: bool
|
:type validate_responses: bool
|
||||||
:param strict_validation: True enables validation on invalid request parameters
|
:param strict_validation: True enables validation on invalid request parameters
|
||||||
@@ -128,6 +113,11 @@ class AbstractApp(object):
|
|||||||
:type resolver_error: int | None
|
:type resolver_error: int | None
|
||||||
:param pythonic_params: When True CamelCase parameters are converted to snake_case
|
:param pythonic_params: When True CamelCase parameters are converted to snake_case
|
||||||
:type pythonic_params: bool
|
:type pythonic_params: bool
|
||||||
|
:param options: New style options dictionary.
|
||||||
|
:type options: dict | None
|
||||||
|
:param old_style_options: Old style options support for backward compatibility. Preference is
|
||||||
|
what is defined in `options` parameter.
|
||||||
|
:type old_style_options: dict
|
||||||
:rtype: AbstractAPI
|
:rtype: AbstractAPI
|
||||||
"""
|
"""
|
||||||
# Turn the resolver_error code into a handler object
|
# Turn the resolver_error code into a handler object
|
||||||
@@ -138,12 +128,8 @@ class AbstractApp(object):
|
|||||||
|
|
||||||
resolver = Resolver(resolver) if hasattr(resolver, '__call__') else resolver
|
resolver = Resolver(resolver) if hasattr(resolver, '__call__') else resolver
|
||||||
|
|
||||||
swagger_json = swagger_json if swagger_json is not None else self.swagger_json
|
|
||||||
swagger_ui = swagger_ui if swagger_ui is not None else self.swagger_ui
|
|
||||||
swagger_path = swagger_path if swagger_path is not None else self.swagger_path
|
|
||||||
swagger_url = swagger_url if swagger_url is not None else self.swagger_url
|
|
||||||
auth_all_paths = auth_all_paths if auth_all_paths is not None else self.auth_all_paths
|
auth_all_paths = auth_all_paths if auth_all_paths is not None else self.auth_all_paths
|
||||||
# TODO test if base_url starts with an / (if not none)
|
# TODO test if base_path starts with an / (if not none)
|
||||||
arguments = arguments or dict()
|
arguments = arguments or dict()
|
||||||
arguments = dict(self.arguments, **arguments) # copy global arguments and update with api specfic
|
arguments = dict(self.arguments, **arguments) # copy global arguments and update with api specfic
|
||||||
|
|
||||||
@@ -152,12 +138,16 @@ class AbstractApp(object):
|
|||||||
else:
|
else:
|
||||||
specification = self.specification_dir / specification
|
specification = self.specification_dir / specification
|
||||||
|
|
||||||
api = self.api_cls(specification=specification,
|
# Old style options have higher priority compared to the already
|
||||||
base_url=base_path, arguments=arguments,
|
# defined options in the App class
|
||||||
swagger_json=swagger_json,
|
api_options = self.options.extend(old_style_options)
|
||||||
swagger_ui=swagger_ui,
|
|
||||||
swagger_path=swagger_path,
|
# locally defined options are added last to preserve highest priority
|
||||||
swagger_url=swagger_url,
|
api_options = api_options.extend(options)
|
||||||
|
|
||||||
|
api = self.api_cls(specification,
|
||||||
|
base_path=base_path,
|
||||||
|
arguments=arguments,
|
||||||
resolver=resolver,
|
resolver=resolver,
|
||||||
resolver_error_handler=resolver_error_handler,
|
resolver_error_handler=resolver_error_handler,
|
||||||
validate_responses=validate_responses,
|
validate_responses=validate_responses,
|
||||||
@@ -165,7 +155,8 @@ class AbstractApp(object):
|
|||||||
auth_all_paths=auth_all_paths,
|
auth_all_paths=auth_all_paths,
|
||||||
debug=self.debug,
|
debug=self.debug,
|
||||||
validator_map=self.validator_map,
|
validator_map=self.validator_map,
|
||||||
pythonic_params=pythonic_params)
|
pythonic_params=pythonic_params,
|
||||||
|
options=api_options.as_dict())
|
||||||
return api
|
return api
|
||||||
|
|
||||||
def _resolver_error_handler(self, *args, **kwargs):
|
def _resolver_error_handler(self, *args, **kwargs):
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import datetime
|
|||||||
import logging
|
import logging
|
||||||
import pathlib
|
import pathlib
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
from types import FunctionType # NOQA
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
import werkzeug.exceptions
|
import werkzeug.exceptions
|
||||||
@@ -10,25 +11,14 @@ from flask import json
|
|||||||
from ..apis.flask_api import FlaskApi
|
from ..apis.flask_api import FlaskApi
|
||||||
from ..exceptions import ProblemException
|
from ..exceptions import ProblemException
|
||||||
from ..problem import problem
|
from ..problem import problem
|
||||||
from ..resolver import Resolver
|
|
||||||
from .abstract import AbstractApp
|
from .abstract import AbstractApp
|
||||||
|
|
||||||
logger = logging.getLogger('connexion.app')
|
logger = logging.getLogger('connexion.app')
|
||||||
|
|
||||||
|
|
||||||
class FlaskApp(AbstractApp):
|
class FlaskApp(AbstractApp):
|
||||||
def __init__(self, import_name, port=None, specification_dir='',
|
def __init__(self, import_name, **kwargs):
|
||||||
server=None, arguments=None, auth_all_paths=False,
|
super(FlaskApp, self).__init__(import_name, FlaskApi, server='flask', **kwargs)
|
||||||
debug=False, swagger_json=True, swagger_ui=True, swagger_path=None,
|
|
||||||
swagger_url=None, host=None, validator_map=None):
|
|
||||||
server = server or 'flask'
|
|
||||||
super(FlaskApp, self).__init__(
|
|
||||||
import_name, port=port, specification_dir=specification_dir,
|
|
||||||
server=server, arguments=arguments, auth_all_paths=auth_all_paths,
|
|
||||||
debug=debug, swagger_json=swagger_json, swagger_ui=swagger_ui,
|
|
||||||
swagger_path=swagger_path, swagger_url=swagger_url,
|
|
||||||
host=host, validator_map=validator_map, api_cls=FlaskApi
|
|
||||||
)
|
|
||||||
|
|
||||||
def create_app(self):
|
def create_app(self):
|
||||||
app = flask.Flask(self.import_name)
|
app = flask.Flask(self.import_name)
|
||||||
@@ -60,27 +50,13 @@ class FlaskApp(AbstractApp):
|
|||||||
|
|
||||||
return FlaskApi.get_response(response)
|
return FlaskApi.get_response(response)
|
||||||
|
|
||||||
def add_api(self, specification, base_path=None, arguments=None,
|
def add_api(self, specification, **kwargs):
|
||||||
auth_all_paths=None, swagger_json=None, swagger_ui=None,
|
api = super(FlaskApp, self).add_api(specification, **kwargs)
|
||||||
swagger_path=None, swagger_url=None, validate_responses=False,
|
|
||||||
strict_validation=False, resolver=Resolver(), resolver_error=None,
|
|
||||||
pythonic_params=False):
|
|
||||||
api = super(FlaskApp, self).add_api(
|
|
||||||
specification, base_path=base_path,
|
|
||||||
arguments=arguments, auth_all_paths=auth_all_paths, swagger_json=swagger_json,
|
|
||||||
swagger_ui=swagger_ui, swagger_path=swagger_path, swagger_url=swagger_url,
|
|
||||||
validate_responses=validate_responses, strict_validation=strict_validation,
|
|
||||||
resolver=resolver, resolver_error=resolver_error, pythonic_params=pythonic_params
|
|
||||||
)
|
|
||||||
self.app.register_blueprint(api.blueprint)
|
self.app.register_blueprint(api.blueprint)
|
||||||
return api
|
return api
|
||||||
|
|
||||||
def add_error_handler(self, error_code, function):
|
def add_error_handler(self, error_code, function):
|
||||||
"""
|
# type: (int, FunctionType) -> None
|
||||||
|
|
||||||
:type error_code: int
|
|
||||||
:type function: types.FunctionType
|
|
||||||
"""
|
|
||||||
self.app.register_error_handler(error_code, function)
|
self.app.register_error_handler(error_code, function)
|
||||||
|
|
||||||
def run(self, port=None, server=None, debug=None, host=None, **options): # pragma: no cover
|
def run(self, port=None, server=None, debug=None, host=None, **options): # pragma: no cover
|
||||||
|
|||||||
@@ -401,7 +401,7 @@ class Operation(SecureOperation):
|
|||||||
|
|
||||||
If the operation mimetype format is json then the function return value is jsonified
|
If the operation mimetype format is json then the function return value is jsonified
|
||||||
|
|
||||||
From Swagger Specfication:
|
From Swagger Specification:
|
||||||
|
|
||||||
**Produces**
|
**Produces**
|
||||||
|
|
||||||
@@ -416,8 +416,8 @@ class Operation(SecureOperation):
|
|||||||
mimetype = self.get_mimetype()
|
mimetype = self.get_mimetype()
|
||||||
if all_json(self.produces): # endpoint will return json
|
if all_json(self.produces): # endpoint will return json
|
||||||
logger.debug('... Produces json', extra=vars(self))
|
logger.debug('... Produces json', extra=vars(self))
|
||||||
jsonify = self.api.jsonifier(mimetype)
|
# TODO: Refactor this.
|
||||||
return jsonify
|
return lambda f: f
|
||||||
|
|
||||||
elif len(self.produces) == 1:
|
elif len(self.produces) == 1:
|
||||||
logger.debug('... Produces %s', mimetype, extra=vars(self))
|
logger.debug('... Produces %s', mimetype, extra=vars(self))
|
||||||
@@ -453,8 +453,9 @@ class Operation(SecureOperation):
|
|||||||
|
|
||||||
def json_loads(self, data):
|
def json_loads(self, data):
|
||||||
"""
|
"""
|
||||||
A Wrapper for calling the jsonifier.
|
A wrapper for calling the API specific JSON loader.
|
||||||
:param data: The json to loads
|
|
||||||
|
:param data: The JSON data in textual form.
|
||||||
:type data: bytes
|
:type data: bytes
|
||||||
"""
|
"""
|
||||||
return self.api.jsonifier.loads(data)
|
return self.api.json_loads(data)
|
||||||
|
|||||||
87
connexion/options.py
Normal file
87
connexion/options.py
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import pathlib
|
||||||
|
from typing import Optional # NOQA
|
||||||
|
|
||||||
|
MODULE_PATH = pathlib.Path(__file__).absolute().parent
|
||||||
|
INTERNAL_CONSOLE_UI_PATH = MODULE_PATH / 'vendor' / 'swagger-ui'
|
||||||
|
|
||||||
|
|
||||||
|
class ConnexionOptions(object):
|
||||||
|
def __init__(self, options=None):
|
||||||
|
self._options = {}
|
||||||
|
if options:
|
||||||
|
self._options.update(filter_values(options))
|
||||||
|
|
||||||
|
def extend(self, new_values=None):
|
||||||
|
# type: (Optional[dict]) -> ConnexionOptions
|
||||||
|
"""
|
||||||
|
Return a new instance of `ConnexionOptions` using as default the currently
|
||||||
|
defined options.
|
||||||
|
"""
|
||||||
|
if new_values is None:
|
||||||
|
new_values = {}
|
||||||
|
|
||||||
|
options = dict(self._options)
|
||||||
|
options.update(filter_values(new_values))
|
||||||
|
return ConnexionOptions(options)
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return self._options
|
||||||
|
|
||||||
|
@property
|
||||||
|
def openapi_spec_available(self):
|
||||||
|
# type: () -> bool
|
||||||
|
"""
|
||||||
|
Whether to make available the OpenAPI Specification under
|
||||||
|
`openapi_console_ui_path`/swagger.json path.
|
||||||
|
|
||||||
|
Default: True
|
||||||
|
"""
|
||||||
|
# NOTE: Under OpenAPI v3 this should change to "/openapi.json"
|
||||||
|
return self._options.get('swagger_json', True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def openapi_console_ui_available(self):
|
||||||
|
# type: () -> bool
|
||||||
|
"""
|
||||||
|
Whether to make the OpenAPI Console UI available under the path
|
||||||
|
defined in `openapi_console_ui_path` option. Note that if enabled,
|
||||||
|
this overrides the `openapi_spec_available` option since the specification
|
||||||
|
is required to be available via a HTTP endpoint to display the console UI.
|
||||||
|
|
||||||
|
Default: True
|
||||||
|
"""
|
||||||
|
return self._options.get('swagger_ui', True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def openapi_console_ui_path(self):
|
||||||
|
# type: () -> str
|
||||||
|
"""
|
||||||
|
Path to mount the OpenAPI Console UI and make it accessible via a browser.
|
||||||
|
|
||||||
|
Default: /ui
|
||||||
|
"""
|
||||||
|
return self._options.get('swagger_url', '/ui')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def openapi_console_ui_from_dir(self):
|
||||||
|
# type: () -> str
|
||||||
|
"""
|
||||||
|
Custom OpenAPI Console UI directory from where Connexion will serve
|
||||||
|
the static files.
|
||||||
|
|
||||||
|
Default: Connexion's vendored version of the OpenAPI Console UI.
|
||||||
|
"""
|
||||||
|
return self._options.get('swagger_path', INTERNAL_CONSOLE_UI_PATH)
|
||||||
|
|
||||||
|
|
||||||
|
def filter_values(dictionary):
|
||||||
|
# type: (dict) -> dict
|
||||||
|
"""
|
||||||
|
Remove `None` value entries in the dictionary.
|
||||||
|
|
||||||
|
:param dictionary:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return dict([(key, value)
|
||||||
|
for key, value in dictionary.items()
|
||||||
|
if value is not None])
|
||||||
3
setup.py
3
setup.py
@@ -31,7 +31,8 @@ install_requires = [
|
|||||||
'requests>=2.9.1',
|
'requests>=2.9.1',
|
||||||
'six>=1.9',
|
'six>=1.9',
|
||||||
'swagger-spec-validator>=2.0.2',
|
'swagger-spec-validator>=2.0.2',
|
||||||
'inflection>=0.3.1'
|
'inflection>=0.3.1',
|
||||||
|
'typing>=3.6.1'
|
||||||
]
|
]
|
||||||
|
|
||||||
flask_require = 'flask>=0.10.1'
|
flask_require = 'flask>=0.10.1'
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ import yaml
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from conftest import TEST_FOLDER, build_app_from_fixture
|
from conftest import TEST_FOLDER, build_app_from_fixture
|
||||||
from connexion import FlaskApp
|
from connexion import App
|
||||||
from connexion.exceptions import InvalidSpecification
|
from connexion.exceptions import InvalidSpecification
|
||||||
|
|
||||||
|
|
||||||
def test_app_with_relative_path(simple_api_spec_dir):
|
def test_app_with_relative_path(simple_api_spec_dir):
|
||||||
# Create the app with a realative path and run the test_app testcase below.
|
# Create the app with a relative path and run the test_app testcase below.
|
||||||
app = FlaskApp(__name__, 5001, '..' / simple_api_spec_dir.relative_to(TEST_FOLDER),
|
app = App(__name__, port=5001,
|
||||||
|
specification_dir='..' / simple_api_spec_dir.relative_to(TEST_FOLDER),
|
||||||
debug=True)
|
debug=True)
|
||||||
app.add_api('swagger.yaml')
|
app.add_api('swagger.yaml')
|
||||||
|
|
||||||
@@ -20,14 +21,15 @@ def test_app_with_relative_path(simple_api_spec_dir):
|
|||||||
|
|
||||||
|
|
||||||
def test_no_swagger_ui(simple_api_spec_dir):
|
def test_no_swagger_ui(simple_api_spec_dir):
|
||||||
app = FlaskApp(__name__, 5001, simple_api_spec_dir, swagger_ui=False, debug=True)
|
app = App(__name__, port=5001, specification_dir=simple_api_spec_dir,
|
||||||
|
swagger_ui=False, debug=True)
|
||||||
app.add_api('swagger.yaml')
|
app.add_api('swagger.yaml')
|
||||||
|
|
||||||
app_client = app.app.test_client()
|
app_client = app.app.test_client()
|
||||||
swagger_ui = app_client.get('/v1.0/ui/') # type: flask.Response
|
swagger_ui = app_client.get('/v1.0/ui/') # type: flask.Response
|
||||||
assert swagger_ui.status_code == 404
|
assert swagger_ui.status_code == 404
|
||||||
|
|
||||||
app2 = FlaskApp(__name__, 5001, simple_api_spec_dir, debug=True)
|
app2 = App(__name__, port=5001, specification_dir=simple_api_spec_dir, debug=True)
|
||||||
app2.add_api('swagger.yaml', swagger_ui=False)
|
app2.add_api('swagger.yaml', swagger_ui=False)
|
||||||
app2_client = app2.app.test_client()
|
app2_client = app2.app.test_client()
|
||||||
swagger_ui2 = app2_client.get('/v1.0/ui/') # type: flask.Response
|
swagger_ui2 = app2_client.get('/v1.0/ui/') # type: flask.Response
|
||||||
@@ -36,7 +38,7 @@ def test_no_swagger_ui(simple_api_spec_dir):
|
|||||||
|
|
||||||
def test_swagger_json_app(simple_api_spec_dir):
|
def test_swagger_json_app(simple_api_spec_dir):
|
||||||
""" Verify the swagger.json file is returned for default setting passed to app. """
|
""" Verify the swagger.json file is returned for default setting passed to app. """
|
||||||
app = FlaskApp(__name__, 5001, simple_api_spec_dir, debug=True)
|
app = App(__name__, port=5001, specification_dir=simple_api_spec_dir, debug=True)
|
||||||
app.add_api('swagger.yaml')
|
app.add_api('swagger.yaml')
|
||||||
|
|
||||||
app_client = app.app.test_client()
|
app_client = app.app.test_client()
|
||||||
@@ -46,7 +48,8 @@ def test_swagger_json_app(simple_api_spec_dir):
|
|||||||
|
|
||||||
def test_no_swagger_json_app(simple_api_spec_dir):
|
def test_no_swagger_json_app(simple_api_spec_dir):
|
||||||
""" Verify the swagger.json file is not returned when set to False when creating app. """
|
""" Verify the swagger.json file is not returned when set to False when creating app. """
|
||||||
app = FlaskApp(__name__, 5001, simple_api_spec_dir, swagger_json=False, debug=True)
|
app = App(__name__, port=5001, specification_dir=simple_api_spec_dir,
|
||||||
|
swagger_json=False, debug=True)
|
||||||
app.add_api('swagger.yaml')
|
app.add_api('swagger.yaml')
|
||||||
|
|
||||||
app_client = app.app.test_client()
|
app_client = app.app.test_client()
|
||||||
@@ -55,7 +58,6 @@ def test_no_swagger_json_app(simple_api_spec_dir):
|
|||||||
|
|
||||||
|
|
||||||
def test_dict_as_yaml_path(simple_api_spec_dir):
|
def test_dict_as_yaml_path(simple_api_spec_dir):
|
||||||
|
|
||||||
swagger_yaml_path = simple_api_spec_dir / 'swagger.yaml'
|
swagger_yaml_path = simple_api_spec_dir / 'swagger.yaml'
|
||||||
|
|
||||||
with swagger_yaml_path.open(mode='rb') as swagger_yaml:
|
with swagger_yaml_path.open(mode='rb') as swagger_yaml:
|
||||||
@@ -68,7 +70,7 @@ def test_dict_as_yaml_path(simple_api_spec_dir):
|
|||||||
swagger_string = jinja2.Template(swagger_template).render({})
|
swagger_string = jinja2.Template(swagger_template).render({})
|
||||||
specification = yaml.safe_load(swagger_string) # type: dict
|
specification = yaml.safe_load(swagger_string) # type: dict
|
||||||
|
|
||||||
app = FlaskApp(__name__, 5001, simple_api_spec_dir, debug=True)
|
app = App(__name__, port=5001, specification_dir=simple_api_spec_dir, debug=True)
|
||||||
app.add_api(specification)
|
app.add_api(specification)
|
||||||
|
|
||||||
app_client = app.app.test_client()
|
app_client = app.app.test_client()
|
||||||
@@ -78,7 +80,7 @@ def test_dict_as_yaml_path(simple_api_spec_dir):
|
|||||||
|
|
||||||
def test_swagger_json_api(simple_api_spec_dir):
|
def test_swagger_json_api(simple_api_spec_dir):
|
||||||
""" Verify the swagger.json file is returned for default setting passed to api. """
|
""" Verify the swagger.json file is returned for default setting passed to api. """
|
||||||
app = FlaskApp(__name__, 5001, simple_api_spec_dir, debug=True)
|
app = App(__name__, port=5001, specification_dir=simple_api_spec_dir, debug=True)
|
||||||
app.add_api('swagger.yaml')
|
app.add_api('swagger.yaml')
|
||||||
|
|
||||||
app_client = app.app.test_client()
|
app_client = app.app.test_client()
|
||||||
@@ -88,7 +90,7 @@ def test_swagger_json_api(simple_api_spec_dir):
|
|||||||
|
|
||||||
def test_no_swagger_json_api(simple_api_spec_dir):
|
def test_no_swagger_json_api(simple_api_spec_dir):
|
||||||
""" Verify the swagger.json file is not returned when set to False when adding api. """
|
""" Verify the swagger.json file is not returned when set to False when adding api. """
|
||||||
app = FlaskApp(__name__, 5001, simple_api_spec_dir, debug=True)
|
app = App(__name__, port=5001, specification_dir=simple_api_spec_dir, debug=True)
|
||||||
app.add_api('swagger.yaml', swagger_json=False)
|
app.add_api('swagger.yaml', swagger_json=False)
|
||||||
|
|
||||||
app_client = app.app.test_client()
|
app_client = app.app.test_client()
|
||||||
@@ -143,7 +145,7 @@ def test_resolve_classmethod(simple_app):
|
|||||||
|
|
||||||
|
|
||||||
def test_add_api_with_function_resolver_function_is_wrapped(simple_api_spec_dir):
|
def test_add_api_with_function_resolver_function_is_wrapped(simple_api_spec_dir):
|
||||||
app = FlaskApp(__name__, specification_dir=simple_api_spec_dir)
|
app = App(__name__, specification_dir=simple_api_spec_dir)
|
||||||
api = app.add_api('swagger.yaml', resolver=lambda oid: (lambda foo: 'bar'))
|
api = app.add_api('swagger.yaml', resolver=lambda oid: (lambda foo: 'bar'))
|
||||||
assert api.resolver.resolve_function_from_operation_id('faux')('bah') == 'bar'
|
assert api.resolver.resolve_function_from_operation_id('faux')('bah') == 'bar'
|
||||||
|
|
||||||
|
|||||||
@@ -8,18 +8,21 @@ def test_app(simple_app):
|
|||||||
assert simple_app.port == 5001
|
assert simple_app.port == 5001
|
||||||
|
|
||||||
app_client = simple_app.app.test_client()
|
app_client = simple_app.app.test_client()
|
||||||
|
|
||||||
|
# by default the Swagger UI is enabled
|
||||||
swagger_ui = app_client.get('/v1.0/ui/') # type: flask.Response
|
swagger_ui = app_client.get('/v1.0/ui/') # type: flask.Response
|
||||||
assert swagger_ui.status_code == 200
|
assert swagger_ui.status_code == 200
|
||||||
assert b"Swagger UI" in swagger_ui.data
|
assert b"Swagger UI" in swagger_ui.data
|
||||||
|
|
||||||
|
# test return Swagger UI static files
|
||||||
swagger_icon = app_client.get('/v1.0/ui/images/favicon.ico') # type: flask.Response
|
swagger_icon = app_client.get('/v1.0/ui/images/favicon.ico') # type: flask.Response
|
||||||
assert swagger_icon.status_code == 200
|
assert swagger_icon.status_code == 200
|
||||||
|
|
||||||
post_greeting = app_client.post('/v1.0/greeting/jsantos', data={}) # type: flask.Response
|
post_greeting = app_client.post('/v1.0/greeting/jsantos', data={}) # type: flask.Response
|
||||||
assert post_greeting.status_code == 200
|
assert post_greeting.status_code == 200
|
||||||
assert post_greeting.content_type == 'application/json'
|
assert post_greeting.content_type == 'application/json'
|
||||||
greeting_reponse = json.loads(post_greeting.data.decode('utf-8'))
|
greeting_response = json.loads(post_greeting.data.decode('utf-8'))
|
||||||
assert greeting_reponse['greeting'] == 'Hello jsantos'
|
assert greeting_response['greeting'] == 'Hello jsantos'
|
||||||
|
|
||||||
get_bye = app_client.get('/v1.0/bye/jsantos') # type: flask.Response
|
get_bye = app_client.get('/v1.0/bye/jsantos') # type: flask.Response
|
||||||
assert get_bye.status_code == 200
|
assert get_bye.status_code == 200
|
||||||
@@ -28,8 +31,8 @@ def test_app(simple_app):
|
|||||||
post_greeting = app_client.post('/v1.0/greeting/jsantos', data={}) # type: flask.Response
|
post_greeting = app_client.post('/v1.0/greeting/jsantos', data={}) # type: flask.Response
|
||||||
assert post_greeting.status_code == 200
|
assert post_greeting.status_code == 200
|
||||||
assert post_greeting.content_type == 'application/json'
|
assert post_greeting.content_type == 'application/json'
|
||||||
greeting_reponse = json.loads(post_greeting.data.decode('utf-8'))
|
greeting_response = json.loads(post_greeting.data.decode('utf-8'))
|
||||||
assert greeting_reponse['greeting'] == 'Hello jsantos'
|
assert greeting_response['greeting'] == 'Hello jsantos'
|
||||||
|
|
||||||
|
|
||||||
def test_produce_decorator(simple_app):
|
def test_produce_decorator(simple_app):
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ from connexion import FlaskApp
|
|||||||
|
|
||||||
|
|
||||||
def test_security_over_inexistent_endpoints(oauth_requests, secure_api_spec_dir):
|
def test_security_over_inexistent_endpoints(oauth_requests, secure_api_spec_dir):
|
||||||
app1 = FlaskApp(__name__, 5001, secure_api_spec_dir, swagger_ui=False,
|
app1 = FlaskApp(__name__, port=5001, specification_dir=secure_api_spec_dir,
|
||||||
debug=True, auth_all_paths=True)
|
swagger_ui=False, debug=True, auth_all_paths=True)
|
||||||
app1.add_api('swagger.yaml')
|
app1.add_api('swagger.yaml')
|
||||||
assert app1.port == 5001
|
assert app1.port == 5001
|
||||||
|
|
||||||
@@ -74,7 +74,8 @@ def test_security(oauth_requests, secure_endpoint_app):
|
|||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
headers = {"Authorization": "Bearer 100"}
|
headers = {"Authorization": "Bearer 100"}
|
||||||
get_bye_good_auth = app_client.get('/v1.0/byesecure-ignoring-context/hjacobs', headers=headers) # type: flask.Response
|
get_bye_good_auth = app_client.get('/v1.0/byesecure-ignoring-context/hjacobs',
|
||||||
|
headers=headers) # type: flask.Response
|
||||||
assert get_bye_good_auth.status_code == 200
|
assert get_bye_good_auth.status_code == 200
|
||||||
assert get_bye_good_auth.data == b'Goodbye hjacobs (Secure!)'
|
assert get_bye_good_auth.data == b'Goodbye hjacobs (Secure!)'
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import logging
|
|||||||
import pathlib
|
import pathlib
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from connexion import FlaskApi, FlaskApp
|
from connexion import App
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
@@ -55,9 +55,9 @@ def oauth_requests(monkeypatch):
|
|||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def app():
|
def app():
|
||||||
app = FlaskApp(__name__, 5001, SPEC_FOLDER, debug=True)
|
cnx_app = App(__name__, port=5001, specification_dir=SPEC_FOLDER, debug=True)
|
||||||
app.add_api('api.yaml', validate_responses=True)
|
cnx_app.add_api('api.yaml', validate_responses=True)
|
||||||
return app
|
return cnx_app
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -84,10 +84,14 @@ def build_app_from_fixture(api_spec_folder, **kwargs):
|
|||||||
debug = True
|
debug = True
|
||||||
if 'debug' in kwargs:
|
if 'debug' in kwargs:
|
||||||
debug = kwargs['debug']
|
debug = kwargs['debug']
|
||||||
del(kwargs['debug'])
|
del (kwargs['debug'])
|
||||||
app = FlaskApp(__name__, 5001, FIXTURES_FOLDER / api_spec_folder, debug=debug)
|
|
||||||
app.add_api('swagger.yaml', **kwargs)
|
cnx_app = App(__name__,
|
||||||
return app
|
port=5001,
|
||||||
|
specification_dir=FIXTURES_FOLDER / api_spec_folder,
|
||||||
|
debug=debug)
|
||||||
|
cnx_app.add_api('swagger.yaml', **kwargs)
|
||||||
|
return cnx_app
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
|
|||||||
@@ -8,112 +8,113 @@ from yaml import YAMLError
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from connexion import FlaskApi
|
from connexion import FlaskApi
|
||||||
from connexion.apis.abstract import canonical_base_url
|
from connexion.apis.abstract import canonical_base_path
|
||||||
from connexion.exceptions import InvalidSpecification, ResolverError
|
from connexion.exceptions import InvalidSpecification, ResolverError
|
||||||
|
from mock import MagicMock
|
||||||
|
|
||||||
TEST_FOLDER = pathlib.Path(__file__).parent
|
TEST_FOLDER = pathlib.Path(__file__).parent
|
||||||
|
|
||||||
|
|
||||||
def test_canonical_base_url():
|
def test_canonical_base_path():
|
||||||
assert canonical_base_url('') == ''
|
assert canonical_base_path('') == ''
|
||||||
assert canonical_base_url('/') == ''
|
assert canonical_base_path('/') == ''
|
||||||
assert canonical_base_url('/api') == '/api'
|
assert canonical_base_path('/api') == '/api'
|
||||||
assert canonical_base_url('/api/') == '/api'
|
assert canonical_base_path('/api/') == '/api'
|
||||||
|
|
||||||
|
|
||||||
def test_api():
|
def test_api():
|
||||||
api = FlaskApi(TEST_FOLDER / "fixtures/simple/swagger.yaml", "/api/v1.0", {})
|
api = FlaskApi(TEST_FOLDER / "fixtures/simple/swagger.yaml", base_path="/api/v1.0")
|
||||||
assert api.blueprint.name == '/api/v1_0'
|
assert api.blueprint.name == '/api/v1_0'
|
||||||
assert api.blueprint.url_prefix == '/api/v1.0'
|
assert api.blueprint.url_prefix == '/api/v1.0'
|
||||||
# TODO test base_url in spec
|
|
||||||
|
|
||||||
api2 = FlaskApi(TEST_FOLDER / "fixtures/simple/swagger.yaml")
|
api2 = FlaskApi(TEST_FOLDER / "fixtures/simple/swagger.yaml")
|
||||||
assert api2.blueprint.name == '/v1_0'
|
assert api2.blueprint.name == '/v1_0'
|
||||||
assert api2.blueprint.url_prefix == '/v1.0'
|
assert api2.blueprint.url_prefix == '/v1.0'
|
||||||
|
|
||||||
|
|
||||||
def test_api_basepath_slash():
|
def test_api_base_path_slash():
|
||||||
api = FlaskApi(TEST_FOLDER / "fixtures/simple/basepath-slash.yaml", None, {})
|
api = FlaskApi(TEST_FOLDER / "fixtures/simple/basepath-slash.yaml")
|
||||||
assert api.blueprint.name == ''
|
assert api.blueprint.name == ''
|
||||||
assert api.blueprint.url_prefix == ''
|
assert api.blueprint.url_prefix == ''
|
||||||
|
|
||||||
|
|
||||||
def test_template():
|
def test_template():
|
||||||
api1 = FlaskApi(TEST_FOLDER / "fixtures/simple/swagger.yaml", "/api/v1.0", {'title': 'test'})
|
api1 = FlaskApi(TEST_FOLDER / "fixtures/simple/swagger.yaml",
|
||||||
|
base_path="/api/v1.0", arguments={'title': 'test'})
|
||||||
assert api1.specification['info']['title'] == 'test'
|
assert api1.specification['info']['title'] == 'test'
|
||||||
|
|
||||||
api2 = FlaskApi(TEST_FOLDER / "fixtures/simple/swagger.yaml", "/api/v1.0", {'title': 'other test'})
|
api2 = FlaskApi(TEST_FOLDER / "fixtures/simple/swagger.yaml",
|
||||||
|
base_path="/api/v1.0", arguments={'title': 'other test'})
|
||||||
assert api2.specification['info']['title'] == 'other test'
|
assert api2.specification['info']['title'] == 'other test'
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_operation_does_stop_application_to_setup():
|
def test_invalid_operation_does_stop_application_to_setup():
|
||||||
with pytest.raises(ImportError):
|
with pytest.raises(ImportError):
|
||||||
FlaskApi(TEST_FOLDER / "fixtures/op_error_api/swagger.yaml", "/api/v1.0",
|
FlaskApi(TEST_FOLDER / "fixtures/op_error_api/swagger.yaml",
|
||||||
{'title': 'OK'})
|
base_path="/api/v1.0", arguments={'title': 'OK'})
|
||||||
|
|
||||||
with pytest.raises(ResolverError):
|
with pytest.raises(ResolverError):
|
||||||
FlaskApi(TEST_FOLDER / "fixtures/missing_op_id/swagger.yaml", "/api/v1.0",
|
FlaskApi(TEST_FOLDER / "fixtures/missing_op_id/swagger.yaml",
|
||||||
{'title': 'OK'})
|
base_path="/api/v1.0", arguments={'title': 'OK'})
|
||||||
|
|
||||||
with pytest.raises(ImportError):
|
with pytest.raises(ImportError):
|
||||||
FlaskApi(TEST_FOLDER / "fixtures/module_not_implemented/swagger.yaml", "/api/v1.0",
|
FlaskApi(TEST_FOLDER / "fixtures/module_not_implemented/swagger.yaml",
|
||||||
{'title': 'OK'})
|
base_path="/api/v1.0", arguments={'title': 'OK'})
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
FlaskApi(TEST_FOLDER / "fixtures/user_module_loading_error/swagger.yaml", "/api/v1.0",
|
FlaskApi(TEST_FOLDER / "fixtures/user_module_loading_error/swagger.yaml",
|
||||||
{'title': 'OK'})
|
base_path="/api/v1.0", arguments={'title': 'OK'})
|
||||||
|
|
||||||
with pytest.raises(ResolverError):
|
with pytest.raises(ResolverError):
|
||||||
FlaskApi(TEST_FOLDER / "fixtures/missing_op_id/swagger.yaml", "/api/v1.0",
|
FlaskApi(TEST_FOLDER / "fixtures/missing_op_id/swagger.yaml",
|
||||||
{'title': 'OK'})
|
base_path="/api/v1.0", arguments={'title': 'OK'})
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_operation_does_not_stop_application_in_debug_mode():
|
def test_invalid_operation_does_not_stop_application_in_debug_mode():
|
||||||
api = FlaskApi(TEST_FOLDER / "fixtures/op_error_api/swagger.yaml", "/api/v1.0",
|
api = FlaskApi(TEST_FOLDER / "fixtures/op_error_api/swagger.yaml",
|
||||||
{'title': 'OK'}, debug=True)
|
base_path="/api/v1.0", arguments={'title': 'OK'}, debug=True)
|
||||||
assert api.specification['info']['title'] == 'OK'
|
assert api.specification['info']['title'] == 'OK'
|
||||||
|
|
||||||
api = FlaskApi(TEST_FOLDER / "fixtures/missing_op_id/swagger.yaml", "/api/v1.0",
|
api = FlaskApi(TEST_FOLDER / "fixtures/missing_op_id/swagger.yaml",
|
||||||
{'title': 'OK'}, debug=True)
|
base_path="/api/v1.0", arguments={'title': 'OK'}, debug=True)
|
||||||
assert api.specification['info']['title'] == 'OK'
|
assert api.specification['info']['title'] == 'OK'
|
||||||
|
|
||||||
api = FlaskApi(TEST_FOLDER / "fixtures/module_not_implemented/swagger.yaml", "/api/v1.0",
|
api = FlaskApi(TEST_FOLDER / "fixtures/module_not_implemented/swagger.yaml",
|
||||||
{'title': 'OK'}, debug=True)
|
base_path="/api/v1.0", arguments={'title': 'OK'}, debug=True)
|
||||||
assert api.specification['info']['title'] == 'OK'
|
assert api.specification['info']['title'] == 'OK'
|
||||||
|
|
||||||
api = FlaskApi(TEST_FOLDER / "fixtures/user_module_loading_error/swagger.yaml", "/api/v1.0",
|
api = FlaskApi(TEST_FOLDER / "fixtures/user_module_loading_error/swagger.yaml",
|
||||||
{'title': 'OK'}, debug=True)
|
base_path="/api/v1.0", arguments={'title': 'OK'}, debug=True)
|
||||||
assert api.specification['info']['title'] == 'OK'
|
assert api.specification['info']['title'] == 'OK'
|
||||||
|
|
||||||
api = FlaskApi(TEST_FOLDER / "fixtures/missing_op_id/swagger.yaml", "/api/v1.0",
|
api = FlaskApi(TEST_FOLDER / "fixtures/missing_op_id/swagger.yaml",
|
||||||
{'title': 'OK'}, debug=True)
|
base_path="/api/v1.0", arguments={'title': 'OK'}, debug=True)
|
||||||
assert api.specification['info']['title'] == 'OK'
|
assert api.specification['info']['title'] == 'OK'
|
||||||
|
|
||||||
|
|
||||||
def test_other_errors_stop_application_to_setup():
|
def test_other_errors_stop_application_to_setup():
|
||||||
# The previous tests were just about operationId not being resolvable.
|
# Errors should still result exceptions!
|
||||||
# Other errors should still result exceptions!
|
|
||||||
with pytest.raises(InvalidSpecification):
|
with pytest.raises(InvalidSpecification):
|
||||||
FlaskApi(TEST_FOLDER / "fixtures/bad_specs/swagger.yaml", "/api/v1.0",
|
FlaskApi(TEST_FOLDER / "fixtures/bad_specs/swagger.yaml",
|
||||||
{'title': 'OK'})
|
base_path="/api/v1.0", arguments={'title': 'OK'})
|
||||||
|
|
||||||
# Debug mode should ignore the error
|
# Debug mode should ignore the error
|
||||||
api = FlaskApi(TEST_FOLDER / "fixtures/bad_specs/swagger.yaml", "/api/v1.0",
|
api = FlaskApi(TEST_FOLDER / "fixtures/bad_specs/swagger.yaml",
|
||||||
{'title': 'OK'}, debug=True)
|
base_path="/api/v1.0", arguments={'title': 'OK'}, debug=True)
|
||||||
assert api.specification['info']['title'] == 'OK'
|
assert api.specification['info']['title'] == 'OK'
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_schema_file_structure():
|
def test_invalid_schema_file_structure():
|
||||||
with pytest.raises(SwaggerValidationError):
|
with pytest.raises(SwaggerValidationError):
|
||||||
FlaskApi(TEST_FOLDER / "fixtures/invalid_schema/swagger.yaml", "/api/v1.0",
|
FlaskApi(TEST_FOLDER / "fixtures/invalid_schema/swagger.yaml",
|
||||||
{'title': 'OK'}, debug=True)
|
base_path="/api/v1.0", arguments={'title': 'OK'}, debug=True)
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_encoding():
|
def test_invalid_encoding():
|
||||||
with tempfile.NamedTemporaryFile(mode='wb') as f:
|
with tempfile.NamedTemporaryFile(mode='wb') as f:
|
||||||
f.write(u"swagger: '2.0'\ninfo:\n title: Foo 整\n version: v1\npaths: {}".encode('gbk'))
|
f.write(u"swagger: '2.0'\ninfo:\n title: Foo 整\n version: v1\npaths: {}".encode('gbk'))
|
||||||
f.flush()
|
f.flush()
|
||||||
FlaskApi(pathlib.Path(f.name), "/api/v1.0")
|
FlaskApi(pathlib.Path(f.name), base_path="/api/v1.0")
|
||||||
|
|
||||||
|
|
||||||
def test_use_of_safe_load_for_yaml_swagger_specs():
|
def test_use_of_safe_load_for_yaml_swagger_specs():
|
||||||
@@ -122,7 +123,7 @@ def test_use_of_safe_load_for_yaml_swagger_specs():
|
|||||||
f.write('!!python/object:object {}\n'.encode())
|
f.write('!!python/object:object {}\n'.encode())
|
||||||
f.flush()
|
f.flush()
|
||||||
try:
|
try:
|
||||||
FlaskApi(pathlib.Path(f.name), "/api/v1.0")
|
FlaskApi(pathlib.Path(f.name), base_path="/api/v1.0")
|
||||||
except SwaggerValidationError:
|
except SwaggerValidationError:
|
||||||
pytest.fail("Could load invalid YAML file, use yaml.safe_load!")
|
pytest.fail("Could load invalid YAML file, use yaml.safe_load!")
|
||||||
|
|
||||||
@@ -132,4 +133,17 @@ def test_validation_error_on_completely_invalid_swagger_spec():
|
|||||||
with tempfile.NamedTemporaryFile() as f:
|
with tempfile.NamedTemporaryFile() as f:
|
||||||
f.write('[1]\n'.encode())
|
f.write('[1]\n'.encode())
|
||||||
f.flush()
|
f.flush()
|
||||||
FlaskApi(pathlib.Path(f.name), "/api/v1.0")
|
FlaskApi(pathlib.Path(f.name), base_path="/api/v1.0")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_api_logger(monkeypatch):
|
||||||
|
mocked_logger = MagicMock(name='mocked_logger')
|
||||||
|
monkeypatch.setattr('connexion.apis.abstract.logger', mocked_logger)
|
||||||
|
return mocked_logger
|
||||||
|
|
||||||
|
|
||||||
|
def test_warn_users_about_base_url_parameter_name_change(mock_api_logger):
|
||||||
|
FlaskApi(TEST_FOLDER / "fixtures/simple/swagger.yaml", base_url="/api/v1")
|
||||||
|
mock_api_logger.warning.assert_called_with(
|
||||||
|
'Parameter base_url should be no longer used. Use base_path instead.')
|
||||||
|
|||||||
Reference in New Issue
Block a user