Skip to content

Commit 5a46eb6

Browse files
luolingchunPeter Uittenbroek
andauthored
Delay throwing validation error (#223)
* Delay throwing validation error * Fix TypeError * Add validate decorator and ability to delegated validation to that * cleanup * cleanup * add another test * Ruff + enforce decorator * More cleaning * fix bandit and unused imports * Revert "Delay throwing validation error" * Use `__delay_validate_request__` for backward compatibility * Update Request.md --------- Co-authored-by: Peter Uittenbroek <peter.uittenbroek@xs2event.com>
1 parent 1b9f92f commit 5a46eb6

File tree

4 files changed

+97
-25
lines changed

4 files changed

+97
-25
lines changed

docs/Usage/Request.md

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,30 @@ def get_book(raw: BookRaw):
103103
return "ok"
104104
```
105105

106+
## @validate_request
107+
108+
Sometimes you want to delay the verification request parameters, such as after login verification:
109+
110+
```python
111+
from flask_openapi3 import validate_request
112+
113+
114+
def login_required(func):
115+
@wraps(func)
116+
def wrapper(*args, **kwargs):
117+
print("login_required ...")
118+
return func(*args, **kwargs)
119+
120+
return wrapper
121+
122+
123+
@app.get("/book")
124+
@login_required
125+
@validate_request()
126+
def get_book(query: BookQuery):
127+
...
128+
```
129+
106130
## Request model
107131

108132
First, you need to define a [pydantic](https://github.com/pydantic/pydantic) model:
@@ -113,9 +137,11 @@ class BookQuery(BaseModel):
113137
author: str = Field(None, description='Author')
114138
```
115139

116-
More information to see [BaseModel](https://docs.pydantic.dev/latest/usage/models/), and you can [Customize the Field](https://docs.pydantic.dev/latest/usage/fields/).
140+
More information to see [BaseModel](https://docs.pydantic.dev/latest/usage/models/), and you
141+
can [Customize the Field](https://docs.pydantic.dev/latest/usage/fields/).
117142

118-
However, you can also use **Field** to extend [Parameter Object](https://spec.openapis.org/oas/v3.1.0#parameter-object). Here is an example:
143+
However, you can also use **Field** to extend [Parameter Object](https://spec.openapis.org/oas/v3.1.0#parameter-object).
144+
Here is an example:
119145

120146
`age` with **`example`** and `author` with **`deprecated`**.
121147

flask_openapi3/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,5 @@
3838
from .models import ValidationErrorModel
3939
from .models import XML
4040
from .openapi import OpenAPI
41+
from .request import validate_request
4142
from .view import APIView

flask_openapi3/request.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
# -*- coding: utf-8 -*-
22
# @Author : llc
33
# @Time : 2022/4/1 16:54
4+
import inspect
45
import json
6+
from functools import wraps
57
from json import JSONDecodeError
68
from typing import Any, Type, Optional
79

@@ -10,6 +12,8 @@
1012
from pydantic.fields import FieldInfo
1113
from werkzeug.datastructures.structures import MultiDict
1214

15+
from .utils import parse_parameters
16+
1317

1418
def _get_list_value(model: Type[BaseModel], args: MultiDict, model_field_key: str, model_field_value: FieldInfo):
1519
if model_field_value.alias and model.model_config.get("populate_by_name"):
@@ -200,3 +204,38 @@ def _validate_request(
200204
abort(validation_error_callback(e))
201205

202206
return func_kwargs
207+
208+
209+
def validate_request():
210+
"""
211+
Decorator to validate the annotated parts of the function and throw and error if applicable.
212+
"""
213+
214+
def decorator(func):
215+
216+
setattr(func, "__delay_validate_request__", True)
217+
218+
is_coroutine_function = inspect.iscoroutinefunction(func)
219+
220+
if is_coroutine_function:
221+
222+
@wraps(func)
223+
async def wrapper(*args, **kwargs):
224+
header, cookie, path, query, form, body, raw = parse_parameters(func)
225+
func_kwargs = _validate_request(header, cookie, path, query, form, body, raw, path_kwargs=kwargs)
226+
227+
return await func(*args, **func_kwargs)
228+
229+
return wrapper
230+
else:
231+
232+
@wraps(func)
233+
def wrapper(*args, **kwargs):
234+
header, cookie, path, query, form, body, raw = parse_parameters(func)
235+
func_kwargs = _validate_request(header, cookie, path, query, form, body, raw, path_kwargs=kwargs)
236+
237+
return func(*args, **func_kwargs)
238+
239+
return wrapper
240+
241+
return decorator

flask_openapi3/scaffold.py

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ def _collect_openapi_info(
3535
doc_ui: bool = True,
3636
method: str = HTTPMethod.GET
3737
) -> ParametersTuple:
38-
raise NotImplementedError # pragma: no cover
38+
raise NotImplementedError # pragma: no cover
3939

4040
def register_api(self, api) -> None:
41-
raise NotImplementedError # pragma: no cover
41+
raise NotImplementedError # pragma: no cover
4242

4343
def _add_url_rule(
4444
self,
@@ -48,7 +48,7 @@ def _add_url_rule(
4848
provide_automatic_options=None,
4949
**options,
5050
) -> None:
51-
raise NotImplementedError # pragma: no cover
51+
raise NotImplementedError # pragma: no cover
5252

5353
@staticmethod
5454
def create_view_func(
@@ -67,16 +67,19 @@ def create_view_func(
6767
if is_coroutine_function:
6868
@wraps(func)
6969
async def view_func(**kwargs) -> FlaskResponse:
70-
func_kwargs = _validate_request(
71-
header=header,
72-
cookie=cookie,
73-
path=path,
74-
query=query,
75-
form=form,
76-
body=body,
77-
raw=raw,
78-
path_kwargs=kwargs
79-
)
70+
if hasattr(func, "__delay_validate_request__") and func.__delay_validate_request__ is True:
71+
func_kwargs = kwargs
72+
else:
73+
func_kwargs = _validate_request(
74+
header=header,
75+
cookie=cookie,
76+
path=path,
77+
query=query,
78+
form=form,
79+
body=body,
80+
raw=raw,
81+
path_kwargs=kwargs
82+
)
8083

8184
# handle async request
8285
if view_class:
@@ -93,16 +96,19 @@ async def view_func(**kwargs) -> FlaskResponse:
9396
else:
9497
@wraps(func)
9598
def view_func(**kwargs) -> FlaskResponse:
96-
func_kwargs = _validate_request(
97-
header=header,
98-
cookie=cookie,
99-
path=path,
100-
query=query,
101-
form=form,
102-
body=body,
103-
raw=raw,
104-
path_kwargs=kwargs
105-
)
99+
if hasattr(func, "__delay_validate_request__") and func.__delay_validate_request__ is True:
100+
func_kwargs = kwargs
101+
else:
102+
func_kwargs = _validate_request(
103+
header=header,
104+
cookie=cookie,
105+
path=path,
106+
query=query,
107+
form=form,
108+
body=body,
109+
raw=raw,
110+
path_kwargs=kwargs
111+
)
106112

107113
# handle request
108114
if view_class:

0 commit comments

Comments
 (0)