Skip to content

WIP: Fixing issue where Optional[List[Enum]] didn't work, among others #61

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 47 additions & 45 deletions README.md

Large diffs are not rendered by default.

72 changes: 44 additions & 28 deletions flask_parameter_validation/parameter_types/parameter.py
Original file line number Diff line number Diff line change
@@ -34,6 +34,7 @@ def __init__(
alias=None, # str: alias for parameter name
json_schema=None, # dict: JSON Schema to check received dicts or lists against
blank_none=None, # bool: Whether blank strings should be converted to None when validating a type of Optional[str]
list_disable_query_csv=None, # bool: Whether query strings should be split by `,` when validating a type of list
):
self.default = default
self.min_list_length = min_list_length
@@ -51,6 +52,8 @@ def __init__(
self.alias = alias
self.json_schema = json_schema
self.blank_none = blank_none
self.list_disable_query_csv = list_disable_query_csv


def func_helper(self, v):
func_result = self.func(v)
@@ -158,53 +161,66 @@ def validate(self, value):

return True

def convert(self, value, allowed_types):
def convert(self, value, allowed_types, current_error=None):
"""Some parameter types require manual type conversion (see Query)"""
print(f"Converting '{value}' ({type(value)}) to '{allowed_types}'")
blank_none = self.blank_none
if blank_none is None: # Default blank_none to False if not provided or set in app config
blank_none = False if "FPV_BLANK_NONE" not in flask.current_app.config else flask.current_app.config["FPV_BLANK_NONE"]

error = None
# Datetime conversion
if None in allowed_types and value is None:
return value
if date in allowed_types:
try:
return date.fromisoformat(str(value))
except ValueError:
error = ValueError("date format does not match ISO 8601")
if time in allowed_types:
try:
return time.fromisoformat(str(value))
except ValueError:
error = ValueError("time format does not match ISO 8601")
if datetime in allowed_types:
if self.datetime_format is None:
try:
return parser.parse(str(value))
except parser._parser.ParserError:
pass
return datetime.fromisoformat(str(value))
except ValueError:
error = ValueError("datetime format does not match ISO 8601")
else:
try:
return datetime.strptime(str(value), self.datetime_format)
except ValueError:
raise ValueError(
f"datetime format does not match: {self.datetime_format}"
)
pass
elif time in allowed_types:
try:
return time.fromisoformat(str(value))
except ValueError:
raise ValueError("time format does not match ISO 8601")
elif date in allowed_types:
try:
return date.fromisoformat(str(value))
except ValueError:
raise ValueError("date format does not match ISO 8601")
elif blank_none and type(None) in allowed_types and str in allowed_types and type(value) is str and len(value) == 0:
error = ValueError(f"datetime format does not match: {self.datetime_format}")
if blank_none and type(None) in allowed_types and str in allowed_types and type(value) is str and len(value) == 0:
return None
elif any(isclass(allowed_type) and (issubclass(allowed_type, str) or issubclass(allowed_type, int) and issubclass(allowed_type, Enum)) for allowed_type in allowed_types):
if any(isclass(allowed_type) and (issubclass(allowed_type, str) or issubclass(allowed_type, int) and issubclass(allowed_type, Enum)) for allowed_type in allowed_types):
print("Enums allowed")
for allowed_type in allowed_types:
if issubclass(allowed_type, Enum):
if issubclass(allowed_types[0], int):
value = int(value)
returning = allowed_types[0](value)
return returning
elif uuid.UUID in allowed_types:
try:
if issubclass(allowed_type, int):
value = int(value)
returning = allowed_type(value)
print(f"type(returning): {type(returning)}")
return returning
except ValueError as e:
error = e
if uuid.UUID in allowed_types:
try:
if type(value) == uuid.UUID: # Handle default of type UUID
return value
return uuid.UUID(value)
try:
return uuid.UUID(value)
except ValueError as e:
error = e
except AttributeError:
raise ValueError("UUID format is incorrect")
return value
error = ValueError("UUID format is incorrect")
if str in allowed_types:
return value
print(value)
print(type(value))
if error and type(value) is str:
raise error
return value
42 changes: 26 additions & 16 deletions flask_parameter_validation/parameter_types/query.py
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
- i.e. sent in GET requests, /?username=myname
"""
import json
from enum import Enum

from .parameter import Parameter

@@ -13,33 +14,42 @@ class Query(Parameter):
def __init__(self, default=None, **kwargs):
super().__init__(default, **kwargs)

def convert(self, value, allowed_types):
def convert(self, value, allowed_types, current_error=None):
"""Convert query parameters to corresponding types."""
print(f"value: {value}, type: {type(value)}")
original_value = value
error = None
if type(value) is str:
# int conversion
# int conversion done before dict to handle potential IntEnum
if int in allowed_types:
try:
value = int(value)
except ValueError:
enum_test = super().convert(value, allowed_types, current_error)
if issubclass(type(enum_test), Enum) and issubclass(type(enum_test), int):
return enum_test
return int(value)
except ValueError or TypeError:
pass
# float conversion
if float in allowed_types:
if dict in allowed_types:
try:
value = float(value)
return json.loads(value)
except ValueError:
pass
error = ValueError(f"invalid JSON")
# float conversion
if float in allowed_types:
if not (type(value) is int and str(value) == original_value): # If we've already converted an int and the conversion is exact, skip float conversion
try:
return float(value)
except ValueError:
pass
# bool conversion
if bool in allowed_types:
try:
if value.lower() == "true":
value = True
return True
elif value.lower() == "false":
value = False
return False
except AttributeError:
pass
if dict in allowed_types:
try:
value = json.loads(value)
except ValueError:
raise ValueError(f"invalid JSON")
return super().convert(value, allowed_types)
if type(value) is not str:
error = None
return super().convert(value, allowed_types, current_error=error)
10 changes: 8 additions & 2 deletions flask_parameter_validation/parameter_types/route.py
Original file line number Diff line number Diff line change
@@ -2,6 +2,8 @@
Route Parameters
- Sent as part of a path, i.e. /user/<int:id>
"""
from enum import Enum

from .parameter import Parameter


@@ -11,13 +13,17 @@ class Route(Parameter):
def __init__(self, default=None, **kwargs):
super().__init__(default, **kwargs)

def convert(self, value, allowed_types):
def convert(self, value, allowed_types, current_error=None):
"""Convert query parameters to corresponding types."""
if type(value) is str:
# int conversion
if int in allowed_types:
try:
value = int(value)
enum_test = super().convert(value, allowed_types, current_error)
if issubclass(type(enum_test), Enum) and issubclass(type(enum_test), int):
value = enum_test
else:
value = int(value)
except ValueError:
pass
# float conversion
183 changes: 117 additions & 66 deletions flask_parameter_validation/parameter_validation.py

Large diffs are not rendered by default.

245 changes: 192 additions & 53 deletions flask_parameter_validation/test/test_form_params.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# String Validation
import datetime
import json
import uuid
from typing import Type, List, Optional

@@ -64,11 +65,6 @@ def test_optional_str(client):

def test_optional_str_blank_none_unset(client, app):
url = "/form/str/blank_none/unset"
# Test that FPV_BLANK_NONE runs as False by default
app.config.update({"FPV_BLANK_NONE": None})
r = client.post(f"{url}", data={"v": ""})
assert "v" in r.json
assert r.json["v"] == ""
# Test that FPV_BLANK_NONE returns empty string when False
app.config.update({"FPV_BLANK_NONE": False})
r = client.post(f"{url}", data={"v": ""})
@@ -79,15 +75,15 @@ def test_optional_str_blank_none_unset(client, app):
r = client.post(f"{url}", data={"v": ""})
assert "v" in r.json
assert r.json["v"] is None
# Test that FPV_BLANK_NONE runs as False by default
app.config.pop("FPV_BLANK_NONE", None)
r = client.post(f"{url}", data={"v": ""})
assert "v" in r.json
assert r.json["v"] == ""


def test_optional_str_blank_none_true(client, app):
url = "/form/str/blank_none/true"
# Test that unset FPV_BLANK_NONE can be overridden to True per-route
app.config.update({"FPV_BLANK_NONE": None})
r = client.post(f"{url}", data={"v": ""})
assert "v" in r.json
assert r.json["v"] is None
# Test that FPV_BLANK_NONE of False can be overridden to True per-route
app.config.update({"FPV_BLANK_NONE": False})
r = client.post(f"{url}", data={"v": ""})
@@ -98,15 +94,15 @@ def test_optional_str_blank_none_true(client, app):
r = client.post(f"{url}", data={"v": ""})
assert "v" in r.json
assert r.json["v"] is None
# Test that unset FPV_BLANK_NONE can be overridden to True per-route
app.config.pop("FPV_BLANK_NONE", None)
r = client.post(f"{url}", data={"v": ""})
assert "v" in r.json
assert r.json["v"] is None


def test_optional_str_blank_none_false(client, app):
url = "/form/str/blank_none/false"
# Test that unset FPV_BLANK_NONE can be 'overridden' to False per-route
app.config.update({"FPV_BLANK_NONE": None})
r = client.post(f"{url}", data={"v": ""})
assert "v" in r.json
assert r.json["v"] == ""
# Test that FPV_BLANK_NONE of False can be 'overridden' to False per-route
app.config.update({"FPV_BLANK_NONE": False})
r = client.post(f"{url}", data={"v": ""})
@@ -117,6 +113,11 @@ def test_optional_str_blank_none_false(client, app):
r = client.post(f"{url}", data={"v": ""})
assert "v" in r.json
assert r.json["v"] == ""
# Test that unset FPV_BLANK_NONE can be 'overridden' to False per-route
app.config.pop("FPV_BLANK_NONE", None)
r = client.post(f"{url}", data={"v": ""})
assert "v" in r.json
assert r.json["v"] == ""


def test_str_default(client):
@@ -735,6 +736,11 @@ def test_union_func(client):
# List Validation
def test_required_list_str(client):
url = "/form/list/req_str"
# Test that preset empty list input yields input value
r = client.post(url, data={"v": ""})
assert "v" in r.json
assert type(r.json["v"]) is list
assert len(r.json["v"]) == 0
# Test that present List[str] input yields input value
v = ["x", "y"]
r = client.post(url, data={"v": v})
@@ -749,6 +755,11 @@ def test_required_list_str(client):

def test_required_list_int(client):
url = "/form/list/req_int"
# Test that present single empty string input yields empty list
r = client.post(url, data={"v": ""})
assert "v" in r.json
assert type(r.json["v"]) is list
assert len(r.json["v"]) == 0
# Test that present List[int] input yields input value
v = [0, 1]
r = client.post(url, data={"v": v})
@@ -766,6 +777,11 @@ def test_required_list_int(client):

def test_required_list_bool(client):
url = "/form/list/req_bool"
# Test that present single empty string input yields empty list
r = client.post(url, data={"v": ""})
assert "v" in r.json
assert type(r.json["v"]) is list
assert len(r.json["v"]) == 0
# Test that present List[bool] input yields input value
v = [False, True]
r = client.post(url, data={"v": v})
@@ -781,42 +797,77 @@ def test_required_list_bool(client):
assert "error" in r.json


# List[Union[]] not currently supported
# def test_required_list_union(client):
# url = "/form/list/req_union"
# # Test that present single int input yields [input value]
# r = client.post(f"{url}?v=2")
# assert "v" in r.json
# assert type(r.json["v"]) is list
# assert len(r.json["v"]) == 1
# assert type(r.json["v"][0]) is int
# assert r.json["v"][0] == 2
# # Test that present single float input yields [input value]
# r = client.post(f"{url}?v=3.14")
# assert "v" in r.json
# assert type(r.json["v"]) is list
# assert len(r.json["v"]) == 1
# assert type(r.json["v"][0]) is float
# assert r.json["v"][0] == 3.14
# # Test that present CSV int/float input yields [input values]
# r = client.post(f"{url}?v=4,5.62")
# assert "v" in r.json
# assert type(r.json["v"]) is list
# assert len(r.json["v"]) == 2
# assert type(r.json["v"][0]) is int
# assert type(r.json["v"][1]) is float
# assert r.json["v"][0] == 4
# assert r.json["v"][1] == 5.62
# # Test that present non-int/float list items yields error
# r = client.post(f"{url}?v=a")
# assert "error" in r.json
# # Test that missing input yields error
# r = client.post(url)
# assert "error" in r.json
def test_required_list_union(client):
url = "/form/list/req_union"
# Test that present single empty string input yields empty list
r = client.post(url, data={"v": ""})
assert "v" in r.json
assert type(r.json["v"]) is list
assert len(r.json["v"]) == 0
# Test that present single int input yields [input value]
r = client.post(f"{url}", data={"v": 2})
assert "v" in r.json
assert type(r.json["v"]) is list
assert len(r.json["v"]) == 1
assert type(r.json["v"][0]) is int
assert r.json["v"][0] == 2
# Test that present single float input yields [input value]
r = client.post(f"{url}", data={"v": 3.14})
assert "v" in r.json
assert type(r.json["v"]) is list
assert len(r.json["v"]) == 1
assert type(r.json["v"][0]) is float
assert r.json["v"][0] == 3.14
# Test that present CSV int/float input yields [input values]
r = client.post(f"{url}", data={"v": [4, 5.62]})
assert "v" in r.json
assert type(r.json["v"]) is list
assert len(r.json["v"]) == 2
assert type(r.json["v"][0]) is int
assert type(r.json["v"][1]) is float
assert r.json["v"][0] == 4
assert r.json["v"][1] == 5.62
# Test that present non-int/float list items yields error
r = client.post(f"{url}", data={"v": "a"})
assert "error" in r.json
# Test that missing input yields error
r = client.post(url)
assert "error" in r.json

def test_required_list_union_everything(client):
url = "/form/list/req_union_everything"
v = [
"testing",
5,
True,
3.14,
datetime.datetime(2025, 4, 20, 15, 13, 32).isoformat(),
datetime.date(2025, 4, 20).isoformat(),
datetime.time(15, 14, 22).isoformat(),
json.dumps({"i": "am", "a": "dictionary"}),
Fruits.APPLE.value,
Binary.ONE.value,
str(uuid.uuid4())
]
r = client.post(f"{url}", data={"v": v})
assert "v" in r.json
assert type(r.json["v"]) is list
assert len(v) == len(r.json["v"])
for i in range(len(v)):
if i == 7:
assert r.json["v"][i] == json.loads(v[i])
else:
assert r.json["v"][i] == v[i]



def test_required_list_datetime(client):
url = "/form/list/req_datetime"
# Test that present single empty string input yields empty list
r = client.post(url, data={"v": ""})
assert "v" in r.json
assert type(r.json["v"]) is list
assert len(r.json["v"]) == 0
# Test that present List[datetime] input yields input value
v = [datetime.datetime(2024, 2, 10, 14, 32, 38),
datetime.datetime(2024, 2, 10, 14, 32, 53)]
@@ -835,6 +886,11 @@ def test_required_list_datetime(client):

def test_required_list_date(client):
url = "/form/list/req_date"
# Test that present single empty string input yields empty list
r = client.post(url, data={"v": ""})
assert "v" in r.json
assert type(r.json["v"]) is list
assert len(r.json["v"]) == 0
# Test that present List[date] input yields input value
v = [datetime.date(2024, 2, 10), datetime.date(2024, 2, 11)]
r = client.post(url, data={"v": [d.isoformat() for d in v]})
@@ -852,6 +908,11 @@ def test_required_list_date(client):

def test_required_list_time(client):
url = "/form/list/req_time"
# Test that present single empty string input yields empty list
r = client.post(url, data={"v": ""})
assert "v" in r.json
assert type(r.json["v"]) is list
assert len(r.json["v"]) == 0
# Test that present List[time] input yields input value
v = [datetime.time(14, 37, 34), datetime.time(14, 37, 45)]
r = client.post(url, data={"v": [d.isoformat() for d in v]})
@@ -867,19 +928,97 @@ def test_required_list_time(client):
assert "error" in r.json


def test_optional_list(client):
url = "/form/list/optional"
# Test that missing input yields None
def test_required_list_dict(client):
url = "/form/list/req_dict"
# Test that present single empty string input yields empty list
r = client.post(url, data={"v": ""})
assert "v" in r.json
assert type(r.json["v"]) is list
assert len(r.json["v"]) == 0
# Test that present single dict input yields [input value]
v = {"hello": "world"}
r = client.post(url, data={"v": json.dumps(v)})
assert "v" in r.json
assert type(r.json["v"]) is list
assert len(r.json["v"]) == 1
assert type(r.json["v"][0]) is dict
assert r.json["v"][0] == v
# Test that present dict input in multiple of the same form param yields [input values]
v = [{"one": "dict"}, {"two": "dict", "red": "dict"}, {"blue": "dict"}]
r = client.post(url, data={"v": [json.dumps(d) for d in v]})
assert "v" in r.json
assert type(r.json["v"]) is list
assert len(r.json["v"]) == 3
list_assertion_helper(2, dict, v, r.json["v"])
# Test that present non-dict list items yields error
r = client.post(url, data={"v": "a"})
assert "error" in r.json
# Test that missing input yields error
r = client.post(url)
assert "error" in r.json


def test_required_list_str_enum(client):
url = "/form/list/req_str_enum"
# Test that present single empty string input yields empty list
r = client.post(url, data={"v": ""})
assert "v" in r.json
assert r.json["v"] is None
# Test that present List[str] input yields input value
v = ["two", "tests"]
assert type(r.json["v"]) is list
assert len(r.json["v"]) == 0
# Test that present single Fruits input yields [input value]
v = Fruits.APPLE
r = client.post(url, data={"v": v.value})
assert "v" in r.json
assert type(r.json["v"]) is list
assert len(r.json["v"]) == 1
assert type(r.json["v"][0]) is str
assert r.json["v"][0] == v.value
# Test that present Fruits input in multiple of the same query param yields [input values]
v = [Fruits.APPLE.value, Fruits.ORANGE.value]
r = client.post(url, data={"v": v})
assert "v" in r.json
assert type(r.json["v"]) is list
assert len(r.json["v"]) == 2
list_assertion_helper(2, str, v, r.json["v"])
# Test that present non-Fruits list items yields error
r = client.post(url, data={"v": "a"})
assert "error" in r.json
# Test that missing input yields error
r = client.post(url)
assert "error" in r.json


def test_required_list_int_enum(client):
url = "/form/list/req_int_enum"
# Test that present single empty string input yields empty list
r = client.post(url, data={"v": ""})
assert "v" in r.json
assert type(r.json["v"]) is list
assert len(r.json["v"]) == 0
# Test that present single Binary input yields [input value]
v = Binary.ZERO
r = client.post(url, data={"v": v.value})
assert "v" in r.json
assert type(r.json["v"]) is list
assert len(r.json["v"]) == 1
assert type(r.json["v"][0]) is int
assert r.json["v"][0] == v.value
# Test that present Binary input in multiple of the same query param yields [input values]
v = [
Binary.ONE.value, Binary.ZERO.value, Binary.ZERO.value,
Binary.ONE.value, Binary.ONE.value
]
r = client.post(url, data={"v": v})
assert "v" in r.json
assert type(r.json["v"]) is list
assert len(r.json["v"]) == 5
list_assertion_helper(5, int, v, r.json["v"])
# Test that present non-Binary list items yields error
r = client.post(url, data={"v": "crying zeros and I'm hearing"})
assert "error" in r.json
# Test that missing input yields error
r = client.post(url)
assert "error" in r.json


def test_list_default(client):
51 changes: 21 additions & 30 deletions flask_parameter_validation/test/test_json_params.py
Original file line number Diff line number Diff line change
@@ -42,11 +42,6 @@ def test_optional_str(client):

def test_optional_str_blank_none_unset(client, app):
url = "/json/str/blank_none/unset"
# Test that FPV_BLANK_NONE runs as False by default
app.config.update({"FPV_BLANK_NONE": None})
r = client.post(f"{url}", json={"v": ""})
assert "v" in r.json
assert r.json["v"] == ""
# Test that FPV_BLANK_NONE returns empty string when False
app.config.update({"FPV_BLANK_NONE": False})
r = client.post(f"{url}", json={"v": ""})
@@ -57,15 +52,15 @@ def test_optional_str_blank_none_unset(client, app):
r = client.post(f"{url}", json={"v": ""})
assert "v" in r.json
assert r.json["v"] is None
# Test that FPV_BLANK_NONE runs as False by default
app.config.pop("FPV_BLANK_NONE", None)
r = client.post(f"{url}", json={"v": ""})
assert "v" in r.json
assert r.json["v"] == ""


def test_optional_str_blank_none_true(client, app):
url = "/json/str/blank_none/true"
# Test that unset FPV_BLANK_NONE can be overridden to True per-route
app.config.update({"FPV_BLANK_NONE": None})
r = client.post(f"{url}", json={"v": ""})
assert "v" in r.json
assert r.json["v"] is None
# Test that FPV_BLANK_NONE of False can be overridden to True per-route
app.config.update({"FPV_BLANK_NONE": False})
r = client.post(f"{url}", json={"v": ""})
@@ -76,15 +71,15 @@ def test_optional_str_blank_none_true(client, app):
r = client.post(f"{url}", json={"v": ""})
assert "v" in r.json
assert r.json["v"] is None
# Test that unset FPV_BLANK_NONE can be overridden to True per-route
app.config.pop("FPV_BLANK_NONE", None)
r = client.post(f"{url}", json={"v": ""})
assert "v" in r.json
assert r.json["v"] is None


def test_optional_str_blank_none_false(client, app):
url = "/json/str/blank_none/false"
# Test that unset FPV_BLANK_NONE can be 'overridden' to False per-route
app.config.update({"FPV_BLANK_NONE": None})
r = client.post(f"{url}", json={"v": ""})
assert "v" in r.json
assert r.json["v"] == ""
# Test that FPV_BLANK_NONE of False can be 'overridden' to False per-route
app.config.update({"FPV_BLANK_NONE": False})
r = client.post(f"{url}", json={"v": ""})
@@ -95,6 +90,11 @@ def test_optional_str_blank_none_false(client, app):
r = client.post(f"{url}", json={"v": ""})
assert "v" in r.json
assert r.json["v"] == ""
# Test that unset FPV_BLANK_NONE can be 'overridden' to False per-route
app.config.pop("FPV_BLANK_NONE", None)
r = client.post(f"{url}", json={"v": ""})
assert "v" in r.json
assert r.json["v"] == ""


def test_str_default(client):
@@ -713,6 +713,11 @@ def test_union_func(client):
# List Validation
def test_required_list_str(client):
url = "/json/list/req_str"
# Test that preset empty list input yields input value
r = client.post(url, json={"v": []})
assert "v" in r.json
assert type(r.json["v"]) is list
assert len(r.json["v"]) == 0
# Test that present List[str] input yields input value
v = ["x", "y"]
r = client.post(url, json={"v": v})
@@ -845,21 +850,6 @@ def test_required_list_time(client):
assert "error" in r.json


def test_optional_list(client):
url = "/json/list/optional"
# Test that missing input yields None
r = client.post(url)
assert "v" in r.json
assert r.json["v"] is None
# Test that present List[str] input yields input value
v = ["two", "tests"]
r = client.post(url, json={"v": v})
assert "v" in r.json
assert type(r.json["v"]) is list
assert len(r.json["v"]) == 2
list_assertion_helper(2, str, v, r.json["v"])


def test_list_default(client):
url = "/json/list/default"
# Test that missing input for required and optional yields default values
@@ -948,6 +938,7 @@ def test_list_json_schema(client):
# Test that passing schema validation yields input
v = [{"user_id": 1, "first_name": "John", "last_name": "Doe", "tags": ["test_account"]}]
r = client.post(url, json={"v": v})
print(r.json)
assert type(r.json["v"]) is list
assert len(r.json["v"]) == 1
assert type(r.json["v"][0]) is dict
1,085 changes: 940 additions & 145 deletions flask_parameter_validation/test/test_query_params.py

Large diffs are not rendered by default.

227 changes: 206 additions & 21 deletions flask_parameter_validation/test/testing_blueprints/list_blueprint.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import datetime
import uuid
from typing import Optional, List, Union

from flask import Blueprint, jsonify

from flask_parameter_validation import ValidateParameters, Route
from flask_parameter_validation.parameter_types.parameter import Parameter
from flask_parameter_validation.test.enums import Binary, Fruits
from flask_parameter_validation.test.testing_blueprints.dummy_decorators import dummy_decorator, dummy_async_decorator


@@ -20,75 +22,258 @@ def get_list_blueprint(ParamType: type[Parameter], bp_name: str, http_verb: str)
@ValidateParameters()
def req_str(v: List[str] = ParamType()):
assert type(v) is list
assert type(v[0]) is str
if len(v) > 0:
assert type(v[0]) is str
return jsonify({"v": v})

@decorator("/decorator/req_str")
@dummy_decorator
@ValidateParameters()
def decorator_req_str(v: List[str] = ParamType()):
assert type(v) is list
assert type(v[0]) is str
if len(v) > 0:
assert type(v[0]) is str
return jsonify({"v": v})

@decorator("/async_decorator/req_str")
@dummy_async_decorator
@ValidateParameters()
async def async_decorator_req_str(v: List[str] = ParamType()):
assert type(v) is list
assert type(v[0]) is str
if len(v) > 0:
assert type(v[0]) is str
return jsonify({"v": v})

@decorator("/opt_str")
@ValidateParameters()
def opt_str(v: Optional[List[str]] = ParamType()):
assert type(v) is list or v is None
if v and len(v) > 0:
assert type(v[0]) is str
return jsonify({"v": v})

@decorator("/disable_query_csv/unset")
@ValidateParameters()
def disable_query_csv_unset(v: List[str] = ParamType()):
return jsonify({"v": v})

@decorator("/disable_query_csv/true")
@ValidateParameters()
def disable_query_csv_true(v: List[str] = ParamType(list_disable_query_csv=True)):
return jsonify({"v": v})

@decorator("/disable_query_csv/false")
@ValidateParameters()
def disable_query_csv_false(v: List[str] = ParamType(list_disable_query_csv=False)):
return jsonify({"v": v})

@decorator("/req_int")
@ValidateParameters()
def req_int(v: List[int] = ParamType()):
assert type(v) is list
assert type(v[0]) is int
if len(v) > 0:
assert type(v[0]) is int
return jsonify({"v": v})

@decorator("/opt_int")
@ValidateParameters()
def opt_int(v: Optional[List[int]] = ParamType()):
assert type(v) is list or v is None
if v and len(v) > 0:
assert type(v[0]) is int
return jsonify({"v": v})

@decorator("/req_bool")
@ValidateParameters()
def req_bool(v: List[bool] = ParamType()):
assert type(v) is list
assert type(v[0]) is bool
if len(v) > 0:
assert type(v[0]) is bool
return jsonify({"v": v})

# List[Union[]] not currently supported
# @decorator("/req_union")
# @ValidateParameters()
# def req_union(v: List[Union[int, float]] = ParamType()):
# assert type(v) is list
# assert type(v[0]) is int
# assert type(v[1]) is float
# return jsonify({"v": v})
@decorator("/opt_bool")
@ValidateParameters()
def opt_bool(v: Optional[List[bool]] = ParamType()):
assert type(v) is list or v is None
if v and len(v) > 0:
assert type(v[0]) is bool
return jsonify({"v": v})

@decorator("/req_float")
@ValidateParameters()
def req_float(v: List[float] = ParamType()):
assert type(v) is list
if len(v) > 0:
assert type(v[0]) is float
return jsonify({"v": v})

@decorator("/opt_float")
@ValidateParameters()
def opt_float(v: Optional[List[float]] = ParamType()):
assert type(v) is list or v is None
if v and len(v) > 0:
assert type(v[0]) is float
return jsonify({"v": v})

@decorator("/req_union")
@ValidateParameters()
def req_union(v: List[Union[int, float]] = ParamType()):
assert type(v) is list
for i in v:
assert type(i) is int or type(i) is float
return jsonify({"v": v})

@decorator("/req_union_everything")
@ValidateParameters()
def req_union_everything(v: List[Union[str, int, bool, float, datetime.datetime, datetime.date, datetime.time, dict, Fruits, Binary, uuid.UUID]] = ParamType(list_disable_query_csv=True)):
assert type(v) is list
assert len(v) > 0
assert type(v[0]) is str
assert type(v[1]) is int
assert type(v[2]) is bool
assert type(v[3]) is float
assert type(v[4]) is datetime.datetime
assert type(v[5]) is datetime.date
assert type(v[6]) is datetime.time
assert type(v[7]) is dict
assert type(v[8]) is Fruits
assert type(v[9]) is Binary
assert type(v[10]) is uuid.UUID
return jsonify({"v": [
v[0], v[1], v[2], v[3],
v[4].isoformat(),
v[5].isoformat(),
v[6].isoformat(),
v[7], v[8], v[9], v[10]
]})

@decorator("/opt_union")
@ValidateParameters()
def opt_union(v: Optional[List[Union[int, bool]]] = ParamType()):
assert type(v) is list or v is None
if v:
for i in v:
assert type(i) is int or type(i) is bool
return jsonify({"v": v})

@decorator("/req_datetime")
@ValidateParameters()
def req_datetime(v: List[datetime.datetime] = ParamType()):
assert type(v) is list
assert type(v[0]) is datetime.datetime
v = [t.isoformat() for t in v]
if len(v) > 0:
assert type(v[0]) is datetime.datetime
v = [t.isoformat() for t in v]
return jsonify({"v": v})

@decorator("/opt_datetime")
@ValidateParameters()
def opt_datetime(v: Optional[List[datetime.datetime]] = ParamType()):
assert type(v) is list or v is None
if v and len(v) > 0:
assert type(v[0]) is datetime.datetime
v = [t.isoformat() for t in v]
return jsonify({"v": v})

@decorator("/req_date")
@ValidateParameters()
def req_date(v: List[datetime.date] = ParamType()):
assert type(v) is list
assert type(v[0]) is datetime.date
v = [t.isoformat() for t in v]
if len(v) > 0:
assert type(v[0]) is datetime.date
v = [t.isoformat() for t in v]
return jsonify({"v": v})

@decorator("/opt_date")
@ValidateParameters()
def opt_date(v: Optional[List[datetime.date]] = ParamType()):
assert type(v) is list or v is None
if v and len(v) > 0:
assert type(v[0]) is datetime.date
v = [t.isoformat() for t in v]
return jsonify({"v": v})

@decorator("/req_time")
@ValidateParameters()
def req_time(v: List[datetime.time] = ParamType()):
assert type(v) is list
assert type(v[0]) is datetime.time
v = [t.isoformat() for t in v]
if len(v) > 0:
assert type(v[0]) is datetime.time
v = [t.isoformat() for t in v]
return jsonify({"v": v})

@decorator("/opt_time")
@ValidateParameters()
def opt_time(v: Optional[List[datetime.time]] = ParamType()):
assert type(v) is list or v is None
if v and len(v) > 0:
assert type(v[0]) is datetime.time
v = [t.isoformat() for t in v]
return jsonify({"v": v})

@decorator("/req_dict")
@ValidateParameters()
def req_dict(v: List[dict] = ParamType(list_disable_query_csv=True)):
assert type(v) is list
if len(v) > 0:
assert type(v[0]) is dict
return jsonify({"v": v})

@decorator("/opt_dict")
@ValidateParameters()
def opt_dict(v: Optional[List[dict]] = ParamType(list_disable_query_csv=True)):
assert type(v) is list or v is None
if v and len(v) > 0:
assert type(v[0]) is dict
return jsonify({"v": v})

@decorator("/req_str_enum")
@ValidateParameters()
def req_str_enum(v: List[Fruits] = ParamType()):
assert type(v) is list
if len(v) > 0:
assert type(v[0]) is Fruits
return jsonify({"v": v})

@decorator("/opt_str_enum")
@ValidateParameters()
def opt_str_enum(v: Optional[List[Fruits]] = ParamType()):
assert type(v) is list or v is None
if v and len(v) > 0:
assert type(v[0]) is Fruits
return jsonify({"v": v})

@decorator("/req_int_enum")
@ValidateParameters()
def req_int_enum(v: List[Binary] = ParamType()):
assert type(v) is list
if len(v) > 0:
assert type(v[0]) is Binary
return jsonify({"v": v})

@decorator("/opt_int_enum")
@ValidateParameters()
def opt_int_enum(v: Optional[List[Binary]] = ParamType()):
assert type(v) is list or v is None
if v and len(v) > 0:
assert type(v[0]) is Binary
return jsonify({"v": v})

@decorator("/req_uuid")
@ValidateParameters()
def req_uuid(v: List[uuid.UUID] = ParamType()):
assert type(v) is list
if len(v) > 0:
assert type(v[0]) is uuid.UUID
v = [str(u) for u in v]
return jsonify({"v": v})

@decorator("/optional")
@decorator("/opt_uuid")
@ValidateParameters()
def optional(v: Optional[List[str]] = ParamType()):
def opt_uuid(v: Optional[List[uuid.UUID]] = ParamType()):
assert type(v) is list or v is None
if v and len(v) > 0:
assert type(v[0]) is uuid.UUID
v = [str(u) for u in v]
return jsonify({"v": v})

@decorator("/default")