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 CHANGED
@@ -16,4 +16,4 @@ View the `Introduction to Rendered.ai Documentation`_ to learn more.
16
16
  from .anaclient.anaclient import client
17
17
  from .annotations.annotations import annotations
18
18
 
19
- __version__ = '5.1.24'
19
+ __version__ = '5.1.25'
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 # Placeholder
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) # Exponential backoff
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'] == 'prod'
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: anatools
3
- Version: 5.1.24
3
+ Version: 5.1.25
4
4
  Summary: Tools for development with the Rendered.ai Platform.
5
5
  Home-page: https://rendered.ai
6
6
  Author: Rendered AI, Inc
@@ -1,4 +1,4 @@
1
- anatools/__init__.py,sha256=KVIC59BmwpX22GRK722YLUjO646VVk1kxz3YZBJrtYI,954
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=YRCZ09w6vYL1b9abN4QOQzEAFgStTXsHyFfJdwWhMqQ,28776
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.24.data/scripts/ana,sha256=qe7LDHRgJRPyomzAumdcYy0D5SuaUVag0N8SVevpxcU,5739
144
- anatools-5.1.24.data/scripts/anadeploy,sha256=udgzz-NlSvJcnypfprFoS-wDad3QrPfZNIYeE1TsqRE,38217
145
- anatools-5.1.24.data/scripts/anamount,sha256=AiaUgaaVVREFY2FLYRmSA8xgSwbfiF9NJYi91SWRA1I,6186
146
- anatools-5.1.24.data/scripts/anaprofile,sha256=1YUUwHiSa4ORQsxVf2HaSakayNTwMTNeF2snEMSJQAM,9779
147
- anatools-5.1.24.data/scripts/anarules,sha256=vT2V-77DoL-o6mApSMVXjqeXRIWm44X3jPXdcNK29N0,8972
148
- anatools-5.1.24.data/scripts/anaserver,sha256=QB1k_vhXAFGMOi9SNIFwgzkzN5LzJeVLtIVkp1oHq4I,8343
149
- anatools-5.1.24.data/scripts/anatransfer,sha256=GbMLjgA3TP4Oo2mbUxWnkkSC4nKpw1DWta-WVfcNftw,14564
150
- anatools-5.1.24.data/scripts/anautils,sha256=fziapZuKuBO0VKRgb4C4Js8p9zxxh8OHQmmkNdo3t3E,9530
151
- anatools-5.1.24.dist-info/licenses/LICENSE,sha256=aw0uaPvFzrHLJxBvuRqUcE2_srfM32-1suya9HbZCY8,1072
152
- anatools-5.1.24.dist-info/METADATA,sha256=704ROOBSZ5RG7vb3wVfFjb1d9i86pNyleoeOqkYEoR8,8218
153
- anatools-5.1.24.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
154
- anatools-5.1.24.dist-info/entry_points.txt,sha256=KsZUmvbH3HXC2CdVpE2GZNR2u_cJNVIbm6BnD658FgM,54
155
- anatools-5.1.24.dist-info/top_level.txt,sha256=p7xa5bG7NX8pSMJOvRunSz1d7rGPGsBd5-A4gzD4r6w,9
156
- anatools-5.1.24.dist-info/RECORD,,
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,,