Skip to content

Commit c504915

Browse files
authored
Merge pull request #60 from Ge0rg3/dev/smt5541/uuid
Add UUID support
2 parents cb5cbd4 + b1baf7c commit c504915

File tree

10 files changed

+487
-17
lines changed

10 files changed

+487
-17
lines changed

README.md

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -118,21 +118,22 @@ Note: "**POST Methods**" refers to the HTTP methods that send data in the reques
118118
#### Type Hints and Accepted Input Types
119119
Type Hints allow for inline specification of the input type of a parameter. Some types are only available to certain `Parameter` subclasses.
120120

121-
| Type Hint / Expected Python Type | Notes | `Route` | `Form` | `Json` | `Query` | `File` |
122-
|-------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|--------|--------|---------|--------|
123-
| `str` | | Y | Y | Y | Y | N |
124-
| `int` | | Y | Y | Y | Y | N |
125-
| `bool` | | Y | Y | Y | Y | N |
126-
| `float` | | Y | Y | Y | Y | N |
127-
| `list`/`typing.List` (`typing.List` is [deprecated](https://docs.python.org/3/library/typing.html#typing.List)) | For `Query` and `Form` inputs, users can pass via either `value=1&value=2&value=3`, or `value=1,2,3`, both will be transformed to a `list` | N | Y | Y | Y | N |
128-
| `typing.Union` | Cannot be used inside of `typing.List` | Y | Y | Y | Y | N |
129-
| `typing.Optional` | Not supported for `Route` inputs | Y | Y | Y | Y | Y |
130-
| `datetime.datetime` | Received as a `str` in ISO-8601 date-time format | Y | Y | Y | Y | N |
131-
| `datetime.date` | Received as a `str` in ISO-8601 full-date format | Y | Y | Y | Y | N |
132-
| `datetime.time` | Received as a `str` in ISO-8601 partial-time format | Y | Y | Y | Y | N |
133-
| `dict` | For `Query` and `Form` inputs, users should pass the stringified JSON | N | Y | Y | Y | N |
134-
| `FileStorage` | | N | N | N | N | Y |
135-
| A subclass of `StrEnum` or `IntEnum`, or a subclass of `Enum` with `str` or `int` mixins prior to Python 3.11 | | Y | Y | Y | Y | N |
121+
| Type Hint / Expected Python Type | Notes | `Route` | `Form` | `Json` | `Query` | `File` |
122+
|-----------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|---------|--------|--------|---------|--------|
123+
| `str` | | Y | Y | Y | Y | N |
124+
| `int` | | Y | Y | Y | Y | N |
125+
| `bool` | | Y | Y | Y | Y | N |
126+
| `float` | | Y | Y | Y | Y | N |
127+
| `list`/`typing.List` (`typing.List` is [deprecated](https://docs.python.org/3/library/typing.html#typing.List)) | For `Query` and `Form` inputs, users can pass via either `value=1&value=2&value=3`, or `value=1,2,3`, both will be transformed to a `list` | N | Y | Y | Y | N |
128+
| `typing.Union` | Cannot be used inside of `typing.List` | Y | Y | Y | Y | N |
129+
| `typing.Optional` | Not supported for `Route` inputs | Y | Y | Y | Y | Y |
130+
| `datetime.datetime` | Received as a `str` in ISO-8601 date-time format | Y | Y | Y | Y | N |
131+
| `datetime.date` | Received as a `str` in ISO-8601 full-date format | Y | Y | Y | Y | N |
132+
| `datetime.time` | Received as a `str` in ISO-8601 partial-time format | Y | Y | Y | Y | N |
133+
| `dict` | For `Query` and `Form` inputs, users should pass the stringified JSON | N | Y | Y | Y | N |
134+
| `FileStorage` | | N | N | N | N | Y |
135+
| A subclass of `StrEnum` or `IntEnum`, or a subclass of `Enum` with `str` or `int` mixins prior to Python 3.11 | | Y | Y | Y | Y | N |
136+
| `uuid.UUID` | Received as a `str` with or without hyphens, case-insensitive | Y | Y | Y | Y | N |
136137

137138
These can be used in tandem to describe a parameter to validate: `parameter_name: type_hint = ParameterSubclass()`
138139
- `parameter_name`: The field name itself, such as username

flask_parameter_validation/parameter_types/parameter.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
Should only be used as child class for other params.
44
"""
55
import re
6+
import uuid
67
from datetime import date, datetime, time
78
from enum import Enum
89
import dateutil.parser as parser
@@ -199,4 +200,11 @@ def convert(self, value, allowed_types):
199200
value = int(value)
200201
returning = allowed_types[0](value)
201202
return returning
203+
elif uuid.UUID in allowed_types:
204+
try:
205+
if type(value) == uuid.UUID: # Handle default of type UUID
206+
return value
207+
return uuid.UUID(value)
208+
except AttributeError:
209+
raise ValueError("UUID format is incorrect")
202210
return value

flask_parameter_validation/test/test_form_params.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# String Validation
22
import datetime
3+
import uuid
34
from typing import Type, List, Optional
45

56
from flask_parameter_validation.test.enums import Fruits, Binary
@@ -1123,3 +1124,97 @@ def test_int_enum_func(client):
11231124
# Test that input failing func yields error
11241125
r = client.post(url, data={"v": Binary.ONE.value})
11251126
assert "error" in r.json
1127+
1128+
# UUID Validation
1129+
def test_required_uuid(client):
1130+
url = "/form/uuid/required"
1131+
# Test that present UUID input yields input value
1132+
u = uuid.uuid4()
1133+
r = client.post(url, data={"v": u})
1134+
assert "v" in r.json
1135+
assert uuid.UUID(r.json["v"]) == u
1136+
# Test that missing input yields error
1137+
r = client.post(url)
1138+
assert "error" in r.json
1139+
# Test that present non-UUID input yields error
1140+
r = client.post(url, data={"v": "a"})
1141+
assert "error" in r.json
1142+
1143+
1144+
def test_required_uuid_decorator(client):
1145+
url = "/form/uuid/decorator/required"
1146+
# Test that present UUID input yields input value
1147+
u = uuid.uuid4()
1148+
r = client.post(url, data={"v": u})
1149+
assert "v" in r.json
1150+
assert uuid.UUID(r.json["v"]) == u
1151+
# Test that missing input yields error
1152+
r = client.post(url)
1153+
assert "error" in r.json
1154+
# Test that present non-UUID input yields error
1155+
r = client.post(url, data={"v": "a"})
1156+
assert "error" in r.json
1157+
1158+
1159+
def test_required_uuid_async_decorator(client):
1160+
url = "/form/uuid/async_decorator/required"
1161+
# Test that present UUID input yields input value
1162+
u = uuid.uuid4()
1163+
r = client.post(url, data={"v": u})
1164+
assert "v" in r.json
1165+
assert uuid.UUID(r.json["v"]) == u
1166+
# Test that missing input yields error
1167+
r = client.post(url)
1168+
assert "error" in r.json
1169+
# Test that present non-UUID input yields error
1170+
r = client.post(url, data={"v": "a"})
1171+
assert "error" in r.json
1172+
1173+
1174+
def test_optional_uuid(client):
1175+
url = "/form/uuid/optional"
1176+
# Test that missing input yields None
1177+
r = client.post(url)
1178+
assert "v" in r.json
1179+
assert r.json["v"] is None
1180+
# Test that present UUID input yields input value
1181+
u = uuid.uuid4()
1182+
r = client.post(url, data={"v": u})
1183+
assert "v" in r.json
1184+
assert uuid.UUID(r.json["v"]) == u
1185+
# Test that present non-UUID input yields error
1186+
r = client.post(url, data={"v": "a"})
1187+
assert "error" in r.json
1188+
1189+
def test_uuid_default(client):
1190+
url = "/form/uuid/default"
1191+
# Test that missing input for required and optional yields default values
1192+
r = client.post(url)
1193+
assert "n_opt" in r.json
1194+
assert r.json["n_opt"] == "9ba0c75f-1574-4464-bd7d-760262e3ea41"
1195+
assert "opt" in r.json
1196+
assert r.json["opt"] == "2f01faa3-29a2-4b36-b406-2ad288fb4969"
1197+
# Test that present UUID input for required and optional yields input values
1198+
r = client.post(url, data={
1199+
"opt": "f2b1e5a0-e050-4618-83b8-f303b887b75d",
1200+
"n_opt": "48c0d213-a889-4ba6-9722-70f6e6a1afca"
1201+
})
1202+
assert "opt" in r.json
1203+
assert r.json["opt"] == "f2b1e5a0-e050-4618-83b8-f303b887b75d"
1204+
assert "n_opt" in r.json
1205+
assert r.json["n_opt"] == "48c0d213-a889-4ba6-9722-70f6e6a1afca"
1206+
# Test that present non-UUID input for required yields error
1207+
r = client.post(url, data={"opt": "a", "n_opt": "b"})
1208+
assert "error" in r.json
1209+
1210+
1211+
def test_uuid_func(client):
1212+
url = "/form/uuid/func"
1213+
# Test that input passing func yields input
1214+
u = "b662e5f5-7e82-4ac7-8844-4efea3afa171"
1215+
r = client.post(url, data={"v": u})
1216+
assert "v" in r.json
1217+
assert r.json["v"] == u
1218+
# Test that input failing func yields error
1219+
r = client.post(url, data={"v": "492c6dfc-1730-11f0-9cd2-0242ac120002"})
1220+
assert "error" in r.json

flask_parameter_validation/test/test_json_params.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# String Validation
22
import datetime
3+
import uuid
34
from typing import Type, List, Optional
45

56
from flask_parameter_validation.test.enums import Binary, Fruits
@@ -1243,3 +1244,97 @@ def test_int_enum_func(client):
12431244
# Test that input failing func yields error
12441245
r = client.post(url, json={"v": Binary.ONE.value})
12451246
assert "error" in r.json
1247+
1248+
# UUID Validation
1249+
def test_required_uuid(client):
1250+
url = "/json/uuid/required"
1251+
# Test that present UUID input yields input value
1252+
u = uuid.uuid4()
1253+
r = client.post(url, json={"v": u})
1254+
assert "v" in r.json
1255+
assert uuid.UUID(r.json["v"]) == u
1256+
# Test that missing input yields error
1257+
r = client.post(url)
1258+
assert "error" in r.json
1259+
# Test that present non-UUID input yields error
1260+
r = client.post(url, json={"v": "a"})
1261+
assert "error" in r.json
1262+
1263+
1264+
def test_required_uuid_decorator(client):
1265+
url = "/json/uuid/decorator/required"
1266+
# Test that present UUID input yields input value
1267+
u = uuid.uuid4()
1268+
r = client.post(url, json={"v": u})
1269+
assert "v" in r.json
1270+
assert uuid.UUID(r.json["v"]) == u
1271+
# Test that missing input yields error
1272+
r = client.post(url)
1273+
assert "error" in r.json
1274+
# Test that present non-UUID input yields error
1275+
r = client.post(url, json={"v": "a"})
1276+
assert "error" in r.json
1277+
1278+
1279+
def test_required_uuid_async_decorator(client):
1280+
url = "/json/uuid/async_decorator/required"
1281+
# Test that present UUID input yields input value
1282+
u = uuid.uuid4()
1283+
r = client.post(url, json={"v": u})
1284+
assert "v" in r.json
1285+
assert uuid.UUID(r.json["v"]) == u
1286+
# Test that missing input yields error
1287+
r = client.post(url)
1288+
assert "error" in r.json
1289+
# Test that present non-UUID input yields error
1290+
r = client.post(url, json={"v": "a"})
1291+
assert "error" in r.json
1292+
1293+
1294+
def test_optional_uuid(client):
1295+
url = "/json/uuid/optional"
1296+
# Test that missing input yields None
1297+
r = client.post(url)
1298+
assert "v" in r.json
1299+
assert r.json["v"] is None
1300+
# Test that present UUID input yields input value
1301+
u = uuid.uuid4()
1302+
r = client.post(url, json={"v": u})
1303+
assert "v" in r.json
1304+
assert uuid.UUID(r.json["v"]) == u
1305+
# Test that present non-UUID input yields error
1306+
r = client.post(url, json={"v": "a"})
1307+
assert "error" in r.json
1308+
1309+
def test_uuid_default(client):
1310+
url = "/json/uuid/default"
1311+
# Test that missing input for required and optional yields default values
1312+
r = client.post(url)
1313+
assert "n_opt" in r.json
1314+
assert r.json["n_opt"] == "9ba0c75f-1574-4464-bd7d-760262e3ea41"
1315+
assert "opt" in r.json
1316+
assert r.json["opt"] == "2f01faa3-29a2-4b36-b406-2ad288fb4969"
1317+
# Test that present UUID input for required and optional yields input values
1318+
r = client.post(url, json={
1319+
"opt": "f2b1e5a0-e050-4618-83b8-f303b887b75d",
1320+
"n_opt": "48c0d213-a889-4ba6-9722-70f6e6a1afca"
1321+
})
1322+
assert "opt" in r.json
1323+
assert r.json["opt"] == "f2b1e5a0-e050-4618-83b8-f303b887b75d"
1324+
assert "n_opt" in r.json
1325+
assert r.json["n_opt"] == "48c0d213-a889-4ba6-9722-70f6e6a1afca"
1326+
# Test that present non-UUID input for required yields error
1327+
r = client.post(url, json={"opt": "a", "n_opt": "b"})
1328+
assert "error" in r.json
1329+
1330+
1331+
def test_uuid_func(client):
1332+
url = "/json/uuid/func"
1333+
# Test that input passing func yields input
1334+
u = "b662e5f5-7e82-4ac7-8844-4efea3afa171"
1335+
r = client.post(url, json={"v": u})
1336+
assert "v" in r.json
1337+
assert r.json["v"] == u
1338+
# Test that input failing func yields error
1339+
r = client.post(url, json={"v": "492c6dfc-1730-11f0-9cd2-0242ac120002"})
1340+
assert "error" in r.json

0 commit comments

Comments
 (0)