-
-
Notifications
You must be signed in to change notification settings - Fork 774
Closed
Description
Description
Connexion's handling of the case where a query parameter has an explicitly defined default value of null is inconsistent and surprising
Steps to reproduce
test_api.yaml
:
swagger: "2.0"
info:
version: 1.0.0
title: Test Service
license:
name: MIT
basePath: /api
schemes:
- http
consumes:
- application/json
produces:
- application/json
paths:
/test_str:
get:
operationId: "test_api.test_str"
parameters:
- name: "test_arg"
in: "query"
required: false
default: null
type: "string"
description: "test"
responses:
default:
description: 'A test'
/test_int:
get:
operationId: "test_api.test_int"
parameters:
- name: "test_arg"
in: "query"
required: false
default: null
type: "integer"
description: "test"
responses:
default:
description: 'A test'
test_app.py
:
import typing as _t
import connexion
def test_str(test_arg: _t.Optional[str]):
return {'test_arg': repr(test_arg), 'type': str(type(test_arg))}
def test_int(test_arg: _t.Optional[int]):
return {'test_arg': repr(test_arg), 'type': str(type(test_arg))}
app = connexion.App(__name__, specification_dir='swagger/')
app.add_api('test_api.yaml')
app.run(port=5000)
Execute queries GET http://test_app:5000/api/test_str?
and GET http://test_app:5000/api/test_int?
.
Expected behaviour
Ideal: Both queries return:
{
"test_arg": "None",
"type": "<class 'NoneType'>"
}
Acceptable: Error at either app.add_api()
or app.run()
(not when the endpoints are called) warning that the given default value of null does not match the parameter type as required by the OpenAPI spec.
Actual behaviour
api/test_str
:
{
"test_arg": "'None'",
"type": "<class 'str'>"
}
api/test_int
:
[2020-02-21 19:52:35,730] ERROR in app: Exception on /api/test_int [GET]
Traceback (most recent call last):
File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 2446, in wsgi_app
response = self.full_dispatch_request()
File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1951, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1820, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/usr/local/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
raise value
File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1949, in full_dispatch_request
rv = self.dispatch_request()
File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1935, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/usr/local/lib/python3.7/site-packages/connexion/decorators/decorator.py", line 48, in wrapper
response = function(request)
File "/usr/local/lib/python3.7/site-packages/connexion/decorators/uri_parsing.py", line 144, in wrapper
response = function(request)
File "/usr/local/lib/python3.7/site-packages/connexion/decorators/validation.py", line 384, in wrapper
return function(request)
File "/usr/local/lib/python3.7/site-packages/connexion/decorators/parameter.py", line 103, in wrapper
request.files, arguments, has_kwargs, sanitize)
File "/usr/local/lib/python3.7/site-packages/connexion/operations/abstract.py", line 268, in get_arguments
has_kwargs, sanitize))
File "/usr/local/lib/python3.7/site-packages/connexion/operations/swagger2.py", line 234, in _get_query_arguments
arguments, has_kwargs, sanitize)
File "/usr/local/lib/python3.7/site-packages/connexion/operations/abstract.py", line 200, in _query_args_helper
res.update({key: self._get_val_from_param(value, query_defn)})
File "/usr/local/lib/python3.7/site-packages/connexion/operations/swagger2.py", line 285, in _get_val_from_param
return make_type(value, query_defn["type"])
File "/usr/local/lib/python3.7/site-packages/connexion/utils.py", line 40, in make_type
return type_func(value)
TypeError: int() argument must be a string, a bytes-like object or a number, not 'NoneType'
172.18.0.47 - - [21/Feb/2020 19:52:35] "GET /api/test_int HTTP/1.1" 500 -
Versions information:
python --version
: Python 3.7.6
pip freeze
:
backcall==0.1.0
certifi==2019.3.9
chardet==3.0.4
Click==7.0
clickclick==1.2.2
colorful==0.5.4
connexion==2.6.0
coverage==4.5.3
decorator==4.4.1
elasticsearch==6.3.1
elasticsearch-dsl==6.4.0
fastavro==0.21.19
Flask==1.1.1
Flask-Injector==0.11.0
idna==2.7
inflection==0.3.1
injector==0.16.0
ipython==7.12.0
ipython-genutils==0.2.0
itsdangerous==1.1.0
jedi==0.16.0
Jinja2==2.11.1
jsonschema==2.6.0
MarkupSafe==1.1.1
openapi-spec-validator==0.2.6
parso==0.6.1
pathlib==1.0.1
pexpect==4.8.0
pickleshare==0.7.5
pika==0.13.1
prettyprinter==0.18.0
prompt-toolkit==3.0.3
ptyprocess==0.6.0
Pygments==2.5.2
python-dateutil==2.8.1
PyYAML==5.1
redis==3.2.1
requests==2.20.1
six==1.12.0
traitlets==4.3.3
typing==3.6.6
urllib3==1.24.1
wcwidth==0.1.8
Werkzeug==0.15.1
Additional info:
- I am aware you can add a
None
default on the function instead of the API in order to check if the argument was passed in or not. However:- In my use case, I'm also using Flask-Injector to supply shared resources to my endpoint-servicing functions. If Connexion does not supply a value for some parameter, Flask-Injector will, based off the hinted type, and it defaults to the empty string for string args and 0 for integers.
- This does not address the underlying issue that the handling of null as a default is surprising (and in the case of string arguments, clearly wrong)
- Adding
x-nullable: true
causes the null default to be correctly handled. However, it also allows the user to supply the parameter with an explicit null, which may not be desirable, especially on a string parameter where 'null' or 'None' is a valid value.
Metadata
Metadata
Assignees
Labels
No labels