Skip to content

Commit

Permalink
Merge pull request spec-first#187 from rafaelcaricio/global_response_…
Browse files Browse the repository at this point in the history
…defs

Response definitions can be defined globally in spec
  • Loading branch information
jmcs committed Mar 21, 2016
2 parents 593b533 + d355d8a commit 5e5d97e
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 23 deletions.
21 changes: 15 additions & 6 deletions connexion/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ def __init__(self, swagger_yaml_path, base_url=None, arguments=None,

self.definitions = self.specification.get('definitions', {})
self.parameter_definitions = self.specification.get('parameters', {})
self.response_definitions = self.specification.get('responses', {})

self.swagger_path = swagger_path or SWAGGER_UI_PATH
self.swagger_url = swagger_url or SWAGGER_UI_URL
Expand Down Expand Up @@ -143,13 +144,21 @@ def add_operation(self, method, path, swagger_operation, path_parameters):
:type path: str
:type swagger_operation: dict
"""
operation = Operation(method=method, path=path, path_parameters=path_parameters,
operation=swagger_operation, app_produces=self.produces,
app_security=self.security, security_definitions=self.security_definitions,
definitions=self.definitions, parameter_definitions=self.parameter_definitions,
validate_responses=self.validate_responses, resolver=self.resolver)
operation = Operation(method=method,
path=path,
path_parameters=path_parameters,
operation=swagger_operation,
app_produces=self.produces,
app_security=self.security,
security_definitions=self.security_definitions,
definitions=self.definitions,
parameter_definitions=self.parameter_definitions,
response_definitions=self.response_definitions,
validate_responses=self.validate_responses,
resolver=self.resolver)
operation_id = operation.operation_id
logger.debug('... Adding %s -> %s', method.upper(), operation_id, extra=vars(operation))
logger.debug('... Adding %s -> %s', method.upper(), operation_id,
extra=vars(operation))

flask_path = utils.flaskify_path(path, operation.get_path_parameter_types())
self.blueprint.add_url_rule(flask_path, operation.endpoint_name, operation.function, methods=[method])
Expand Down
41 changes: 27 additions & 14 deletions connexion/operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,10 @@ class Operation(SecureOperation):
A single API operation on a path.
"""

def __init__(self, method, path, path_parameters, operation, app_produces,
app_security, security_definitions, definitions,
parameter_definitions, resolver, validate_responses=False):
def __init__(self, method, path, operation, resolver, app_produces,
path_parameters=None, app_security=None, security_definitions=None,
definitions=None, parameter_definitions=None, response_definitions=None,
validate_responses=False):
"""
This class uses the OperationID identify the module and function that will handle the operation
Expand All @@ -118,12 +119,13 @@ def __init__(self, method, path, path_parameters, operation, app_produces,
:type method: str
:param path:
:type path: str
:param path_parameters: Parameters defined in the path level
:type path_parameters: list
:param operation: swagger operation object
:type operation: dict
:param resolver: Callable that maps operationID to a function
:param app_produces: list of content types the application can return by default
:type app_produces: list
:param path_parameters: Parameters defined in the path level
:type path_parameters: list
:param app_security: list of security rules the application uses by default
:type app_security: list
:param security_definitions: `Security Definitions Object
Expand All @@ -134,19 +136,22 @@ def __init__(self, method, path, path_parameters, operation, app_produces,
:type definitions: dict
:param parameter_definitions: Global parameter definitions
:type parameter_definitions: dict
:param resolver: Callable that maps operationID to a function
:param response_definitions: Global response definitions
:type response_definitions: dict
:param validate_responses: True enables validation. Validation errors generate HTTP 500 responses.
:type validate_responses: bool
"""

self.method = method
self.path = path
self.security_definitions = security_definitions
self.definitions = definitions
self.parameter_definitions = parameter_definitions
self.security_definitions = security_definitions or {}
self.definitions = definitions or {}
self.parameter_definitions = parameter_definitions or {}
self.response_definitions = response_definitions or {}
self.definitions_map = {
'definitions': self.definitions,
'parameters': self.parameter_definitions
'parameters': self.parameter_definitions,
'responses': self.response_definitions
}
self.validate_responses = validate_responses
self.operation = operation
Expand Down Expand Up @@ -232,21 +237,29 @@ def check_references(self, schema):
def _retrieve_reference(self, reference):
if not reference.startswith('#/'):
raise InvalidSpecification(
"{method} {path} '$ref' needs to start with '#/'".format(**vars(self)))
"{method} {path} '$ref' needs to start with '#/'".format(**vars(self)))
path = reference.split('/')
definition_type = path[1]
try:
definitions = self.definitions_map[definition_type]
except KeyError:
ref_possible = ', '.join(self.definitions_map.keys())
raise InvalidSpecification(
"{method} {path} '$ref' needs to point to definitions or parameters".format(**vars(self)))
"{method} {path} $ref \"{reference}\" needs to point to one of: "
"{ref_possible}".format(
method=self.method,
path=self.path,
reference=reference,
ref_possible=ref_possible
))
definition_name = path[-1]
try:
# Get sub definition
definition = deepcopy(definitions[definition_name])
except KeyError:
raise InvalidSpecification("{method} {path} Definition '{definition_name}' not found".format(
definition_name=definition_name, method=self.method, path=self.path))
raise InvalidSpecification(
"{method} {path} Definition '{definition_name}' not found".format(
definition_name=definition_name, method=self.method, path=self.path))

return definition

Expand Down
6 changes: 6 additions & 0 deletions tests/api/test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,3 +223,9 @@ def test_schema_int(schema_app):
assert array_request.content_type == 'application/json'
array_response = json.loads(array_request.data.decode()) # type: list
assert array_response == 42


def test_global_response_definitions(schema_app):
app_client = schema_app.app.test_client()
resp = app_client.get('/v1.0/define_global_response')
assert json.loads(resp.data.decode()) == ['general', 'list']
4 changes: 4 additions & 0 deletions tests/fakeapi/hello.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,3 +285,7 @@ def test_default_missmatch_definition(age):

def test_array_in_path(names):
return names, 200


def test_global_response_definition():
return ['general', 'list'], 200
16 changes: 16 additions & 0 deletions tests/fixtures/different_schemas/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ info:

basePath: /v1.0

responses:
GeneralList:
description: A nice string array
schema:
type: array
items:
type: string

paths:
/test_schema:
post:
Expand Down Expand Up @@ -275,6 +283,14 @@ paths:
200:
description: OK

/define_global_response:
get:
description: Should allow global response definitions
operationId: fakeapi.hello.test_global_response_definition
responses:
200:
$ref: '#/responses/GeneralList'

definitions:
new_stack:
type: object
Expand Down
6 changes: 3 additions & 3 deletions tests/test_operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,8 +371,8 @@ def test_invalid_reference():
operation.body_schema

exception = exc_info.value
assert str(exception) == "<InvalidSpecification: GET endpoint '$ref' needs to point to definitions or parameters>"
assert repr(exception) == "<InvalidSpecification: GET endpoint '$ref' needs to point to definitions or parameters>"
assert str(exception).startswith("<InvalidSpecification: GET endpoint $ref")
assert repr(exception).startswith("<InvalidSpecification: GET endpoint $ref")


def test_no_token_info():
Expand Down Expand Up @@ -423,7 +423,7 @@ def test_resolve_invalid_reference():
resolver=Resolver())

exception = exc_info.value # type: InvalidSpecification
assert exception.reason == "GET endpoint '$ref' needs to start with '#/'"
assert exception.reason == "GET endpoint '$ref' needs to start with '#/'"


def test_default():
Expand Down

0 comments on commit 5e5d97e

Please sign in to comment.