Skip to content

bug: forward files in async _build_request so async post uploads keep the multipart body#437

Open
anxkhn wants to merge 1 commit into
Portkey-AI:mainfrom
anxkhn:fix/async-build-request-files
Open

bug: forward files in async _build_request so async post uploads keep the multipart body#437
anxkhn wants to merge 1 commit into
Portkey-AI:mainfrom
anxkhn:fix/async-build-request-files

Conversation

@anxkhn

@anxkhn anxkhn commented Jul 10, 2026

Copy link
Copy Markdown

Title: Forward files in the async _build_request so async POST file uploads send the multipart body

Description:

  • The async AsyncAPIClient._build_request called httpx build_request without
    files=options.files, while the sync APIClient._build_request passes it. As a
    result, multipart uploads made through the async POST proxy
    (AsyncPortkey.post(url=..., files=...)) silently dropped the file body and sent a
    plain JSON request instead. The identical sync call already worked.
  • Added files=options.files to the self._client.build_request(...) call in the
    async _build_request, in the same argument position as the sync path, so the two
    code paths are byte-for-byte identical.
  • Added offline regression tests (tests/test_post_files.py) that use
    httpx.MockTransport to capture the outgoing request and assert it carries a
    multipart/form-data content type with the file name and bytes in the body, for
    both the sync and async POST file paths.

Motivation:
files= support was added to the POST method for the sync client in commit 27312b5
("feat: accepting files in post method") but was never mirrored on the async client,
so async and sync diverged: AsyncPortkey.post(url=..., files=...) looked like it
uploaded a file but sent no file body. The sync and async clients are meant to behave
identically, and file uploads (for example to a /files endpoint) are a normal use of
the POST proxy. This restores parity with a one-line change.

Related Issues:
None (self-reported bug).


How to verify (reviewer copy/paste)

python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
make lint                                  # mypy + black --check + ruff, all clean
python -m pytest tests/test_post_files.py -v

Regression proof: remove only the added files=options.files, line from the async
_build_request and re-run the tests. The async test fails with
assert 'multipart/form-data' in 'application/json' (the request falls back to a JSON
body, dropping the file); the sync test still passes. Restore the line and both pass.

The async AsyncAPIClient._build_request omitted the files argument when
calling httpx build_request, so multipart file uploads made through the
async post proxy (AsyncPortkey.post(url=..., files=...)) silently dropped
the file body. The sync APIClient._build_request already passes
files=options.files; mirror it on the async path so async and sync behave
identically.

Add offline regression tests that assert the outgoing request carries a
multipart/form-data body for both the sync and async post file paths.

Signed-off-by: Anas Khan <83116240+anxkhn@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a parity bug between sync and async clients where async post(..., files=...) dropped multipart file bodies by not forwarding files into the underlying httpx request builder, and adds regression tests to prevent reintroduction.

Changes:

  • Forward options.files in AsyncAPIClient._build_request(...) to ensure multipart uploads are preserved.
  • Add sync + async regression tests using httpx.MockTransport to assert outgoing requests are multipart and include the uploaded filename/bytes.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
portkey_ai/api_resources/base_client.py Adds files=options.files to async request construction to match sync behavior.
tests/test_post_files.py Adds offline regression coverage for sync/async multipart file uploads via the POST proxy.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/test_post_files.py
Comment on lines +22 to +38
def test_sync_post_sends_multipart_files() -> None:
captured: Dict[str, object] = {}
http_client = httpx.Client(
transport=_capture_transport(captured), base_url=BASE_URL
)
client = Portkey(api_key="dummy", base_url=BASE_URL, http_client=http_client)

client.post(
url="/files",
purpose="assistants",
files={"file": ("hello.txt", b"hello world", "text/plain")},
)

assert "multipart/form-data" in captured["content_type"]
body = captured["content"]
assert b"hello.txt" in body
assert b"hello world" in body
Comment thread tests/test_post_files.py
Comment on lines +41 to +58
@pytest.mark.asyncio
async def test_async_post_sends_multipart_files() -> None:
captured: Dict[str, object] = {}
http_client = httpx.AsyncClient(
transport=_capture_transport(captured), base_url=BASE_URL
)
client = AsyncPortkey(api_key="dummy", base_url=BASE_URL, http_client=http_client)

await client.post(
url="/files",
purpose="assistants",
files={"file": ("hello.txt", b"hello world", "text/plain")},
)

assert "multipart/form-data" in captured["content_type"]
body = captured["content"]
assert b"hello.txt" in body
assert b"hello world" in body
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants