Skip to content

WIP: Fixing issue where Optional[List[Enum]] didn't work, among others #61

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

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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
92 changes: 47 additions & 45 deletions README.md

Large diffs are not rendered by default.

72 changes: 44 additions & 28 deletions flask_parameter_validation/parameter_types/parameter.py
Original file line number Diff line number Diff line change
@@ -34,6 +34,7 @@ def __init__(
alias=None, # str: alias for parameter name
json_schema=None, # dict: JSON Schema to check received dicts or lists against
blank_none=None, # bool: Whether blank strings should be converted to None when validating a type of Optional[str]
list_disable_query_csv=None, # bool: Whether query strings should be split by `,` when validating a type of list
):
self.default = default
self.min_list_length = min_list_length
@@ -51,6 +52,8 @@ def __init__(
self.alias = alias
self.json_schema = json_schema
self.blank_none = blank_none
self.list_disable_query_csv = list_disable_query_csv


def func_helper(self, v):
func_result = self.func(v)
@@ -158,53 +161,66 @@ def validate(self, value):

return True

def convert(self, value, allowed_types):
def convert(self, value, allowed_types, current_error=None):
"""Some parameter types require manual type conversion (see Query)"""
print(f"Converting '{value}' ({type(value)}) to '{allowed_types}'")
blank_none = self.blank_none
if blank_none is None: # Default blank_none to False if not provided or set in app config
blank_none = False if "FPV_BLANK_NONE" not in flask.current_app.config else flask.current_app.config["FPV_BLANK_NONE"]

error = None
# Datetime conversion
if None in allowed_types and value is None:
return value
if date in allowed_types:
try:
return date.fromisoformat(str(value))
except ValueError:
error = ValueError("date format does not match ISO 8601")
if time in allowed_types:
try:
return time.fromisoformat(str(value))
except ValueError:
error = ValueError("time format does not match ISO 8601")
if datetime in allowed_types:
if self.datetime_format is None:
try:
return parser.parse(str(value))
except parser._parser.ParserError:
pass
return datetime.fromisoformat(str(value))
except ValueError:
error = ValueError("datetime format does not match ISO 8601")
else:
try:
return datetime.strptime(str(value), self.datetime_format)
except ValueError:
raise ValueError(
f"datetime format does not match: {self.datetime_format}"
)
pass
elif time in allowed_types:
try:
return time.fromisoformat(str(value))
except ValueError:
raise ValueError("time format does not match ISO 8601")
elif date in allowed_types:
try:
return date.fromisoformat(str(value))
except ValueError:
raise ValueError("date format does not match ISO 8601")
elif blank_none and type(None) in allowed_types and str in allowed_types and type(value) is str and len(value) == 0:
error = ValueError(f"datetime format does not match: {self.datetime_format}")
if blank_none and type(None) in allowed_types and str in allowed_types and type(value) is str and len(value) == 0:
return None
elif any(isclass(allowed_type) and (issubclass(allowed_type, str) or issubclass(allowed_type, int) and issubclass(allowed_type, Enum)) for allowed_type in allowed_types):
if any(isclass(allowed_type) and (issubclass(allowed_type, str) or issubclass(allowed_type, int) and issubclass(allowed_type, Enum)) for allowed_type in allowed_types):
print("Enums allowed")
for allowed_type in allowed_types:
if issubclass(allowed_type, Enum):
if issubclass(allowed_types[0], int):
value = int(value)
returning = allowed_types[0](value)
return returning
elif uuid.UUID in allowed_types:
try:
if issubclass(allowed_type, int):
value = int(value)
returning = allowed_type(value)
print(f"type(returning): {type(returning)}")
return returning
except ValueError as e:
error = e
if uuid.UUID in allowed_types:
try:
if type(value) == uuid.UUID: # Handle default of type UUID
return value
return uuid.UUID(value)
try:
return uuid.UUID(value)
except ValueError as e:
error = e
except AttributeError:
raise ValueError("UUID format is incorrect")
return value
error = ValueError("UUID format is incorrect")
if str in allowed_types:
return value
print(value)
print(type(value))
if error and type(value) is str:
raise error
return value
42 changes: 26 additions & 16 deletions flask_parameter_validation/parameter_types/query.py
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
- i.e. sent in GET requests, /?username=myname
"""
import json
from enum import Enum

from .parameter import Parameter

@@ -13,33 +14,42 @@ class Query(Parameter):
def __init__(self, default=None, **kwargs):
super().__init__(default, **kwargs)

def convert(self, value, allowed_types):
def convert(self, value, allowed_types, current_error=None):
"""Convert query parameters to corresponding types."""
print(f"value: {value}, type: {type(value)}")
original_value = value
error = None
if type(value) is str:
# int conversion
# int conversion done before dict to handle potential IntEnum
if int in allowed_types:
try:
value = int(value)
except ValueError:
enum_test = super().convert(value, allowed_types, current_error)
if issubclass(type(enum_test), Enum) and issubclass(type(enum_test), int):
return enum_test
return int(value)
except ValueError or TypeError:
pass
# float conversion
if float in allowed_types:
if dict in allowed_types:
try:
value = float(value)
return json.loads(value)
except ValueError:
pass
error = ValueError(f"invalid JSON")
# float conversion
if float in allowed_types:
if not (type(value) is int and str(value) == original_value): # If we've already converted an int and the conversion is exact, skip float conversion
try:
return float(value)
except ValueError:
pass
# bool conversion
if bool in allowed_types:
try:
if value.lower() == "true":
value = True
return True
elif value.lower() == "false":
value = False
return False
except AttributeError:
pass
if dict in allowed_types:
try:
value = json.loads(value)
except ValueError:
raise ValueError(f"invalid JSON")
return super().convert(value, allowed_types)
if type(value) is not str:
error = None
return super().convert(value, allowed_types, current_error=error)
10 changes: 8 additions & 2 deletions flask_parameter_validation/parameter_types/route.py
Original file line number Diff line number Diff line change
@@ -2,6 +2,8 @@
Route Parameters
- Sent as part of a path, i.e. /user/<int:id>
"""
from enum import Enum

from .parameter import Parameter


@@ -11,13 +13,17 @@ class Route(Parameter):
def __init__(self, default=None, **kwargs):
super().__init__(default, **kwargs)

def convert(self, value, allowed_types):
def convert(self, value, allowed_types, current_error=None):
"""Convert query parameters to corresponding types."""
if type(value) is str:
# int conversion
if int in allowed_types:
try:
value = int(value)
enum_test = super().convert(value, allowed_types, current_error)
if issubclass(type(enum_test), Enum) and issubclass(type(enum_test), int):
value = enum_test
else:
value = int(value)
except ValueError:
pass
# float conversion
Loading