Skip to content
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
20 changes: 20 additions & 0 deletions docs/Usage/Request.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,26 @@ Receive flask **`request.headers`**.

Receive flask **`request.cookies`**.

### raw

Receive flask **`request`** and no data validation.

```python
from flask_openapi3 import RawModel


class BookRaw(RawModel):
mimetypes = ["text/csv", "application/json"]


@app.post("/book")
def get_book(raw: BookRaw):
# raw equals to flask.request
print(raw.data)
print(raw.mimetype)
return "ok"
```

## Request model

First, you need to define a [pydantic](https://github.com/pydantic/pydantic) model:
Expand Down
23 changes: 23 additions & 0 deletions examples/raw_request_demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# @Author : llc
# @Time : 2023/9/12 17:19
from flask_openapi3 import OpenAPI
from flask_openapi3 import RawModel

app = OpenAPI(__name__)


class BookRaw(RawModel):
mimetypes = ["text/csv", "application/json"]


@app.post("/book")
def get_book(raw: BookRaw):
# raw equals to flask.request
print(raw.data)
print(raw.mimetype)
return "ok"


if __name__ == "__main__":
app.run(debug=True)
1 change: 1 addition & 0 deletions flask_openapi3/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from .models import Parameter
from .models import ParameterInType
from .models import PathItem
from .models import RawModel
from .models import Reference
from .models import RequestBody
from .models import Response
Expand Down
8 changes: 4 additions & 4 deletions flask_openapi3/blueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ def _collect_openapi_info(
tags = tags + self.abp_tags if tags else self.abp_tags
parse_and_store_tags(tags, self.tags, self.tag_names, operation)
# Parse parameters
header, cookie, path, query, form, body = parse_parameters(
header, cookie, path, query, form, body, raw = parse_parameters(
func,
components_schemas=self.components_schemas,
operation=operation
Expand All @@ -191,8 +191,8 @@ def _collect_openapi_info(

# Parse method
parse_method(uri, method, self.paths, operation)
return header, cookie, path, query, form, body
return header, cookie, path, query, form, body, raw
else:
# Parse parameters
header, cookie, path, query, form, body = parse_parameters(func, doc_ui=False)
return header, cookie, path, query, form, body
header, cookie, path, query, form, body, raw = parse_parameters(func, doc_ui=False)
return header, cookie, path, query, form, body, raw
5 changes: 5 additions & 0 deletions flask_openapi3/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from typing import Optional, List, Dict

from flask import Request
from pydantic import BaseModel

from .callback import Callback
Expand Down Expand Up @@ -83,6 +84,10 @@ class OAuthConfig(BaseModel):
usePkceWithAuthorizationCodeGrant: Optional[bool] = False


class RawModel(Request):
mimetypes: List[str] = ["application/json"]


Encoding.model_rebuild()
Operation.model_rebuild()
PathItem.model_rebuild()
8 changes: 4 additions & 4 deletions flask_openapi3/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ def _collect_openapi_info(
tags = []
parse_and_store_tags(tags, self.tags, self.tag_names, operation)
# Parse parameters
header, cookie, path, query, form, body = parse_parameters(
header, cookie, path, query, form, body, raw = parse_parameters(
func,
components_schemas=self.components_schemas,
operation=operation
Expand All @@ -435,8 +435,8 @@ def _collect_openapi_info(
uri = re.sub(r"<([^<:]+:)?", "{", rule).replace(">", "}")
# Parse method
parse_method(uri, method, self.paths, operation)
return header, cookie, path, query, form, body
return header, cookie, path, query, form, body, raw
else:
# Parse parameters
header, cookie, path, query, form, body = parse_parameters(func, doc_ui=False)
return header, cookie, path, query, form, body
header, cookie, path, query, form, body, raw = parse_parameters(func, doc_ui=False)
return header, cookie, path, query, form, body, raw
3 changes: 3 additions & 0 deletions flask_openapi3/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ def _validate_request(
query: Optional[Type[BaseModel]] = None,
form: Optional[Type[BaseModel]] = None,
body: Optional[Type[BaseModel]] = None,
raw: Optional[Type[BaseModel]] = None,
path_kwargs: Optional[Dict[Any, Any]] = None
) -> Dict:
"""
Expand Down Expand Up @@ -135,6 +136,8 @@ def _validate_request(
_validate_form(form, func_kwargs)
if body:
_validate_body(body, func_kwargs)
if raw:
func_kwargs.update({"raw": request})
except ValidationError as e:
# Create a response with validation error details
validation_error_callback = getattr(current_app, "validation_error_callback")
Expand Down
23 changes: 13 additions & 10 deletions flask_openapi3/scaffold.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ def create_view_func(
query,
form,
body,
raw,
view_class=None,
view_kwargs=None
):
Expand All @@ -73,6 +74,7 @@ async def view_func(**kwargs) -> FlaskResponse:
query=query,
form=form,
body=body,
raw=raw,
path_kwargs=kwargs
)

Expand All @@ -98,6 +100,7 @@ def view_func(**kwargs) -> FlaskResponse:
query=query,
form=form,
body=body,
raw=raw,
path_kwargs=kwargs
)

Expand Down Expand Up @@ -156,7 +159,7 @@ def get(
"""

def decorator(func) -> Callable:
header, cookie, path, query, form, body = \
header, cookie, path, query, form, body, raw = \
self._collect_openapi_info(
rule,
func,
Expand All @@ -174,7 +177,7 @@ def decorator(func) -> Callable:
method=HTTPMethod.GET
)

view_func = self.create_view_func(func, header, cookie, path, query, form, body)
view_func = self.create_view_func(func, header, cookie, path, query, form, body, raw)
options.update({"methods": [HTTPMethod.GET]})
self._add_url_rule(rule, view_func=view_func, **options)

Expand Down Expand Up @@ -219,7 +222,7 @@ def post(
"""

def decorator(func) -> Callable:
header, cookie, path, query, form, body = \
header, cookie, path, query, form, body, raw = \
self._collect_openapi_info(
rule,
func,
Expand All @@ -237,7 +240,7 @@ def decorator(func) -> Callable:
method=HTTPMethod.POST
)

view_func = self.create_view_func(func, header, cookie, path, query, form, body)
view_func = self.create_view_func(func, header, cookie, path, query, form, body, raw)
options.update({"methods": [HTTPMethod.POST]})
self._add_url_rule(rule, view_func=view_func, **options)

Expand Down Expand Up @@ -282,7 +285,7 @@ def put(
"""

def decorator(func) -> Callable:
header, cookie, path, query, form, body = \
header, cookie, path, query, form, body, raw = \
self._collect_openapi_info(
rule,
func,
Expand All @@ -300,7 +303,7 @@ def decorator(func) -> Callable:
method=HTTPMethod.PUT
)

view_func = self.create_view_func(func, header, cookie, path, query, form, body)
view_func = self.create_view_func(func, header, cookie, path, query, form, body, raw)
options.update({"methods": [HTTPMethod.PUT]})
self._add_url_rule(rule, view_func=view_func, **options)

Expand Down Expand Up @@ -345,7 +348,7 @@ def delete(
"""

def decorator(func) -> Callable:
header, cookie, path, query, form, body = \
header, cookie, path, query, form, body, raw = \
self._collect_openapi_info(
rule,
func,
Expand All @@ -363,7 +366,7 @@ def decorator(func) -> Callable:
method=HTTPMethod.DELETE
)

view_func = self.create_view_func(func, header, cookie, path, query, form, body)
view_func = self.create_view_func(func, header, cookie, path, query, form, body, raw)
options.update({"methods": [HTTPMethod.DELETE]})
self._add_url_rule(rule, view_func=view_func, **options)

Expand Down Expand Up @@ -408,7 +411,7 @@ def patch(
"""

def decorator(func) -> Callable:
header, cookie, path, query, form, body = \
header, cookie, path, query, form, body, raw = \
self._collect_openapi_info(
rule,
func,
Expand All @@ -426,7 +429,7 @@ def decorator(func) -> Callable:
method=HTTPMethod.PATCH
)

view_func = self.create_view_func(func, header, cookie, path, query, form, body)
view_func = self.create_view_func(func, header, cookie, path, query, form, body, raw)
options.update({"methods": [HTTPMethod.PATCH]})
self._add_url_rule(rule, view_func=view_func, **options)

Expand Down
4 changes: 3 additions & 1 deletion flask_openapi3/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from pydantic import BaseModel

from .models import RawModel
from .models import SecurityScheme

_ResponseDictValue = Union[Type[BaseModel], Dict[Any, Any], None]
Expand All @@ -22,5 +23,6 @@
Optional[Type[BaseModel]],
Optional[Type[BaseModel]],
Optional[Type[BaseModel]],
Optional[Type[BaseModel]]
Optional[Type[BaseModel]],
Optional[Type[RawModel]]
]
35 changes: 26 additions & 9 deletions flask_openapi3/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@
from .models import Parameter
from .models import ParameterInType
from .models import PathItem
from .models import RawModel
from .models import RequestBody
from .models import Response
from .models import Schema
from .models import Tag
from .models.data_type import DataType
from .types import ParametersTuple
from .types import ResponseDict
from .types import ResponseStrKeyDict
Expand Down Expand Up @@ -411,19 +413,20 @@ def parse_parameters(

"""
# Get the type hints from the function
annotations: Dict[str, Type[BaseModel]] = get_type_hints(func)
annotations = get_type_hints(func)

# Get the types for header, cookie, path, query, form, and body parameters
header = annotations.get("header")
cookie = annotations.get("cookie")
path = annotations.get("path")
query = annotations.get("query")
form = annotations.get("form")
body = annotations.get("body")
header: Optional[Type[BaseModel]] = annotations.get("header")
cookie: Optional[Type[BaseModel]] = annotations.get("cookie")
path: Optional[Type[BaseModel]] = annotations.get("path")
query: Optional[Type[BaseModel]] = annotations.get("query")
form: Optional[Type[BaseModel]] = annotations.get("form")
body: Optional[Type[BaseModel]] = annotations.get("body")
raw: Optional[Type[RawModel]] = annotations.get("raw")

# If doc_ui is False, return the types without further processing
if doc_ui is False:
return header, cookie, path, query, form, body
return header, cookie, path, query, form, body, raw

parameters = []

Expand Down Expand Up @@ -482,10 +485,24 @@ def parse_parameters(
request_body.content["application/json"].encoding = openapi_extra.get("encoding")
operation.requestBody = request_body

if raw:
_content = {}
for mimetype in raw.mimetypes:
if mimetype.startswith("application/json"):
_content[mimetype] = MediaType(
schema=Schema(type=DataType.OBJECT)
)
else:
_content[mimetype] = MediaType(
schema=Schema(type=DataType.STRING)
)
request_body = RequestBody(content=_content)
operation.requestBody = request_body

# Set the parsed parameters in the operation object
operation.parameters = parameters if parameters else None

return header, cookie, path, query, form, body
return header, cookie, path, query, form, body, raw


def parse_method(uri: str, method: str, paths: dict, operation: Operation) -> None:
Expand Down
3 changes: 2 additions & 1 deletion flask_openapi3/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ def register(self, app: "OpenAPI", view_kwargs: Optional[Dict[Any, Any]] = None)
for rule, (cls, methods) in self.views.items():
for method in methods:
func = getattr(cls, method.lower())
header, cookie, path, query, form, body = parse_parameters(func, doc_ui=False)
header, cookie, path, query, form, body, raw = parse_parameters(func, doc_ui=False)
view_func = app.create_view_func(
func,
header,
Expand All @@ -204,6 +204,7 @@ def register(self, app: "OpenAPI", view_kwargs: Optional[Dict[Any, Any]] = None)
query,
form,
body,
raw,
view_class=cls,
view_kwargs=view_kwargs
)
Expand Down
19 changes: 18 additions & 1 deletion tests/test_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import pytest
from pydantic import BaseModel, Field

from flask_openapi3 import OpenAPI, FileStorage
from flask_openapi3 import OpenAPI, FileStorage, RawModel

app = OpenAPI(__name__)
app.config["TESTING"] = True
Expand Down Expand Up @@ -85,6 +85,18 @@ def api_cookie(cookie: BookCookie):
return {"code": 0, "message": "ok"}


class BookRaw(RawModel):
mimetypes = ["text/csv", "application/json"]


@app.post("/raw")
def api_raw(raw: BookRaw):
# raw equals to flask.request
assert raw.data == b"raw"
assert raw.mimetype == "text/plain"
return "ok"


def test_query(client):
r = client.get("/query")
print(r.json)
Expand Down Expand Up @@ -120,3 +132,8 @@ def test_header(client):
print(resp.json)
assert resp.status_code == 200
assert resp.json == headers


def test_raw(client):
resp = client.post("/raw", data="raw", headers={"Content-Type": "text/plain"})
assert resp.status_code == 200