Skip to content

Commit 9a7f186

Browse files
authored
Merge pull request #46 from Ge0rg3/dev/smt5541/enum
Implement Enum Validation
2 parents 2edb5e5 + 099b399 commit 9a7f186

File tree

9 files changed

+571
-57
lines changed

9 files changed

+571
-57
lines changed

Diff for: README.md

+49-38
Large diffs are not rendered by default.

Diff for: flask_parameter_validation/parameter_types/parameter.py

+23-17
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"""
55
import re
66
from datetime import date, datetime, time
7-
7+
from enum import Enum
88
import dateutil.parser as parser
99
import jsonschema
1010
from jsonschema.exceptions import ValidationError as JSONSchemaValidationError
@@ -14,22 +14,23 @@ class Parameter:
1414

1515
# Parameter initialisation
1616
def __init__(
17-
self,
18-
default=None, # any: default parameter value
19-
min_str_length=None, # int: min parameter length
20-
max_str_length=None, # int: max parameter length
21-
min_list_length=None, # int: min number of items in list
22-
max_list_length=None, # int: max number of items in list
23-
min_int=None, # int: min number (if val is int)
24-
max_int=None, # int: max number (if val is int)
25-
whitelist=None, # str: character whitelist
26-
blacklist=None, # str: character blacklist
27-
pattern=None, # str: regexp pattern
28-
func=None, # Callable -> Union[bool, tuple[bool, str]]: function performing a fully customized validation
29-
datetime_format=None, # str: datetime format string (https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes),
30-
comment=None, # str: comment for autogenerated documentation
31-
alias=None, # str: alias for parameter name
32-
json_schema=None, # dict: JSON Schema to check received dicts or lists against
17+
self,
18+
default=None, # any: default parameter value
19+
min_str_length=None, # int: min parameter length
20+
max_str_length=None, # int: max parameter length
21+
min_list_length=None, # int: min number of items in list
22+
max_list_length=None, # int: max number of items in list
23+
min_int=None, # int: min number (if val is int)
24+
max_int=None, # int: max number (if val is int)
25+
whitelist=None, # str: character whitelist
26+
blacklist=None, # str: character blacklist
27+
pattern=None, # str: regexp pattern
28+
func=None, # Callable -> Union[bool, tuple[bool, str]]: function performing a fully customized validation
29+
datetime_format=None,
30+
# str: datetime format string (https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes),
31+
comment=None, # str: comment for autogenerated documentation
32+
alias=None, # str: alias for parameter name
33+
json_schema=None, # dict: JSON Schema to check received dicts or lists against
3334
):
3435
self.default = default
3536
self.min_list_length = min_list_length
@@ -182,4 +183,9 @@ def convert(self, value, allowed_types):
182183
return date.fromisoformat(str(value))
183184
except ValueError:
184185
raise ValueError("date format does not match ISO 8601")
186+
elif len(allowed_types) == 1 and (issubclass(allowed_types[0], str) or issubclass(allowed_types[0], int) and issubclass(allowed_types[0], Enum)):
187+
if issubclass(allowed_types[0], int):
188+
value = int(value)
189+
returning = allowed_types[0](value)
190+
return returning
185191
return value

Diff for: flask_parameter_validation/test/enums.py

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from enum import Enum
2+
3+
4+
class Fruits(str, Enum):
5+
APPLE = "apple"
6+
ORANGE = "orange"
7+
8+
9+
class Binary(int, Enum):
10+
ZERO = 0
11+
ONE = 1

Diff for: flask_parameter_validation/test/test_form_params.py

+121
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import datetime
33
from typing import Type, List, Optional
44

5+
from flask_parameter_validation.test.enums import Fruits, Binary
6+
57

68
def list_assertion_helper(length: int, list_children_type: Type, expected_list: List, tested_list,
79
expected_call: Optional[str] = None):
@@ -945,3 +947,122 @@ def test_non_typing_optional_list_str(client):
945947
assert type(r.json["v"]) is list
946948
assert len(r.json["v"]) == 2
947949
list_assertion_helper(2, str, v, r.json["v"])
950+
951+
952+
# Enum validation
953+
def test_required_str_enum(client):
954+
url = "/form/str_enum/required"
955+
# Test that present str_enum input yields input value
956+
r = client.post(url, data={"v": Fruits.APPLE.value})
957+
assert "v" in r.json
958+
assert r.json["v"] == Fruits.APPLE.value
959+
# Test that missing input yields error
960+
r = client.post(url)
961+
assert "error" in r.json
962+
# Test that present non-str_enum input yields error
963+
r = client.post(url, data={"v": "a"})
964+
assert "error" in r.json
965+
966+
967+
def test_optional_str_enum(client):
968+
url = "/form/str_enum/optional"
969+
# Test that missing input yields None
970+
r = client.post(url)
971+
assert "v" in r.json
972+
assert r.json["v"] is None
973+
# Test that present str_enum input yields input value
974+
r = client.post(url, data={"v": Fruits.ORANGE.value})
975+
assert "v" in r.json
976+
assert r.json["v"] == Fruits.ORANGE.value
977+
# Test that present non-str_enum input yields error
978+
r = client.post(url, data={"v": "v"})
979+
assert "error" in r.json
980+
981+
982+
def test_str_enum_default(client):
983+
url = "/form/str_enum/default"
984+
# Test that missing input for required and optional yields default values
985+
r = client.post(url)
986+
assert "n_opt" in r.json
987+
assert r.json["n_opt"] == Fruits.APPLE.value
988+
assert "opt" in r.json
989+
assert r.json["opt"] == Fruits.ORANGE.value
990+
# Test that present str_enum input for required and optional yields input values
991+
r = client.post(url, data={"opt": Fruits.ORANGE.value, "n_opt": Fruits.APPLE.value})
992+
assert "opt" in r.json
993+
assert r.json["opt"] == Fruits.ORANGE.value
994+
assert "n_opt" in r.json
995+
assert r.json["n_opt"] == Fruits.APPLE.value
996+
# Test that present non-str_enum input for required yields error
997+
r = client.post(url, data={"opt": "a", "n_opt": "b"})
998+
assert "error" in r.json
999+
1000+
1001+
def test_str_enum_func(client):
1002+
url = "/form/str_enum/func"
1003+
# Test that input passing func yields input
1004+
r = client.post(url, data={"v": Fruits.ORANGE.value})
1005+
assert "v" in r.json
1006+
assert r.json["v"] == Fruits.ORANGE.value
1007+
# Test that input failing func yields error
1008+
r = client.post(url, data={"v": Fruits.APPLE.value})
1009+
assert "error" in r.json
1010+
1011+
1012+
def test_required_int_enum(client):
1013+
url = "/form/int_enum/required"
1014+
# Test that present int_enum input yields input value
1015+
r = client.post(url, data={"v": Binary.ONE.value})
1016+
assert "v" in r.json
1017+
assert r.json["v"] == Binary.ONE.value
1018+
# Test that missing input yields error
1019+
r = client.post(url)
1020+
assert "error" in r.json
1021+
# Test that present non-int_enum input yields error
1022+
r = client.post(url, data={"v": 8})
1023+
assert "error" in r.json
1024+
1025+
1026+
def test_optional_int_enum(client):
1027+
url = "/form/int_enum/optional"
1028+
# Test that missing input yields None
1029+
r = client.post(url)
1030+
assert "v" in r.json
1031+
assert r.json["v"] is None
1032+
# Test that present int_enum input yields input value
1033+
r = client.post(url, data={"v": Binary.ZERO.value})
1034+
assert "v" in r.json
1035+
assert r.json["v"] == Binary.ZERO.value
1036+
# Test that present non-int_enum input yields error
1037+
r = client.post(url, data={"v": 8})
1038+
assert "error" in r.json
1039+
1040+
1041+
def test_int_enum_default(client):
1042+
url = "/form/int_enum/default"
1043+
# Test that missing input for required and optional yields default values
1044+
r = client.post(url)
1045+
assert "n_opt" in r.json
1046+
assert r.json["n_opt"] == Binary.ZERO.value
1047+
assert "opt" in r.json
1048+
assert r.json["opt"] == Binary.ONE.value
1049+
# Test that present int_enum input for required and optional yields input values
1050+
r = client.post(url, data={"opt": Binary.ONE.value, "n_opt": Binary.ZERO.value})
1051+
assert "opt" in r.json
1052+
assert r.json["opt"] == Binary.ONE.value
1053+
assert "n_opt" in r.json
1054+
assert r.json["n_opt"] == Binary.ZERO.value
1055+
# Test that present non-int_enum input for required yields error
1056+
r = client.post(url, data={"opt": "a", "n_opt": 9})
1057+
assert "error" in r.json
1058+
1059+
1060+
def test_int_enum_func(client):
1061+
url = "/form/int_enum/func"
1062+
# Test that input passing func yields input
1063+
r = client.post(url, data={"v": Binary.ZERO.value})
1064+
assert "v" in r.json
1065+
assert r.json["v"] == Binary.ZERO.value
1066+
# Test that input failing func yields error
1067+
r = client.post(url, data={"v": Binary.ONE.value})
1068+
assert "error" in r.json

Diff for: flask_parameter_validation/test/test_json_params.py

+122
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import datetime
33
from typing import Type, List, Optional
44

5+
from flask_parameter_validation.test.enums import Binary, Fruits
6+
57

68
def list_assertion_helper(length: int, list_children_type: Type, expected_list: List, tested_list,
79
expected_call: Optional[str] = None):
@@ -1064,3 +1066,123 @@ def test_non_typing_optional_list_str(client):
10641066
assert type(r.json["v"]) is list
10651067
assert len(r.json["v"]) == 2
10661068
list_assertion_helper(2, str, v, r.json["v"])
1069+
1070+
1071+
1072+
# Enum validation
1073+
def test_required_str_enum(client):
1074+
url = "/json/str_enum/required"
1075+
# Test that present str_enum input yields input value
1076+
r = client.post(url, json={"v": Fruits.APPLE.value})
1077+
assert "v" in r.json
1078+
assert r.json["v"] == Fruits.APPLE.value
1079+
# Test that missing input yields error
1080+
r = client.post(url)
1081+
assert "error" in r.json
1082+
# Test that present non-str_enum input yields error
1083+
r = client.post(url, json={"v": "a"})
1084+
assert "error" in r.json
1085+
1086+
1087+
def test_optional_str_enum(client):
1088+
url = "/json/str_enum/optional"
1089+
# Test that missing input yields None
1090+
r = client.post(url)
1091+
assert "v" in r.json
1092+
assert r.json["v"] is None
1093+
# Test that present str_enum input yields input value
1094+
r = client.post(url, json={"v": Fruits.ORANGE.value})
1095+
assert "v" in r.json
1096+
assert r.json["v"] == Fruits.ORANGE.value
1097+
# Test that present non-str_enum input yields error
1098+
r = client.post(url, json={"v": "v"})
1099+
assert "error" in r.json
1100+
1101+
1102+
def test_str_enum_default(client):
1103+
url = "/json/str_enum/default"
1104+
# Test that missing input for required and optional yields default values
1105+
r = client.post(url)
1106+
assert "n_opt" in r.json
1107+
assert r.json["n_opt"] == Fruits.APPLE.value
1108+
assert "opt" in r.json
1109+
assert r.json["opt"] == Fruits.ORANGE.value
1110+
# Test that present str_enum input for required and optional yields input values
1111+
r = client.post(url, json={"opt": Fruits.ORANGE.value, "n_opt": Fruits.APPLE.value})
1112+
assert "opt" in r.json
1113+
assert r.json["opt"] == Fruits.ORANGE.value
1114+
assert "n_opt" in r.json
1115+
assert r.json["n_opt"] == Fruits.APPLE.value
1116+
# Test that present non-str_enum input for required yields error
1117+
r = client.post(url, json={"opt": "a", "n_opt": "b"})
1118+
assert "error" in r.json
1119+
1120+
1121+
def test_str_enum_func(client):
1122+
url = "/json/str_enum/func"
1123+
# Test that input passing func yields input
1124+
r = client.post(url, json={"v": Fruits.ORANGE.value})
1125+
assert "v" in r.json
1126+
assert r.json["v"] == Fruits.ORANGE.value
1127+
# Test that input failing func yields error
1128+
r = client.post(url, json={"v": Fruits.APPLE.value})
1129+
assert "error" in r.json
1130+
1131+
1132+
def test_required_int_enum(client):
1133+
url = "/json/int_enum/required"
1134+
# Test that present int_enum input yields input value
1135+
r = client.post(url, json={"v": Binary.ONE.value})
1136+
assert "v" in r.json
1137+
assert r.json["v"] == Binary.ONE.value
1138+
# Test that missing input yields error
1139+
r = client.post(url)
1140+
assert "error" in r.json
1141+
# Test that present non-int_enum input yields error
1142+
r = client.post(url, json={"v": 8})
1143+
assert "error" in r.json
1144+
1145+
1146+
def test_optional_int_enum(client):
1147+
url = "/json/int_enum/optional"
1148+
# Test that missing input yields None
1149+
r = client.post(url)
1150+
assert "v" in r.json
1151+
assert r.json["v"] is None
1152+
# Test that present int_enum input yields input value
1153+
r = client.post(url, json={"v": Binary.ZERO.value})
1154+
assert "v" in r.json
1155+
assert r.json["v"] == Binary.ZERO.value
1156+
# Test that present non-int_enum input yields error
1157+
r = client.post(url, json={"v": 8})
1158+
assert "error" in r.json
1159+
1160+
1161+
def test_int_enum_default(client):
1162+
url = "/json/int_enum/default"
1163+
# Test that missing input for required and optional yields default values
1164+
r = client.post(url)
1165+
assert "n_opt" in r.json
1166+
assert r.json["n_opt"] == Binary.ZERO.value
1167+
assert "opt" in r.json
1168+
assert r.json["opt"] == Binary.ONE.value
1169+
# Test that present int_enum input for required and optional yields input values
1170+
r = client.post(url, json={"opt": Binary.ONE.value, "n_opt": Binary.ZERO.value})
1171+
assert "opt" in r.json
1172+
assert r.json["opt"] == Binary.ONE.value
1173+
assert "n_opt" in r.json
1174+
assert r.json["n_opt"] == Binary.ZERO.value
1175+
# Test that present non-int_enum input for required yields error
1176+
r = client.post(url, json={"opt": "a", "n_opt": 9})
1177+
assert "error" in r.json
1178+
1179+
1180+
def test_int_enum_func(client):
1181+
url = "/json/int_enum/func"
1182+
# Test that input passing func yields input
1183+
r = client.post(url, json={"v": Binary.ZERO})
1184+
assert "v" in r.json
1185+
assert r.json["v"] == Binary.ZERO.value
1186+
# Test that input failing func yields error
1187+
r = client.post(url, json={"v": Binary.ONE.value})
1188+
assert "error" in r.json

0 commit comments

Comments
 (0)