Skip to content

Implement Enum Validation #46

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

Merged
merged 7 commits into from
Aug 13, 2024
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
87 changes: 49 additions & 38 deletions README.md

Large diffs are not rendered by default.

40 changes: 23 additions & 17 deletions flask_parameter_validation/parameter_types/parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"""
import re
from datetime import date, datetime, time

from enum import Enum
import dateutil.parser as parser
import jsonschema
from jsonschema.exceptions import ValidationError as JSONSchemaValidationError
Expand All @@ -14,22 +14,23 @@ class Parameter:

# Parameter initialisation
def __init__(
self,
default=None, # any: default parameter value
min_str_length=None, # int: min parameter length
max_str_length=None, # int: max parameter length
min_list_length=None, # int: min number of items in list
max_list_length=None, # int: max number of items in list
min_int=None, # int: min number (if val is int)
max_int=None, # int: max number (if val is int)
whitelist=None, # str: character whitelist
blacklist=None, # str: character blacklist
pattern=None, # str: regexp pattern
func=None, # Callable -> Union[bool, tuple[bool, str]]: function performing a fully customized validation
datetime_format=None, # str: datetime format string (https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes),
comment=None, # str: comment for autogenerated documentation
alias=None, # str: alias for parameter name
json_schema=None, # dict: JSON Schema to check received dicts or lists against
self,
default=None, # any: default parameter value
min_str_length=None, # int: min parameter length
max_str_length=None, # int: max parameter length
min_list_length=None, # int: min number of items in list
max_list_length=None, # int: max number of items in list
min_int=None, # int: min number (if val is int)
max_int=None, # int: max number (if val is int)
whitelist=None, # str: character whitelist
blacklist=None, # str: character blacklist
pattern=None, # str: regexp pattern
func=None, # Callable -> Union[bool, tuple[bool, str]]: function performing a fully customized validation
datetime_format=None,
# str: datetime format string (https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes),
comment=None, # str: comment for autogenerated documentation
alias=None, # str: alias for parameter name
json_schema=None, # dict: JSON Schema to check received dicts or lists against
):
self.default = default
self.min_list_length = min_list_length
Expand Down Expand Up @@ -182,4 +183,9 @@ def convert(self, value, allowed_types):
return date.fromisoformat(str(value))
except ValueError:
raise ValueError("date format does not match ISO 8601")
elif len(allowed_types) == 1 and (issubclass(allowed_types[0], str) or issubclass(allowed_types[0], int) and issubclass(allowed_types[0], Enum)):
if issubclass(allowed_types[0], int):
value = int(value)
returning = allowed_types[0](value)
return returning
return value
11 changes: 11 additions & 0 deletions flask_parameter_validation/test/enums.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from enum import Enum


class Fruits(str, Enum):
APPLE = "apple"
ORANGE = "orange"


class Binary(int, Enum):
ZERO = 0
ONE = 1
121 changes: 121 additions & 0 deletions flask_parameter_validation/test/test_form_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import datetime
from typing import Type, List, Optional

from flask_parameter_validation.test.enums import Fruits, Binary


def list_assertion_helper(length: int, list_children_type: Type, expected_list: List, tested_list,
expected_call: Optional[str] = None):
Expand Down Expand Up @@ -945,3 +947,122 @@ def test_non_typing_optional_list_str(client):
assert type(r.json["v"]) is list
assert len(r.json["v"]) == 2
list_assertion_helper(2, str, v, r.json["v"])


# Enum validation
def test_required_str_enum(client):
url = "/form/str_enum/required"
# Test that present str_enum input yields input value
r = client.post(url, data={"v": Fruits.APPLE.value})
assert "v" in r.json
assert r.json["v"] == Fruits.APPLE.value
# Test that missing input yields error
r = client.post(url)
assert "error" in r.json
# Test that present non-str_enum input yields error
r = client.post(url, data={"v": "a"})
assert "error" in r.json


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


def test_str_enum_default(client):
url = "/form/str_enum/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"] == Fruits.APPLE.value
assert "opt" in r.json
assert r.json["opt"] == Fruits.ORANGE.value
# Test that present str_enum input for required and optional yields input values
r = client.post(url, data={"opt": Fruits.ORANGE.value, "n_opt": Fruits.APPLE.value})
assert "opt" in r.json
assert r.json["opt"] == Fruits.ORANGE.value
assert "n_opt" in r.json
assert r.json["n_opt"] == Fruits.APPLE.value
# Test that present non-str_enum input for required yields error
r = client.post(url, data={"opt": "a", "n_opt": "b"})
assert "error" in r.json


def test_str_enum_func(client):
url = "/form/str_enum/func"
# Test that input passing func yields input
r = client.post(url, data={"v": Fruits.ORANGE.value})
assert "v" in r.json
assert r.json["v"] == Fruits.ORANGE.value
# Test that input failing func yields error
r = client.post(url, data={"v": Fruits.APPLE.value})
assert "error" in r.json


def test_required_int_enum(client):
url = "/form/int_enum/required"
# Test that present int_enum input yields input value
r = client.post(url, data={"v": Binary.ONE.value})
assert "v" in r.json
assert r.json["v"] == Binary.ONE.value
# Test that missing input yields error
r = client.post(url)
assert "error" in r.json
# Test that present non-int_enum input yields error
r = client.post(url, data={"v": 8})
assert "error" in r.json


def test_optional_int_enum(client):
url = "/form/int_enum/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 int_enum input yields input value
r = client.post(url, data={"v": Binary.ZERO.value})
assert "v" in r.json
assert r.json["v"] == Binary.ZERO.value
# Test that present non-int_enum input yields error
r = client.post(url, data={"v": 8})
assert "error" in r.json


def test_int_enum_default(client):
url = "/form/int_enum/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"] == Binary.ZERO.value
assert "opt" in r.json
assert r.json["opt"] == Binary.ONE.value
# Test that present int_enum input for required and optional yields input values
r = client.post(url, data={"opt": Binary.ONE.value, "n_opt": Binary.ZERO.value})
assert "opt" in r.json
assert r.json["opt"] == Binary.ONE.value
assert "n_opt" in r.json
assert r.json["n_opt"] == Binary.ZERO.value
# Test that present non-int_enum input for required yields error
r = client.post(url, data={"opt": "a", "n_opt": 9})
assert "error" in r.json


def test_int_enum_func(client):
url = "/form/int_enum/func"
# Test that input passing func yields input
r = client.post(url, data={"v": Binary.ZERO.value})
assert "v" in r.json
assert r.json["v"] == Binary.ZERO.value
# Test that input failing func yields error
r = client.post(url, data={"v": Binary.ONE.value})
assert "error" in r.json
122 changes: 122 additions & 0 deletions flask_parameter_validation/test/test_json_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import datetime
from typing import Type, List, Optional

from flask_parameter_validation.test.enums import Binary, Fruits


def list_assertion_helper(length: int, list_children_type: Type, expected_list: List, tested_list,
expected_call: Optional[str] = None):
Expand Down Expand Up @@ -1064,3 +1066,123 @@ def test_non_typing_optional_list_str(client):
assert type(r.json["v"]) is list
assert len(r.json["v"]) == 2
list_assertion_helper(2, str, v, r.json["v"])



# Enum validation
def test_required_str_enum(client):
url = "/json/str_enum/required"
# Test that present str_enum input yields input value
r = client.post(url, json={"v": Fruits.APPLE.value})
assert "v" in r.json
assert r.json["v"] == Fruits.APPLE.value
# Test that missing input yields error
r = client.post(url)
assert "error" in r.json
# Test that present non-str_enum input yields error
r = client.post(url, json={"v": "a"})
assert "error" in r.json


def test_optional_str_enum(client):
url = "/json/str_enum/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 str_enum input yields input value
r = client.post(url, json={"v": Fruits.ORANGE.value})
assert "v" in r.json
assert r.json["v"] == Fruits.ORANGE.value
# Test that present non-str_enum input yields error
r = client.post(url, json={"v": "v"})
assert "error" in r.json


def test_str_enum_default(client):
url = "/json/str_enum/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"] == Fruits.APPLE.value
assert "opt" in r.json
assert r.json["opt"] == Fruits.ORANGE.value
# Test that present str_enum input for required and optional yields input values
r = client.post(url, json={"opt": Fruits.ORANGE.value, "n_opt": Fruits.APPLE.value})
assert "opt" in r.json
assert r.json["opt"] == Fruits.ORANGE.value
assert "n_opt" in r.json
assert r.json["n_opt"] == Fruits.APPLE.value
# Test that present non-str_enum input for required yields error
r = client.post(url, json={"opt": "a", "n_opt": "b"})
assert "error" in r.json


def test_str_enum_func(client):
url = "/json/str_enum/func"
# Test that input passing func yields input
r = client.post(url, json={"v": Fruits.ORANGE.value})
assert "v" in r.json
assert r.json["v"] == Fruits.ORANGE.value
# Test that input failing func yields error
r = client.post(url, json={"v": Fruits.APPLE.value})
assert "error" in r.json


def test_required_int_enum(client):
url = "/json/int_enum/required"
# Test that present int_enum input yields input value
r = client.post(url, json={"v": Binary.ONE.value})
assert "v" in r.json
assert r.json["v"] == Binary.ONE.value
# Test that missing input yields error
r = client.post(url)
assert "error" in r.json
# Test that present non-int_enum input yields error
r = client.post(url, json={"v": 8})
assert "error" in r.json


def test_optional_int_enum(client):
url = "/json/int_enum/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 int_enum input yields input value
r = client.post(url, json={"v": Binary.ZERO.value})
assert "v" in r.json
assert r.json["v"] == Binary.ZERO.value
# Test that present non-int_enum input yields error
r = client.post(url, json={"v": 8})
assert "error" in r.json


def test_int_enum_default(client):
url = "/json/int_enum/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"] == Binary.ZERO.value
assert "opt" in r.json
assert r.json["opt"] == Binary.ONE.value
# Test that present int_enum input for required and optional yields input values
r = client.post(url, json={"opt": Binary.ONE.value, "n_opt": Binary.ZERO.value})
assert "opt" in r.json
assert r.json["opt"] == Binary.ONE.value
assert "n_opt" in r.json
assert r.json["n_opt"] == Binary.ZERO.value
# Test that present non-int_enum input for required yields error
r = client.post(url, json={"opt": "a", "n_opt": 9})
assert "error" in r.json


def test_int_enum_func(client):
url = "/json/int_enum/func"
# Test that input passing func yields input
r = client.post(url, json={"v": Binary.ZERO})
assert "v" in r.json
assert r.json["v"] == Binary.ZERO.value
# Test that input failing func yields error
r = client.post(url, json={"v": Binary.ONE.value})
assert "error" in r.json
Loading