@@ -192,13 +192,48 @@ def _transfer_config(self) -> TransferConfig:
192
192
@property
193
193
def client (self ) -> boto3 .client :
194
194
"""Client object to address remote resource."""
195
- # Defer import for circular dependencies
196
- return getS3Client ()
195
+ return getS3Client (self ._profile )
196
+
197
+ @property
198
+ def _profile (self ) -> str | None :
199
+ """Profile name to use for looking up S3 credentials and endpoint."""
200
+ return self ._uri .username
201
+
202
+ @property
203
+ def _bucket (self ) -> str :
204
+ """S3 bucket where the files are stored."""
205
+ # Notionally the bucket is stored in the 'hostname' part of the URI.
206
+ # However, Ceph S3 uses a "multi-tenant" syntax for bucket names in the
207
+ # form 'tenant:bucket'. The part after the colon is parsed as the port
208
+ # portion of the URI, and urllib throws an exception if you try to read
209
+ # a non-integer port value. So manually split off this portion of the
210
+ # URI.
211
+ split = self ._uri .netloc .split ("@" )
212
+ num_components = len (split )
213
+ if num_components == 2 :
214
+ # There is a profile@ portion of the URL, so take the second half.
215
+ bucket = split [1 ]
216
+ elif num_components == 1 :
217
+ # There is no profile@, so take the whole netloc.
218
+ bucket = split [0 ]
219
+ else :
220
+ raise ValueError (f"Unexpected extra '@' in S3 URI: '{ str (self )} '" )
221
+
222
+ if not bucket :
223
+ raise ValueError (f"S3 URI does not include bucket name: '{ str (self )} '" )
224
+
225
+ return bucket
197
226
198
227
@classmethod
199
228
def _mexists (cls , uris : Iterable [ResourcePath ]) -> dict [ResourcePath , bool ]:
200
- # Force client to be created before creating threads.
201
- getS3Client ()
229
+ # Force client to be created for each profile before creating threads.
230
+ profiles = set [str | None ]()
231
+ for path in uris :
232
+ if path .scheme == "s3" :
233
+ path = cast (S3ResourcePath , path )
234
+ profiles .add (path ._profile )
235
+ for profile in profiles :
236
+ getS3Client (profile )
202
237
203
238
return super ()._mexists (uris )
204
239
@@ -207,16 +242,16 @@ def exists(self) -> bool:
207
242
"""Check that the S3 resource exists."""
208
243
if self .is_root :
209
244
# Only check for the bucket since the path is irrelevant
210
- return bucketExists (self .netloc )
211
- exists , _ = s3CheckFileExists (self , client = self .client )
245
+ return bucketExists (self ._bucket , self . client )
246
+ exists , _ = s3CheckFileExists (self , bucket = self . _bucket , client = self .client )
212
247
return exists
213
248
214
249
@backoff .on_exception (backoff .expo , retryable_io_errors , max_time = max_retry_time )
215
250
def size (self ) -> int :
216
251
"""Return the size of the resource in bytes."""
217
252
if self .dirLike :
218
253
return 0
219
- exists , sz = s3CheckFileExists (self , client = self .client )
254
+ exists , sz = s3CheckFileExists (self , bucket = self . _bucket , client = self .client )
220
255
if not exists :
221
256
raise FileNotFoundError (f"Resource { self } does not exist" )
222
257
return sz
@@ -229,7 +264,7 @@ def remove(self) -> None:
229
264
# for checking all the keys again, reponse is HTTP 204 OK
230
265
# response all the time
231
266
try :
232
- self .client .delete_object (Bucket = self .netloc , Key = self .relativeToPathRoot )
267
+ self .client .delete_object (Bucket = self ._bucket , Key = self .relativeToPathRoot )
233
268
except (self .client .exceptions .NoSuchKey , self .client .exceptions .NoSuchBucket ) as err :
234
269
raise FileNotFoundError ("No such resource: {self}" ) from err
235
270
@@ -239,7 +274,7 @@ def read(self, size: int = -1) -> bytes:
239
274
if size > 0 :
240
275
args ["Range" ] = f"bytes=0-{ size - 1 } "
241
276
try :
242
- response = self .client .get_object (Bucket = self .netloc , Key = self .relativeToPathRoot , ** args )
277
+ response = self .client .get_object (Bucket = self ._bucket , Key = self .relativeToPathRoot , ** args )
243
278
except (self .client .exceptions .NoSuchKey , self .client .exceptions .NoSuchBucket ) as err :
244
279
raise FileNotFoundError (f"No such resource: { self } " ) from err
245
280
except ClientError as err :
@@ -255,20 +290,20 @@ def write(self, data: bytes, overwrite: bool = True) -> None:
255
290
if not overwrite and self .exists ():
256
291
raise FileExistsError (f"Remote resource { self } exists and overwrite has been disabled" )
257
292
with time_this (log , msg = "Write to %s" , args = (self ,)):
258
- self .client .put_object (Bucket = self .netloc , Key = self .relativeToPathRoot , Body = data )
293
+ self .client .put_object (Bucket = self ._bucket , Key = self .relativeToPathRoot , Body = data )
259
294
260
295
@backoff .on_exception (backoff .expo , all_retryable_errors , max_time = max_retry_time )
261
296
def mkdir (self ) -> None :
262
297
"""Write a directory key to S3."""
263
- if not bucketExists (self .netloc ):
264
- raise ValueError (f"Bucket { self .netloc } does not exist for { self } !" )
298
+ if not bucketExists (self ._bucket , self . client ):
299
+ raise ValueError (f"Bucket { self ._bucket } does not exist for { self } !" )
265
300
266
301
if not self .dirLike :
267
302
raise NotADirectoryError (f"Can not create a 'directory' for file-like URI { self } " )
268
303
269
304
# don't create S3 key when root is at the top-level of an Bucket
270
305
if self .path != "/" :
271
- self .client .put_object (Bucket = self .netloc , Key = self .relativeToPathRoot )
306
+ self .client .put_object (Bucket = self ._bucket , Key = self .relativeToPathRoot )
272
307
273
308
@backoff .on_exception (backoff .expo , all_retryable_errors , max_time = max_retry_time )
274
309
def _download_file (self , local_file : IO , progress : ProgressPercentage | None ) -> None :
@@ -279,7 +314,7 @@ def _download_file(self, local_file: IO, progress: ProgressPercentage | None) ->
279
314
"""
280
315
try :
281
316
self .client .download_fileobj (
282
- self .netloc ,
317
+ self ._bucket ,
283
318
self .relativeToPathRoot ,
284
319
local_file ,
285
320
Callback = progress ,
@@ -324,7 +359,7 @@ def _upload_file(self, local_file: ResourcePath, progress: ProgressPercentage |
324
359
"""
325
360
try :
326
361
self .client .upload_file (
327
- local_file .ospath , self .netloc , self .relativeToPathRoot , Callback = progress
362
+ local_file .ospath , self ._bucket , self .relativeToPathRoot , Callback = progress
328
363
)
329
364
except self .client .exceptions .NoSuchBucket as err :
330
365
raise NotADirectoryError (f"Target does not exist: { err } " ) from err
@@ -333,13 +368,13 @@ def _upload_file(self, local_file: ResourcePath, progress: ProgressPercentage |
333
368
raise
334
369
335
370
@backoff .on_exception (backoff .expo , all_retryable_errors , max_time = max_retry_time )
336
- def _copy_from (self , src : ResourcePath ) -> None :
371
+ def _copy_from (self , src : S3ResourcePath ) -> None :
337
372
copy_source = {
338
- "Bucket" : src .netloc ,
373
+ "Bucket" : src ._bucket ,
339
374
"Key" : src .relativeToPathRoot ,
340
375
}
341
376
try :
342
- self .client .copy_object (CopySource = copy_source , Bucket = self .netloc , Key = self .relativeToPathRoot )
377
+ self .client .copy_object (CopySource = copy_source , Bucket = self ._bucket , Key = self .relativeToPathRoot )
343
378
except (self .client .exceptions .NoSuchKey , self .client .exceptions .NoSuchBucket ) as err :
344
379
raise FileNotFoundError ("No such resource to transfer: {self}" ) from err
345
380
except ClientError as err :
@@ -469,7 +504,7 @@ def walk(
469
504
filenames = []
470
505
files_there = False
471
506
472
- for page in s3_paginator .paginate (Bucket = self .netloc , Prefix = prefix , Delimiter = "/" ):
507
+ for page in s3_paginator .paginate (Bucket = self ._bucket , Prefix = prefix , Delimiter = "/" ):
473
508
# All results are returned as full key names and we must
474
509
# convert them back to the root form. The prefix is fixed
475
510
# and delimited so that is a simple trim
@@ -507,7 +542,7 @@ def _openImpl(
507
542
* ,
508
543
encoding : str | None = None ,
509
544
) -> Iterator [ResourceHandleProtocol ]:
510
- with S3ResourceHandle (mode , log , self .client , self .netloc , self .relativeToPathRoot ) as handle :
545
+ with S3ResourceHandle (mode , log , self .client , self ._bucket , self .relativeToPathRoot ) as handle :
511
546
if "b" in mode :
512
547
yield handle
513
548
else :
@@ -529,6 +564,6 @@ def generate_presigned_put_url(self, *, expiration_time_seconds: int) -> str:
529
564
def _generate_presigned_url (self , method : str , expiration_time_seconds : int ) -> str :
530
565
return self .client .generate_presigned_url (
531
566
method ,
532
- Params = {"Bucket" : self .netloc , "Key" : self .relativeToPathRoot },
567
+ Params = {"Bucket" : self ._bucket , "Key" : self .relativeToPathRoot },
533
568
ExpiresIn = expiration_time_seconds ,
534
569
)
0 commit comments