Skip to content

Commit e8b91d5

Browse files
committed
Add tests
1 parent d7d0294 commit e8b91d5

File tree

8 files changed

+215
-42
lines changed

8 files changed

+215
-42
lines changed

examples/multi_content_type.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# -*- coding: utf-8 -*-
22
# @Author : llc
33
# @Time : 2024/12/27 15:30
4+
from flask import Request
45
from pydantic import BaseModel
56

67
from flask_openapi3 import OpenAPI
@@ -30,6 +31,17 @@ class CatBody(BaseModel):
3031
}
3132

3233

34+
class BsonModel(BaseModel):
35+
e: int = None
36+
f: str = None
37+
38+
model_config = {
39+
"openapi_extra": {
40+
"content_type": "application/bson"
41+
}
42+
}
43+
44+
3345
class ContentTypeModel(BaseModel):
3446
model_config = {
3547
"openapi_extra": {
@@ -38,9 +50,28 @@ class ContentTypeModel(BaseModel):
3850
}
3951

4052

41-
@app.post("/a", responses={200: DogBody | CatBody | ContentTypeModel})
42-
def index_a(body: DogBody | CatBody | ContentTypeModel):
53+
@app.post("/a", responses={200: DogBody | CatBody | ContentTypeModel | BsonModel})
54+
def index_a(body: DogBody | CatBody | ContentTypeModel | BsonModel):
55+
"""
56+
This may be confusing, if the content-type is application/json, the type of body will be auto parsed to
57+
DogBody or CatBody, otherwise it cannot be parsed to ContentTypeModel or BsonModel.
58+
The body is equivalent to the request variable in Flask, and you can use body.data, body.text, etc ...
59+
"""
4360
print(body)
61+
if isinstance(body, Request):
62+
if body.mimetype == "text/csv":
63+
# processing csv data
64+
...
65+
elif body.mimetype == "application/bson":
66+
# processing bson data
67+
from bson import BSON
68+
69+
obj = BSON(body.data).decode()
70+
new_body = body.model_validate(obj=obj)
71+
print(new_body)
72+
else:
73+
# DogBody or CatBody
74+
...
4475
return {"hello": "world"}
4576

4677

flask_openapi3/request.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88

99
try:
1010
from types import UnionType # type: ignore
11-
except ImportError:
12-
# python < 3.9
11+
except ImportError: # pragma: no cover
12+
# python < 3.10
1313
UnionType = type(Union) # type: ignore
1414

1515
from flask import request, current_app, abort
@@ -51,10 +51,8 @@ def _get_value(model: Type[BaseModel], args: MultiDict, model_field_key: str, mo
5151
def _validate_header(header: Type[BaseModel], func_kwargs: dict):
5252
request_headers = dict(request.headers)
5353
header_dict = {}
54-
model_properties = header.model_json_schema().get("properties", {})
5554
for model_field_key, model_field_value in header.model_fields.items():
5655
key_title = model_field_key.replace("_", "-").title()
57-
model_field_schema = model_properties.get(model_field_value.alias or model_field_key)
5856
if model_field_value.alias and header.model_config.get("populate_by_name"):
5957
key = model_field_value.alias
6058
key_alias_title = model_field_value.alias.replace("_", "-").title()
@@ -65,11 +63,9 @@ def _validate_header(header: Type[BaseModel], func_kwargs: dict):
6563
value = request_headers.get(key_alias_title)
6664
else:
6765
key = model_field_key
68-
value = request_headers[key_title]
66+
value = request_headers.get(key_title)
6967
if value is not None:
7068
header_dict[key] = value
71-
if model_field_schema.get("type") == "null":
72-
header_dict[key] = value # type:ignore
7369
# extra keys
7470
for key, value in request_headers.items():
7571
if key not in header_dict.keys():

flask_openapi3/utils.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212

1313
try:
1414
from types import UnionType # type: ignore
15-
except ImportError:
16-
# python < 3.9
15+
except ImportError: # pragma: no cover
16+
# python < 3.10
1717
UnionType = type(Union) # type: ignore
1818

1919
from flask import make_response, current_app
@@ -424,6 +424,7 @@ def _parse_response(_key, _model):
424424
_schemas[normalize_name(name)] = Schema(**value)
425425

426426
for key, response in responses.items():
427+
print(key, response)
427428
if isinstance(response, dict) and "model" in response:
428429
response_model = response.get("model")
429430
response_description = response.get("description")

tests/test_api_blueprint.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import pytest
88
from pydantic import BaseModel, Field
99

10-
from flask_openapi3 import APIBlueprint, OpenAPI
10+
from flask_openapi3 import APIBlueprint, OpenAPI, Server, ExternalDocumentation
1111
from flask_openapi3 import Tag, Info
1212

1313
info = Info(title='book API', version='1.0.0')
@@ -82,7 +82,17 @@ def update_book1(path: BookPath, body: BookBody):
8282
return {"code": 0, "message": "ok"}
8383

8484

85-
@api.patch('/v2/book/<int:bid>')
85+
@api.patch(
86+
'/v2/book/<int:bid>',
87+
servers=[Server(
88+
url="http://127.0.0.1:5000",
89+
variables=None
90+
)],
91+
external_docs=ExternalDocumentation(
92+
url="https://www.openapis.org/",
93+
description="Something great got better, get excited!"),
94+
deprecated=True
95+
)
8696
def update_book1_v2(path: BookPath, body: BookBody):
8797
assert path.bid == 1
8898
assert body.age == 3

tests/test_api_view.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import pytest
88
from pydantic import BaseModel, Field
99

10-
from flask_openapi3 import APIView
10+
from flask_openapi3 import APIView, Server, ExternalDocumentation
1111
from flask_openapi3 import OpenAPI, Tag, Info
1212

1313
info = Info(title='book API', version='1.0.0')
@@ -73,7 +73,17 @@ def put(self, path: BookPath):
7373
print(path)
7474
return "put"
7575

76-
@api_view.doc(summary="delete book", deprecated=True)
76+
@api_view.doc(
77+
summary="delete book",
78+
servers=[Server(
79+
url="http://127.0.0.1:5000",
80+
variables=None
81+
)],
82+
external_docs=ExternalDocumentation(
83+
url="https://www.openapis.org/",
84+
description="Something great got better, get excited!"),
85+
deprecated=True
86+
)
7787
def delete(self, path: BookPath):
7888
print(path)
7989
return "delete"

tests/test_multi_content_type.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# -*- coding: utf-8 -*-
2+
# @Author : llc
3+
# @Time : 2025/1/6 16:37
4+
from typing import Union
5+
6+
import pytest
7+
from flask import Request
8+
from pydantic import BaseModel
9+
10+
from flask_openapi3 import OpenAPI
11+
12+
app = OpenAPI(__name__)
13+
app.config["TESTING"] = True
14+
15+
16+
class DogBody(BaseModel):
17+
a: int = None
18+
b: str = None
19+
20+
model_config = {
21+
"openapi_extra": {
22+
"content_type": "application/vnd.dog+json"
23+
}
24+
}
25+
26+
27+
class CatBody(BaseModel):
28+
c: int = None
29+
d: str = None
30+
31+
model_config = {
32+
"openapi_extra": {
33+
"content_type": "application/vnd.cat+json"
34+
}
35+
}
36+
37+
38+
class BsonModel(BaseModel):
39+
e: int = None
40+
f: str = None
41+
42+
model_config = {
43+
"openapi_extra": {
44+
"content_type": "application/bson"
45+
}
46+
}
47+
48+
49+
class ContentTypeModel(BaseModel):
50+
model_config = {
51+
"openapi_extra": {
52+
"content_type": "text/csv"
53+
}
54+
}
55+
56+
57+
@app.post("/a", responses={200: Union[DogBody, CatBody, ContentTypeModel, BsonModel]})
58+
def index_a(body: Union[DogBody, CatBody, ContentTypeModel, BsonModel]):
59+
"""
60+
This may be confusing, if the content-type is application/json, the type of body will be auto parsed to
61+
DogBody or CatBody, otherwise it cannot be parsed to ContentTypeModel or BsonModel.
62+
The body is equivalent to the request variable in Flask, and you can use body.data, body.text, etc ...
63+
"""
64+
print(body)
65+
if isinstance(body, Request):
66+
if body.mimetype == "text/csv":
67+
# processing csv data
68+
...
69+
elif body.mimetype == "application/bson":
70+
# processing bson data
71+
...
72+
else:
73+
# DogBody or CatBody
74+
...
75+
return {"hello": "world"}
76+
77+
78+
@app.post("/b", responses={200: Union[ContentTypeModel, BsonModel]})
79+
def index_b(body: Union[ContentTypeModel, BsonModel]):
80+
"""
81+
This may be confusing, if the content-type is application/json, the type of body will be auto parsed to
82+
DogBody or CatBody, otherwise it cannot be parsed to ContentTypeModel or BsonModel.
83+
The body is equivalent to the request variable in Flask, and you can use body.data, body.text, etc ...
84+
"""
85+
print(body)
86+
if isinstance(body, Request):
87+
if body.mimetype == "text/csv":
88+
# processing csv data
89+
...
90+
elif body.mimetype == "application/bson":
91+
# processing bson data
92+
...
93+
else:
94+
# DogBody or CatBody
95+
...
96+
return {"hello": "world"}
97+
98+
99+
@pytest.fixture
100+
def client():
101+
client = app.test_client()
102+
103+
return client
104+
105+
106+
def test_openapi(client):
107+
resp = client.get("/openapi/openapi.json")
108+
assert resp.status_code == 200
109+
110+
resp = client.post("/a", json={"a": 1, "b": "2"})
111+
assert resp.status_code == 200
112+
113+
resp = client.post("/a", data="a,b,c\n1,2,3", headers={"Content-Type": "text/csv"})
114+
assert resp.status_code == 200

tests/test_restapi.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from flask import Response
1212
from pydantic import BaseModel, RootModel, Field
1313

14-
from flask_openapi3 import ExternalDocumentation
14+
from flask_openapi3 import ExternalDocumentation, Server
1515
from flask_openapi3 import Info, Tag
1616
from flask_openapi3 import OpenAPI
1717

@@ -50,6 +50,8 @@ def get_operation_id_for_path_callback(*, name: str, path: str, method: str) ->
5050

5151
class BookQuery(BaseModel):
5252
age: Optional[int] = Field(None, description='Age')
53+
author: str
54+
none: Optional[None] = None
5355

5456

5557
class BookBody(BaseModel):
@@ -104,8 +106,13 @@ def client():
104106
external_docs=ExternalDocumentation(
105107
url="https://www.openapis.org/",
106108
description="Something great got better, get excited!"),
109+
servers=[Server(
110+
url="http://127.0.0.1:5000",
111+
variables=None
112+
)],
107113
responses={"200": BookResponse},
108-
security=security
114+
security=security,
115+
deprecated=True,
109116
)
110117
def get_book(path: BookPath):
111118
"""Get a book
@@ -117,7 +124,7 @@ def get_book(path: BookPath):
117124

118125

119126
@app.get('/book', tags=[book_tag], responses={"200": BookListResponseV1})
120-
def get_books(query: BookBody):
127+
def get_books(query: BookQuery):
121128
"""get books
122129
to get all books
123130
"""

tests/test_server.py

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,41 +3,45 @@
33
# @Time : 2024/11/10 12:17
44
from pydantic import ValidationError
55

6-
from flask_openapi3 import Server, ServerVariable
6+
from flask_openapi3 import OpenAPI, Server, ServerVariable, ExternalDocumentation
77

88

99
def test_server_variable():
1010
Server(
1111
url="http://127.0.0.1:5000",
1212
variables=None
1313
)
14+
error = 0
1415
try:
1516
variables = {"one": ServerVariable(default="one", enum=[])}
16-
Server(
17-
url="http://127.0.0.1:5000",
18-
variables=variables
19-
)
20-
error = 0
2117
except ValidationError:
2218
error = 1
2319
assert error == 1
24-
try:
25-
variables = {"one": ServerVariable(default="one")}
26-
Server(
27-
url="http://127.0.0.1:5000",
28-
variables=variables
29-
)
30-
error = 0
31-
except ValidationError:
32-
error = 1
20+
variables = {"one": ServerVariable(default="one")}
21+
Server(
22+
url="http://127.0.0.1:5000",
23+
variables=variables
24+
)
25+
error = 0
3326
assert error == 0
34-
try:
35-
variables = {"one": ServerVariable(default="one", enum=["one", "two"])}
36-
Server(
37-
url="http://127.0.0.1:5000",
38-
variables=variables
39-
)
40-
error = 0
41-
except ValidationError:
42-
error = 1
27+
variables = {"one": ServerVariable(default="one", enum=["one", "two"])}
28+
Server(
29+
url="http://127.0.0.1:5000",
30+
variables=variables
31+
)
32+
error = 0
4333
assert error == 0
34+
35+
app = OpenAPI(
36+
__name__,
37+
servers=[Server(
38+
url="http://127.0.0.1:5000",
39+
variables=None
40+
)],
41+
external_docs=ExternalDocumentation(
42+
url="https://www.openapis.org/",
43+
description="Something great got better, get excited!")
44+
)
45+
46+
assert "servers" in app.api_doc
47+
assert "externalDocs" in app.api_doc

0 commit comments

Comments
 (0)