Skip to content

Commit

Permalink
Merge pull request #46 from Ge0rg3/dev/smt5541/enum
Browse files Browse the repository at this point in the history
Implement Enum Validation
  • Loading branch information
smt5541 authored Aug 13, 2024
2 parents 2edb5e5 + 099b399 commit 9a7f186
Show file tree
Hide file tree
Showing 9 changed files with 571 additions and 57 deletions.
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

0 comments on commit 9a7f186

Please sign in to comment.