From dcebc78eb5e5c9f7fb3ae8175b8e8171b07df933 Mon Sep 17 00:00:00 2001 From: dudegladiator Date: Fri, 20 Feb 2026 19:28:33 +0530 Subject: [PATCH 1/2] fix: properly unpack _execute() tuple in drafts.create() and drafts.update() for multipart requests When creating or updating a draft with attachments >= 3MB (multipart/form-data), _http_client._execute() returns a (json_response, headers) tuple. The multipart code paths in create() and update() assigned the entire tuple to a single variable instead of unpacking it, causing TypeError. Applied the same tuple-unpacking pattern already used in send() to both create() and update() multipart code paths. Fixes #454 --- nylas/resources/drafts.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nylas/resources/drafts.py b/nylas/resources/drafts.py index e817d5f..14d07d4 100644 --- a/nylas/resources/drafts.py +++ b/nylas/resources/drafts.py @@ -114,14 +114,14 @@ def create( for attachment in request_body.get("attachments", []) ) if attachment_size >= MAXIMUM_JSON_ATTACHMENT_SIZE: - json_response = self._http_client._execute( + json_response, headers = self._http_client._execute( method="POST", path=path, data=_build_form_request(request_body), overrides=overrides, ) - return Response.from_dict(json_response, Draft) + return Response.from_dict(json_response, Draft, headers) # Encode the content of the attachments to base64 for attachment in request_body.get("attachments", []): @@ -162,14 +162,14 @@ def update( for attachment in request_body.get("attachments", []) ) if attachment_size >= MAXIMUM_JSON_ATTACHMENT_SIZE: - json_response = self._http_client._execute( + json_response, headers = self._http_client._execute( method="PUT", path=path, data=_build_form_request(request_body), overrides=overrides, ) - return Response.from_dict(json_response, Draft) + return Response.from_dict(json_response, Draft, headers) # Encode the content of the attachments to base64 for attachment in request_body.get("attachments", []): From 53b20bf81243f2530edbcfbf74fb9bee9f94dc67 Mon Sep 17 00:00:00 2001 From: dudegladiator Date: Fri, 5 Jun 2026 09:49:36 +0530 Subject: [PATCH 2/2] Add changelog entry and regression tests for drafts multipart fix --- CHANGELOG.md | 1 + tests/resources/test_drafts.py | 86 ++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5e0eed..17858d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ nylas-python Changelog ====================== Unreleased ---------- +* Fixed `TypeError` in `drafts.create()` and `drafts.update()` when attachments trigger the multipart code path (>3MB) — `_execute()` returns a `(json_response, headers)` tuple, and both methods now unpack it correctly and forward `headers` to `Response.from_dict` v6.15.0 ---------- diff --git a/tests/resources/test_drafts.py b/tests/resources/test_drafts.py index a0d3dbe..3b3297b 100644 --- a/tests/resources/test_drafts.py +++ b/tests/resources/test_drafts.py @@ -530,3 +530,89 @@ def test_create_draft_with_special_characters_large_attachment(self, http_client data=mock_encoder, overrides=None, ) + + def test_create_draft_large_attachment_unpacks_execute_tuple(self): + """Regression test for #454: drafts.create() must unpack the + (json_response, headers) tuple returned by _execute() when the + multipart code path is triggered (attachments >= 3MB).""" + mock_http_client = Mock() + mock_http_client._execute.return_value = ( + { + "request_id": "req-multipart-create", + "data": { + "id": "draft-large-1", + "grant_id": "abc-123", + "object": "draft", + }, + }, + {"X-Test-Header": "multipart"}, + ) + drafts = Drafts(mock_http_client) + request_body = { + "subject": "Large attachment regression", + "to": [{"name": "Jon Snow", "email": "jsnow@gmail.com"}], + "body": "Body", + "attachments": [ + { + "filename": "big.bin", + "content_type": "application/octet-stream", + "content": b"x", + "size": 3 * 1024 * 1024, + }, + ], + } + + with patch( + "nylas.resources.drafts._build_form_request", return_value=Mock() + ): + response = drafts.create(identifier="abc-123", request_body=request_body) + + assert isinstance(response.data, Draft) + assert response.data.id == "draft-large-1" + assert response.request_id == "req-multipart-create" + assert response.headers == {"X-Test-Header": "multipart"} + + def test_update_draft_large_attachment_unpacks_execute_tuple(self): + """Regression test for #454: drafts.update() must unpack the + (json_response, headers) tuple returned by _execute() when the + multipart code path is triggered (attachments >= 3MB).""" + mock_http_client = Mock() + mock_http_client._execute.return_value = ( + { + "request_id": "req-multipart-update", + "data": { + "id": "draft-large-2", + "grant_id": "abc-123", + "object": "draft", + }, + }, + {"X-Test-Header": "multipart"}, + ) + drafts = Drafts(mock_http_client) + request_body = { + "subject": "Large attachment regression update", + "to": [{"name": "Jon Snow", "email": "jsnow@gmail.com"}], + "body": "Body", + "attachments": [ + { + "filename": "big.bin", + "content_type": "application/octet-stream", + "content": b"x", + "size": 3 * 1024 * 1024, + }, + ], + } + + with patch( + "nylas.resources.drafts._build_form_request", return_value=Mock() + ): + response = drafts.update( + identifier="abc-123", + draft_id="draft-large-2", + request_body=request_body, + ) + + assert isinstance(response.data, Draft) + assert response.data.id == "draft-large-2" + assert response.request_id == "req-multipart-update" + assert response.headers == {"X-Test-Header": "multipart"}