From 2558a611f41e8c6cacf7486a685db8eac910acc1 Mon Sep 17 00:00:00 2001 From: Rafael Caricio Date: Fri, 4 Mar 2016 18:41:35 +0100 Subject: [PATCH] #104 typecasting in path parameters --- README.rst | 1 - connexion/decorators/parameter.py | 8 ++ docs/conf.py | 4 +- tests/api/test_parameters.py | 9 ++ tests/fakeapi/__init__.py | 2 +- tests/fakeapi/foo_bar.py | 1 + tests/fakeapi/hello.py | 4 + tests/fixtures/simple/swagger.yaml | 22 ++++ tests/test_operation.py | 158 ++++++++++++++--------------- 9 files changed, 126 insertions(+), 83 deletions(-) diff --git a/README.rst b/README.rst index 74e56482f..2d4b82a3a 100644 --- a/README.rst +++ b/README.rst @@ -265,7 +265,6 @@ available type castings are: +--------------+-------------+ | Swagger Type | Python Type | -| | | +==============+=============+ | integer | int | +--------------+-------------+ diff --git a/connexion/decorators/parameter.py b/connexion/decorators/parameter.py index 6bd53884a..d82b4557e 100644 --- a/connexion/decorators/parameter.py +++ b/connexion/decorators/parameter.py @@ -65,6 +65,8 @@ def parameter_to_arg(parameters, function): for parameter in parameters if parameter['in'] == 'query'} # type: dict[str, str] form_types = {parameter['name']: parameter for parameter in parameters if parameter['in'] == 'formData'} + path_types = {parameter['name']: parameter + for parameter in parameters if parameter['in'] == 'path'} arguments = get_function_arguments(function) default_query_params = {param['name']: param['default'] for param in parameters if param['in'] == 'query' and 'default' in param} @@ -83,6 +85,12 @@ def wrapper(*args, **kwargs): if default_body and not request_body: request_body = default_body + # Parse path parameters + for key, path_param_definitions in path_types.items(): + if key in kwargs: + kwargs[key] = get_val_from_param(kwargs[key], + path_param_definitions) + # Add body parameters if request_body is not None: if body_name not in arguments: diff --git a/docs/conf.py b/docs/conf.py index 346eb9612..21301b76f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -12,8 +12,8 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys -import os +# import sys +# import os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the diff --git a/tests/api/test_parameters.py b/tests/api/test_parameters.py index fd30f2d05..3a6f1cd7d 100644 --- a/tests/api/test_parameters.py +++ b/tests/api/test_parameters.py @@ -170,3 +170,12 @@ def test_parameters_defined_in_path_level(simple_app): resp = app_client.get('/v1.0/parameters-in-root-path') assert resp.status_code == 400 + + +def test_array_in_path(simple_app): + app_client = simple_app.app.test_client() + resp = app_client.get('/v1.0/test-array-in-path/one_item') + assert json.loads(resp.data.decode()) == ["one_item"] + + resp = app_client.get('/v1.0/test-array-in-path/one_item,another_item') + assert json.loads(resp.data.decode()) == ["one_item", "another_item"] diff --git a/tests/fakeapi/__init__.py b/tests/fakeapi/__init__.py index 9b694f584..97ef9e1b0 100644 --- a/tests/fakeapi/__init__.py +++ b/tests/fakeapi/__init__.py @@ -1,2 +1,2 @@ def get(): - return '' \ No newline at end of file + return '' diff --git a/tests/fakeapi/foo_bar.py b/tests/fakeapi/foo_bar.py index 05bdd99ac..3b68520ec 100755 --- a/tests/fakeapi/foo_bar.py +++ b/tests/fakeapi/foo_bar.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 + def search(): return '' diff --git a/tests/fakeapi/hello.py b/tests/fakeapi/hello.py index d033c2950..c40c45efa 100755 --- a/tests/fakeapi/hello.py +++ b/tests/fakeapi/hello.py @@ -281,3 +281,7 @@ def path_parameters_in_get_method(title): def test_default_missmatch_definition(age): return 'OK' + + +def test_array_in_path(names): + return names, 200 diff --git a/tests/fixtures/simple/swagger.yaml b/tests/fixtures/simple/swagger.yaml index fc3796b12..de3ee7405 100644 --- a/tests/fixtures/simple/swagger.yaml +++ b/tests/fixtures/simple/swagger.yaml @@ -474,6 +474,28 @@ paths: type: string description: 204 no content + /test-array-in-path/{names}: + get: + operationId: fakeapi.hello.test_array_in_path + produces: + - application/json + parameters: + - name: names + description: List of names. + in: path + type: array + items: + type: string + required: true + collectionFormat: csv + responses: + 200: + description: OK + schema: + type: array + items: + type: string + definitions: new_stack: diff --git a/tests/test_operation.py b/tests/test_operation.py index 8f23c8713..4805d63b3 100644 --- a/tests/test_operation.py +++ b/tests/test_operation.py @@ -14,44 +14,44 @@ 'type': 'object', 'properties': {'keep_stacks': {'type': 'integer', 'description': - 'Number of older stacks to keep'}, + 'Number of older stacks to keep'}, 'image_version': {'type': 'string', 'description': - 'Docker image version to deploy'}, + 'Docker image version to deploy'}, 'senza_yaml': {'type': 'string', '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 ' - 'information needed to keep track of deployment', + 'information needed to keep track of deployment', 'operationId': 'fakeapi.hello.post_greeting', 'parameters': [{'in': 'body', 'name': 'new_stack', 'required': True, 'schema': {'$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.', + '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', + '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', + 'access token was not provided or was ' + 'not valid for this operation', 'schema': {'$ref': '#/definitions/problem'}}}, 'security': [{'oauth': ['uid']}], 'summary': 'Create new stack'} OPERATION2 = {'description': 'Adds a new stack to be created by lizzy and returns the ' - 'information needed to keep track of deployment', + 'information needed to keep track of deployment', 'operationId': 'fakeapi.hello.post_greeting', 'parameters': [{'in': 'body', 'name': 'new_stack', @@ -62,38 +62,38 @@ 'required': True, 'schema': {'$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.', + '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', + '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', + 'access token was not provided or was ' + 'not valid for this operation', 'schema': {'$ref': '#/definitions/problem'}}}, 'security': [{'oauth': ['uid']}], 'summary': 'Create new stack'} OPERATION3 = {'description': 'Adds a new stack to be created by lizzy and returns the ' - 'information needed to keep track of deployment', + 'information needed to keep track of deployment', 'operationId': 'fakeapi.hello.post_greeting', 'parameters': [{'in': 'body', 'name': 'new_stack', 'required': True, 'schema': {'$ref': '#/notdefinitions/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.', + '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', + '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', + 'access token was not provided or was ' + 'not valid for this operation', 'schema': {'$ref': '#/definitions/problem'}}}, 'security': [{'oauth': ['uid']}], 'summary': 'Create new stack'} @@ -105,7 +105,7 @@ 'parameters': [{'$ref': '/parameters/fail'}]} OPERATION6 = {'description': 'Adds a new stack to be created by lizzy and returns the ' - 'information needed to keep track of deployment', + 'information needed to keep track of deployment', 'operationId': 'fakeapi.hello.post_greeting', 'parameters': [ { @@ -122,22 +122,22 @@ } ], 'responses': {201: {'description': 'Stack to be created. The ' - 'CloudFormation Stack creation can ' - "still fail if it's rejected by senza " - 'or AWS CF.', + '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', + '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', + 'access token was not provided or was ' + 'not valid for this operation', 'schema': {'$ref': '#/definitions/problem'}}}, 'summary': 'Create new stack'} OPERATION7 = { 'description': 'Adds a new stack to be created by lizzy and returns the ' - 'information needed to keep track of deployment', + 'information needed to keep track of deployment', 'operationId': 'fakeapi.hello.post_greeting', 'parameters': [ { @@ -149,16 +149,16 @@ } ], 'responses': {'201': {'description': 'Stack to be created. The ' - 'CloudFormation Stack creation can ' - "still fail if it's rejected by senza " - 'or AWS CF.', + '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', + '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', + 'access token was not provided or was ' + 'not valid for this operation', 'schema': {'$ref': '#/definitions/problem'}}}, 'security': [{'oauth': ['uid']}], 'summary': 'Create new stack' @@ -182,45 +182,45 @@ } OPERATION9 = {'description': 'Adds a new stack to be created by lizzy and returns the ' - 'information needed to keep track of deployment', + '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.', + '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', + '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', + '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', + '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.', + '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', + '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', + 'access token was not provided or was ' + 'not valid for this operation', 'schema': {'$ref': '#/definitions/problem'}}}, 'security': [{'oauth': ['uid']}], 'summary': 'Create new stack'} @@ -321,15 +321,15 @@ def test_operation_composed_definition(): def test_non_existent_reference(): with pytest.raises(InvalidSpecification) as exc_info: # type: py.code.ExceptionInfo operation = Operation(method='GET', - path='endpoint', - path_parameters=[], - operation=OPERATION1, - app_produces=['application/json'], - app_security=[], - security_definitions={}, - definitions={}, - parameter_definitions={}, - resolver=Resolver()) + path='endpoint', + path_parameters=[], + operation=OPERATION1, + app_produces=['application/json'], + app_security=[], + security_definitions={}, + definitions={}, + parameter_definitions={}, + resolver=Resolver()) operation.body_schema exception = exc_info.value @@ -340,15 +340,15 @@ def test_non_existent_reference(): def test_multi_body(): with pytest.raises(InvalidSpecification) as exc_info: # type: py.code.ExceptionInfo operation = Operation(method='GET', - path='endpoint', - path_parameters=[], - operation=OPERATION2, - app_produces=['application/json'], - app_security=[], - security_definitions={}, - definitions=DEFINITIONS, - parameter_definitions=PARAMETER_DEFINITIONS, - resolver=Resolver()) + path='endpoint', + path_parameters=[], + operation=OPERATION2, + app_produces=['application/json'], + app_security=[], + security_definitions={}, + definitions=DEFINITIONS, + parameter_definitions=PARAMETER_DEFINITIONS, + resolver=Resolver()) operation.body_schema exception = exc_info.value @@ -359,15 +359,15 @@ def test_multi_body(): def test_invalid_reference(): with pytest.raises(InvalidSpecification) as exc_info: # type: py.code.ExceptionInfo operation = Operation(method='GET', - path='endpoint', - path_parameters=[], - operation=OPERATION3, - app_produces=['application/json'], - app_security=[], - security_definitions={}, - definitions=DEFINITIONS, - parameter_definitions=PARAMETER_DEFINITIONS, - resolver=Resolver()) + path='endpoint', + path_parameters=[], + operation=OPERATION3, + app_produces=['application/json'], + app_security=[], + security_definitions={}, + definitions=DEFINITIONS, + parameter_definitions=PARAMETER_DEFINITIONS, + resolver=Resolver()) operation.body_schema exception = exc_info.value