@@ -221,6 +221,36 @@ def getS3Client(profile: str | None = None) -> boto3.client:
221
221
if botocore is None :
222
222
raise ModuleNotFoundError ("Could not find botocore. Are you sure it is installed?" )
223
223
224
+ endpoint_config = _get_s3_connection_parameters (profile )
225
+
226
+ return _get_s3_client (endpoint_config , not _s3_should_validate_bucket ())
227
+
228
+
229
+ def _s3_should_validate_bucket () -> bool :
230
+ """Indicate whether bucket validation should be enabled.
231
+
232
+ Returns
233
+ -------
234
+ validate : `bool`
235
+ If `True` bucket names should be validated.
236
+ """
237
+ disable_value = os .environ .get ("LSST_DISABLE_BUCKET_VALIDATION" , "0" )
238
+ return bool (re .search (r"^(0|f|n|false)?$" , disable_value , re .I ))
239
+
240
+
241
+ def _get_s3_connection_parameters (profile : str | None = None ) -> _EndpointConfig :
242
+ """Calculate the connection details.
243
+
244
+ Parameters
245
+ ----------
246
+ profile : `str`, optional
247
+ The name of an S3 profile describing which S3 service to use.
248
+
249
+ Returns
250
+ -------
251
+ config : _EndPointConfig
252
+ All the information necessary to connect to the bucket.
253
+ """
224
254
endpoint = None
225
255
if profile is not None :
226
256
var_name = f"LSST_RESOURCES_S3_PROFILE_{ profile } "
@@ -230,26 +260,29 @@ def getS3Client(profile: str | None = None) -> boto3.client:
230
260
if not endpoint :
231
261
endpoint = None # Handle ""
232
262
233
- disable_value = os .environ .get ("LSST_DISABLE_BUCKET_VALIDATION" , "0" )
234
- skip_validation = not re .search (r"^(0|f|n|false)?$" , disable_value , re .I )
263
+ return _parse_endpoint_config (endpoint , profile )
264
+
265
+
266
+ def _s3_disable_bucket_validation (client : boto3 .client ) -> None :
267
+ """Disable the bucket name validation in the client.
235
268
236
- return _get_s3_client (endpoint , profile , skip_validation )
269
+ This removes the ``validate_bucket_name`` handler from the handlers
270
+ registered for this client.
271
+
272
+ Parameters
273
+ ----------
274
+ client : `boto3.client`
275
+ The client to modify.
276
+ """
277
+ client .meta .events .unregister ("before-parameter-build.s3" , validate_bucket_name )
237
278
238
279
239
280
@functools .lru_cache
240
- def _get_s3_client (endpoint : str | None , profile : str | None , skip_validation : bool ) -> boto3 .client :
281
+ def _get_s3_client (endpoint_config : _EndpointConfig , skip_validation : bool ) -> boto3 .client :
241
282
# Helper function to cache the client for this endpoint
242
283
config = botocore .config .Config (read_timeout = 180 , retries = {"mode" : "adaptive" , "max_attempts" : 10 })
243
284
244
- endpoint_config = _parse_endpoint_config (endpoint )
245
-
246
- if endpoint_config .access_key_id is not None and endpoint_config .secret_access_key is not None :
247
- # We already have the necessary configuration for the profile, so do
248
- # not pass the profile to boto3. boto3 will raise an exception if the
249
- # profile is not defined in its configuration file, whether or not it
250
- # needs to read the configuration from it.
251
- profile = None
252
- session = boto3 .Session (profile_name = profile )
285
+ session = boto3 .Session (profile_name = endpoint_config .profile )
253
286
254
287
client = session .client (
255
288
"s3" ,
@@ -259,19 +292,20 @@ def _get_s3_client(endpoint: str | None, profile: str | None, skip_validation: b
259
292
config = config ,
260
293
)
261
294
if skip_validation :
262
- client . meta . events . unregister ( "before-parameter-build.s3" , validate_bucket_name )
295
+ _s3_disable_bucket_validation ( client )
263
296
return client
264
297
265
298
266
299
class _EndpointConfig (NamedTuple ):
267
300
endpoint_url : str | None = None
268
301
access_key_id : str | None = None
269
302
secret_access_key : str | None = None
303
+ profile : str | None = None
270
304
271
305
272
- def _parse_endpoint_config (endpoint : str | None ) -> _EndpointConfig :
306
+ def _parse_endpoint_config (endpoint : str | None , profile : str | None = None ) -> _EndpointConfig :
273
307
if not endpoint :
274
- return _EndpointConfig ()
308
+ return _EndpointConfig (profile = profile )
275
309
276
310
parsed = parse_url (endpoint )
277
311
@@ -288,8 +322,18 @@ def _parse_endpoint_config(endpoint: str | None) -> _EndpointConfig:
288
322
access_key_id = urllib .parse .unquote (access_key_id )
289
323
secret_access_key = urllib .parse .unquote (secret_access_key )
290
324
325
+ if access_key_id is not None and secret_access_key is not None :
326
+ # We already have the necessary configuration for the profile, so do
327
+ # not pass the profile to boto3. boto3 will raise an exception if the
328
+ # profile is not defined in its configuration file, whether or not it
329
+ # needs to read the configuration from it.
330
+ profile = None
331
+
291
332
return _EndpointConfig (
292
- endpoint_url = endpoint_url , access_key_id = access_key_id , secret_access_key = secret_access_key
333
+ endpoint_url = endpoint_url ,
334
+ access_key_id = access_key_id ,
335
+ secret_access_key = secret_access_key ,
336
+ profile = profile ,
293
337
)
294
338
295
339
0 commit comments