anatools 5.1.24__py3-none-any.whl → 5.1.25__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.
- anatools/__init__.py +1 -1
- anatools/lib/transfer.py +121 -4
- {anatools-5.1.24.data → anatools-5.1.25.data}/scripts/anadeploy +1 -1
- {anatools-5.1.24.dist-info → anatools-5.1.25.dist-info}/METADATA +1 -1
- {anatools-5.1.24.dist-info → anatools-5.1.25.dist-info}/RECORD +16 -16
- {anatools-5.1.24.data → anatools-5.1.25.data}/scripts/ana +0 -0
- {anatools-5.1.24.data → anatools-5.1.25.data}/scripts/anamount +0 -0
- {anatools-5.1.24.data → anatools-5.1.25.data}/scripts/anaprofile +0 -0
- {anatools-5.1.24.data → anatools-5.1.25.data}/scripts/anarules +0 -0
- {anatools-5.1.24.data → anatools-5.1.25.data}/scripts/anaserver +0 -0
- {anatools-5.1.24.data → anatools-5.1.25.data}/scripts/anatransfer +0 -0
- {anatools-5.1.24.data → anatools-5.1.25.data}/scripts/anautils +0 -0
- {anatools-5.1.24.dist-info → anatools-5.1.25.dist-info}/WHEEL +0 -0
- {anatools-5.1.24.dist-info → anatools-5.1.25.dist-info}/entry_points.txt +0 -0
- {anatools-5.1.24.dist-info → anatools-5.1.25.dist-info}/licenses/LICENSE +0 -0
- {anatools-5.1.24.dist-info → anatools-5.1.25.dist-info}/top_level.txt +0 -0
anatools/__init__.py
CHANGED
anatools/lib/transfer.py
CHANGED
|
@@ -17,11 +17,13 @@ from threading import Lock
|
|
|
17
17
|
try:
|
|
18
18
|
import boto3
|
|
19
19
|
from botocore.exceptions import ClientError
|
|
20
|
+
from boto3.s3.transfer import TransferConfig
|
|
20
21
|
BOTO3_AVAILABLE = True
|
|
21
22
|
except ImportError:
|
|
22
23
|
BOTO3_AVAILABLE = False
|
|
23
24
|
boto3 = None
|
|
24
|
-
ClientError = Exception
|
|
25
|
+
ClientError = Exception
|
|
26
|
+
TransferConfig = None # Placeholder
|
|
25
27
|
|
|
26
28
|
from anatools.lib.print import print_color
|
|
27
29
|
|
|
@@ -479,9 +481,16 @@ def upload_file_to_s3(s3_client, local_file, bucket, key, max_retries=3):
|
|
|
479
481
|
Returns:
|
|
480
482
|
bool: True if successful, False otherwise
|
|
481
483
|
"""
|
|
484
|
+
config = TransferConfig(
|
|
485
|
+
multipart_threshold=100 * 1024 * 1024,
|
|
486
|
+
max_concurrency=10,
|
|
487
|
+
multipart_chunksize=100 * 1024 * 1024,
|
|
488
|
+
use_threads=True
|
|
489
|
+
)
|
|
490
|
+
|
|
482
491
|
for attempt in range(max_retries):
|
|
483
492
|
try:
|
|
484
|
-
s3_client.upload_file(local_file, bucket, key)
|
|
493
|
+
s3_client.upload_file(local_file, bucket, key, Config=config)
|
|
485
494
|
return True
|
|
486
495
|
except ClientError as e:
|
|
487
496
|
if attempt < max_retries - 1:
|
|
@@ -509,9 +518,16 @@ def download_file_from_s3(s3_client, bucket, key, local_file, max_retries=3):
|
|
|
509
518
|
# Create directory if it doesn't exist
|
|
510
519
|
os.makedirs(os.path.dirname(local_file), exist_ok=True)
|
|
511
520
|
|
|
521
|
+
config = TransferConfig(
|
|
522
|
+
multipart_threshold=100 * 1024 * 1024,
|
|
523
|
+
max_concurrency=10,
|
|
524
|
+
multipart_chunksize=100 * 1024 * 1024,
|
|
525
|
+
use_threads=True
|
|
526
|
+
)
|
|
527
|
+
|
|
512
528
|
for attempt in range(max_retries):
|
|
513
529
|
try:
|
|
514
|
-
s3_client.download_file(bucket, key, local_file)
|
|
530
|
+
s3_client.download_file(bucket, key, local_file, Config=config)
|
|
515
531
|
return True
|
|
516
532
|
except ClientError as e:
|
|
517
533
|
if attempt < max_retries - 1:
|
|
@@ -525,6 +541,7 @@ def download_file_from_s3(s3_client, bucket, key, local_file, max_retries=3):
|
|
|
525
541
|
def copy_s3_object(s3_client, source_bucket, source_key, dest_bucket, dest_key, max_retries=3):
|
|
526
542
|
"""
|
|
527
543
|
Copy an object within S3 (server-side copy).
|
|
544
|
+
Uses multipart copy for files >5GB to handle large files efficiently.
|
|
528
545
|
|
|
529
546
|
Args:
|
|
530
547
|
s3_client: boto3 S3 client
|
|
@@ -539,6 +556,18 @@ def copy_s3_object(s3_client, source_bucket, source_key, dest_bucket, dest_key,
|
|
|
539
556
|
"""
|
|
540
557
|
copy_source = {'Bucket': source_bucket, 'Key': source_key}
|
|
541
558
|
|
|
559
|
+
try:
|
|
560
|
+
head_response = s3_client.head_object(Bucket=source_bucket, Key=source_key)
|
|
561
|
+
file_size = head_response['ContentLength']
|
|
562
|
+
except ClientError as e:
|
|
563
|
+
print_color(f"Failed to get object metadata for s3://{source_bucket}/{source_key}: {e}", 'error')
|
|
564
|
+
return False
|
|
565
|
+
|
|
566
|
+
MULTIPART_THRESHOLD = 5 * 1024 * 1024 * 1024
|
|
567
|
+
|
|
568
|
+
if file_size >= MULTIPART_THRESHOLD:
|
|
569
|
+
return _multipart_copy_s3_object(s3_client, source_bucket, source_key, dest_bucket, dest_key, file_size, max_retries)
|
|
570
|
+
|
|
542
571
|
for attempt in range(max_retries):
|
|
543
572
|
try:
|
|
544
573
|
s3_client.copy_object(
|
|
@@ -549,13 +578,101 @@ def copy_s3_object(s3_client, source_bucket, source_key, dest_bucket, dest_key,
|
|
|
549
578
|
return True
|
|
550
579
|
except ClientError as e:
|
|
551
580
|
if attempt < max_retries - 1:
|
|
552
|
-
time.sleep(2 ** attempt)
|
|
581
|
+
time.sleep(2 ** attempt)
|
|
553
582
|
else:
|
|
554
583
|
print_color(f"Failed to copy s3://{source_bucket}/{source_key} to s3://{dest_bucket}/{dest_key}: {e}", 'error')
|
|
555
584
|
return False
|
|
556
585
|
return False
|
|
557
586
|
|
|
558
587
|
|
|
588
|
+
def _multipart_copy_s3_object(s3_client, source_bucket, source_key, dest_bucket, dest_key, file_size, max_retries=3):
|
|
589
|
+
"""
|
|
590
|
+
Perform multipart copy for large S3 objects (>5GB).
|
|
591
|
+
|
|
592
|
+
Args:
|
|
593
|
+
s3_client: boto3 S3 client
|
|
594
|
+
source_bucket: Source S3 bucket
|
|
595
|
+
source_key: Source S3 key
|
|
596
|
+
dest_bucket: Destination S3 bucket
|
|
597
|
+
dest_key: Destination S3 key
|
|
598
|
+
file_size: Size of the source object in bytes
|
|
599
|
+
max_retries: Maximum number of retry attempts
|
|
600
|
+
|
|
601
|
+
Returns:
|
|
602
|
+
bool: True if successful, False otherwise
|
|
603
|
+
"""
|
|
604
|
+
PART_SIZE = 500 * 1024 * 1024
|
|
605
|
+
|
|
606
|
+
try:
|
|
607
|
+
mpu = s3_client.create_multipart_upload(
|
|
608
|
+
Bucket=dest_bucket,
|
|
609
|
+
Key=dest_key
|
|
610
|
+
)
|
|
611
|
+
upload_id = mpu['UploadId']
|
|
612
|
+
|
|
613
|
+
parts = []
|
|
614
|
+
part_number = 1
|
|
615
|
+
bytes_copied = 0
|
|
616
|
+
|
|
617
|
+
while bytes_copied < file_size:
|
|
618
|
+
start_byte = bytes_copied
|
|
619
|
+
end_byte = min(bytes_copied + PART_SIZE - 1, file_size - 1)
|
|
620
|
+
|
|
621
|
+
for attempt in range(max_retries):
|
|
622
|
+
try:
|
|
623
|
+
part_response = s3_client.upload_part_copy(
|
|
624
|
+
Bucket=dest_bucket,
|
|
625
|
+
Key=dest_key,
|
|
626
|
+
PartNumber=part_number,
|
|
627
|
+
UploadId=upload_id,
|
|
628
|
+
CopySource={
|
|
629
|
+
'Bucket': source_bucket,
|
|
630
|
+
'Key': source_key
|
|
631
|
+
},
|
|
632
|
+
CopySourceRange=f'bytes={start_byte}-{end_byte}'
|
|
633
|
+
)
|
|
634
|
+
|
|
635
|
+
parts.append({
|
|
636
|
+
'ETag': part_response['CopyPartResult']['ETag'],
|
|
637
|
+
'PartNumber': part_number
|
|
638
|
+
})
|
|
639
|
+
break
|
|
640
|
+
except ClientError as e:
|
|
641
|
+
if attempt < max_retries - 1:
|
|
642
|
+
time.sleep(2 ** attempt)
|
|
643
|
+
else:
|
|
644
|
+
s3_client.abort_multipart_upload(
|
|
645
|
+
Bucket=dest_bucket,
|
|
646
|
+
Key=dest_key,
|
|
647
|
+
UploadId=upload_id
|
|
648
|
+
)
|
|
649
|
+
print_color(f"Failed to copy part {part_number} of s3://{source_bucket}/{source_key}: {e}", 'error')
|
|
650
|
+
return False
|
|
651
|
+
|
|
652
|
+
bytes_copied = end_byte + 1
|
|
653
|
+
part_number += 1
|
|
654
|
+
|
|
655
|
+
s3_client.complete_multipart_upload(
|
|
656
|
+
Bucket=dest_bucket,
|
|
657
|
+
Key=dest_key,
|
|
658
|
+
UploadId=upload_id,
|
|
659
|
+
MultipartUpload={'Parts': parts}
|
|
660
|
+
)
|
|
661
|
+
return True
|
|
662
|
+
|
|
663
|
+
except ClientError as e:
|
|
664
|
+
print_color(f"Failed multipart copy of s3://{source_bucket}/{source_key} to s3://{dest_bucket}/{dest_key}: {e}", 'error')
|
|
665
|
+
try:
|
|
666
|
+
s3_client.abort_multipart_upload(
|
|
667
|
+
Bucket=dest_bucket,
|
|
668
|
+
Key=dest_key,
|
|
669
|
+
UploadId=upload_id
|
|
670
|
+
)
|
|
671
|
+
except:
|
|
672
|
+
pass
|
|
673
|
+
return False
|
|
674
|
+
|
|
675
|
+
|
|
559
676
|
def local_to_s3_transfer(client, source_path, dest_volume_id, dest_prefix='',
|
|
560
677
|
recursive=True, parallel=10, dry_run=False, verbose=False):
|
|
561
678
|
"""
|
|
@@ -418,7 +418,7 @@ try:
|
|
|
418
418
|
if localservice.remotes and len(localservice.remotes) > 0:
|
|
419
419
|
for i, remote in enumerate(localservice.remotes):
|
|
420
420
|
try:
|
|
421
|
-
if 'environment' not in remote: remote['environment']
|
|
421
|
+
if 'environment' not in remote: remote['environment'] = 'prod'
|
|
422
422
|
if client.environment == remote['environment']:
|
|
423
423
|
s = client.get_services(serviceId=remote['serviceId'])[0]
|
|
424
424
|
organization = client.get_organizations(organizationId=s['organizationId'])[0]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
anatools/__init__.py,sha256=
|
|
1
|
+
anatools/__init__.py,sha256=NS0kdcxltaz_gtuNFFGIX74ZBHX6_4od1uo05xuipng,954
|
|
2
2
|
anatools/anacreate.py,sha256=wK1GKxGSzFdeQtqBkGhpai1sFjSJaKZ2686zrVJPBd0,5622
|
|
3
3
|
anatools/anaclient/__init__.py,sha256=tjCd-MMkWOjhjY1mlbGtHNhEdHLj-W687GiYJHsf_J0,50
|
|
4
4
|
anatools/anaclient/_menu.py,sha256=WSD8h7_SPRq9F6I0ohzkA4L0aLUx7L9YtIihk9eiD_o,5965
|
|
@@ -120,7 +120,7 @@ anatools/lib/scene.py,sha256=ANTcP_UVNQQ_6WjgmqHF6u_9HpJsElKpqszSUiN75u4,8339
|
|
|
120
120
|
anatools/lib/search_utils.py,sha256=DUu7fOIgTWPXItAjPlzfiMazrN9J8ytSjjKW2oWCLbw,3757
|
|
121
121
|
anatools/lib/service.py,sha256=4HfNBKIj3cHqKxD2fJcxlP8MMD1FozHayAsuoRGWURI,2720
|
|
122
122
|
anatools/lib/tools.py,sha256=C1vnbGU6yZJ9q1xsaUP6RDHTohfmm69KZRz7nIjrMLo,5847
|
|
123
|
-
anatools/lib/transfer.py,sha256=
|
|
123
|
+
anatools/lib/transfer.py,sha256=QZ47zD5MsjhBY5jaQMNI84WELUm4QGwFvCjFDy9LOJU,32788
|
|
124
124
|
anatools/nodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
125
125
|
anatools/nodes/constants.py,sha256=D0nikvblG1XR5umQFqAgbHjUeh9m8eEDiaYOqfInegk,1115
|
|
126
126
|
anatools/nodes/constants.yml,sha256=MZJjhUOFth5Mg8EPU-_wnQkJ9DqPJEzyiLsXyS9UVME,1285
|
|
@@ -140,17 +140,17 @@ anatools/nodes/volume_directory.py,sha256=oe721h7qOplRj6N6NpGuyY1HCM277NSYA-6Uo3
|
|
|
140
140
|
anatools/nodes/volume_directory.yml,sha256=MbyuLUlcqWIlQadYcn4Rvf6roypqh5IiP3dP57TilbY,901
|
|
141
141
|
anatools/nodes/volume_file.py,sha256=YA4zCyRvVzF_9mMefGx29JLE7o9i6-NPaC40BFGv4_c,557
|
|
142
142
|
anatools/nodes/volume_file.yml,sha256=i8bo9QeQmTLeWjv9Rh4EDoDqwOBULNPV7SMLO5AK8DI,862
|
|
143
|
-
anatools-5.1.
|
|
144
|
-
anatools-5.1.
|
|
145
|
-
anatools-5.1.
|
|
146
|
-
anatools-5.1.
|
|
147
|
-
anatools-5.1.
|
|
148
|
-
anatools-5.1.
|
|
149
|
-
anatools-5.1.
|
|
150
|
-
anatools-5.1.
|
|
151
|
-
anatools-5.1.
|
|
152
|
-
anatools-5.1.
|
|
153
|
-
anatools-5.1.
|
|
154
|
-
anatools-5.1.
|
|
155
|
-
anatools-5.1.
|
|
156
|
-
anatools-5.1.
|
|
143
|
+
anatools-5.1.25.data/scripts/ana,sha256=qe7LDHRgJRPyomzAumdcYy0D5SuaUVag0N8SVevpxcU,5739
|
|
144
|
+
anatools-5.1.25.data/scripts/anadeploy,sha256=GW0J6YzEvGY2TMbyzkfi-4Ov_gsw4qz2hHJwRx2o7nc,38216
|
|
145
|
+
anatools-5.1.25.data/scripts/anamount,sha256=AiaUgaaVVREFY2FLYRmSA8xgSwbfiF9NJYi91SWRA1I,6186
|
|
146
|
+
anatools-5.1.25.data/scripts/anaprofile,sha256=1YUUwHiSa4ORQsxVf2HaSakayNTwMTNeF2snEMSJQAM,9779
|
|
147
|
+
anatools-5.1.25.data/scripts/anarules,sha256=vT2V-77DoL-o6mApSMVXjqeXRIWm44X3jPXdcNK29N0,8972
|
|
148
|
+
anatools-5.1.25.data/scripts/anaserver,sha256=QB1k_vhXAFGMOi9SNIFwgzkzN5LzJeVLtIVkp1oHq4I,8343
|
|
149
|
+
anatools-5.1.25.data/scripts/anatransfer,sha256=GbMLjgA3TP4Oo2mbUxWnkkSC4nKpw1DWta-WVfcNftw,14564
|
|
150
|
+
anatools-5.1.25.data/scripts/anautils,sha256=fziapZuKuBO0VKRgb4C4Js8p9zxxh8OHQmmkNdo3t3E,9530
|
|
151
|
+
anatools-5.1.25.dist-info/licenses/LICENSE,sha256=aw0uaPvFzrHLJxBvuRqUcE2_srfM32-1suya9HbZCY8,1072
|
|
152
|
+
anatools-5.1.25.dist-info/METADATA,sha256=Pg2TjtE7TGhheLDoVYgyLS3qbZdcc5Iltr969jmhMC0,8218
|
|
153
|
+
anatools-5.1.25.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
154
|
+
anatools-5.1.25.dist-info/entry_points.txt,sha256=KsZUmvbH3HXC2CdVpE2GZNR2u_cJNVIbm6BnD658FgM,54
|
|
155
|
+
anatools-5.1.25.dist-info/top_level.txt,sha256=p7xa5bG7NX8pSMJOvRunSz1d7rGPGsBd5-A4gzD4r6w,9
|
|
156
|
+
anatools-5.1.25.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|