aipmodel 0.2.40__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1380 @@
1
+ import json
2
+ import os
3
+ import random
4
+ import shutil
5
+ import string
6
+ import subprocess
7
+ import sys
8
+ import boto3
9
+ from botocore.exceptions import ClientError, EndpointConnectionError
10
+ from tqdm import tqdm
11
+ import mimetypes
12
+ import requests
13
+
14
+ # CephS3Manager handles interaction with Ceph-compatible S3 storage
15
+ class CephS3Manager:
16
+ def __init__(self, CEPH_ENDPOINT_URL, CEPH_ADMIN_ACCESS_KEY, CEPH_ADMIN_SECRET_KEY, CEPH_USER_BUCKET, verbose=True):
17
+ if verbose:
18
+ print("Initializing CephS3Manager...")
19
+ if not all([CEPH_ENDPOINT_URL, CEPH_ADMIN_ACCESS_KEY, CEPH_ADMIN_SECRET_KEY, CEPH_USER_BUCKET]):
20
+ error_msg = "Missing required Ceph configuration parameters"
21
+ print(f"[FAIL] {error_msg}")
22
+ raise ValueError(error_msg)
23
+
24
+ self.bucket_name = CEPH_USER_BUCKET
25
+ self.verbose = verbose
26
+ if verbose:
27
+ print(f"Setting bucket name to {self.bucket_name}")
28
+
29
+ if verbose:
30
+ print("Creating boto3 S3 client...")
31
+ self.s3 = boto3.client(
32
+ "s3",
33
+ endpoint_url=CEPH_ENDPOINT_URL,
34
+ aws_access_key_id=CEPH_ADMIN_ACCESS_KEY,
35
+ aws_secret_access_key=CEPH_ADMIN_SECRET_KEY,
36
+ )
37
+ if verbose:
38
+ print("S3 client created successfully")
39
+
40
+ # Perform connection, authentication, and bucket checks
41
+ if verbose:
42
+ print("Checking connection...")
43
+ if not self.check_connection():
44
+ error_msg = "Ceph connection not established."
45
+ print(f"[FAIL] {error_msg}")
46
+ raise ValueError(error_msg)
47
+
48
+ if verbose:
49
+ print("Checking authentication...")
50
+ if not self.check_auth():
51
+ error_msg = "Ceph Authentication not correct."
52
+ print(f"[FAIL] {error_msg}")
53
+ raise ValueError(error_msg)
54
+
55
+ if verbose:
56
+ print("Ensuring bucket exists...")
57
+ self.ensure_bucket_exists()
58
+ print("[OK] CephS3Manager initialized successfully")
59
+
60
+ def generate_random_string(self, length=12):
61
+ if self.verbose:
62
+ print(f"Generating random string of length {length}...")
63
+ try:
64
+ characters = string.ascii_letters + string.digits
65
+ result = "".join(random.choice(characters) for _ in range(length))
66
+ if self.verbose:
67
+ print(f"Random string generated successfully: {result}")
68
+ return result
69
+ except Exception as e:
70
+ error_msg = f"Unexpected error generating random string: {str(e)}"
71
+ print(f"[FAIL] {error_msg}")
72
+ raise ValueError(error_msg)
73
+
74
+ def generate_key(self, length=12, characters=None):
75
+ if self.verbose:
76
+ print(f"Generating key of length {length}...")
77
+ try:
78
+ if characters is None:
79
+ characters = string.ascii_letters + string.digits
80
+ result = "".join(random.choice(characters) for _ in range(length))
81
+ if self.verbose:
82
+ print(f"Key generated successfully: {result}")
83
+ return result
84
+ except Exception as e:
85
+ error_msg = f"Unexpected error generating key: {str(e)}"
86
+ print(f"[FAIL] {error_msg}")
87
+ raise ValueError(error_msg)
88
+
89
+ def generate_access_key(self):
90
+ if self.verbose:
91
+ print("Generating access key...")
92
+ try:
93
+ characters = string.ascii_uppercase + string.digits
94
+ result = "".join(random.choice(characters) for _ in range(20))
95
+ if self.verbose:
96
+ print(f"Access key generated successfully: {result}")
97
+ return result
98
+ except Exception as e:
99
+ error_msg = f"Unexpected error generating access key: {str(e)}"
100
+ print(f"[FAIL] {error_msg}")
101
+ raise ValueError(error_msg)
102
+
103
+ def generate_secret_key(self):
104
+ if self.verbose:
105
+ print("Generating secret key...")
106
+ try:
107
+ characters = string.ascii_letters + string.digits
108
+ result = "".join(random.choice(characters) for _ in range(40))
109
+ if self.verbose:
110
+ print(f"Secret key generated successfully: {result}")
111
+ return result
112
+ except Exception as e:
113
+ error_msg = f"Unexpected error generating secret key: {str(e)}"
114
+ print(f"[FAIL] {error_msg}")
115
+ raise ValueError(error_msg)
116
+
117
+ def create_user(self, username):
118
+ if self.verbose:
119
+ print(f"Starting user creation for {username} using Admin Ops API...")
120
+ try:
121
+ if self.verbose:
122
+ print("Generating access and secret keys...")
123
+ access_key = self.generate_access_key()
124
+ secret_key = self.generate_secret_key()
125
+ if self.verbose:
126
+ print("Keys generated successfully")
127
+
128
+ if self.verbose:
129
+ print("Extracting admin credentials and endpoint...")
130
+ endpoint_url = self.s3.meta.endpoint_url
131
+ admin_access_key = self.s3.meta.client._request_signer._credentials.access_key
132
+ admin_secret_key = self.s3.meta.client._request_signer._credentials.secret_key
133
+ if self.verbose:
134
+ print("Credentials extracted successfully")
135
+
136
+ if self.verbose:
137
+ print("Preparing API parameters...")
138
+ params = {
139
+ 'uid': username,
140
+ 'display-name': username,
141
+ 'access-key': access_key,
142
+ 'secret-key': secret_key,
143
+ 'format': 'json'
144
+ }
145
+ if self.verbose:
146
+ print("Parameters prepared")
147
+
148
+ admin_path = '/admin/user'
149
+ if self.verbose:
150
+ print(f"Sending PUT request to {endpoint_url}{admin_path}...")
151
+ response = requests.put(
152
+ f"{endpoint_url}{admin_path}",
153
+ params=params,
154
+ auth=(admin_access_key, admin_secret_key)
155
+ )
156
+ if self.verbose:
157
+ print(f"Response received with status code {response.status_code}")
158
+
159
+ if response.status_code != 200:
160
+ error_msg = f"API error: {response.status_code} - {response.text}"
161
+ print(f"[FAIL] {error_msg}")
162
+ raise ValueError(error_msg)
163
+
164
+ if self.verbose:
165
+ print("Parsing JSON response...")
166
+ try:
167
+ output = response.json()
168
+ if self.verbose:
169
+ print(f"[INFO] API response: {json.dumps(output, indent=2)}")
170
+ except json.JSONDecodeError as e:
171
+ error_msg = f"Invalid JSON output from API: {str(e)}"
172
+ print(f"[FAIL] {error_msg}")
173
+ raise ValueError(error_msg)
174
+
175
+ print(f"[OK] User {username} created successfully")
176
+ return access_key, secret_key
177
+
178
+ except ClientError as e:
179
+ error_msg = f"Boto3 client error: {str(e)}"
180
+ print(f"[FAIL] {error_msg}")
181
+ raise ValueError(error_msg)
182
+ except requests.exceptions.RequestException as e:
183
+ error_msg = f"Network error connecting to Ceph RGW: {str(e)}"
184
+ print(f"[FAIL] {error_msg}")
185
+ raise ValueError(error_msg)
186
+ except Exception as e:
187
+ error_msg = f"Unexpected error creating user {username}: {str(e)}"
188
+ print(f"[FAIL] {error_msg}")
189
+ raise ValueError(error_msg)
190
+
191
+ def set_user_quota(self, username, quota_gb):
192
+ if self.verbose:
193
+ print(f"Starting to set quota for user {username} to {quota_gb} GB using Admin Ops API...")
194
+ try:
195
+ if self.verbose:
196
+ print("Extracting admin credentials and endpoint...")
197
+ endpoint_url = self.s3.meta.endpoint_url
198
+ admin_access_key = self.s3.meta.client._request_signer._credentials.access_key
199
+ admin_secret_key = self.s3.meta.client._request_signer._credentials.secret_key
200
+ if self.verbose:
201
+ print("Credentials extracted successfully")
202
+
203
+ if self.verbose:
204
+ print("Converting quota to bytes...")
205
+ max_size_bytes = int(quota_gb * 1024 * 1024 * 1024)
206
+ if self.verbose:
207
+ print(f"Quota converted to {max_size_bytes} bytes")
208
+
209
+ if self.verbose:
210
+ print("Preparing API parameters...")
211
+ params = {
212
+ 'uid': username,
213
+ 'quota-type': 'user',
214
+ 'max-size': str(max_size_bytes),
215
+ 'enabled': 'true',
216
+ 'format': 'json'
217
+ }
218
+ if self.verbose:
219
+ print("Parameters prepared")
220
+
221
+ admin_path = '/admin/user?quota'
222
+ if self.verbose:
223
+ print(f"Sending PUT request to {endpoint_url}{admin_path}...")
224
+ response = requests.put(
225
+ f"{endpoint_url}{admin_path}",
226
+ params=params,
227
+ auth=(admin_access_key, admin_secret_key)
228
+ )
229
+ if self.verbose:
230
+ print(f"Response received with status code {response.status_code}")
231
+
232
+ if response.status_code != 200:
233
+ error_msg = f"API error: {response.status_code} - {response.text}"
234
+ print(f"[FAIL] {error_msg}")
235
+ raise ValueError(error_msg)
236
+
237
+ print(f"[OK] Quota set successfully for user {username}")
238
+
239
+ except requests.exceptions.RequestException as e:
240
+ error_msg = f"Network error: {str(e)}"
241
+ print(f"[FAIL] {error_msg}")
242
+ raise ValueError(error_msg)
243
+ except Exception as e:
244
+ error_msg = f"Unexpected error setting quota for user {username}: {str(e)}"
245
+ print(f"[FAIL] {error_msg}")
246
+ raise ValueError(error_msg)
247
+
248
+ def enforce_storage_limit(self, bucket_name, storage_limit):
249
+ if self.verbose:
250
+ print(f"Starting to enforce storage limit for bucket {bucket_name} with limit {storage_limit} GB...")
251
+ try:
252
+ if self.verbose:
253
+ print("Getting bucket size...")
254
+ size_mb = self.get_uri_size(f"s3://{bucket_name}/")
255
+ size_gb = size_mb / 1024
256
+ if self.verbose:
257
+ print(f"Bucket size calculated: {size_gb:.2f} GB")
258
+
259
+ if size_gb > storage_limit:
260
+ warning_msg = f"Bucket {bucket_name} size ({size_gb:.2f} GB) exceeds limit ({storage_limit} GB)"
261
+ print(f"[WARN] {warning_msg}")
262
+ return False
263
+
264
+ print(f"[OK] Bucket {bucket_name} size ({size_gb:.2f} GB) within limit ({storage_limit} GB)")
265
+ print("[OK] Storage limit enforced successfully")
266
+ return True
267
+
268
+ except ValueError as e:
269
+ error_msg = f"Bucket {bucket_name} does not exist: {str(e)}"
270
+ print(f"[FAIL] {error_msg}")
271
+ raise ValueError(error_msg)
272
+ except Exception as e:
273
+ error_msg = f"Unexpected error enforcing storage limit for {bucket_name}: {str(e)}"
274
+ print(f"[FAIL] {error_msg}")
275
+ raise ValueError(error_msg)
276
+
277
+ def run_cmd(self, cmd, shell=False):
278
+ if self.verbose:
279
+ print(f"Starting to run command: {cmd if isinstance(cmd, str) else ' '.join(cmd)} with shell={shell}...")
280
+ try:
281
+ result = subprocess.run(cmd, capture_output=True, text=True, shell=shell, check=True)
282
+ if self.verbose:
283
+ print(f"[OK] Command executed successfully: {cmd if isinstance(cmd, str) else ' '.join(cmd)}")
284
+ print(f"stdout: {result.stdout.strip()}")
285
+ print(f"stderr: {result.stderr.strip()}")
286
+ return result
287
+ except subprocess.CalledProcessError as e:
288
+ error_msg = f"Command failed: {e.stderr}"
289
+ print(f"[FAIL] {error_msg}")
290
+ raise ValueError(error_msg)
291
+ except Exception as e:
292
+ error_msg = f"Unexpected error executing command: {str(e)}"
293
+ print(f"[FAIL] {error_msg}")
294
+ raise ValueError(error_msg)
295
+
296
+ def check_s5cmd(self):
297
+ if self.verbose:
298
+ print("Checking if s5cmd is installed...")
299
+ try:
300
+ cmd = ["s5cmd", "--version"]
301
+ result = subprocess.run(cmd, capture_output=True, text=True, check=True)
302
+ if self.verbose:
303
+ print(f"[OK] s5cmd is installed: {result.stdout.strip()}")
304
+ print("s5cmd check completed successfully")
305
+ return True
306
+ except subprocess.CalledProcessError:
307
+ error_msg = "s5cmd is not installed"
308
+ print(f"[FAIL] {error_msg}")
309
+ return False
310
+ except Exception as e:
311
+ error_msg = f"Unexpected error checking s5cmd: {str(e)}"
312
+ print(f"[FAIL] {error_msg}")
313
+ return False
314
+
315
+ def check_command_exists(self, cmd_name, path=None):
316
+ if self.verbose:
317
+ print(f"Checking if command {cmd_name} exists...")
318
+ try:
319
+ if path:
320
+ result = os.path.isfile(path) and os.access(path, os.X_OK)
321
+ if result:
322
+ if self.verbose:
323
+ print(f"[OK] Command {cmd_name} exists at path {path}")
324
+ else:
325
+ if self.verbose:
326
+ print(f"[FAIL] Command {cmd_name} does not exist at path {path}")
327
+ if self.verbose:
328
+ print("Command existence check completed")
329
+ return result
330
+ result = shutil.which(cmd_name)
331
+ if result:
332
+ if self.verbose:
333
+ print(f"[OK] Command {cmd_name} found at: {result}")
334
+ print("Command existence check completed successfully")
335
+ return True
336
+ if self.verbose:
337
+ print(f"[FAIL] Command {cmd_name} not found")
338
+ print("Command existence check completed")
339
+ return False
340
+ except Exception as e:
341
+ error_msg = f"Unexpected error checking command {cmd_name}: {str(e)}"
342
+ print(f"[FAIL] {error_msg}")
343
+ raise ValueError(error_msg)
344
+
345
+ def check_aws_credentials_folder(self):
346
+ if self.verbose:
347
+ print("Checking AWS credentials folder...")
348
+ try:
349
+ aws_dir = os.path.expanduser("~/.aws")
350
+ os.makedirs(aws_dir, exist_ok=True)
351
+ if self.verbose:
352
+ print(f"[OK] AWS credentials folder exists: {aws_dir}")
353
+ print("AWS credentials folder check completed successfully")
354
+ return True
355
+ except Exception as e:
356
+ error_msg = f"Failed to create AWS credentials folder: {str(e)}"
357
+ print(f"[FAIL] {error_msg}")
358
+ raise ValueError(error_msg)
359
+
360
+ def _list_all_files(self):
361
+ if self.verbose:
362
+ print("Listing all files in bucket...")
363
+ try:
364
+ response = self.s3.list_objects_v2(Bucket=self.bucket_name)
365
+ result = [obj["Key"] for obj in response.get("Contents", [])] if "Contents" in response else []
366
+ if self.verbose:
367
+ print(f"[OK] Listed {len(result)} files")
368
+ print("File listing completed successfully")
369
+ return result
370
+ except Exception as e:
371
+ error_msg = f"Unexpected error listing files: {str(e)}"
372
+ print(f"[FAIL] {error_msg}")
373
+ raise ValueError(error_msg)
374
+
375
+ def _find_closest_match(self, target_name, file_list):
376
+ if self.verbose:
377
+ print(f"Finding closest match for {target_name}...")
378
+ try:
379
+ import difflib
380
+ matches = difflib.get_close_matches(target_name, file_list, n=1, cutoff=0.5)
381
+ result = matches[0] if matches else None
382
+ if result:
383
+ if self.verbose:
384
+ print(f"[OK] Closest match found: {result}")
385
+ else:
386
+ if self.verbose:
387
+ print("[WARN] No close match found")
388
+ if self.verbose:
389
+ print("Closest match search completed successfully")
390
+ return result
391
+ except Exception as e:
392
+ error_msg = f"Unexpected error finding closest match: {str(e)}"
393
+ print(f"[FAIL] {error_msg}")
394
+ raise ValueError(error_msg)
395
+
396
+ def get_local_path(self, key):
397
+ if self.verbose:
398
+ print(f"Generating local path for key {key}...")
399
+ try:
400
+ result = os.path.join("./downloads", self.bucket_name, key)
401
+ if self.verbose:
402
+ print(f"[OK] Local path generated: {result}")
403
+ print("Local path generation completed successfully")
404
+ return result
405
+ except Exception as e:
406
+ error_msg = f"Unexpected error generating local path: {str(e)}"
407
+ print(f"[FAIL] {error_msg}")
408
+ raise ValueError(error_msg)
409
+
410
+ def print_file_info(self, file_key, response):
411
+ if self.verbose:
412
+ print(f"Printing file info for {file_key}...")
413
+ try:
414
+ metadata = response.get("ResponseMetadata", {}).get("HTTPHeaders", {})
415
+ file_size = metadata.get("content-length", "Unknown Size")
416
+ file_type = metadata.get("content-type", "Unknown Type")
417
+ last_modified = response.get("LastModified", "Unknown Date")
418
+ print("\nDownloaded File Information:")
419
+ print(f"File Name: {file_key}")
420
+ print(f"File Size: {file_size} bytes")
421
+ print(f"File Type: {file_type}")
422
+ print(f"Last Modified: {last_modified}")
423
+ print("[OK] File info printed successfully")
424
+ except Exception as e:
425
+ error_msg = f"Unexpected error printing file info: {str(e)}"
426
+ print(f"[FAIL] {error_msg}")
427
+ raise ValueError(error_msg)
428
+
429
+ def read_file_from_s3(self, key):
430
+ if self.verbose:
431
+ print(f"Starting to read file from S3: {key}...")
432
+ try:
433
+ if self.verbose:
434
+ print("Checking if file exists...")
435
+ if not self.check_if_exists(key):
436
+ file_list = self._list_all_files()
437
+ closest_match = self._find_closest_match(key, file_list)
438
+ if closest_match:
439
+ error_msg = f"File '{key}' not found. Similar file found: '{closest_match}'"
440
+ print(f"[FAIL] {error_msg}")
441
+ raise ValueError(error_msg)
442
+ error_msg = f"File '{key}' does not exist in bucket '{self.bucket_name}'."
443
+ print(f"[FAIL] {error_msg}")
444
+ raise ValueError(error_msg)
445
+ if self.verbose:
446
+ print("File exists")
447
+
448
+ if self.verbose:
449
+ print("Detecting file type...")
450
+ file_type, _ = mimetypes.guess_type(key)
451
+ file_type = file_type if file_type else "Unknown file type"
452
+ if self.verbose:
453
+ print(f"File type detected: {file_type}")
454
+
455
+ if self.verbose:
456
+ print("Getting object from S3...")
457
+ response = self.s3.get_object(Bucket=self.bucket_name, Key=key)
458
+ body = response["Body"].read()
459
+ if self.verbose:
460
+ print("Object retrieved successfully")
461
+
462
+ if self.verbose:
463
+ print("Processing file based on type...")
464
+ if file_type and ("text" in file_type or file_type in ["application/json", "application/xml"]):
465
+ content = body.decode("utf-8")
466
+ if file_type == "application/json":
467
+ content = json.dumps(json.loads(content), indent=4)
468
+ if self.verbose:
469
+ print(f"\nFile Content:\n{content}")
470
+ print("[OK] Text file processed successfully")
471
+ return content
472
+ if file_type and "image" in file_type:
473
+ local_path = "downloaded_image.jpg"
474
+ with open(local_path, "wb") as f:
475
+ f.write(body)
476
+ print(f"[OK] Image saved as '{local_path}'")
477
+ if self.verbose:
478
+ print("Image file processed successfully")
479
+ return local_path
480
+ if file_type and "audio" in file_type:
481
+ local_path = "downloaded_audio.mp3"
482
+ with open(local_path, "wb") as f:
483
+ f.write(body)
484
+ print(f"[OK] Audio file saved as '{local_path}'")
485
+ if self.verbose:
486
+ print("Audio file processed successfully")
487
+ return local_path
488
+ if file_type and "pdf" in file_type:
489
+ local_path = "downloaded_file.pdf"
490
+ with open(local_path, "wb") as f:
491
+ f.write(body)
492
+ print(f"[OK] PDF file saved as '{local_path}'")
493
+ if self.verbose:
494
+ print("PDF file processed successfully")
495
+ return local_path
496
+ local_path = "downloaded_file.bin"
497
+ with open(local_path, "wb") as f:
498
+ f.write(body)
499
+ print(f"[OK] Binary file saved as '{local_path}'")
500
+ if self.verbose:
501
+ print("Binary file processed successfully")
502
+ return local_path
503
+
504
+ except ClientError as e:
505
+ error_msg = f"Failed to read file '{key}': {e.response['Error']['Code']}"
506
+ print(f"[FAIL] {error_msg}")
507
+ raise ValueError(error_msg)
508
+ except Exception as e:
509
+ error_msg = f"Unexpected error reading file '{key}': {str(e)}"
510
+ print(f"[FAIL] {error_msg}")
511
+ raise ValueError(error_msg)
512
+
513
+ def get_identity(self):
514
+ if self.verbose:
515
+ print("Starting to get caller identity using STS...")
516
+ try:
517
+ if self.verbose:
518
+ print("Creating STS client...")
519
+ sts_client = boto3.client(
520
+ "sts",
521
+ endpoint_url=self.s3.meta.endpoint_url,
522
+ aws_access_key_id=self.s3.meta.client._request_signer._credentials.access_key,
523
+ aws_secret_access_key=self.s3.meta.client._request_signer._credentials.secret_key,
524
+ )
525
+ if self.verbose:
526
+ print("STS client created")
527
+
528
+ if self.verbose:
529
+ print("Getting caller identity...")
530
+ identity = sts_client.get_caller_identity()
531
+ if self.verbose:
532
+ print(f"[OK] Caller Identity: {identity}")
533
+ print("[OK] Caller identity retrieved successfully")
534
+ return identity
535
+ except ClientError as e:
536
+ error_msg = f"Failed to get caller identity: {e.response['Error']['Code']}"
537
+ print(f"[FAIL] {error_msg}")
538
+ raise ValueError(error_msg)
539
+ except Exception as e:
540
+ error_msg = f"Unexpected error getting caller identity: {str(e)}"
541
+ print(f"[FAIL] {error_msg}")
542
+ raise ValueError(error_msg)
543
+
544
+ def get_user_info(self):
545
+ if self.verbose:
546
+ print("Starting to get user info using IAM...")
547
+ try:
548
+ if self.verbose:
549
+ print("Creating IAM client...")
550
+ iam_client = boto3.client(
551
+ "iam",
552
+ endpoint_url=self.s3.meta.endpoint_url,
553
+ aws_access_key_id=self.s3.meta.client._request_signer._credentials.access_key,
554
+ aws_secret_access_key=self.s3.meta.client._request_signer._credentials.secret_key,
555
+ )
556
+ if self.verbose:
557
+ print("IAM client created")
558
+
559
+ if self.verbose:
560
+ print("Getting user info...")
561
+ user_info = iam_client.get_user()
562
+ if self.verbose:
563
+ print(f"[OK] User Info: {user_info['User']}")
564
+ print("[OK] User info retrieved successfully")
565
+ return user_info["User"]
566
+ except ClientError as e:
567
+ error_msg = f"Failed to get user info: {e.response['Error']['Code']}"
568
+ print(f"[FAIL] {error_msg}")
569
+ raise ValueError(error_msg)
570
+ except Exception as e:
571
+ error_msg = f"Unexpected error getting user info: {str(e)}"
572
+ print(f"[FAIL] {error_msg}")
573
+ raise ValueError(error_msg)
574
+
575
+ def ensure_bucket_exists(self):
576
+ if self.verbose:
577
+ print(f"Ensuring bucket {self.bucket_name} exists...")
578
+ try:
579
+ if self.verbose:
580
+ print("Listing buckets...")
581
+ buckets = self.s3.list_buckets()
582
+ names = [b["Name"] for b in buckets.get("Buckets", [])]
583
+ if self.verbose:
584
+ print(f"Existing buckets: {names}")
585
+
586
+ if self.bucket_name not in names:
587
+ if self.verbose:
588
+ print(f"Creating bucket {self.bucket_name}...")
589
+ self.s3.create_bucket(Bucket=self.bucket_name)
590
+ print(f"[OK] Ceph S3 Bucket Created: {self.bucket_name}")
591
+ else:
592
+ print(f"[OK] Ceph S3 Bucket Exists: {self.bucket_name}")
593
+ print("[OK] Bucket ensured successfully")
594
+ except ClientError as e:
595
+ error_msg = f"Failed to ensure bucket exists: {e.response['Error']['Code']}"
596
+ print(f"[FAIL] {error_msg}")
597
+ raise ValueError(error_msg)
598
+ except Exception as e:
599
+ error_msg = f"Unexpected error ensuring bucket exists: {str(e)}"
600
+ print(f"[FAIL] {error_msg}")
601
+ raise ValueError(error_msg)
602
+
603
+ def check_connection(self):
604
+ if self.verbose:
605
+ print("Checking Ceph S3 connection...")
606
+ try:
607
+ self.s3.list_buckets()
608
+ print("[OK] Ceph S3 Connection")
609
+ if self.verbose:
610
+ print("Connection check completed successfully")
611
+ return True
612
+ except EndpointConnectionError as e:
613
+ error_msg = "Ceph S3 Connection failed"
614
+ print(f"[FAIL] {error_msg}: {str(e)}")
615
+ raise ValueError(error_msg)
616
+ except ClientError as e:
617
+ error_msg = f"Ceph S3 ClientError: {e.response['Error']['Code']}"
618
+ print(f"[FAIL] {error_msg}")
619
+ raise ValueError(error_msg)
620
+ except Exception as e:
621
+ error_msg = f"Ceph S3 Unknown error: {str(e)}"
622
+ print(f"[FAIL] {error_msg}")
623
+ raise ValueError("Ceph S3 Connection failed")
624
+
625
+ def check_auth(self):
626
+ if self.verbose:
627
+ print("Checking Ceph S3 authentication...")
628
+ try:
629
+ self.s3.list_buckets()
630
+ print("[OK] Ceph S3 Auth")
631
+ if self.verbose:
632
+ print("Authentication check completed successfully")
633
+ return True
634
+ except ClientError as e:
635
+ code = e.response["Error"]["Code"]
636
+ if code in ["InvalidAccessKeyId", "SignatureDoesNotMatch"]:
637
+ error_msg = "Ceph S3 Auth Invalid"
638
+ else:
639
+ error_msg = f"Ceph S3 Auth: {code}"
640
+ print(f"[FAIL] {error_msg}")
641
+ raise ValueError(f"Ceph S3 Authentication failed: {code}")
642
+ except Exception as e:
643
+ error_msg = f"Ceph S3 Auth Unknown: {str(e)}"
644
+ print(f"[FAIL] {error_msg}")
645
+ raise ValueError("Ceph S3 Authentication failed")
646
+
647
+ def check_if_exists(self, key):
648
+ if self.verbose:
649
+ print(f"Checking if key '{key}' exists in bucket {self.bucket_name}...")
650
+ try:
651
+ resp = self.s3.list_objects_v2(Bucket=self.bucket_name, Prefix=key)
652
+ result = resp.get("Contents", []) if "Contents" in resp else None
653
+ if result:
654
+ if self.verbose:
655
+ print(f"[OK] Key '{key}' exists with {len(result)} objects")
656
+ else:
657
+ if self.verbose:
658
+ print(f"[WARN] Key '{key}' does not exist")
659
+ if self.verbose:
660
+ print("Existence check completed successfully")
661
+ return result
662
+ except ClientError as e:
663
+ error_msg = f"Failed to check if key '{key}' exists: {e.response['Error']['Code']}"
664
+ print(f"[FAIL] {error_msg}")
665
+ raise ValueError(error_msg)
666
+ except Exception as e:
667
+ error_msg = f"Unexpected error checking if key '{key}' exists: {str(e)}"
668
+ print(f"[FAIL] {error_msg}")
669
+ raise ValueError(error_msg)
670
+
671
+ def get_uri_size(self, uri):
672
+ if self.verbose:
673
+ print(f"Starting to get size for URI {uri}...")
674
+ try:
675
+ import re
676
+ if self.verbose:
677
+ print("Parsing URI...")
678
+ pattern = r"^s3://([^/]+)/(.+)$"
679
+ match = re.match(pattern, uri)
680
+ if not match:
681
+ error_msg = f"Invalid S3 URI: {uri}"
682
+ print(f"[FAIL] {error_msg}")
683
+ raise ValueError(error_msg)
684
+ bucket, key = match.groups()
685
+ if bucket != self.bucket_name:
686
+ error_msg = f"URI bucket '{bucket}' does not match initialized bucket '{self.bucket_name}'"
687
+ print(f"[FAIL] {error_msg}")
688
+ raise ValueError(error_msg)
689
+ if self.verbose:
690
+ print("URI parsed successfully")
691
+
692
+ if self.verbose:
693
+ print("Attempting to get file size...")
694
+ try:
695
+ response = self.s3.head_object(Bucket=self.bucket_name, Key=key)
696
+ size = response["ContentLength"] / (1024**2)
697
+ print(f"[OK] Found file: {key} ({size:.2f} MB)")
698
+ if self.verbose:
699
+ print("URI size retrieved successfully")
700
+ return size
701
+ except self.s3.exceptions.ClientError as e:
702
+ if e.response["Error"]["Code"] == "404":
703
+ if self.verbose:
704
+ print("[WARN] Object not found, checking as folder...")
705
+ if not key.endswith("/"):
706
+ key += "/"
707
+ else:
708
+ error_msg = f"Failed to get size for {uri}: {e.response['Error']['Code']}"
709
+ print(f"[FAIL] {error_msg}")
710
+ raise ValueError(error_msg)
711
+
712
+ if self.verbose:
713
+ print("Scanning folder for size...")
714
+ paginator = self.s3.get_paginator("list_objects_v2")
715
+ pages = paginator.paginate(Bucket=self.bucket_name, Prefix=key)
716
+ total_size = 0
717
+ found = False
718
+ for page in tqdm(pages, desc=f"Scanning {uri}"):
719
+ contents = page.get("Contents", [])
720
+ if contents:
721
+ found = True
722
+ for obj in contents:
723
+ total_size += obj["Size"]
724
+ if not found:
725
+ print(f"[WARN] No objects found at URI '{uri}'.")
726
+ return 0.0
727
+ size_mb = total_size / (1024**2)
728
+ print(f"[OK] Folder total size: {size_mb:.2f} MB")
729
+ if self.verbose:
730
+ print("Folder size calculation completed successfully")
731
+ return size_mb
732
+ except Exception as e:
733
+ error_msg = f"Unexpected error getting URI size: {str(e)}"
734
+ print(f"[FAIL] {error_msg}")
735
+ raise ValueError(error_msg)
736
+
737
+ def list_buckets(self):
738
+ if self.verbose:
739
+ print("Starting to list buckets...")
740
+ try:
741
+ response = self.s3.list_buckets()
742
+ buckets = response.get("Buckets", [])
743
+ bucket_data = [{"Name": b["Name"], "CreationDate": str(b["CreationDate"])} for b in buckets]
744
+ if not buckets:
745
+ print("No buckets found.")
746
+ else:
747
+ print("Available S3 buckets:")
748
+ for bucket in buckets:
749
+ print(f" - {bucket['Name']} (Created: {bucket['CreationDate']})")
750
+ print("[OK] Buckets listed successfully")
751
+ return bucket_data
752
+ except ClientError as e:
753
+ error_msg = f"Failed to list buckets: {e.response['Error']['Code']}"
754
+ print(f"[FAIL] {error_msg}")
755
+ raise ValueError(error_msg)
756
+ except Exception as e:
757
+ error_msg = f"Unexpected error while listing buckets: {str(e)}"
758
+ print(f"[FAIL] {error_msg}")
759
+ raise ValueError(error_msg)
760
+
761
+ def list_folder_contents(self, folder_prefix):
762
+ if self.verbose:
763
+ print(f"Starting to list folder contents for {folder_prefix}...")
764
+ try:
765
+ if not folder_prefix.endswith("/"):
766
+ folder_prefix += "/"
767
+ if self.verbose:
768
+ print(f"Adjusted prefix: {folder_prefix}")
769
+
770
+ response = self.s3.list_objects_v2(Bucket=self.bucket_name, Prefix=folder_prefix)
771
+ if "Contents" not in response:
772
+ all_files = self._list_all_files()
773
+ closest_match = self._find_closest_match(folder_prefix, all_files)
774
+ if closest_match:
775
+ print(f"Folder '{folder_prefix}' was not found.")
776
+ print(f"However, a similar folder was found: '{closest_match}'")
777
+ folder_prefix = closest_match
778
+ response = self.s3.list_objects_v2(Bucket=self.bucket_name, Prefix=folder_prefix)
779
+ else:
780
+ error_msg = f"Folder '{folder_prefix}' does not exist in bucket '{self.bucket_name}'."
781
+ print(f"[FAIL] {error_msg}")
782
+ raise ValueError(error_msg)
783
+ print(f"\nFiles in folder: {folder_prefix}\n")
784
+ for obj in response.get("Contents", []):
785
+ print(f" - {obj['Key']} (Last Modified: {obj['LastModified']})")
786
+ print("[OK] Folder contents listed successfully")
787
+ except ClientError as e:
788
+ error_msg = f"Failed to list folder contents: {e.response['Error']['Code']}"
789
+ print(f"[FAIL] {error_msg}")
790
+ raise ValueError(error_msg)
791
+ except Exception as e:
792
+ error_msg = f"Unexpected error while listing folder contents: {str(e)}"
793
+ print(f"[FAIL] {error_msg}")
794
+ raise ValueError(error_msg)
795
+
796
+ def list_available_buckets(self):
797
+ if self.verbose:
798
+ print("Starting to list available buckets...")
799
+ try:
800
+ response = self.s3.list_buckets()
801
+ buckets = [bucket["Name"] for bucket in response.get("Buckets", [])]
802
+ if not buckets:
803
+ print("No buckets found in Ceph S3.")
804
+ print("[OK] Available buckets listed successfully")
805
+ return buckets
806
+ except ClientError as e:
807
+ error_msg = f"Failed to list buckets: {e.response['Error']['Code']}"
808
+ print(f"[FAIL] {error_msg}")
809
+ raise ValueError(error_msg)
810
+ except Exception as e:
811
+ error_msg = f"Unexpected error while listing buckets: {str(e)}"
812
+ print(f"[FAIL] {error_msg}")
813
+ raise ValueError(error_msg)
814
+
815
+ def print_bucket_full_detail(self):
816
+ if self.verbose:
817
+ print("Starting to print full bucket details...")
818
+ try:
819
+ import json
820
+ response = self.s3.list_buckets()
821
+ print(json.dumps(response, indent=4, default=str))
822
+ print("[OK] Full bucket details printed successfully")
823
+ return response
824
+ except ClientError as e:
825
+ error_msg = f"Failed to retrieve bucket details: {e.response['Error']['Code']}"
826
+ print(f"[FAIL] {error_msg}")
827
+ raise ValueError(error_msg)
828
+ except Exception as e:
829
+ error_msg = f"Unexpected error while retrieving bucket details: {str(e)}"
830
+ print(f"[FAIL] {error_msg}")
831
+ raise ValueError(error_msg)
832
+
833
+ def print_bucket_short_detail(self):
834
+ if self.verbose:
835
+ print("Starting to print short bucket details...")
836
+ try:
837
+ from tabulate import tabulate
838
+ response = self.s3.list_buckets()
839
+ buckets = response.get("Buckets", [])
840
+ bucket_data = [[b["Name"], b["CreationDate"]] for b in buckets]
841
+ if bucket_data:
842
+ print("\nAvailable S3 Buckets:")
843
+ print(tabulate(bucket_data, headers=["Bucket Name", "Creation Date"], tablefmt="fancy_grid"))
844
+ else:
845
+ print("No buckets found.")
846
+ print("[OK] Short bucket details printed successfully")
847
+ except ImportError as e:
848
+ error_msg = "Tabulate library is not installed. Please install it using 'pip install tabulate'."
849
+ print(f"[FAIL] {error_msg}")
850
+ raise ValueError(error_msg)
851
+ except ClientError as e:
852
+ error_msg = f"Failed to retrieve bucket details: {e.response['Error']['Code']}"
853
+ print(f"[FAIL] {error_msg}")
854
+ raise ValueError(error_msg)
855
+ except Exception as e:
856
+ error_msg = f"Unexpected error while printing bucket details: {str(e)}"
857
+ print(f"[FAIL] {error_msg}")
858
+ raise ValueError(error_msg)
859
+
860
+ def find_file(self, file_or_folder_name):
861
+ if self.verbose:
862
+ print(f"Starting to find file or folder {file_or_folder_name}...")
863
+ try:
864
+ import difflib
865
+ import mimetypes
866
+
867
+ def list_all_files():
868
+ if self.verbose:
869
+ print("Listing all files for finding...")
870
+ response = self.s3.list_objects_v2(Bucket=self.bucket_name)
871
+ result = [obj["Key"] for obj in response.get("Contents", [])] if "Contents" in response else []
872
+ if self.verbose:
873
+ print(f"Listed {len(result)} files")
874
+ return result
875
+
876
+ def find_closest_match(target, file_list):
877
+ if self.verbose:
878
+ print(f"Finding closest match for {target}...")
879
+ matches = difflib.get_close_matches(target, file_list, n=1, cutoff=0.5)
880
+ result = matches[0] if matches else None
881
+ if result:
882
+ if self.verbose:
883
+ print(f"Closest match: {result}")
884
+ else:
885
+ if self.verbose:
886
+ print("No close match")
887
+ return result
888
+
889
+ def check_if_exists(key):
890
+ if self.verbose:
891
+ print(f"Checking existence of {key}...")
892
+ response = self.s3.list_objects_v2(Bucket=self.bucket_name, Prefix=key)
893
+ result = "Contents" in response
894
+ if self.verbose:
895
+ print(f"Existence: {result}")
896
+ return result
897
+
898
+ def list_folder_contents(prefix):
899
+ if not prefix.endswith("/"):
900
+ prefix += "/"
901
+ if self.verbose:
902
+ print(f"Listing folder contents for {prefix}...")
903
+ response = self.s3.list_objects_v2(Bucket=self.bucket_name, Prefix=prefix)
904
+ if "Contents" not in response:
905
+ closest_match = find_closest_match(prefix, list_all_files())
906
+ if closest_match:
907
+ error_msg = f"Folder '{prefix}' not found. Similar folder found: '{closest_match}'"
908
+ print(f"[FAIL] {error_msg}")
909
+ raise ValueError(error_msg)
910
+ error_msg = f"Folder '{prefix}' does not exist in bucket '{self.bucket_name}'."
911
+ print(f"[FAIL] {error_msg}")
912
+ raise ValueError(error_msg)
913
+ results = []
914
+ for obj in response.get("Contents", []):
915
+ results.append((obj["Key"], obj["LastModified"]))
916
+ if self.verbose:
917
+ print(f"Found {len(results)} items in folder")
918
+ return results
919
+
920
+ def read_file(key):
921
+ if self.verbose:
922
+ print(f"Reading file {key}...")
923
+ file_list = list_all_files()
924
+ if not check_if_exists(key):
925
+ closest_match = find_closest_match(key, file_list)
926
+ if closest_match:
927
+ error_msg = f"File '{key}' not found. Similar file found: '{closest_match}'"
928
+ print(f"[FAIL] {error_msg}")
929
+ raise ValueError(error_msg)
930
+ error_msg = f"File '{key}' does not exist in bucket '{self.bucket_name}'."
931
+ print(f"[FAIL] {error_msg}")
932
+ raise ValueError(error_msg)
933
+ file_type, _ = mimetypes.guess_type(key)
934
+ result = [(key, file_type or "Unknown file type")]
935
+ if self.verbose:
936
+ print("File read successfully")
937
+ return result
938
+
939
+ if file_or_folder_name.endswith("/") or "." not in file_or_folder_name:
940
+ results = list_folder_contents(file_or_folder_name)
941
+ print(f"\nFiles in folder: {file_or_folder_name}\n")
942
+ for key, last_modified in results:
943
+ print(f"- {key} (Last Modified: {last_modified})")
944
+ print("[OK] Folder finding completed successfully")
945
+ return results
946
+ results = read_file(file_or_folder_name)
947
+ print(f"\nFile Type Detected: {results[0][1]}\n")
948
+ print("[OK] File finding completed successfully")
949
+ return results
950
+ except ClientError as e:
951
+ error_msg = f"Failed to find file/folder: {e.response['Error']['Code']}"
952
+ print(f"[FAIL] {error_msg}")
953
+ raise ValueError(error_msg)
954
+ except Exception as e:
955
+ error_msg = f"Unexpected error while finding file/folder: {str(e)}"
956
+ print(f"[FAIL] {error_msg}")
957
+ raise ValueError(error_msg)
958
+
959
+ def list_model_classes(self):
960
+ if self.verbose:
961
+ print("Starting to list model classes...")
962
+ try:
963
+ response = self.s3.list_objects_v2(Bucket=self.bucket_name, Prefix="models/", Delimiter="/")
964
+ if "CommonPrefixes" not in response:
965
+ print(f"No models found in bucket '{self.bucket_name}'.")
966
+ if self.verbose:
967
+ print("Model classes list completed (empty)")
968
+ return []
969
+ model_classes = [prefix["Prefix"].split("/")[1] for prefix in response["CommonPrefixes"]]
970
+ if self.verbose:
971
+ print(f"Found model classes: {model_classes}")
972
+ print("[OK] Model classes listed successfully")
973
+ return model_classes
974
+ except ClientError as e:
975
+ error_msg = f"Failed to list model classes: {e.response['Error']['Code']}"
976
+ print(f"[FAIL] {error_msg}")
977
+ if e.response["Error"]["Code"] == "NoSuchBucket":
978
+ raise ValueError(f"Bucket '{self.bucket_name}' does not exist.")
979
+ raise ValueError(error_msg)
980
+ except Exception as e:
981
+ error_msg = f"Unexpected error while listing model classes: {str(e)}"
982
+ print(f"[FAIL] {error_msg}")
983
+ raise ValueError(error_msg)
984
+
985
+ def list_buckets_and_model_classes(self):
986
+ if self.verbose:
987
+ print("Starting to list buckets and model classes...")
988
+ try:
989
+ result = {}
990
+ if self.verbose:
991
+ print("Listing buckets...")
992
+ buckets = self.s3.list_buckets()
993
+ bucket_names = [bucket["Name"] for bucket in buckets.get("Buckets", [])]
994
+ if self.verbose:
995
+ print(f"Found buckets: {bucket_names}")
996
+
997
+ for bucket in bucket_names:
998
+ try:
999
+ if self.verbose:
1000
+ print(f"Processing bucket {bucket}...")
1001
+ response = self.s3.list_objects_v2(Bucket=bucket, Prefix="models/", Delimiter="/")
1002
+ if "CommonPrefixes" in response:
1003
+ model_classes = [prefix["Prefix"].split("/")[1] for prefix in response["CommonPrefixes"]]
1004
+ else:
1005
+ model_classes = []
1006
+ result[bucket] = model_classes
1007
+ print(f"Bucket: {bucket}")
1008
+ if model_classes:
1009
+ print(f" Model Classes: {model_classes}")
1010
+ else:
1011
+ print(" No model classes found")
1012
+ if self.verbose:
1013
+ print("-" * 40)
1014
+ except ClientError as e:
1015
+ code = e.response["Error"]["Code"]
1016
+ if code == "NoSuchBucket":
1017
+ print(f"Bucket '{bucket}' does not exist.")
1018
+ continue
1019
+ error_msg = f"Failed to list model classes for bucket '{bucket}': {code}"
1020
+ print(f"[FAIL] {error_msg}")
1021
+ raise ValueError(error_msg)
1022
+ print("[OK] Buckets and model classes listed successfully")
1023
+ return result
1024
+ except ClientError as e:
1025
+ error_msg = f"Failed to list buckets: {e.response['Error']['Code']}"
1026
+ print(f"[FAIL] {error_msg}")
1027
+ raise ValueError(error_msg)
1028
+ except Exception as e:
1029
+ error_msg = f"Unexpected error while listing buckets and model classes: {str(e)}"
1030
+ print(f"[FAIL] {error_msg}")
1031
+ raise ValueError(error_msg)
1032
+
1033
+ def list_models_and_versions(self):
1034
+ if self.verbose:
1035
+ print("Starting to list models and versions...")
1036
+ try:
1037
+ all_models = {}
1038
+ if self.verbose:
1039
+ print("Listing model classes...")
1040
+ response = self.s3.list_objects_v2(Bucket=self.bucket_name, Prefix="models/", Delimiter="/")
1041
+ if "CommonPrefixes" not in response:
1042
+ print(f"No models found in bucket '{self.bucket_name}'.")
1043
+ if self.verbose:
1044
+ print("Models and versions list completed (empty)")
1045
+ return all_models
1046
+ model_classes = [prefix["Prefix"].split("/")[1] for prefix in response["CommonPrefixes"]]
1047
+ if self.verbose:
1048
+ print(f"Model classes: {model_classes}")
1049
+
1050
+ for model_class in model_classes:
1051
+ all_models[model_class] = {}
1052
+ if self.verbose:
1053
+ print(f"Processing model class {model_class}...")
1054
+ response = self.s3.list_objects_v2(
1055
+ Bucket=self.bucket_name, Prefix=f"models/{model_class}/", Delimiter="/"
1056
+ )
1057
+ if "CommonPrefixes" not in response:
1058
+ continue
1059
+ models = [prefix["Prefix"].split("/")[2] for prefix in response["CommonPrefixes"]]
1060
+ if self.verbose:
1061
+ print(f"Models in {model_class}: {models}")
1062
+
1063
+ for model in models:
1064
+ if self.verbose:
1065
+ print(f"Processing model {model} in {model_class}...")
1066
+ version_response = self.s3.list_objects_v2(
1067
+ Bucket=self.bucket_name, Prefix=f"models/{model_class}/{model}/", Delimiter="/"
1068
+ )
1069
+ if "CommonPrefixes" in version_response:
1070
+ versions = [prefix["Prefix"].split("/")[-2] for prefix in version_response["CommonPrefixes"]]
1071
+ numeric_versions = sorted(
1072
+ [v for v in versions if v.startswith("model_v") and v[7:].isdigit()],
1073
+ key=lambda v: int(v[7:]),
1074
+ )
1075
+ non_numeric_versions = [v for v in versions if v not in numeric_versions]
1076
+ all_models[model_class][model] = numeric_versions + non_numeric_versions
1077
+ else:
1078
+ all_models[model_class][model] = []
1079
+ print(f"\nCategory: {model_class}")
1080
+ print(f" Model: {model}")
1081
+ print(
1082
+ f" Versions: {', '.join(all_models[model_class][model]) if all_models[model_class][model] else 'No versions found'}"
1083
+ )
1084
+ if self.verbose:
1085
+ print("-" * 50)
1086
+ print("[OK] Models and versions listed successfully")
1087
+ return all_models
1088
+ except ClientError as e:
1089
+ error_msg = f"Failed to list models and versions: {e.response['Error']['Code']}"
1090
+ print(f"[FAIL] {error_msg}")
1091
+ if e.response["Error"]["Code"] == "NoSuchBucket":
1092
+ raise ValueError(f"Bucket '{self.bucket_name}' does not exist.")
1093
+ raise ValueError(error_msg)
1094
+ except Exception as e:
1095
+ error_msg = f"Unexpected error while listing models and versions: {str(e)}"
1096
+ print(f"[FAIL] {error_msg}")
1097
+ raise ValueError(error_msg)
1098
+
1099
+ def is_folder(self, key):
1100
+ if self.verbose:
1101
+ print(f"Checking if {key} is a folder...")
1102
+ try:
1103
+ contents = self.check_if_exists(key)
1104
+ result = bool(contents) and any(obj["Key"] != key for obj in contents)
1105
+ if result:
1106
+ print(f"[OK] {key} is a folder")
1107
+ else:
1108
+ print(f"[OK] {key} is not a folder")
1109
+ if self.verbose:
1110
+ print("Folder check completed successfully")
1111
+ return result
1112
+ except Exception as e:
1113
+ error_msg = f"Unexpected error checking if {key} is folder: {str(e)}"
1114
+ print(f"[FAIL] {error_msg}")
1115
+ raise ValueError(error_msg)
1116
+
1117
+ def _download_file_with_progress_bar(self, remote_path, local_path):
1118
+ if self.verbose:
1119
+ print(f"Downloading file {remote_path} with progress bar to {local_path}...")
1120
+ try:
1121
+ if self.verbose:
1122
+ print("Fetching metadata...")
1123
+ meta_data = self.s3.head_object(Bucket=self.bucket_name, Key=remote_path)
1124
+ total_length = int(meta_data.get("ContentLength", 0))
1125
+ if self.verbose:
1126
+ print(f"Metadata fetched, total length: {total_length}")
1127
+
1128
+ except Exception as e:
1129
+ error_msg = f"Failed to fetch metadata for '{remote_path}': {str(e)}"
1130
+ print(f"[ERROR] {error_msg}")
1131
+ total_length = None
1132
+
1133
+ try:
1134
+ with tqdm(
1135
+ total=total_length,
1136
+ desc=os.path.basename(remote_path),
1137
+ unit="B",
1138
+ unit_scale=True,
1139
+ unit_divisor=1024,
1140
+ leave=False,
1141
+ dynamic_ncols=True,
1142
+ ncols=100,
1143
+ file=sys.stdout,
1144
+ ascii=True,
1145
+ ) as pbar:
1146
+ with open(local_path, "wb") as f:
1147
+ self.s3.download_fileobj(self.bucket_name, remote_path, f, Callback=pbar.update)
1148
+ print(f"[OK] File downloaded successfully to {local_path}")
1149
+ except Exception as e:
1150
+ error_msg = f"Error downloading file with progress: {str(e)}"
1151
+ print(f"[FAIL] {error_msg}")
1152
+ raise ValueError(error_msg)
1153
+
1154
+ def download_file(self, remote_path, local_path):
1155
+ if self.verbose:
1156
+ print(f"Starting to download file {remote_path} to {local_path}...")
1157
+ try:
1158
+ if os.path.isdir(local_path):
1159
+ local_path = os.path.join(local_path, os.path.basename(remote_path))
1160
+ if self.verbose:
1161
+ print(f"Adjusted local path to {local_path}")
1162
+
1163
+ if self.verbose:
1164
+ print("Creating directories if needed...")
1165
+ os.makedirs(os.path.dirname(local_path), exist_ok=True)
1166
+ if self.verbose:
1167
+ print("Directories ready")
1168
+
1169
+ self._download_file_with_progress_bar(remote_path, local_path)
1170
+ print(f"[OK] File {remote_path} downloaded successfully to {local_path}")
1171
+ except Exception as e:
1172
+ error_msg = f"Error downloading file '{remote_path}': {str(e)}"
1173
+ print(f"[FAIL] {error_msg}")
1174
+ raise ValueError(error_msg)
1175
+
1176
+ def download_folder(self, remote_folder, local_folder, keep_folder=False, exclude=[], overwrite=False):
1177
+ if self.verbose:
1178
+ print(f"Starting to download folder {remote_folder} to {local_folder} (keep_folder={keep_folder}, overwrite={overwrite})...")
1179
+ try:
1180
+ if not remote_folder.endswith("/"):
1181
+ remote_folder += "/"
1182
+ if self.verbose:
1183
+ print(f"Adjusted remote folder to {remote_folder}")
1184
+
1185
+ if self.verbose:
1186
+ print("Listing objects in folder...")
1187
+ resp = self.s3.list_objects_v2(Bucket=self.bucket_name, Prefix=remote_folder)
1188
+ if "Contents" not in resp:
1189
+ error_msg = f"Folder {remote_folder} not found"
1190
+ print(f"[FAIL] {error_msg}")
1191
+ raise ValueError(error_msg)
1192
+ if self.verbose:
1193
+ print(f"Found {len(resp['Contents'])} objects")
1194
+
1195
+ if self.verbose:
1196
+ print("Preparing local folder...")
1197
+ if keep_folder:
1198
+ local_folder = os.path.join(local_folder, remote_folder.split("/")[-2])
1199
+ if self.verbose:
1200
+ print(f"Adjusted local folder to {local_folder}")
1201
+ os.makedirs(local_folder, exist_ok=True)
1202
+ if self.verbose:
1203
+ print("Local folder ready")
1204
+
1205
+ with tqdm(total=len(resp["Contents"]), desc="Downloading") as pbar:
1206
+ for obj in resp["Contents"]:
1207
+ file_key = obj["Key"]
1208
+ relative_path = file_key[len(remote_folder):]
1209
+ if any(x in relative_path for x in exclude):
1210
+ if self.verbose:
1211
+ print(f"Skipped file {file_key}. File matches excluded pattern.")
1212
+ pbar.update(1)
1213
+ continue
1214
+ local_file_path = os.path.join(local_folder, relative_path)
1215
+ if not overwrite and os.path.exists(local_file_path):
1216
+ if self.verbose:
1217
+ print(f"Skipped file {file_key}. File already exists.")
1218
+ pbar.update(1)
1219
+ continue
1220
+ self.download_file(file_key, local_file_path)
1221
+ pbar.update(1)
1222
+ print(f"[OK] Folder {remote_folder} downloaded successfully")
1223
+ except Exception as e:
1224
+ error_msg = f"Error downloading folder {remote_folder}: {str(e)}"
1225
+ print(f"[FAIL] {error_msg}")
1226
+ raise ValueError(error_msg)
1227
+
1228
+ def download(self, remote_path, local_path, keep_folder=False, exclude=[], overwrite=False):
1229
+ if self.verbose:
1230
+ print(f"Starting download from {remote_path} to {local_path}...")
1231
+ try:
1232
+ if os.path.isfile(local_path) and self.is_folder(remote_path):
1233
+ error_msg = "Cannot download folder to file path"
1234
+ print(f"[FAIL] {error_msg}")
1235
+ raise ValueError(error_msg)
1236
+ if os.path.isdir(local_path) and not self.is_folder(remote_path):
1237
+ local_path = os.path.join(local_path, os.path.basename(remote_path))
1238
+ if self.verbose:
1239
+ print(f"Adjusted local path to {local_path}")
1240
+
1241
+ if self.is_folder(remote_path):
1242
+ self.download_folder(remote_path, local_path, keep_folder=keep_folder, exclude=exclude, overwrite=overwrite)
1243
+ else:
1244
+ self.download_file(remote_path, local_path)
1245
+ print("[OK] Download completed successfully")
1246
+ except Exception as e:
1247
+ error_msg = f"Error in download: {str(e)}"
1248
+ print(f"[FAIL] {error_msg}")
1249
+ raise ValueError(error_msg)
1250
+
1251
+ def upload_file(self, local_file_path, remote_path):
1252
+ if self.verbose:
1253
+ print(f"Starting to upload file {local_file_path} to {remote_path}...")
1254
+ try:
1255
+ self.s3.upload_file(local_file_path, self.bucket_name, remote_path)
1256
+ print(f"[OK] Uploaded {local_file_path} -> s3://{self.bucket_name}/{remote_path}")
1257
+ if self.verbose:
1258
+ print("File upload completed successfully")
1259
+ except Exception as e:
1260
+ error_msg = f"Failed to upload file {local_file_path} to {remote_path}: {str(e)}"
1261
+ print(f"[FAIL] {error_msg}")
1262
+ raise ValueError(error_msg)
1263
+
1264
+ def upload(self, local_path, remote_path):
1265
+ if self.verbose:
1266
+ print(f"Starting upload from {local_path} to {remote_path}...")
1267
+ try:
1268
+ if os.path.isfile(local_path) and self.is_folder(remote_path):
1269
+ error_msg = "Cannot upload file to folder path"
1270
+ print(f"[FAIL] {error_msg}")
1271
+ raise ValueError(error_msg)
1272
+ if os.path.isdir(local_path):
1273
+ if self.check_if_exists(remote_path) and not self.is_folder(remote_path):
1274
+ error_msg = "Cannot upload folder to file path"
1275
+ print(f"[FAIL] {error_msg}")
1276
+ raise ValueError(error_msg)
1277
+
1278
+ uploaded_files = []
1279
+ if self.check_s5cmd() and os.path.isdir(local_path):
1280
+ if self.verbose:
1281
+ print("Using s5cmd for upload...")
1282
+ cmd = ["s5cmd", "cp", f"{local_path}/*", f"s3://{self.bucket_name}/{remote_path}"]
1283
+ self.run_cmd(cmd)
1284
+ if self.verbose:
1285
+ print("s5cmd upload completed")
1286
+ else:
1287
+ if os.path.isdir(local_path):
1288
+ if self.verbose:
1289
+ print("Uploading folder files one by one...")
1290
+ for root, _, files in os.walk(local_path):
1291
+ for file in files:
1292
+ local_file = os.path.join(root, file)
1293
+ s3_key = os.path.join(remote_path, os.path.relpath(local_file, local_path)).replace("\\", "/")
1294
+ self.upload_file(local_file, s3_key)
1295
+ uploaded_files.append(s3_key)
1296
+ else:
1297
+ self.upload_file(local_path, remote_path)
1298
+ uploaded_files.append(remote_path)
1299
+
1300
+ uri = f"s3://{self.bucket_name}/{remote_path}"
1301
+ try:
1302
+ if self.verbose:
1303
+ print("Calculating uploaded size...")
1304
+ size_mb = self.get_uri_size(uri)
1305
+ print(f"[OK] Uploaded {local_path} to {uri}, size: {size_mb:.2f} MB")
1306
+ if self.verbose:
1307
+ print("Upload completed successfully")
1308
+ return size_mb
1309
+ except Exception as e:
1310
+ warning_msg = f"Failed to calculate size for {uri}: {str(e)}"
1311
+ print(f"[WARN] {warning_msg}")
1312
+ return None
1313
+ except Exception as e:
1314
+ error_msg = f"Upload failed: {str(e)}"
1315
+ print(f"[FAIL] {error_msg}")
1316
+ for s3_key in uploaded_files:
1317
+ try:
1318
+ if self.verbose:
1319
+ print(f"Cleaning up {s3_key}...")
1320
+ self.s3.delete_object(Bucket=self.bucket_name, Key=s3_key)
1321
+ print(f"[OK] Cleaned up {s3_key}")
1322
+ except Exception as cleanup_error:
1323
+ print(f"[ERROR] Failed to clean up {s3_key}: {str(cleanup_error)}")
1324
+ raise ValueError(error_msg)
1325
+
1326
+ def delete_folder(self, prefix):
1327
+ if self.verbose:
1328
+ print(f"Starting to delete folder {prefix}...")
1329
+ try:
1330
+ objects = self.check_if_exists(prefix)
1331
+ if objects:
1332
+ if self.verbose:
1333
+ print(f"Found {len(objects)} objects to delete")
1334
+ for obj in objects:
1335
+ if self.verbose:
1336
+ print(f"Deleting {obj['Key']}...")
1337
+ self.s3.delete_object(Bucket=self.bucket_name, Key=obj["Key"])
1338
+ if self.verbose:
1339
+ print(f"Deleted {obj['Key']}")
1340
+ print(f"[OK] Deleted s3://{self.bucket_name}/{prefix}")
1341
+ else:
1342
+ print("[WARN] No objects found to delete")
1343
+ print("[OK] Folder deleted successfully")
1344
+ except Exception as e:
1345
+ error_msg = f"Failed to delete folder {prefix}: {str(e)}"
1346
+ print(f"[FAIL] {error_msg}")
1347
+ raise ValueError(error_msg)
1348
+
1349
+ def move_folder(self, src_prefix, dest_prefix):
1350
+ if self.verbose:
1351
+ print(f"Starting to move folder from {src_prefix} to {dest_prefix}...")
1352
+ try:
1353
+ objects = self.check_if_exists(src_prefix)
1354
+ if not objects:
1355
+ error_msg = f"Source folder {src_prefix} does not exist"
1356
+ print(f"[FAIL] {error_msg}")
1357
+ raise ValueError(error_msg)
1358
+ if self.verbose:
1359
+ print(f"Found {len(objects)} objects to move")
1360
+
1361
+ for obj in objects:
1362
+ src_key = obj["Key"]
1363
+ dest_key = src_key.replace(src_prefix, dest_prefix, 1)
1364
+ if self.verbose:
1365
+ print(f"Copying {src_key} to {dest_key}...")
1366
+ self.s3.copy_object(Bucket=self.bucket_name, CopySource={'Bucket': self.bucket_name, 'Key': src_key}, Key=dest_key)
1367
+ if self.verbose:
1368
+ print(f"Copied {src_key}")
1369
+ if self.verbose:
1370
+ print(f"Deleting {src_key}...")
1371
+ self.s3.delete_object(Bucket=self.bucket_name, Key=src_key)
1372
+ if self.verbose:
1373
+ print(f"Deleted {src_key}")
1374
+
1375
+ print(f"[OK] Moved s3://{self.bucket_name}/{src_prefix} -> s3://{self.bucket_name}/{dest_prefix}")
1376
+ print("[OK] Folder moved successfully")
1377
+ except Exception as e:
1378
+ error_msg = f"Failed to move folder from {src_prefix} to {dest_prefix}: {str(e)}"
1379
+ print(f"[FAIL] {error_msg}")
1380
+ raise ValueError(error_msg)