Skip to content

Commit 13f31a4

Browse files
authored
Merge pull request #967 from python-openapi/dependabot/pip/falcon-4.0.2
Bump falcon from 3.1.3 to 4.0.2
2 parents 3143d4f + 02f05e7 commit 13f31a4

File tree

12 files changed

+135
-49
lines changed

12 files changed

+135
-49
lines changed

docs/integrations/falcon.md

+4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
This section describes the integration with the [Falcon](https://falconframework.org) web framework.
44
The integration supports Falcon version 3.0 and above.
55

6+
!!! warning
7+
8+
This integration does not support multipart form body requests.
9+
610
## Middleware
711

812
The Falcon API can be integrated using the `FalconOpenAPIMiddleware` middleware.

openapi_core/contrib/falcon/requests.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,7 @@ def body(self) -> Optional[bytes]:
6666
self.request.content_type, self.request.options.default_media_type
6767
)
6868
try:
69-
body = handler.serialize(
70-
media, content_type=self.request.content_type
71-
)
69+
body = handler.serialize(media, content_type=self.content_type)
7270
# multipart form serialization is not supported
7371
except NotImplementedError:
7472
warnings.warn(

openapi_core/contrib/falcon/responses.py

+13-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"""OpenAPI core contrib falcon responses module"""
22

3+
from io import BytesIO
34
from itertools import tee
5+
from typing import Iterable
46

57
from falcon.response import Response
68
from werkzeug.datastructures import Headers
@@ -17,16 +19,22 @@ def data(self) -> bytes:
1719
if self.response.text is None:
1820
if self.response.stream is None:
1921
return b""
20-
resp_iter1, resp_iter2 = tee(self.response.stream)
21-
self.response.stream = resp_iter1
22-
content = b"".join(resp_iter2)
23-
return content
22+
if isinstance(self.response.stream, Iterable):
23+
resp_iter1, resp_iter2 = tee(self.response.stream)
24+
self.response.stream = resp_iter1
25+
content = b"".join(resp_iter2)
26+
return content
27+
# checks ReadableIO protocol
28+
if hasattr(self.response.stream, "read"):
29+
data = self.response.stream.read()
30+
self.response.stream = BytesIO(data)
31+
return data
2432
assert isinstance(self.response.text, str)
2533
return self.response.text.encode("utf-8")
2634

2735
@property
2836
def status_code(self) -> int:
29-
return int(self.response.status[:3])
37+
return self.response.status_code
3038

3139
@property
3240
def content_type(self) -> str:

openapi_core/contrib/falcon/util.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
from typing import Any
2-
from typing import Dict
32
from typing import Generator
3+
from typing import Mapping
44
from typing import Tuple
55

66

77
def unpack_params(
8-
params: Dict[str, Any]
8+
params: Mapping[str, Any]
99
) -> Generator[Tuple[str, Any], None, None]:
1010
for k, v in params.items():
1111
if isinstance(v, list):

poetry.lock

+49-35
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/integration/contrib/django/test_django_project.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ def test_post_media_type_invalid(self, client):
184184
"title": (
185185
"Content for the following mimetype not found: "
186186
"text/html. "
187-
"Valid mimetypes: ['application/json', 'application/x-www-form-urlencoded', 'text/plain']"
187+
"Valid mimetypes: ['application/json', 'application/x-www-form-urlencoded', 'multipart/form-data', 'text/plain']"
188188
),
189189
}
190190
]

tests/integration/contrib/falcon/test_falcon_project.py

+39-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from json import dumps
33

44
import pytest
5+
from urllib3 import encode_multipart_formdata
56

67

78
class BaseTestFalconProject:
@@ -204,7 +205,7 @@ def test_post_media_type_invalid(self, client):
204205
"title": (
205206
"Content for the following mimetype not found: "
206207
f"{content_type}. "
207-
"Valid mimetypes: ['application/json', 'application/x-www-form-urlencoded', 'text/plain']"
208+
"Valid mimetypes: ['application/json', 'application/x-www-form-urlencoded', 'multipart/form-data', 'text/plain']"
208209
),
209210
}
210211
]
@@ -292,6 +293,43 @@ def test_post_valid(self, client, data_json):
292293
assert response.status_code == 201
293294
assert not response.content
294295

296+
@pytest.mark.xfail(
297+
reason="falcon multipart form serialization unsupported",
298+
strict=True,
299+
)
300+
def test_post_multipart_valid(self, client, data_gif):
301+
cookies = {"user": 1}
302+
auth = "authuser"
303+
fields = {
304+
"name": "Cat",
305+
"address": (
306+
"aaddress.json",
307+
dumps(dict(city="Warsaw")),
308+
"application/json",
309+
),
310+
"photo": (
311+
"photo.jpg",
312+
data_gif,
313+
"image/jpeg",
314+
),
315+
}
316+
body, content_type_header = encode_multipart_formdata(fields)
317+
headers = {
318+
"Authorization": f"Basic {auth}",
319+
"Content-Type": content_type_header,
320+
}
321+
322+
response = client.simulate_post(
323+
"/v1/pets",
324+
host="staging.gigantic-server.com",
325+
headers=headers,
326+
body=body,
327+
cookies=cookies,
328+
protocol="https",
329+
)
330+
331+
assert response.status_code == 200
332+
295333

296334
class TestPetDetailResource:
297335
def test_get_server_invalid(self, client):

tests/integration/contrib/fastapi/test_fastapi_project.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ def test_post_media_type_invalid(self, client):
183183
"title": (
184184
"Content for the following mimetype not found: "
185185
"text/html. "
186-
"Valid mimetypes: ['application/json', 'application/x-www-form-urlencoded', 'text/plain']"
186+
"Valid mimetypes: ['application/json', 'application/x-www-form-urlencoded', 'multipart/form-data', 'text/plain']"
187187
),
188188
}
189189
]

tests/integration/contrib/starlette/test_starlette_project.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ def test_post_media_type_invalid(self, client):
183183
"title": (
184184
"Content for the following mimetype not found: "
185185
"text/html. "
186-
"Valid mimetypes: ['application/json', 'application/x-www-form-urlencoded', 'text/plain']"
186+
"Valid mimetypes: ['application/json', 'application/x-www-form-urlencoded', 'multipart/form-data', 'text/plain']"
187187
),
188188
}
189189
]

tests/integration/data/v3.0/petstore.yaml

+22
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ paths:
150150
application/x-www-form-urlencoded:
151151
schema:
152152
$ref: '#/components/schemas/PetCreate'
153+
multipart/form-data:
154+
schema:
155+
$ref: '#/components/schemas/PetWithPhotoCreate'
153156
text/plain: {}
154157
responses:
155158
'201':
@@ -375,6 +378,16 @@ components:
375378
oneOf:
376379
- $ref: "#/components/schemas/Cat"
377380
- $ref: "#/components/schemas/Bird"
381+
PetWithPhotoCreate:
382+
type: object
383+
x-model: PetWithPhotoCreate
384+
allOf:
385+
- $ref: "#/components/schemas/PetCreatePartOne"
386+
- $ref: "#/components/schemas/PetCreatePartTwo"
387+
- $ref: "#/components/schemas/PetCreatePartPhoto"
388+
oneOf:
389+
- $ref: "#/components/schemas/Cat"
390+
- $ref: "#/components/schemas/Bird"
378391
PetCreatePartOne:
379392
type: object
380393
x-model: PetCreatePartOne
@@ -395,6 +408,15 @@ components:
395408
$ref: "#/components/schemas/Position"
396409
healthy:
397410
type: boolean
411+
PetCreatePartPhoto:
412+
type: object
413+
x-model: PetCreatePartPhoto
414+
properties:
415+
photo:
416+
$ref: "#/components/schemas/PetPhoto"
417+
PetPhoto:
418+
type: string
419+
format: binary
398420
Bird:
399421
type: object
400422
x-model: Bird

tests/integration/unmarshalling/test_request_unmarshaller.py

+1
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ def test_invalid_content_type(self, request_unmarshaller):
201201
availableMimetypes=[
202202
"application/json",
203203
"application/x-www-form-urlencoded",
204+
"multipart/form-data",
204205
"text/plain",
205206
],
206207
)

tests/integration/validation/test_request_validators.py

+1
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ def test_media_type_not_found(self, request_validator):
106106
availableMimetypes=[
107107
"application/json",
108108
"application/x-www-form-urlencoded",
109+
"multipart/form-data",
109110
"text/plain",
110111
],
111112
)

0 commit comments

Comments
 (0)