@@ -192,19 +192,29 @@ def _transfer_config(self) -> TransferConfig:
192
192
@property
193
193
def client (self ) -> boto3 .client :
194
194
"""Client object to address remote resource."""
195
- return getS3Client (self .profile )
195
+ return getS3Client (self ._profile )
196
196
197
197
@property
198
- def profile (self ) -> str | None :
198
+ def _profile (self ) -> str | None :
199
+ """Profile name to use for looking up S3 credentials and endpoint."""
199
200
return self ._uri .username
200
201
201
202
@property
202
- def bucket (self ) -> str :
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.
203
211
split = self ._uri .netloc .split ("@" )
204
212
num_components = len (split )
205
213
if num_components == 2 :
214
+ # There is a profile@ portion of the URL, so take the second half.
206
215
bucket = split [1 ]
207
216
elif num_components == 1 :
217
+ # There is no profile@, so take the whole netloc.
208
218
bucket = split [0 ]
209
219
else :
210
220
raise ValueError (f"Unexpected extra '@' in S3 URI: '{ str (self )} '" )
@@ -216,12 +226,12 @@ def bucket(self) -> str:
216
226
217
227
@classmethod
218
228
def _mexists (cls , uris : Iterable [ResourcePath ]) -> dict [ResourcePath , bool ]:
219
- # Force client to be created before creating threads.
229
+ # Force client to be created for each profile before creating threads.
220
230
profiles = set [str | None ]()
221
231
for path in uris :
222
232
if path .scheme == "s3" :
223
233
path = cast (S3ResourcePath , path )
224
- profiles .add (path .profile )
234
+ profiles .add (path ._profile )
225
235
for profile in profiles :
226
236
getS3Client (profile )
227
237
@@ -232,16 +242,16 @@ def exists(self) -> bool:
232
242
"""Check that the S3 resource exists."""
233
243
if self .is_root :
234
244
# Only check for the bucket since the path is irrelevant
235
- return bucketExists (self .bucket , self .client )
236
- exists , _ = s3CheckFileExists (self , bucket = self .bucket , client = self .client )
245
+ return bucketExists (self ._bucket , self .client )
246
+ exists , _ = s3CheckFileExists (self , bucket = self ._bucket , client = self .client )
237
247
return exists
238
248
239
249
@backoff .on_exception (backoff .expo , retryable_io_errors , max_time = max_retry_time )
240
250
def size (self ) -> int :
241
251
"""Return the size of the resource in bytes."""
242
252
if self .dirLike :
243
253
return 0
244
- exists , sz = s3CheckFileExists (self , bucket = self .bucket , client = self .client )
254
+ exists , sz = s3CheckFileExists (self , bucket = self ._bucket , client = self .client )
245
255
if not exists :
246
256
raise FileNotFoundError (f"Resource { self } does not exist" )
247
257
return sz
@@ -254,7 +264,7 @@ def remove(self) -> None:
254
264
# for checking all the keys again, reponse is HTTP 204 OK
255
265
# response all the time
256
266
try :
257
- self .client .delete_object (Bucket = self .bucket , Key = self .relativeToPathRoot )
267
+ self .client .delete_object (Bucket = self ._bucket , Key = self .relativeToPathRoot )
258
268
except (self .client .exceptions .NoSuchKey , self .client .exceptions .NoSuchBucket ) as err :
259
269
raise FileNotFoundError ("No such resource: {self}" ) from err
260
270
@@ -264,7 +274,7 @@ def read(self, size: int = -1) -> bytes:
264
274
if size > 0 :
265
275
args ["Range" ] = f"bytes=0-{ size - 1 } "
266
276
try :
267
- response = self .client .get_object (Bucket = self .bucket , Key = self .relativeToPathRoot , ** args )
277
+ response = self .client .get_object (Bucket = self ._bucket , Key = self .relativeToPathRoot , ** args )
268
278
except (self .client .exceptions .NoSuchKey , self .client .exceptions .NoSuchBucket ) as err :
269
279
raise FileNotFoundError (f"No such resource: { self } " ) from err
270
280
except ClientError as err :
@@ -280,20 +290,20 @@ def write(self, data: bytes, overwrite: bool = True) -> None:
280
290
if not overwrite and self .exists ():
281
291
raise FileExistsError (f"Remote resource { self } exists and overwrite has been disabled" )
282
292
with time_this (log , msg = "Write to %s" , args = (self ,)):
283
- self .client .put_object (Bucket = self .bucket , Key = self .relativeToPathRoot , Body = data )
293
+ self .client .put_object (Bucket = self ._bucket , Key = self .relativeToPathRoot , Body = data )
284
294
285
295
@backoff .on_exception (backoff .expo , all_retryable_errors , max_time = max_retry_time )
286
296
def mkdir (self ) -> None :
287
297
"""Write a directory key to S3."""
288
- if not bucketExists (self .bucket , self .client ):
289
- raise ValueError (f"Bucket { self .bucket } 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 } !" )
290
300
291
301
if not self .dirLike :
292
302
raise NotADirectoryError (f"Can not create a 'directory' for file-like URI { self } " )
293
303
294
304
# don't create S3 key when root is at the top-level of an Bucket
295
305
if self .path != "/" :
296
- self .client .put_object (Bucket = self .bucket , Key = self .relativeToPathRoot )
306
+ self .client .put_object (Bucket = self ._bucket , Key = self .relativeToPathRoot )
297
307
298
308
@backoff .on_exception (backoff .expo , all_retryable_errors , max_time = max_retry_time )
299
309
def _download_file (self , local_file : IO , progress : ProgressPercentage | None ) -> None :
@@ -304,7 +314,7 @@ def _download_file(self, local_file: IO, progress: ProgressPercentage | None) ->
304
314
"""
305
315
try :
306
316
self .client .download_fileobj (
307
- self .bucket ,
317
+ self ._bucket ,
308
318
self .relativeToPathRoot ,
309
319
local_file ,
310
320
Callback = progress ,
@@ -349,7 +359,7 @@ def _upload_file(self, local_file: ResourcePath, progress: ProgressPercentage |
349
359
"""
350
360
try :
351
361
self .client .upload_file (
352
- local_file .ospath , self .bucket , self .relativeToPathRoot , Callback = progress
362
+ local_file .ospath , self ._bucket , self .relativeToPathRoot , Callback = progress
353
363
)
354
364
except self .client .exceptions .NoSuchBucket as err :
355
365
raise NotADirectoryError (f"Target does not exist: { err } " ) from err
@@ -360,11 +370,11 @@ def _upload_file(self, local_file: ResourcePath, progress: ProgressPercentage |
360
370
@backoff .on_exception (backoff .expo , all_retryable_errors , max_time = max_retry_time )
361
371
def _copy_from (self , src : S3ResourcePath ) -> None :
362
372
copy_source = {
363
- "Bucket" : src .bucket ,
373
+ "Bucket" : src ._bucket ,
364
374
"Key" : src .relativeToPathRoot ,
365
375
}
366
376
try :
367
- self .client .copy_object (CopySource = copy_source , Bucket = self .bucket , Key = self .relativeToPathRoot )
377
+ self .client .copy_object (CopySource = copy_source , Bucket = self ._bucket , Key = self .relativeToPathRoot )
368
378
except (self .client .exceptions .NoSuchKey , self .client .exceptions .NoSuchBucket ) as err :
369
379
raise FileNotFoundError ("No such resource to transfer: {self}" ) from err
370
380
except ClientError as err :
@@ -494,7 +504,7 @@ def walk(
494
504
filenames = []
495
505
files_there = False
496
506
497
- for page in s3_paginator .paginate (Bucket = self .bucket , Prefix = prefix , Delimiter = "/" ):
507
+ for page in s3_paginator .paginate (Bucket = self ._bucket , Prefix = prefix , Delimiter = "/" ):
498
508
# All results are returned as full key names and we must
499
509
# convert them back to the root form. The prefix is fixed
500
510
# and delimited so that is a simple trim
@@ -532,7 +542,7 @@ def _openImpl(
532
542
* ,
533
543
encoding : str | None = None ,
534
544
) -> Iterator [ResourceHandleProtocol ]:
535
- with S3ResourceHandle (mode , log , self .client , self .bucket , self .relativeToPathRoot ) as handle :
545
+ with S3ResourceHandle (mode , log , self .client , self ._bucket , self .relativeToPathRoot ) as handle :
536
546
if "b" in mode :
537
547
yield handle
538
548
else :
@@ -554,6 +564,6 @@ def generate_presigned_put_url(self, *, expiration_time_seconds: int) -> str:
554
564
def _generate_presigned_url (self , method : str , expiration_time_seconds : int ) -> str :
555
565
return self .client .generate_presigned_url (
556
566
method ,
557
- Params = {"Bucket" : self .bucket , "Key" : self .relativeToPathRoot },
567
+ Params = {"Bucket" : self ._bucket , "Key" : self .relativeToPathRoot },
558
568
ExpiresIn = expiration_time_seconds ,
559
569
)
0 commit comments