Skip to content

Commit 4e5cd70

Browse files
committed
Add TransferConfig support for CRTTransferManager
1 parent 1461069 commit 4e5cd70

File tree

4 files changed

+104
-4
lines changed

4 files changed

+104
-4
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"type": "enhancement",
3+
"category": "``awscrt``",
4+
"description": "``CRTTransferManager`` now supports the following ``TransferConfig`` options - ``multipart_threshold``, ``multipart_chunksize``"
5+
}

s3transfer/crt.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,9 @@ class CRTTransferManager:
193193

194194
_UNSUPPORTED_BUCKET_PATTERNS = TransferManager._UNSUPPORTED_BUCKET_PATTERNS
195195

196-
def __init__(self, crt_s3_client, crt_request_serializer, osutil=None):
196+
def __init__(
197+
self, crt_s3_client, crt_request_serializer, osutil=None, config=None
198+
):
197199
"""A transfer manager interface for Amazon S3 on CRT s3 client.
198200
199201
:type crt_s3_client: awscrt.s3.S3Client
@@ -207,12 +209,18 @@ def __init__(self, crt_s3_client, crt_request_serializer, osutil=None):
207209
:type osutil: s3transfer.utils.OSUtils
208210
:param osutil: OSUtils object to use for os-related behavior when
209211
using with transfer manager.
212+
213+
:type config: s3transfer.manager.TransferConfig
214+
:param config: The transfer configuration to be used when
215+
making CRT S3 client requests.
210216
"""
211217
if osutil is None:
212218
self._osutil = OSUtils()
213219
self._crt_s3_client = crt_s3_client
214220
self._s3_args_creator = S3ClientArgsCreator(
215-
crt_request_serializer, self._osutil
221+
crt_request_serializer,
222+
self._osutil,
223+
config,
216224
)
217225
self._crt_exception_translator = (
218226
crt_request_serializer.translate_crt_exception
@@ -732,9 +740,22 @@ def set_s3_request(self, s3_request):
732740

733741

734742
class S3ClientArgsCreator:
735-
def __init__(self, crt_request_serializer, os_utils):
743+
def __init__(self, crt_request_serializer, os_utils, config=None):
736744
self._request_serializer = crt_request_serializer
737745
self._os_utils = os_utils
746+
self._config = config
747+
748+
def _get_crt_transfer_config_options(self):
749+
part_size = self._config.multipart_chunksize
750+
if (
751+
self._config.get_deep_attr('multipart_chunksize')
752+
is self._config.UNSET_DEFAULT
753+
):
754+
# Let CRT dynamically calculate part size.
755+
part_size = None
756+
return {
757+
'part_size': part_size,
758+
}
738759

739760
def get_make_request_args(
740761
self, request_type, call_args, coordinator, future, on_done_after_calls
@@ -823,6 +844,11 @@ def _get_make_request_args_put_object(
823844
)
824845
make_request_args['send_filepath'] = send_filepath
825846
make_request_args['checksum_config'] = checksum_config
847+
if self._config is not None:
848+
make_request_args.update(self._get_crt_transfer_config_options())
849+
make_request_args['multipart_upload_threshold'] = (
850+
self._config.multipart_threshold
851+
)
826852
return make_request_args
827853

828854
def _get_make_request_args_get_object(
@@ -859,6 +885,8 @@ def _get_make_request_args_get_object(
859885
make_request_args['recv_filepath'] = recv_filepath
860886
make_request_args['on_body'] = on_body
861887
make_request_args['checksum_config'] = checksum_config
888+
if self._config is not None:
889+
make_request_args.update(self._get_crt_transfer_config_options())
862890
return make_request_args
863891

864892
def _default_get_make_request_args(

s3transfer/manager.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050

5151

5252
class TransferConfig:
53+
UNSET_DEFAULT = object()
54+
5355
def __init__(
5456
self,
5557
multipart_threshold=8 * MB,
@@ -152,12 +154,19 @@ def __init__(
152154

153155
def _validate_attrs_are_nonzero(self):
154156
for attr, attr_val in self.__dict__.items():
155-
if attr_val is not None and attr_val <= 0:
157+
if (
158+
attr_val is not None
159+
and attr_val is not self.UNSET_DEFAULT
160+
and attr_val <= 0
161+
):
156162
raise ValueError(
157163
f'Provided parameter {attr} of value {attr_val} must '
158164
'be greater than 0.'
159165
)
160166

167+
def get_deep_attr(self, item):
168+
return object.__getattribute__(self, item)
169+
161170

162171
class TransferManager:
163172
ALLOWED_DOWNLOAD_ARGS = ALLOWED_DOWNLOAD_ARGS

tests/functional/test_crt.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
from botocore.session import Session
2020

21+
from s3transfer.constants import MB
22+
from s3transfer.manager import TransferConfig
2123
from s3transfer.subscribers import BaseSubscriber
2224
from tests import (
2325
HAS_CRT,
@@ -769,3 +771,59 @@ def test_crt_s3_client_error_handling(self):
769771
)
770772
with self.assertRaises(awscrt.exceptions.AwsCrtError):
771773
future.result()
774+
775+
def test_transfer_config_used_in_upload_request(self):
776+
config = TransferConfig(
777+
multipart_threshold=4 * MB,
778+
multipart_chunksize=2 * MB,
779+
)
780+
transfer_manager = s3transfer.crt.CRTTransferManager(
781+
crt_s3_client=self.s3_crt_client,
782+
crt_request_serializer=self.request_serializer,
783+
config=config,
784+
)
785+
future = transfer_manager.upload(
786+
self.filename, self.bucket, self.key, {}, []
787+
)
788+
future.result()
789+
790+
callargs_kwargs = self.s3_crt_client.make_request.call_args[1]
791+
assert callargs_kwargs['multipart_upload_threshold'] == 4 * MB
792+
assert callargs_kwargs['part_size'] == 2 * MB
793+
794+
def test_transfer_config_used_in_download_request(self):
795+
config = TransferConfig(
796+
multipart_threshold=4 * MB,
797+
multipart_chunksize=2 * MB,
798+
)
799+
transfer_manager = s3transfer.crt.CRTTransferManager(
800+
crt_s3_client=self.s3_crt_client,
801+
crt_request_serializer=self.request_serializer,
802+
config=config,
803+
)
804+
future = transfer_manager.download(
805+
self.bucket, self.key, self.filename, {}, []
806+
)
807+
future.result()
808+
809+
callargs_kwargs = self.s3_crt_client.make_request.call_args[1]
810+
assert callargs_kwargs['part_size'] == 2 * MB
811+
# Config option only used for PUT requests.
812+
assert 'multipart_upload_threshold' not in callargs_kwargs
813+
814+
def test_unset_part_size_defaults_to_none_in_upload_request(self):
815+
config = TransferConfig(
816+
multipart_chunksize=TransferConfig.UNSET_DEFAULT,
817+
)
818+
transfer_manager = s3transfer.crt.CRTTransferManager(
819+
crt_s3_client=self.s3_crt_client,
820+
crt_request_serializer=self.request_serializer,
821+
config=config,
822+
)
823+
future = transfer_manager.upload(
824+
self.filename, self.bucket, self.key, {}, []
825+
)
826+
future.result()
827+
828+
callargs_kwargs = self.s3_crt_client.make_request.call_args[1]
829+
assert callargs_kwargs['part_size'] is None

0 commit comments

Comments
 (0)