anatools 5.1.23__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.23'
19
+ __version__ = '5.1.25'
@@ -46,10 +46,16 @@ def getVolumeData(self, volumeId, keys=[], dir=None, recursive=False, cursor=Non
46
46
  "cursor": cursor,
47
47
  "filters": filters
48
48
  },
49
- "query": f"""query
49
+ "query": f"""query
50
50
  getVolumeData($volumeId: String!, $keys: [String], $dir: String, $recursive: Boolean, $limit: Int, $cursor: String, $filters: VolumeDataFilter) {{
51
51
  getVolumeData(volumeId: $volumeId, keys: $keys, dir: $dir, recursive: $recursive, limit: $limit, cursor: $cursor, filters: $filters) {{
52
52
  keys {{{fields}}}
53
+ pageInfo {{
54
+ totalItems
55
+ cursor
56
+ offset
57
+ limit
58
+ }}
53
59
  }}
54
60
  }}"""})
55
61
  return self.errorhandler(response, "getVolumeData")
@@ -144,17 +144,17 @@ def get_volume_data(self, volumeId, files=None, dir=None, recursive=False, curso
144
144
  self.check_logout()
145
145
  if volumeId is None: raise Exception('The volumeId parameter is required!')
146
146
  if limit is not None and limit <= 100: items = limit
147
+ else: items = 100
147
148
  if files is None: files = []
148
149
  if dir is None: dir = ''
149
- if cursor is None: cursor = 0
150
- else: items = 100
151
150
  volumedata = []
152
151
  while True:
153
152
  if limit and len(volumedata) + items > limit: items = limit - len(volumedata)
154
- ret = self.ana_api.getVolumeData(volumeId=volumeId, keys=files, dir=dir, recursive=recursive, cursor=str(cursor), limit=limit)
155
- volumedata.extend(ret)
156
- if len(ret) < items or len(volumedata) == limit: break
157
- cursor += items
153
+ ret = self.ana_api.getVolumeData(volumeId=volumeId, keys=files, dir=dir, recursive=recursive, cursor=cursor, limit=items)
154
+ volumedata.extend(ret['keys'])
155
+ if len(ret['keys']) < items or len(volumedata) == limit: break
156
+ cursor = ret['pageInfo']['cursor'] if ret.get('pageInfo') and ret['pageInfo'].get('cursor') else None
157
+ if cursor is None: break
158
158
  return volumedata
159
159
 
160
160
 
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
  """
@@ -321,6 +321,7 @@ try:
321
321
  if selection == str(options-2): remotechannel = create_channel(client=client, organization=remoteorganization, name=args.channel.split('/')[-1].split('.')[0], volumes=volumes)
322
322
  else:
323
323
  channels = sorted(client.get_channels(organizationId=remoteorganization['organizationId']), key=lambda x: x['name'].lower())
324
+ channels = [c for c in channels if c['organizationId'] == remoteorganization['organizationId']]
324
325
  if len(channels) > 0:
325
326
  print_color("Choose a channel to deploy to: ", color='brand')
326
327
  for i, channel in enumerate(channels):
@@ -417,7 +418,7 @@ try:
417
418
  if localservice.remotes and len(localservice.remotes) > 0:
418
419
  for i, remote in enumerate(localservice.remotes):
419
420
  try:
420
- if 'environment' not in remote: remote['environment'] == 'prod'
421
+ if 'environment' not in remote: remote['environment'] = 'prod'
421
422
  if client.environment == remote['environment']:
422
423
  s = client.get_services(serviceId=remote['serviceId'])[0]
423
424
  organization = client.get_organizations(organizationId=s['organizationId'])[0]
@@ -480,6 +481,7 @@ try:
480
481
  if selection == str(options-2): remoteservice = create_service(client=client, organization=remoteorganization, name=localservice.name, volumes=volumes)
481
482
  else:
482
483
  services = sorted(client.get_services(organizationId=remoteorganization['organizationId']), key=lambda x: x['name'].lower())
484
+ services = [s for s in services if s['organizationId'] == remoteorganization['organizationId']]
483
485
  if len(services) > 0:
484
486
  print_color("Choose a service to deploy to: ", color='brand')
485
487
  for i, service in enumerate(services):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: anatools
3
- Version: 5.1.23
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=oCvITQd3wdDEjbgafcqf2cV8z4u76klewKrwnoCrtUY,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
@@ -22,7 +22,7 @@ anatools/anaclient/preview.py,sha256=524-If_otuRAuwJtVRcQxL8G0wB-1i_DbgYoi_yePXI
22
22
  anatools/anaclient/rules.py,sha256=BA5KHLfyh4ddQYRdGmRwX-hGITL3GolMVzDuBX-hZno,3903
23
23
  anatools/anaclient/services.py,sha256=sRRdxM7ZEUfTDKU4700C9KcKsWY8AUTaw210pulwML4,18367
24
24
  anatools/anaclient/umap.py,sha256=Dy37yqoNb8HQWPmCh8ZqEIBMijSTqv9tcRcfO0_IXWQ,4144
25
- anatools/anaclient/volumes.py,sha256=f_4y7Xa7jMRRW0a2QeVReTqyIHTLs2m319_NanXsc0U,21346
25
+ anatools/anaclient/volumes.py,sha256=7NOBC1g_UV5miVLgaQ_LRgoyWiSWBvA-3381f0gVV6A,21442
26
26
  anatools/anaclient/workspaces.py,sha256=4aUBfqfW7jzlb6P55LcA0IHmZoNzIWej9tL0DY2pNog,7956
27
27
  anatools/anaclient/api/__init__.py,sha256=SrGDhIFobFBhOlE-hOtnJvdrnBAuypG-Im_oyzSPa3E,39
28
28
  anatools/anaclient/api/analytics.py,sha256=AFeJj_pl1h70oki5teFDGYRgPIOwHn7yJB-TYNouZsc,3666
@@ -46,7 +46,7 @@ anatools/anaclient/api/preview.py,sha256=OUN4MkYvC0HK6_3ZDxq7jayjPyJpxtgkHhEytmY
46
46
  anatools/anaclient/api/rules.py,sha256=yvxbk48m0ZZrWoHpmwS6kC4E2m7iSaY7iaLp0d6xmoc,4780
47
47
  anatools/anaclient/api/services.py,sha256=htSjiRz7-kiDivt-xl5wtCSBZPLjDrPjKMKVHcdAM_o,11459
48
48
  anatools/anaclient/api/umap.py,sha256=iMRE_z4Umg4-U_uEvdxho93QXOocF3-lN96qHfgbu64,3490
49
- anatools/anaclient/api/volumes.py,sha256=ZpNGIpO722HFyK2e2OkYO0di-D99-kgCduEYiE36r1Y,9356
49
+ anatools/anaclient/api/volumes.py,sha256=gOc-RPg7I988uKWk38w-DJIPM1Ltgby_wkrpwL_f-fE,9555
50
50
  anatools/anaclient/api/workspaces.py,sha256=kIXQAkznBGab38v5aaYcGwP1AnOp4PgID33lPMseRvA,8711
51
51
  anatools/anaclient/tests/__init__.py,sha256=uEOcjK2hof0s7RItreGywkCIULWxgcdKbI95JjrEGzk,104
52
52
  anatools/anaclient/tests/agents_test.py,sha256=dUrVzb4WmfAE_0Uu5V1zSdW4MUA4I1t1IfNgXTdP7rA,361
@@ -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.23.data/scripts/ana,sha256=qe7LDHRgJRPyomzAumdcYy0D5SuaUVag0N8SVevpxcU,5739
144
- anatools-5.1.23.data/scripts/anadeploy,sha256=zmyxXblJBftJpyaCU2osRjFtNG0Lfc0C89f07I5baI0,37985
145
- anatools-5.1.23.data/scripts/anamount,sha256=AiaUgaaVVREFY2FLYRmSA8xgSwbfiF9NJYi91SWRA1I,6186
146
- anatools-5.1.23.data/scripts/anaprofile,sha256=1YUUwHiSa4ORQsxVf2HaSakayNTwMTNeF2snEMSJQAM,9779
147
- anatools-5.1.23.data/scripts/anarules,sha256=vT2V-77DoL-o6mApSMVXjqeXRIWm44X3jPXdcNK29N0,8972
148
- anatools-5.1.23.data/scripts/anaserver,sha256=QB1k_vhXAFGMOi9SNIFwgzkzN5LzJeVLtIVkp1oHq4I,8343
149
- anatools-5.1.23.data/scripts/anatransfer,sha256=GbMLjgA3TP4Oo2mbUxWnkkSC4nKpw1DWta-WVfcNftw,14564
150
- anatools-5.1.23.data/scripts/anautils,sha256=fziapZuKuBO0VKRgb4C4Js8p9zxxh8OHQmmkNdo3t3E,9530
151
- anatools-5.1.23.dist-info/licenses/LICENSE,sha256=aw0uaPvFzrHLJxBvuRqUcE2_srfM32-1suya9HbZCY8,1072
152
- anatools-5.1.23.dist-info/METADATA,sha256=hdvNRbuUCsV4PI-cX4v1wU8lDBGTLghohV8XJDFe4gw,8218
153
- anatools-5.1.23.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
154
- anatools-5.1.23.dist-info/entry_points.txt,sha256=KsZUmvbH3HXC2CdVpE2GZNR2u_cJNVIbm6BnD658FgM,54
155
- anatools-5.1.23.dist-info/top_level.txt,sha256=p7xa5bG7NX8pSMJOvRunSz1d7rGPGsBd5-A4gzD4r6w,9
156
- anatools-5.1.23.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,,