Skip to content

Surprising behavior of null-default query parameters #1167

@macdjord

Description

@macdjord

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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions