From 619046cae7f685a4fff2dcf938e563108e3c983a Mon Sep 17 00:00:00 2001 From: Christopher Adams Date: Sun, 26 Jun 2022 15:01:19 -0400 Subject: [PATCH] Returned ETag for uploaded entities - If 'ETag' is present in service response, return it inside a metadata dictionary for both `tasks.CompleteMultipartUploadTask` and `upload.PutObjectTask`. - This change will allow integrations which rely on these call sites to access the ETag. --- s3transfer/tasks.py | 8 +++++++- s3transfer/upload.py | 9 ++++++++- tests/unit/test_tasks.py | 9 ++++++--- tests/unit/test_upload.py | 7 +++++-- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/s3transfer/tasks.py b/s3transfer/tasks.py index 1bad9812..3c161c8b 100644 --- a/s3transfer/tasks.py +++ b/s3transfer/tasks.py @@ -377,11 +377,17 @@ def _main(self, client, bucket, key, upload_id, parts, extra_args): ``UploadPartTask.main()``. :param extra_args: A dictionary of any extra arguments that may be used in completing the multipart transfer. + :returns: A dictionary representing metadata information about the + upload, such as the ETag for the uploaded entity. """ - client.complete_multipart_upload( + response = client.complete_multipart_upload( Bucket=bucket, Key=key, UploadId=upload_id, MultipartUpload={'Parts': parts}, **extra_args, ) + metadata = dict() + if 'ETag' in response: + metadata['ETag'] = response['ETag'] + return metadata diff --git a/s3transfer/upload.py b/s3transfer/upload.py index 0c99bd7b..62f2c252 100644 --- a/s3transfer/upload.py +++ b/s3transfer/upload.py @@ -753,9 +753,16 @@ def _main(self, client, fileobj, bucket, key, extra_args): :param key: The name of the key to upload to :param extra_args: A dictionary of any extra arguments that may be used in the upload. + :returns: A dictionary representing metadata information about the + upload, such as the ETag for the uploaded entity. """ with fileobj as body: - client.put_object(Bucket=bucket, Key=key, Body=body, **extra_args) + response = client.put_object(Bucket=bucket, Key=key, Body=body, + **extra_args) + metadata = dict() + if 'ETag' in response: + metadata['ETag'] = response['ETag'] + return metadata class UploadPartTask(Task): diff --git a/tests/unit/test_tasks.py b/tests/unit/test_tasks.py index 9759e8fb..84df092e 100644 --- a/tests/unit/test_tasks.py +++ b/tests/unit/test_tasks.py @@ -779,7 +779,8 @@ def test_main(self): class TestCompleteMultipartUploadTask(BaseMultipartTaskTest): def test_main(self): upload_id = 'my-id' - parts = [{'ETag': 'etag', 'PartNumber': 0}] + parts = [{'ETag': 'part-etag', 'PartNumber': 0}] + etag = 'uploaded-etag' task = self.get_task( CompleteMultipartUploadTask, main_kwargs={ @@ -793,7 +794,7 @@ def test_main(self): ) self.stubber.add_response( method='complete_multipart_upload', - service_response={}, + service_response={'ETag': etag}, expected_params={ 'Bucket': self.bucket, 'Key': self.key, @@ -801,7 +802,9 @@ def test_main(self): 'MultipartUpload': {'Parts': parts}, }, ) - task() + task_response = task() + self.assertIn("ETag", task_response) + self.assertEqual(task_response["ETag"], etag) self.stubber.assert_no_pending_responses() def test_includes_extra_args(self): diff --git a/tests/unit/test_upload.py b/tests/unit/test_upload.py index 4c523011..123d395b 100644 --- a/tests/unit/test_upload.py +++ b/tests/unit/test_upload.py @@ -631,6 +631,7 @@ def test_submits_tag_for_multipart_fileobj(self): class TestPutObjectTask(BaseUploadTest): def test_main(self): extra_args = {'Metadata': {'foo': 'bar'}} + etag = 'uploaded-etag' with open(self.filename, 'rb') as fileobj: task = self.get_task( PutObjectTask, @@ -644,7 +645,7 @@ def test_main(self): ) self.stubber.add_response( method='put_object', - service_response={}, + service_response={'ETag': etag}, expected_params={ 'Body': ANY, 'Bucket': self.bucket, @@ -652,7 +653,9 @@ def test_main(self): 'Metadata': {'foo': 'bar'}, }, ) - task() + task_response = task() + self.assertIn("ETag", task_response) + self.assertEqual(task_response["ETag"], etag) self.stubber.assert_no_pending_responses() self.assertEqual(self.sent_bodies, [self.content])