Skip to content
Merged
Show file tree
Hide file tree
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
31 changes: 16 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,21 +118,22 @@ Note: "**POST Methods**" refers to the HTTP methods that send data in the reques
#### Type Hints and Accepted Input Types
Type Hints allow for inline specification of the input type of a parameter. Some types are only available to certain `Parameter` subclasses.

| Type Hint / Expected Python Type | Notes | `Route` | `Form` | `Json` | `Query` | `File` |
|-------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|--------|--------|---------|--------|
| `str` | | Y | Y | Y | Y | N |
| `int` | | Y | Y | Y | Y | N |
| `bool` | | Y | Y | Y | Y | N |
| `float` | | Y | Y | Y | Y | N |
| `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 |
| `typing.Union` | Cannot be used inside of `typing.List` | Y | Y | Y | Y | N |
| `typing.Optional` | Not supported for `Route` inputs | Y | Y | Y | Y | Y |
| `datetime.datetime` | Received as a `str` in ISO-8601 date-time format | Y | Y | Y | Y | N |
| `datetime.date` | Received as a `str` in ISO-8601 full-date format | Y | Y | Y | Y | N |
| `datetime.time` | Received as a `str` in ISO-8601 partial-time format | Y | Y | Y | Y | N |
| `dict` | For `Query` and `Form` inputs, users should pass the stringified JSON | N | Y | Y | Y | N |
| `FileStorage` | | N | N | N | N | Y |
| 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 |
| Type Hint / Expected Python Type | Notes | `Route` | `Form` | `Json` | `Query` | `File` |
|-----------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|---------|--------|--------|---------|--------|
| `str` | | Y | Y | Y | Y | N |
| `int` | | Y | Y | Y | Y | N |
| `bool` | | Y | Y | Y | Y | N |
| `float` | | Y | Y | Y | Y | N |
| `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 |
| `typing.Union` | Cannot be used inside of `typing.List` | Y | Y | Y | Y | N |
| `typing.Optional` | Not supported for `Route` inputs | Y | Y | Y | Y | Y |
| `datetime.datetime` | Received as a `str` in ISO-8601 date-time format | Y | Y | Y | Y | N |
| `datetime.date` | Received as a `str` in ISO-8601 full-date format | Y | Y | Y | Y | N |
| `datetime.time` | Received as a `str` in ISO-8601 partial-time format | Y | Y | Y | Y | N |
| `dict` | For `Query` and `Form` inputs, users should pass the stringified JSON | N | Y | Y | Y | N |
| `FileStorage` | | N | N | N | N | Y |
| 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 |
| `uuid.UUID` | Received as a `str` with or without hyphens, case-insensitive | Y | Y | Y | Y | N |

These can be used in tandem to describe a parameter to validate: `parameter_name: type_hint = ParameterSubclass()`
- `parameter_name`: The field name itself, such as username
Expand Down
8 changes: 8 additions & 0 deletions flask_parameter_validation/parameter_types/parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
Should only be used as child class for other params.
"""
import re
import uuid
from datetime import date, datetime, time
from enum import Enum
import dateutil.parser as parser
Expand Down Expand Up @@ -199,4 +200,11 @@ def convert(self, value, allowed_types):
value = int(value)
returning = allowed_types[0](value)
return returning
elif uuid.UUID in allowed_types:
try:
if type(value) == uuid.UUID: # Handle default of type UUID
return value
return uuid.UUID(value)
except AttributeError:
raise ValueError("UUID format is incorrect")
return value
95 changes: 95 additions & 0 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 uuid
from typing import Type, List, Optional

from flask_parameter_validation.test.enums import Fruits, Binary
Expand Down Expand Up @@ -1123,3 +1124,97 @@ def test_int_enum_func(client):
# Test that input failing func yields error
r = client.post(url, data={"v": Binary.ONE.value})
assert "error" in r.json

# UUID Validation
def test_required_uuid(client):
url = "/form/uuid/required"
# Test that present UUID input yields input value
u = uuid.uuid4()
r = client.post(url, data={"v": u})
assert "v" in r.json
assert uuid.UUID(r.json["v"]) == u
# Test that missing input yields error
r = client.post(url)
assert "error" in r.json
# Test that present non-UUID input yields error
r = client.post(url, data={"v": "a"})
assert "error" in r.json


def test_required_uuid_decorator(client):
url = "/form/uuid/decorator/required"
# Test that present UUID input yields input value
u = uuid.uuid4()
r = client.post(url, data={"v": u})
assert "v" in r.json
assert uuid.UUID(r.json["v"]) == u
# Test that missing input yields error
r = client.post(url)
assert "error" in r.json
# Test that present non-UUID input yields error
r = client.post(url, data={"v": "a"})
assert "error" in r.json


def test_required_uuid_async_decorator(client):
url = "/form/uuid/async_decorator/required"
# Test that present UUID input yields input value
u = uuid.uuid4()
r = client.post(url, data={"v": u})
assert "v" in r.json
assert uuid.UUID(r.json["v"]) == u
# Test that missing input yields error
r = client.post(url)
assert "error" in r.json
# Test that present non-UUID input yields error
r = client.post(url, data={"v": "a"})
assert "error" in r.json


def test_optional_uuid(client):
url = "/form/uuid/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 UUID input yields input value
u = uuid.uuid4()
r = client.post(url, data={"v": u})
assert "v" in r.json
assert uuid.UUID(r.json["v"]) == u
# Test that present non-UUID input yields error
r = client.post(url, data={"v": "a"})
assert "error" in r.json

def test_uuid_default(client):
url = "/form/uuid/default"
# Test that missing input for required and optional yields default values
r = client.post(url)
assert "n_opt" in r.json
assert r.json["n_opt"] == "9ba0c75f-1574-4464-bd7d-760262e3ea41"
assert "opt" in r.json
assert r.json["opt"] == "2f01faa3-29a2-4b36-b406-2ad288fb4969"
# Test that present UUID input for required and optional yields input values
r = client.post(url, data={
"opt": "f2b1e5a0-e050-4618-83b8-f303b887b75d",
"n_opt": "48c0d213-a889-4ba6-9722-70f6e6a1afca"
})
assert "opt" in r.json
assert r.json["opt"] == "f2b1e5a0-e050-4618-83b8-f303b887b75d"
assert "n_opt" in r.json
assert r.json["n_opt"] == "48c0d213-a889-4ba6-9722-70f6e6a1afca"
# Test that present non-UUID input for required yields error
r = client.post(url, data={"opt": "a", "n_opt": "b"})
assert "error" in r.json


def test_uuid_func(client):
url = "/form/uuid/func"
# Test that input passing func yields input
u = "b662e5f5-7e82-4ac7-8844-4efea3afa171"
r = client.post(url, data={"v": u})
assert "v" in r.json
assert r.json["v"] == u
# Test that input failing func yields error
r = client.post(url, data={"v": "492c6dfc-1730-11f0-9cd2-0242ac120002"})
assert "error" in r.json
95 changes: 95 additions & 0 deletions flask_parameter_validation/test/test_json_params.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# String Validation
import datetime
import uuid
from typing import Type, List, Optional

from flask_parameter_validation.test.enums import Binary, Fruits
Expand Down Expand Up @@ -1243,3 +1244,97 @@ def test_int_enum_func(client):
# Test that input failing func yields error
r = client.post(url, json={"v": Binary.ONE.value})
assert "error" in r.json

# UUID Validation
def test_required_uuid(client):
url = "/json/uuid/required"
# Test that present UUID input yields input value
u = uuid.uuid4()
r = client.post(url, json={"v": u})
assert "v" in r.json
assert uuid.UUID(r.json["v"]) == u
# Test that missing input yields error
r = client.post(url)
assert "error" in r.json
# Test that present non-UUID input yields error
r = client.post(url, json={"v": "a"})
assert "error" in r.json


def test_required_uuid_decorator(client):
url = "/json/uuid/decorator/required"
# Test that present UUID input yields input value
u = uuid.uuid4()
r = client.post(url, json={"v": u})
assert "v" in r.json
assert uuid.UUID(r.json["v"]) == u
# Test that missing input yields error
r = client.post(url)
assert "error" in r.json
# Test that present non-UUID input yields error
r = client.post(url, json={"v": "a"})
assert "error" in r.json


def test_required_uuid_async_decorator(client):
url = "/json/uuid/async_decorator/required"
# Test that present UUID input yields input value
u = uuid.uuid4()
r = client.post(url, json={"v": u})
assert "v" in r.json
assert uuid.UUID(r.json["v"]) == u
# Test that missing input yields error
r = client.post(url)
assert "error" in r.json
# Test that present non-UUID input yields error
r = client.post(url, json={"v": "a"})
assert "error" in r.json


def test_optional_uuid(client):
url = "/json/uuid/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 UUID input yields input value
u = uuid.uuid4()
r = client.post(url, json={"v": u})
assert "v" in r.json
assert uuid.UUID(r.json["v"]) == u
# Test that present non-UUID input yields error
r = client.post(url, json={"v": "a"})
assert "error" in r.json

def test_uuid_default(client):
url = "/json/uuid/default"
# Test that missing input for required and optional yields default values
r = client.post(url)
assert "n_opt" in r.json
assert r.json["n_opt"] == "9ba0c75f-1574-4464-bd7d-760262e3ea41"
assert "opt" in r.json
assert r.json["opt"] == "2f01faa3-29a2-4b36-b406-2ad288fb4969"
# Test that present UUID input for required and optional yields input values
r = client.post(url, json={
"opt": "f2b1e5a0-e050-4618-83b8-f303b887b75d",
"n_opt": "48c0d213-a889-4ba6-9722-70f6e6a1afca"
})
assert "opt" in r.json
assert r.json["opt"] == "f2b1e5a0-e050-4618-83b8-f303b887b75d"
assert "n_opt" in r.json
assert r.json["n_opt"] == "48c0d213-a889-4ba6-9722-70f6e6a1afca"
# Test that present non-UUID input for required yields error
r = client.post(url, json={"opt": "a", "n_opt": "b"})
assert "error" in r.json


def test_uuid_func(client):
url = "/json/uuid/func"
# Test that input passing func yields input
u = "b662e5f5-7e82-4ac7-8844-4efea3afa171"
r = client.post(url, json={"v": u})
assert "v" in r.json
assert r.json["v"] == u
# Test that input failing func yields error
r = client.post(url, json={"v": "492c6dfc-1730-11f0-9cd2-0242ac120002"})
assert "error" in r.json
Loading