Skip to content
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

Refactor validate params #39

Closed
wants to merge 3 commits into from
Closed
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
18 changes: 16 additions & 2 deletions flask_utils/decorators.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from functools import wraps
from typing import Any
from typing import Dict
from typing import get_args
from typing import get_origin
from typing import get_type_hints
from typing import Optional
from typing import Type
from typing import Union
Expand Down Expand Up @@ -173,7 +173,6 @@ def _check_type(value: Any, expected_type: Type, allow_empty: bool = False, curr


def validate_params(
parameters: Dict[Any, Any],
allow_empty: bool = False,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need allow_empty since we have the Optional types ? I use to use allow_empty for empty strings for example but I don't know how typing.Optional handles that

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I don't quite understand :P What does the allow_empty option do?
It means to count empty strings when validating as strings?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the idea was that sometimes I wanted to allow empty strings to be passed to a flask route :)

Copy link
Contributor Author

@Mews Mews Jun 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean it should work the same as it did before :P
I didn't change the _check_type function, and the actual format of the data in paramaters hasn't changed because get_type_hints returns a dict in the same format as it was before.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay no worries. I'm writing a bunch of tests anyway to see if what you did does what I want haha, so I'll let you know soon !

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good 👍

):
"""
Expand Down Expand Up @@ -241,6 +240,14 @@ def example():
def decorator(fn):
@wraps(fn)
def wrapper(*args, **kwargs):

# Load expected parameter types from function type hints
parameters = get_type_hints(fn)

# Remove return value type hints
if "return" in parameters:
del parameters["return"]

try:
data = request.get_json()
except BadRequest:
Expand Down Expand Up @@ -271,6 +278,13 @@ def wrapper(*args, **kwargs):
if key in parameters and not _check_type(data[key], parameters[key], allow_empty):
raise BadRequestError(f"Wrong type for key {key}.", f"It should be {parameters[key]}")

for key in parameters:
if _is_optional(parameters[key]) and key not in data:
kwargs[key] = None

else:
kwargs[key] = data[key]

return fn(*args, **kwargs)

return wrapper
Expand Down
56 changes: 21 additions & 35 deletions tests/test_validate_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ class TestBadFormat:
@pytest.fixture(autouse=True)
def setup_routes(self, flask_client):
@flask_client.post("/bad-format")
@validate_params({"name": str})
def bad_format():
@validate_params()
def bad_format(name: str):
return "OK", 200

def test_malformed_body(self, client):
Expand Down Expand Up @@ -53,17 +53,8 @@ class TestDefaultTypes:
@pytest.fixture(autouse=True)
def setup_routes(self, flask_client):
@flask_client.post("/default-types")
@validate_params(
{
"name": str,
"age": int,
"is_active": bool,
"weight": float,
"hobbies": list,
"address": dict,
}
)
def default_types():
@validate_params()
def default_types(name: str, age: int, is_active: bool, weight: float, hobbies: list, address: dict):
return "OK", 200

def test_valid_request(self, client):
Expand Down Expand Up @@ -240,12 +231,13 @@ def test_wrong_type(self, client, key, data):
assert error_dict["message"] == f"Wrong type for key {key}."


@pytest.mark.skip(reason="Skipping this test until I hear from Seluj78")
class TestTupleUnion:
@pytest.fixture(autouse=True)
def setup_routes(self, flask_client):
@flask_client.post("/tuple-union")
@validate_params({"name": (str, int)})
def union():
@validate_params()
def union(name: (str, int)):
return "OK", 200

def test_valid_request(self, client):
Expand All @@ -267,8 +259,8 @@ class TestUnion:
@pytest.fixture(autouse=True)
def setup_routes(self, flask_client):
@flask_client.post("/union")
@validate_params({"name": Union[str, int]})
def union():
@validate_params()
def union(name: Union[str, int]):
return "OK", 200

def test_valid_request(self, client):
Expand All @@ -290,8 +282,8 @@ class TestOptional:
@pytest.fixture(autouse=True)
def setup_routes(self, flask_client):
@flask_client.post("/optional")
@validate_params({"name": str, "age": Optional[int]})
def optional():
@validate_params()
def optional(name: str, age: Optional[int]):
return "OK", 200

def test_valid_request(self, client):
Expand All @@ -313,8 +305,8 @@ class TestList:
@pytest.fixture(autouse=True)
def setup_routes(self, flask_client):
@flask_client.post("/list")
@validate_params({"name": List[str]})
def list():
@validate_params()
def list(name: List[str]):
return "OK", 200

def test_valid_request(self, client):
Expand All @@ -333,8 +325,8 @@ class TestDict:
@pytest.fixture(autouse=True)
def setup_routes(self, flask_client):
@flask_client.post("/dict")
@validate_params({"name": Dict[str, int]})
def dict_route():
@validate_params()
def dict_route(name: Dict[str, int]):
return "OK", 200

def test_valid_request(self, client):
Expand All @@ -353,8 +345,8 @@ class TestAny:
@pytest.fixture(autouse=True)
def setup_routes(self, flask_client):
@flask_client.post("/any")
@validate_params({"name": Any})
def any_route():
@validate_params()
def any_route(name: Any):
return "OK", 200

def test_valid_request(self, client):
Expand All @@ -381,16 +373,10 @@ class TestMixAndMatch:
@pytest.fixture(autouse=True)
def setup_routes(self, flask_client):
@flask_client.post("/mix-and-match")
@validate_params(
{
"name": Union[str, int],
"age": Optional[int],
"hobbies": List[str],
"address": Dict[str, int],
"is_active": Any,
}
)
def mix_and_match():
@validate_params()
def mix_and_match(
name: Union[str, int], age: Optional[int], hobbies: List[str], address: Dict[str, int], is_active: Any
):
return "OK", 200

def test_valid_request(self, client):
Expand Down
Loading