Skip to content

Commit

Permalink
Merge pull request #118 from cyprien-g/fix-list-of-definition
Browse files Browse the repository at this point in the history
Handle array of definition and definition properties in a definition
  • Loading branch information
hjacobs committed Jan 12, 2016
2 parents c5eea22 + 00def7f commit bbdeb4d
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 4 deletions.
26 changes: 23 additions & 3 deletions connexion/operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
language governing permissions and limitations under the License.
"""

from copy import deepcopy
import functools
import logging
import os
Expand Down Expand Up @@ -121,8 +122,10 @@ def validate_defaults(self):
param_type=param['type']))

def resolve_reference(self, schema):
schema = schema.copy() # avoid changing the original schema
schema = deepcopy(schema) # avoid changing the original schema
reference = schema.get('$ref') # type: str
if not reference and 'items' in schema:
reference = schema['items'].get('$ref')
if reference:
if not reference.startswith('#/'):
raise InvalidSpecification(
Expand All @@ -136,11 +139,28 @@ def resolve_reference(self, schema):
"{method} {path} '$ref' needs to point to definitions or parameters".format(**vars(self)))
definition_name = path[-1]
try:
schema.update(definitions[definition_name])
# Get sub definition
definition = deepcopy(definitions[definition_name])
for prop, prop_spec in definition.get('properties', {}).items():
resolved = self.resolve_reference(prop_spec.get('schema', {}))
if resolved == {}:
resolved = self.resolve_reference(prop_spec)

if not resolved == {}:
definition['properties'][prop] = resolved

# Update schema
if '$ref' in schema:
schema.update(definition)
else:
schema['items'].update(definition)
except KeyError:
raise InvalidSpecification("{method} {path} Definition '{definition_name}' not found".format(
definition_name=definition_name, method=self.method, path=self.path))
del schema['$ref']
if '$ref' in schema:
del schema['$ref']
else:
del schema['items']['$ref']
return schema

def get_mimetype(self):
Expand Down
96 changes: 95 additions & 1 deletion tests/test_operation.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from copy import deepcopy
import pathlib
import types

Expand All @@ -22,7 +23,10 @@
'description': 'YAML to provide to senza'},
'new_traffic': {'type': 'integer',
'description':
'Percentage of the traffic'}}}}
'Percentage of the traffic'}}},
'composed': {'required': ['test'],
'type': 'object',
'properties': {'test': {'schema': {'$ref': '#/definitions/new_stack'}}}}}
PARAMETER_DEFINITIONS = {'myparam': {'in': 'path', 'type': 'integer'}}

OPERATION1 = {'description': 'Adds a new stack to be created by lizzy and returns the '
Expand Down Expand Up @@ -178,6 +182,50 @@
'summary': 'Create new stack'
}

OPERATION9 = {'description': 'Adds a new stack to be created by lizzy and returns the '
'information needed to keep track of deployment',
'operationId': 'fakeapi.hello.post_greeting',
'parameters': [{'in': 'body',
'name': 'new_stack',
'required': True,
'schema': {'type': 'array', 'items': {'$ref': '#/definitions/new_stack'}}}],
'responses': {201: {'description': 'Stack to be created. The '
'CloudFormation Stack creation can '
"still fail if it's rejected by senza "
'or AWS CF.',
'schema': {'$ref': '#/definitions/stack'}},
400: {'description': 'Stack was not created because request '
'was invalid',
'schema': {'$ref': '#/definitions/problem'}},
401: {'description': 'Stack was not created because the '
'access token was not provided or was '
'not valid for this operation',
'schema': {'$ref': '#/definitions/problem'}}},
'security': [{'oauth': ['uid']}],
'summary': 'Create new stack'}

OPERATION10 = {'description': 'Adds a new stack to be created by lizzy and returns the '
'information needed to keep track of deployment',
'operationId': 'fakeapi.hello.post_greeting',
'parameters': [{'in': 'body',
'name': 'test',
'required': True,
'schema': {'$ref': '#/definitions/composed'}}],
'responses': {201: {'description': 'Stack to be created. The '
'CloudFormation Stack creation can '
"still fail if it's rejected by senza "
'or AWS CF.',
'schema': {'$ref': '#/definitions/stack'}},
400: {'description': 'Stack was not created because request '
'was invalid',
'schema': {'$ref': '#/definitions/problem'}},
401: {'description': 'Stack was not created because the '
'access token was not provided or was '
'not valid for this operation',
'schema': {'$ref': '#/definitions/problem'}}},
'security': [{'oauth': ['uid']}],
'summary': 'Create new stack'}

SECURITY_DEFINITIONS = {'oauth': {'type': 'oauth2',
'flow': 'password',
'x-tokenInfoUrl': 'https://ouath.example/token_info',
Expand Down Expand Up @@ -210,6 +258,52 @@ def test_operation():
assert operation.body_schema == DEFINITIONS['new_stack']


def test_operation_array():
operation = Operation(method='GET',
path='endpoint',
operation=OPERATION9,
app_produces=['application/json'],
app_security=[],
security_definitions=SECURITY_DEFINITIONS,
definitions=DEFINITIONS,
parameter_definitions=PARAMETER_DEFINITIONS,
resolver=Resolver())
assert isinstance(operation.function, types.FunctionType)
# security decorator should be a partial with verify_oauth as the function and token url and scopes as arguments.
# See https://docs.python.org/2/library/functools.html#partial-objects
assert operation._Operation__security_decorator.func is verify_oauth
assert operation._Operation__security_decorator.args == ('https://ouath.example/token_info', set(['uid']))

assert operation.method == 'GET'
assert operation.produces == ['application/json']
assert operation.security == [{'oauth': ['uid']}]
assert operation.body_schema == {'type': 'array', 'items': DEFINITIONS['new_stack']}


def test_operation_composed_definition():
operation = Operation(method='GET',
path='endpoint',
operation=OPERATION10,
app_produces=['application/json'],
app_security=[],
security_definitions=SECURITY_DEFINITIONS,
definitions=DEFINITIONS,
parameter_definitions=PARAMETER_DEFINITIONS,
resolver=Resolver())
assert isinstance(operation.function, types.FunctionType)
# security decorator should be a partial with verify_oauth as the function and token url and scopes as arguments.
# See https://docs.python.org/2/library/functools.html#partial-objects
assert operation._Operation__security_decorator.func is verify_oauth
assert operation._Operation__security_decorator.args == ('https://ouath.example/token_info', set(['uid']))

assert operation.method == 'GET'
assert operation.produces == ['application/json']
assert operation.security == [{'oauth': ['uid']}]
definition = deepcopy(DEFINITIONS['composed'])
definition['properties']['test'] = DEFINITIONS['new_stack']
assert operation.body_schema == definition


def test_non_existent_reference():
operation = Operation(method='GET',
path='endpoint',
Expand Down

0 comments on commit bbdeb4d

Please sign in to comment.