FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

fix: Let httpx2 choose the multipart/form-data boundary by RClaudiuM · Pull Request #1511 · openapi-generators/openapi-python-client · GitHub

Repository navigation

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .jinja  (1) .md  (1) .py  (5) All 3 file types selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
default: patch
---

# Let httpx2 choose the boundary for `multipart/form-data` requests

Since 0.29.0, generated endpoints sent every `multipart/form-data` body with the fixed boundary `+++`, so an uploaded file containing `\r\n--+++` was cut short at that point (#1509). These bodies no longer set `Content-Type`, so httpx2 sets it with a random boundary for each request, as single-body endpoints did before 0.29.0. Endpoints with several body types still get a boundary (#947).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import email.parser
import email.policy
import io

import httpx2

from end_to_end_tests.functional_tests.helpers import (
with_generated_client_fixture,
with_generated_code_imports,
)

# Content that would end the part early if the request used a fixed "+++" boundary
FILE_CONTENT = b"first line\r\n--+++\r\nstill the same file\r\n"


def _send_and_capture(Client, sync_detailed, body) -> httpx2.Request:
sent: list[httpx2.Request] = []

def handler(request: httpx2.Request) -> httpx2.Response:
request.read()
sent.append(request)
return httpx2.Response(200)

client = Client(base_url="http://test", httpx_args={"transport": httpx2.MockTransport(handler)})
sync_detailed(client=client, body=body)
assert len(sent) == 1
return sent[0]


def _parse_parts(request: httpx2.Request) -> dict[str, bytes]:
content_type = request.headers["content-type"]
assert content_type.startswith("multipart/form-data; boundary=")
message = email.parser.BytesParser(policy=email.policy.HTTP).parsebytes(
b"Content-Type: " + content_type.encode() + b"\r\n\r\n" + request.content
)
return {
part.get_param("name", header="content-disposition"): part.get_payload(decode=True)
for part in message.iter_parts()
}


@with_generated_client_fixture(
"""
paths:
/upload:
post:
operationId: upload
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
properties:
file: {type: string, format: binary}
responses:
"200": {description: OK}
"""
)
@with_generated_code_imports(
".api.default.upload.sync_detailed",
".client.Client",
".models.UploadBody",
".types.File",
)
class TestSingleMultipartBody:
def test_file_content_is_not_split_by_boundary(self, sync_detailed, Client, UploadBody, File):
body = UploadBody(file=File(payload=io.BytesIO(FILE_CONTENT), file_name="a.txt"))

request = _send_and_capture(Client, sync_detailed, body)

assert _parse_parts(request) == {"file": FILE_CONTENT}


@with_generated_client_fixture(
"""
paths:
/upload:
post:
operationId: upload
requestBody:
content:
application/json:
schema:
type: object
properties:
name: {type: string}
multipart/form-data:
schema:
type: object
properties:
file: {type: string, format: binary}
responses:
"200": {description: OK}
"""
)
@with_generated_code_imports(
".api.default.upload.sync_detailed",
".client.Client",
".models.UploadJsonBody",
".models.UploadFilesBody",
".types.File",
)
class TestMultipartBodyAmongSeveralBodies:
def test_file_content_is_not_split_by_boundary(self, sync_detailed, Client, UploadJsonBody, UploadFilesBody, File):
body = UploadFilesBody(file=File(payload=io.BytesIO(FILE_CONTENT), file_name="a.txt"))

request = _send_and_capture(Client, sync_detailed, body)

assert _parse_parts(request) == {"file": FILE_CONTENT}

def test_json_body_still_sets_its_content_type(self, sync_detailed, Client, UploadJsonBody, UploadFilesBody, File):
request = _send_and_capture(Client, sync_detailed, UploadJsonBody(name="a"))

assert request.headers["content-type"] == "application/json"
assert request.content == b'{"name":"a"}'
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ def _get_kwargs(
if not isinstance(body, Unset):
_kwargs["files"] = body.to_multipart()

headers["Content-Type"] = "multipart/form-data; boundary=+++"

_kwargs["headers"] = headers
return _kwargs

Expand Down
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@ def _get_kwargs(
if not isinstance(body, Unset):
_kwargs["files"] = body.to_multipart()

headers["Content-Type"] = "multipart/form-data; boundary=+++"

_kwargs["headers"] = headers
return _kwargs

Expand Down
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ def _get_kwargs(

_kwargs["files"] = body.to_multipart()

headers["Content-Type"] = "multipart/form-data; boundary=+++"

_kwargs["headers"] = headers
return _kwargs

Expand Down
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ def _get_kwargs(
if not isinstance(body, Unset):
_kwargs["files"] = body.to_multipart()

headers["Content-Type"] = "multipart/form-data; boundary=+++"

_kwargs["headers"] = headers
return _kwargs

Expand Down
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,15 @@ def _get_kwargs(
{% for body in endpoint.bodies %}
if isinstance(body, {{body.prop.get_type_string(no_optional=True) | as_unembedded_code }}):
{{ body_to_kwarg(body) | indent(8) }}
{%- if body.content_type == "multipart/form-data" %}
headers["Content-Type"] = "multipart/form-data; boundary=+++"
{% else %}
{#- httpx2 sets multipart/form-data with a random boundary itself, a fixed one could appear in the content #}
{%- if body.content_type != "multipart/form-data" %}
headers["Content-Type"] = "{{ body.content_type | in_double_quote_literal }}"
{% endif %}
{% endfor %}
{% elif endpoint.bodies | length == 1 %}
{% set body = endpoint.bodies[0] %}
{{ body_to_kwarg(body) | indent(4) }}
{%- if body.content_type == "multipart/form-data" %}
headers["Content-Type"] = "multipart/form-data; boundary=+++"
{% else %}
{%- if body.content_type != "multipart/form-data" %}
headers["Content-Type"] = "{{ body.content_type | in_double_quote_literal }}"
{% endif %}
{% endif %}
Expand Down
Loading

Back | FazBrowse Home | New Git URL