44from typing import cast
55
66import aioboto3
7- import botocore .exceptions
87from aiobotocore .session import ClientCreatorContext
98from botocore .client import Config
109from models_library .api_schemas_storage import S3BucketName
1110from pydantic import AnyUrl , parse_obj_as
1211from settings_library .s3 import S3Settings
1312from types_aiobotocore_s3 import S3Client
1413
15- from .errors import S3RuntimeError
14+ from .errors import s3_exception_handler
1615
1716_logger = logging .getLogger (__name__ )
1817
18+ _S3_MAX_CONCURRENCY_DEFAULT = 10
19+
1920
2021@dataclass (frozen = True )
2122class SimcoreS3API :
2223 client : S3Client
2324 session : aioboto3 .Session
2425 exit_stack : contextlib .AsyncExitStack
26+ transfer_max_concurrency : int
2527
2628 @classmethod
27- async def create (cls , settings : S3Settings ) -> "SimcoreS3API" :
29+ async def create (
30+ cls , settings : S3Settings , s3_max_concurrency : int = _S3_MAX_CONCURRENCY_DEFAULT
31+ ) -> "SimcoreS3API" :
2832 session = aioboto3 .Session ()
2933 session_client = session .client (
3034 "s3" ,
@@ -37,10 +41,11 @@ async def create(cls, settings: S3Settings) -> "SimcoreS3API":
3741 )
3842 assert isinstance (session_client , ClientCreatorContext ) # nosec
3943 exit_stack = contextlib .AsyncExitStack ()
40- s3_client = cast (
41- S3Settings , await exit_stack .enter_async_context (session_client )
42- )
43- return cls (s3_client , session , exit_stack )
44+ s3_client = cast (S3Client , await exit_stack .enter_async_context (session_client ))
45+ # NOTE: this triggers a botocore.exception.ClientError in case the connection is not made to the S3 backend
46+ await s3_client .list_buckets ()
47+
48+ return cls (s3_client , session , exit_stack , s3_max_concurrency )
4449
4550 async def close (self ) -> None :
4651 await self .exit_stack .aclose ()
@@ -53,21 +58,19 @@ async def http_check_bucket_connected(self, bucket: S3BucketName) -> bool:
5358 except Exception : # pylint: disable=broad-except
5459 return False
5560
56- async def create_presigned_download_link (
61+ @s3_exception_handler (_logger )
62+ async def create_single_presigned_download_link (
5763 self ,
5864 bucket_name : S3BucketName ,
5965 object_key : str ,
6066 expiration_secs : int ,
6167 ) -> AnyUrl :
62- try :
63- # NOTE: ensure the bucket/object exists, this will raise if not
64- await self .client .head_bucket (Bucket = bucket_name )
65- generated_link = await self .client .generate_presigned_url (
66- "get_object" ,
67- Params = {"Bucket" : bucket_name , "Key" : object_key },
68- ExpiresIn = expiration_secs ,
69- )
70- url : AnyUrl = parse_obj_as (AnyUrl , generated_link )
71- return url
72- except botocore .exceptions .ClientError as exc :
73- raise S3RuntimeError from exc # pragma: no cover
68+ # NOTE: ensure the bucket/object exists, this will raise if not
69+ await self .client .head_bucket (Bucket = bucket_name )
70+ generated_link = await self .client .generate_presigned_url (
71+ "get_object" ,
72+ Params = {"Bucket" : bucket_name , "Key" : object_key },
73+ ExpiresIn = expiration_secs ,
74+ )
75+ url : AnyUrl = parse_obj_as (AnyUrl , generated_link )
76+ return url
0 commit comments