mirror of
https://github.com/LukeHagar/connexion.git
synced 2025-12-10 12:27:46 +00:00
More liberal flask number converters for float and int in paths (#1306)
* Use more liberal flask converters for float and int These don't try to enforce a "single representation" of paths but instead try to convert the numbers that callers pass in. Addresses #1040 and #1041 * Use f-strings instead of string concat or %-formats * Complying with style rules added long after this PR was made
This commit is contained in:
@@ -27,6 +27,8 @@ class FlaskApp(AbstractApp):
|
|||||||
def create_app(self):
|
def create_app(self):
|
||||||
app = flask.Flask(self.import_name, **self.server_args)
|
app = flask.Flask(self.import_name, **self.server_args)
|
||||||
app.json_encoder = FlaskJSONEncoder
|
app.json_encoder = FlaskJSONEncoder
|
||||||
|
app.url_map.converters['float'] = NumberConverter
|
||||||
|
app.url_map.converters['int'] = IntegerConverter
|
||||||
return app
|
return app
|
||||||
|
|
||||||
def get_root_path(self):
|
def get_root_path(self):
|
||||||
@@ -142,3 +144,19 @@ class FlaskJSONEncoder(json.JSONEncoder):
|
|||||||
return float(o)
|
return float(o)
|
||||||
|
|
||||||
return json.JSONEncoder.default(self, o)
|
return json.JSONEncoder.default(self, o)
|
||||||
|
|
||||||
|
|
||||||
|
class NumberConverter(werkzeug.routing.BaseConverter):
|
||||||
|
""" Flask converter for OpenAPI number type """
|
||||||
|
regex = r"[+-]?[0-9]*(\.[0-9]*)?"
|
||||||
|
|
||||||
|
def to_python(self, value):
|
||||||
|
return float(value)
|
||||||
|
|
||||||
|
|
||||||
|
class IntegerConverter(werkzeug.routing.BaseConverter):
|
||||||
|
""" Flask converter for OpenAPI integer type """
|
||||||
|
regex = r"[+-]?[0-9]+"
|
||||||
|
|
||||||
|
def to_python(self, value):
|
||||||
|
return int(value)
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import json
|
import json
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
def test_parameter_validation(simple_app):
|
def test_parameter_validation(simple_app):
|
||||||
app_client = simple_app.app.test_client()
|
app_client = simple_app.app.test_client()
|
||||||
@@ -124,22 +126,57 @@ def test_strict_formdata_param(strict_app):
|
|||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def test_path_parameter_someint(simple_app):
|
@pytest.mark.parametrize('arg, result', [
|
||||||
|
# The cases accepted by the Flask/Werkzeug converter
|
||||||
|
['123', 'int 123'],
|
||||||
|
['0', 'int 0'],
|
||||||
|
['0000', 'int 0'],
|
||||||
|
# Additional cases that we want to support
|
||||||
|
['+123', 'int 123'],
|
||||||
|
['+0', 'int 0'],
|
||||||
|
['-0', 'int 0'],
|
||||||
|
['-123', 'int -123'],
|
||||||
|
])
|
||||||
|
def test_path_parameter_someint(simple_app, arg, result):
|
||||||
|
assert isinstance(arg, str) # sanity check
|
||||||
app_client = simple_app.app.test_client()
|
app_client = simple_app.app.test_client()
|
||||||
resp = app_client.get('/v1.0/test-int-path/123') # type: flask.Response
|
resp = app_client.get(f'/v1.0/test-int-path/{arg}') # type: flask.Response
|
||||||
assert resp.data.decode('utf-8', 'replace') == '"int"\n'
|
assert resp.data.decode('utf-8', 'replace') == f'"{result}"\n'
|
||||||
|
|
||||||
|
|
||||||
|
def test_path_parameter_someint__bad(simple_app):
|
||||||
# non-integer values will not match Flask route
|
# non-integer values will not match Flask route
|
||||||
|
app_client = simple_app.app.test_client()
|
||||||
resp = app_client.get('/v1.0/test-int-path/foo') # type: flask.Response
|
resp = app_client.get('/v1.0/test-int-path/foo') # type: flask.Response
|
||||||
assert resp.status_code == 404
|
assert resp.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
def test_path_parameter_somefloat(simple_app):
|
@pytest.mark.parametrize('arg, result', [
|
||||||
|
# The cases accepted by the Flask/Werkzeug converter
|
||||||
|
['123.45', 'float 123.45'],
|
||||||
|
['123.0', 'float 123'],
|
||||||
|
['0.999999999999999999', 'float 1'],
|
||||||
|
# Additional cases that we want to support
|
||||||
|
['+123.45', 'float 123.45'],
|
||||||
|
['-123.45', 'float -123.45'],
|
||||||
|
['123.', 'float 123'],
|
||||||
|
['.45', 'float 0.45'],
|
||||||
|
['123', 'float 123'],
|
||||||
|
['0', 'float 0'],
|
||||||
|
['0000', 'float 0'],
|
||||||
|
['-0.000000001', 'float -1e-09'],
|
||||||
|
['100000000000', 'float 1e+11'],
|
||||||
|
])
|
||||||
|
def test_path_parameter_somefloat(simple_app, arg, result):
|
||||||
|
assert isinstance(arg, str) # sanity check
|
||||||
app_client = simple_app.app.test_client()
|
app_client = simple_app.app.test_client()
|
||||||
resp = app_client.get('/v1.0/test-float-path/123.45') # type: flask.Response
|
resp = app_client.get(f'/v1.0/test-float-path/{arg}') # type: flask.Response
|
||||||
assert resp.data.decode('utf-8' , 'replace') == '"float"\n'
|
assert resp.data.decode('utf-8', 'replace') == f'"{result}"\n'
|
||||||
|
|
||||||
|
|
||||||
|
def test_path_parameter_somefloat__bad(simple_app):
|
||||||
# non-float values will not match Flask route
|
# non-float values will not match Flask route
|
||||||
|
app_client = simple_app.app.test_client()
|
||||||
resp = app_client.get('/v1.0/test-float-path/123,45') # type: flask.Response
|
resp = app_client.get('/v1.0/test-float-path/123,45') # type: flask.Response
|
||||||
assert resp.status_code == 404
|
assert resp.status_code == 404
|
||||||
|
|
||||||
|
|||||||
@@ -266,11 +266,11 @@ def test_schema_int(test_int):
|
|||||||
|
|
||||||
|
|
||||||
def test_get_someint(someint):
|
def test_get_someint(someint):
|
||||||
return type(someint).__name__
|
return f'{type(someint).__name__} {someint:g}'
|
||||||
|
|
||||||
|
|
||||||
def test_get_somefloat(somefloat):
|
def test_get_somefloat(somefloat):
|
||||||
return type(somefloat).__name__
|
return f'{type(somefloat).__name__} {somefloat:g}'
|
||||||
|
|
||||||
|
|
||||||
def test_default_param(name):
|
def test_default_param(name):
|
||||||
|
|||||||
Reference in New Issue
Block a user