fix(connections): preserve filename and content type in multipart file uploads#1611
Open
Dennis-UiPath wants to merge 2 commits intomainfrom
Open
fix(connections): preserve filename and content type in multipart file uploads#1611Dennis-UiPath wants to merge 2 commits intomainfrom
Dennis-UiPath wants to merge 2 commits intomainfrom
Conversation
…oads
`_build_activity_request_spec` always constructed
`files[key] = (key, val, None)` for multipart activities, which used the
form-field name (e.g. `attachment[file]`) as the multipart filename and
sent a `None` content type. Downstream services that store attachments
by filename ended up with literal names like `attachment_file_.` and no
extension.
Branch on the value type so callers can supply httpx's standard tuple
shape:
* tuple -> passed through (recommended for files)
* bytes / file-like -> legacy fallback, key as filename,
octet-stream content type (backwards
compatible)
* scalar (str/int/...) -> plain multipart form field, no fake
filename in Content-Disposition
The two pre-existing TODO comments
(`# files not supported yet supported so this will likely not work`
and the content-type note) are removed by the change.
Tests: 4 new cases under `TestMultipartFileUpload` in
`test_connections_service.py` exercise each branch by inspecting the
serialized multipart body. Verified that 3 of them fail against the
unpatched serializer (the bytes-fallback case passes either way because
httpx defaults a `None` content type to `application/octet-stream`).
`uv run pytest tests/` reports `1105 passed, 7 skipped` (pre-existing
LLM-integration skips that need real credentials), and `uv run ruff
check` is clean on the changed file.
Required by the `check-versions` CI gate so the multipart filename fix can ship as a new release. Also bumps the `uipath-platform` floor in `packages/uipath/pyproject.toml` so `uipath` pulls the fixed version.
30560ad to
7b86865
Compare
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ConnectionsService._build_activity_request_speccurrently constructsfiles[key] = (key, val, None)for every multipart parameter,regardless of value type. Two consequences for any connector activity
that uploads a file:
httpxinterprets the first element of a3-tuple as the filename for
Content-Disposition: form-data; name="…"; filename="…".Today every file is uploaded with the form-field name as its
filename, e.g.
attachment[file]. Downstream services that storeattachments by name end up with literal
attachment_file_.(Coupasanitises
[/], then appends.because there's no extension).None. httpx then defaults toapplication/octet-stream,so previewers / browsers cannot pick a sensible viewer.
The current source even flags the gap explicitly:
packages/uipath-platform/src/uipath/platform/connections/_connections_service.py#L791-L796A real-world manifestation: every Coupa attachment uploaded by an agent using
coupa.add_attachmentlands asattachment_file_.instead of the document's real filename.Fix
Branch on the value type to accept httpx's standard tuple shape, while keeping the existing bytes-only path working:
("invoice.pdf", b"…", "application/pdf")(recommended)Content-Disposition: form-data; name="attachment[file]"; filename="invoice.pdf"+Content-Type: application/pdf("invoice.pdf", b"…")(2-tuple shorthand)b"…"(legacy)"some string"(scalar)filename=…is omittedBackwards compatible: existing callers passing raw
byteskeep working with no observable change on the wire.Tests
Added
TestMultipartFileUploadinpackages/uipath-platform/tests/services/test_connections_service.pywith four cases that inspect the serialized multipart body:test_invoke_activity_multipart_tuple_3_preserves_filename— assertsfilename="invoice.pdf"andContent-Type: application/pdfland in the wire body.test_invoke_activity_multipart_tuple_2_preserves_filename— 2-tuple shorthand preserves filename.test_invoke_activity_multipart_bytes_backwards_compatible— legacy raw-bytes callers keep behaviour: filename = form-field name,application/octet-streamcontent type.test_invoke_activity_multipart_scalar_is_plain_form_field— non-file params (e.g.payload="{}") are emitted without a bogusfilename=inContent-Disposition.Test results
Ran from
packages/uipath-platform/:uv run pytest tests/services/test_connections_service.py→ 57 passed (53 pre-existing + 4 new).uv run pytest tests/→ 1105 passed, 7 skipped (the skipped ones are pre-existing LLM integration tests gated on real credentials).uv run ruff check src/uipath/platform/connections/_connections_service.py→ clean.Verification that the new tests catch the bug
I
git stashed the fix and re-ranpytest …::TestMultipartFileUpload— 3 of the 4 new tests fail against the unpatched serializer (tuple_3, tuple_2, scalar). The bytes-backwards-compat test passes either way, because httpx defaults aNonecontent type toapplication/octet-streamon the wire.End-to-end validation
The downstream change in our agent that motivated this — using the patched SDK to send the real document filename to Coupa via
add_attachment— was verified end-to-end against the staging Coupa instance: same byte payload as before, but the attachment is now stored with the real filename (invoice_<id>.pdf) instead ofattachment_file_..