dayhoff-tools 1.3.7__py3-none-any.whl → 1.3.8__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.
@@ -27,10 +27,10 @@ console = Console()
27
27
  # Cost information
28
28
  HOURLY_COSTS = {
29
29
  "cpu": 0.50, # r6i.2xlarge
30
- "cpumax": 1.00, # r7i.8xlarge
31
- "t4": 1.00, # g4dn.2xlarge
32
- "a10g": 2.00, # g5.2xlarge
33
- "a100": 5.00, # p4d.24xlarge
30
+ "cpumax": 2.02, # r7i.8xlarge
31
+ "t4": 0.75, # g4dn.2xlarge
32
+ "a10g": 1.50, # g5.2xlarge
33
+ "a100": 21.96, # p4d.24xlarge
34
34
  }
35
35
 
36
36
  # SSH config management
@@ -90,7 +90,10 @@ def get_api_url() -> str:
90
90
 
91
91
 
92
92
  def make_api_request(
93
- method: str, endpoint: str, json_data: Optional[Dict] = None, params: Optional[Dict] = None
93
+ method: str,
94
+ endpoint: str,
95
+ json_data: Optional[Dict] = None,
96
+ params: Optional[Dict] = None,
94
97
  ) -> requests.Response:
95
98
  """Make an API request with error handling."""
96
99
  api_url = get_api_url()
@@ -134,14 +137,14 @@ def parse_launch_time(launch_time_str: str) -> datetime:
134
137
  "%Y-%m-%dT%H:%M:%S+00:00", # Explicit UTC offset
135
138
  "%Y-%m-%d %H:%M:%S",
136
139
  ]
137
-
140
+
138
141
  # First try parsing with fromisoformat for better timezone handling
139
142
  try:
140
143
  # Handle the ISO format properly
141
- return datetime.fromisoformat(launch_time_str.replace('Z', '+00:00'))
144
+ return datetime.fromisoformat(launch_time_str.replace("Z", "+00:00"))
142
145
  except (ValueError, AttributeError):
143
146
  pass
144
-
147
+
145
148
  # Fallback to manual format parsing
146
149
  for fmt in formats:
147
150
  try:
@@ -152,7 +155,7 @@ def parse_launch_time(launch_time_str: str) -> datetime:
152
155
  return parsed
153
156
  except ValueError:
154
157
  continue
155
-
158
+
156
159
  # Fallback: assume it's recent
157
160
  return datetime.now(timezone.utc)
158
161
 
@@ -213,7 +216,9 @@ def resolve_engine(name_or_id: str, engines: List[Dict]) -> Dict:
213
216
  while True:
214
217
  try:
215
218
  choice = IntPrompt.ask(
216
- "Select engine", default=1, choices=[str(i) for i in range(1, len(matches) + 1)]
219
+ "Select engine",
220
+ default=1,
221
+ choices=[str(i) for i in range(1, len(matches) + 1)],
217
222
  )
218
223
  return matches[choice - 1]
219
224
  except (ValueError, IndexError):
@@ -307,7 +312,9 @@ def launch_engine(
307
312
  raise typer.Exit(1)
308
313
 
309
314
  cost = HOURLY_COSTS.get(engine_type, 0)
310
- console.print(f"Launching [cyan]{name}[/cyan] ({engine_type}) for ${cost:.2f}/hour...")
315
+ console.print(
316
+ f"Launching [cyan]{name}[/cyan] ({engine_type}) for ${cost:.2f}/hour..."
317
+ )
311
318
 
312
319
  with Progress(
313
320
  SpinnerColumn(),
@@ -337,8 +344,12 @@ def launch_engine(
337
344
  @engine_app.command("list")
338
345
  def list_engines(
339
346
  user: Optional[str] = typer.Option(None, "--user", "-u", help="Filter by user"),
340
- running_only: bool = typer.Option(False, "--running", help="Show only running engines"),
341
- stopped_only: bool = typer.Option(False, "--stopped", help="Show only stopped engines"),
347
+ running_only: bool = typer.Option(
348
+ False, "--running", help="Show only running engines"
349
+ ),
350
+ stopped_only: bool = typer.Option(
351
+ False, "--stopped", help="Show only stopped engines"
352
+ ),
342
353
  ):
343
354
  """List engines (shows all engines by default)."""
344
355
  current_user = check_aws_sso()
@@ -425,7 +436,9 @@ def engine_status(
425
436
  engine = resolve_engine(name_or_id, engines)
426
437
 
427
438
  # Get attached studios info
428
- response = make_api_request("GET", f"/engines/{engine['instance_id']}/attached-studios")
439
+ response = make_api_request(
440
+ "GET", f"/engines/{engine['instance_id']}/attached-studios"
441
+ )
429
442
  attached_studios = []
430
443
  if response.status_code == 200:
431
444
  attached_studios = response.json().get("studios", [])
@@ -468,7 +481,9 @@ def engine_status(
468
481
  @engine_app.command("stop")
469
482
  def stop_engine(
470
483
  name_or_id: str = typer.Argument(help="Engine name or instance ID"),
471
- force: bool = typer.Option(False, "--force", "-f", help="Force stop and detach all studios"),
484
+ force: bool = typer.Option(
485
+ False, "--force", "-f", help="Force stop and detach all studios"
486
+ ),
472
487
  ):
473
488
  """Stop an engine."""
474
489
  check_aws_sso()
@@ -568,7 +583,9 @@ def terminate_engine(
568
583
  hourly_cost = HOURLY_COSTS.get(engine["engine_type"], 0)
569
584
  total_cost = hourly_cost * (uptime.total_seconds() / 3600)
570
585
 
571
- console.print(f"\n[yellow]⚠️ This will permanently terminate engine '{engine['name']}'[/yellow]")
586
+ console.print(
587
+ f"\n[yellow]⚠️ This will permanently terminate engine '{engine['name']}'[/yellow]"
588
+ )
572
589
  console.print(f"Total cost for this session: ${total_cost:.2f}")
573
590
 
574
591
  if not Confirm.ask("\nAre you sure you want to terminate this engine?"):
@@ -616,7 +633,9 @@ def ssh_engine(
616
633
  @engine_app.command("config-ssh")
617
634
  def config_ssh(
618
635
  clean: bool = typer.Option(False, "--clean", help="Remove all managed entries"),
619
- all_engines: bool = typer.Option(False, "--all", "-a", help="Include all engines from all users"),
636
+ all_engines: bool = typer.Option(
637
+ False, "--all", "-a", help="Include all engines from all users"
638
+ ),
620
639
  ):
621
640
  """Update SSH config with available engines."""
622
641
  username = check_aws_sso()
@@ -627,7 +646,9 @@ def config_ssh(
627
646
  if all_engines:
628
647
  console.print("Updating SSH config with all running engines...")
629
648
  else:
630
- console.print(f"Updating SSH config with running engines for [cyan]{username}[/cyan] and [cyan]shared[/cyan]...")
649
+ console.print(
650
+ f"Updating SSH config with running engines for [cyan]{username}[/cyan] and [cyan]shared[/cyan]..."
651
+ )
631
652
 
632
653
  # Get all engines
633
654
  response = make_api_request("GET", "/engines")
@@ -637,13 +658,12 @@ def config_ssh(
637
658
 
638
659
  engines = response.json().get("engines", [])
639
660
  running_engines = [e for e in engines if e["state"].lower() == "running"]
640
-
661
+
641
662
  # Filter engines based on options
642
663
  if not all_engines:
643
664
  # Show only current user's engines and shared engines
644
665
  running_engines = [
645
- e for e in running_engines
646
- if e["user"] == username or e["user"] == "shared"
666
+ e for e in running_engines if e["user"] == username or e["user"] == "shared"
647
667
  ]
648
668
 
649
669
  # Read existing config
@@ -680,7 +700,7 @@ def config_ssh(
680
700
  f"Host {engine['name']} {SSH_MANAGED_COMMENT}",
681
701
  f" HostName {engine['instance_id']}",
682
702
  f" User {username}",
683
- f' ProxyCommand sh -c "aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters \'portNumber=%p\'"',
703
+ f" ProxyCommand sh -c \"aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p'\"",
684
704
  ]
685
705
  )
686
706
 
@@ -695,8 +715,12 @@ def config_ssh(
695
715
  f"[green]✓ Updated SSH config with {len(running_engines)} engines[/green]"
696
716
  )
697
717
  for engine in running_engines:
698
- user_display = f"[dim]({engine['user']})[/dim]" if engine['user'] != username else ""
699
- console.print(f"{engine['name']} {engine['instance_id']} {user_display}")
718
+ user_display = (
719
+ f"[dim]({engine['user']})[/dim]" if engine["user"] != username else ""
720
+ )
721
+ console.print(
722
+ f" • {engine['name']} → {engine['instance_id']} {user_display}"
723
+ )
700
724
 
701
725
 
702
726
  @engine_app.command("keep-awake")
@@ -709,6 +733,7 @@ def keep_awake(
709
733
 
710
734
  # Parse duration
711
735
  import re
736
+
712
737
  match = re.match(r"(?:(\d+)h)?(?:(\d+)m)?", duration)
713
738
  if not match or (not match.group(1) and not match.group(2)):
714
739
  console.print(f"[red]❌ Invalid duration format: {duration}[/red]")
@@ -752,6 +777,7 @@ def keep_awake(
752
777
 
753
778
  # Wait for command to complete
754
779
  import time
780
+
755
781
  for _ in range(10):
756
782
  time.sleep(1)
757
783
  result = ssm.get_command_invocation(
@@ -770,7 +796,9 @@ def keep_awake(
770
796
  "[dim]Use keep-awake for nohup operations or other background tasks.[/dim]"
771
797
  )
772
798
  else:
773
- console.print(f"[red]❌ Failed to set keep-awake: {result.get('StatusDetails', 'Unknown error')}[/red]")
799
+ console.print(
800
+ f"[red]❌ Failed to set keep-awake: {result.get('StatusDetails', 'Unknown error')}[/red]"
801
+ )
774
802
 
775
803
  except ClientError as e:
776
804
  console.print(f"[red]❌ Failed to set keep-awake: {e}[/red]")
@@ -810,6 +838,7 @@ def cancel_keep_awake(
810
838
 
811
839
  # Wait for command to complete
812
840
  import time
841
+
813
842
  for _ in range(10):
814
843
  time.sleep(1)
815
844
  result = ssm.get_command_invocation(
@@ -820,9 +849,13 @@ def cancel_keep_awake(
820
849
  break
821
850
 
822
851
  if result["Status"] == "Success":
823
- console.print("[green]✓ Keep-awake cancelled, auto-shutdown re-enabled[/green]")
852
+ console.print(
853
+ "[green]✓ Keep-awake cancelled, auto-shutdown re-enabled[/green]"
854
+ )
824
855
  else:
825
- console.print(f"[red]❌ Failed to cancel keep-awake: {result.get('StatusDetails', 'Unknown error')}[/red]")
856
+ console.print(
857
+ f"[red]❌ Failed to cancel keep-awake: {result.get('StatusDetails', 'Unknown error')}[/red]"
858
+ )
826
859
 
827
860
  except ClientError as e:
828
861
  console.print(f"[red]❌ Failed to cancel keep-awake: {e}[/red]")
@@ -830,7 +863,9 @@ def cancel_keep_awake(
830
863
 
831
864
  @engine_app.command("create-ami")
832
865
  def create_ami(
833
- name_or_id: str = typer.Argument(help="Engine name or instance ID to create AMI from"),
866
+ name_or_id: str = typer.Argument(
867
+ help="Engine name or instance ID to create AMI from"
868
+ ),
834
869
  ):
835
870
  """Create a Golden AMI from an engine."""
836
871
  check_aws_sso()
@@ -859,7 +894,9 @@ def create_ami(
859
894
 
860
895
  console.print(f"AMI Name: [cyan]{ami_name}[/cyan]")
861
896
  console.print(f"Description: {ami_description}")
862
- console.print("\n[yellow]⚠️ Important: This will reboot the engine to ensure a clean snapshot.[/yellow]")
897
+ console.print(
898
+ "\n[yellow]⚠️ Important: This will reboot the engine to ensure a clean snapshot.[/yellow]"
899
+ )
863
900
 
864
901
  if not Confirm.ask("\nContinue with AMI creation?"):
865
902
  console.print("AMI creation cancelled.")
@@ -872,7 +909,7 @@ def create_ami(
872
909
  # First, we need to clean up the sentinel file via SSM
873
910
  console.print("Cleaning up bootstrap sentinel file...")
874
911
  ssm = boto3.client("ssm", region_name="us-east-1")
875
-
912
+
876
913
  cleanup_response = ssm.send_command(
877
914
  InstanceIds=[engine["instance_id"]],
878
915
  DocumentName="AWS-RunShellScript",
@@ -888,6 +925,7 @@ def create_ami(
888
925
 
889
926
  # Wait for cleanup to complete
890
927
  import time
928
+
891
929
  command_id = cleanup_response["Command"]["CommandId"]
892
930
  for _ in range(10):
893
931
  time.sleep(1)
@@ -899,21 +937,25 @@ def create_ami(
899
937
  break
900
938
 
901
939
  if result["Status"] != "Success":
902
- console.print("[yellow]⚠️ Warning: Cleanup command may have failed[/yellow]")
940
+ console.print(
941
+ "[yellow]⚠️ Warning: Cleanup command may have failed[/yellow]"
942
+ )
903
943
 
904
944
  # Get instance details to find volumes to exclude
905
945
  instances = ec2.describe_instances(InstanceIds=[engine["instance_id"]])
906
946
  instance = instances["Reservations"][0]["Instances"][0]
907
-
947
+
908
948
  root_device = instance.get("RootDeviceName", "/dev/xvda")
909
949
  block_mappings = instance.get("BlockDeviceMappings", [])
910
-
950
+
911
951
  # Build exclusion list for non-root volumes
912
952
  block_device_mappings = []
913
953
  for mapping in block_mappings:
914
954
  device_name = mapping.get("DeviceName", "")
915
955
  if device_name != root_device:
916
- block_device_mappings.append({"DeviceName": device_name, "NoDevice": ""})
956
+ block_device_mappings.append(
957
+ {"DeviceName": device_name, "NoDevice": ""}
958
+ )
917
959
  console.print(f" Excluding volume at {device_name}")
918
960
 
919
961
  # Create the AMI
@@ -922,7 +964,9 @@ def create_ami(
922
964
  TextColumn("[progress.description]{task.description}"),
923
965
  transient=True,
924
966
  ) as progress:
925
- progress.add_task("Creating AMI (this will take several minutes)...", total=None)
967
+ progress.add_task(
968
+ "Creating AMI (this will take several minutes)...", total=None
969
+ )
926
970
 
927
971
  create_params = {
928
972
  "InstanceId": engine["instance_id"],
@@ -950,7 +994,7 @@ def create_ami(
950
994
  ami_id = response["ImageId"]
951
995
  console.print(f"[green]✓ AMI creation initiated![/green]")
952
996
  console.print(f"AMI ID: [cyan]{ami_id}[/cyan]")
953
-
997
+
954
998
  # Restore the source engine to a normal state
955
999
  console.print("Restoring source engine state...")
956
1000
  restore_response = ssm.send_command(
@@ -973,13 +1017,20 @@ def create_ami(
973
1017
  InstanceId=engine["instance_id"],
974
1018
  )
975
1019
  if result["Status"] not in ["Pending", "InProgress", "Success"]:
976
- console.print("[yellow]⚠️ Warning: Failed to restore source engine state.[/yellow]")
1020
+ console.print(
1021
+ "[yellow]⚠️ Warning: Failed to restore source engine state.[/yellow]"
1022
+ )
977
1023
  else:
978
- console.print("[green]✓ Source engine restored to normal operation.[/green]")
979
-
1024
+ console.print(
1025
+ "[green]✓ Source engine restored to normal operation.[/green]"
1026
+ )
980
1027
 
981
- console.print("\n[dim]The AMI creation process will continue in the background.[/dim]")
982
- console.print("[dim]You can monitor progress in the EC2 Console under 'AMIs'.[/dim]")
1028
+ console.print(
1029
+ "\n[dim]The AMI creation process will continue in the background.[/dim]"
1030
+ )
1031
+ console.print(
1032
+ "[dim]You can monitor progress in the EC2 Console under 'AMIs'.[/dim]"
1033
+ )
983
1034
  console.print(
984
1035
  f"\nOnce complete, run [cyan]terraform apply[/cyan] in "
985
1036
  f"terraform/environments/dev to use the new AMI."
@@ -1015,7 +1066,9 @@ def create_studio(
1015
1066
  # Check if user already has a studio
1016
1067
  existing = get_user_studio(username)
1017
1068
  if existing:
1018
- console.print(f"[yellow]You already have a studio: {existing['studio_id']}[/yellow]")
1069
+ console.print(
1070
+ f"[yellow]You already have a studio: {existing['studio_id']}[/yellow]"
1071
+ )
1019
1072
  return
1020
1073
 
1021
1074
  console.print(f"Creating {size_gb}GB studio for user [cyan]{username}[/cyan]...")
@@ -1057,14 +1110,14 @@ def studio_status():
1057
1110
 
1058
1111
  # Create status panel
1059
1112
  # Format status with colors
1060
- status = studio['status']
1113
+ status = studio["status"]
1061
1114
  if status == "in-use":
1062
1115
  status_display = "[bright_blue]attached[/bright_blue]"
1063
1116
  elif status in ["attaching", "detaching"]:
1064
1117
  status_display = f"[yellow]{status}[/yellow]"
1065
1118
  else:
1066
1119
  status_display = f"[green]{status}[/green]"
1067
-
1120
+
1068
1121
  status_lines = [
1069
1122
  f"[bold]Studio ID:[/bold] {studio['studio_id']}",
1070
1123
  f"[bold]User:[/bold] {studio['user']}",
@@ -1075,17 +1128,19 @@ def studio_status():
1075
1128
 
1076
1129
  if studio.get("attached_vm_id"):
1077
1130
  status_lines.append(f"[bold]Attached to:[/bold] {studio['attached_vm_id']}")
1078
-
1131
+
1079
1132
  # Try to get engine details
1080
1133
  response = make_api_request("GET", "/engines")
1081
1134
  if response.status_code == 200:
1082
1135
  engines = response.json().get("engines", [])
1083
1136
  attached_engine = next(
1084
1137
  (e for e in engines if e["instance_id"] == studio["attached_vm_id"]),
1085
- None
1138
+ None,
1086
1139
  )
1087
1140
  if attached_engine:
1088
- status_lines.append(f"[bold]Engine Name:[/bold] {attached_engine['name']}")
1141
+ status_lines.append(
1142
+ f"[bold]Engine Name:[/bold] {attached_engine['name']}"
1143
+ )
1089
1144
 
1090
1145
  panel = Panel(
1091
1146
  "\n".join(status_lines),
@@ -1145,14 +1200,19 @@ def attach_studio(
1145
1200
 
1146
1201
  if engine["state"].lower() != "running":
1147
1202
  console.print(f"[yellow]⚠️ Engine is {engine['state']}[/yellow]")
1148
- if engine["state"].lower() == "stopped" and Confirm.ask("Start the engine first?"):
1149
- response = make_api_request("POST", f"/engines/{engine['instance_id']}/start")
1203
+ if engine["state"].lower() == "stopped" and Confirm.ask(
1204
+ "Start the engine first?"
1205
+ ):
1206
+ response = make_api_request(
1207
+ "POST", f"/engines/{engine['instance_id']}/start"
1208
+ )
1150
1209
  if response.status_code != 200:
1151
1210
  console.print("[red]❌ Failed to start engine[/red]")
1152
1211
  raise typer.Exit(1)
1153
1212
  console.print("[green]✓ Engine started[/green]")
1154
1213
  console.print("Waiting for engine to be ready...")
1155
1214
  import time
1215
+
1156
1216
  time.sleep(10)
1157
1217
  else:
1158
1218
  raise typer.Exit(1)
@@ -1233,7 +1293,9 @@ def delete_studio():
1233
1293
  console.print("[yellow]You don't have a studio to delete.[/yellow]")
1234
1294
  return
1235
1295
 
1236
- console.print("[red]⚠️ WARNING: This will permanently delete your studio and all data![/red]")
1296
+ console.print(
1297
+ "[red]⚠️ WARNING: This will permanently delete your studio and all data![/red]"
1298
+ )
1237
1299
  console.print(f"Studio ID: {studio['studio_id']}")
1238
1300
  console.print(f"Size: {studio['size_gb']}GB")
1239
1301
 
@@ -1246,9 +1308,7 @@ def delete_studio():
1246
1308
  console.print("Deletion cancelled.")
1247
1309
  return
1248
1310
 
1249
- typed_confirm = Prompt.ask(
1250
- 'Type "DELETE" to confirm permanent deletion'
1251
- )
1311
+ typed_confirm = Prompt.ask('Type "DELETE" to confirm permanent deletion')
1252
1312
  if typed_confirm != "DELETE":
1253
1313
  console.print("Deletion cancelled.")
1254
1314
  return
@@ -1264,7 +1324,9 @@ def delete_studio():
1264
1324
 
1265
1325
  @studio_app.command("list")
1266
1326
  def list_studios(
1267
- all_users: bool = typer.Option(False, "--all", "-a", help="Show all users' studios"),
1327
+ all_users: bool = typer.Option(
1328
+ False, "--all", "-a", help="Show all users' studios"
1329
+ ),
1268
1330
  ):
1269
1331
  """List studios."""
1270
1332
  username = check_aws_sso()
@@ -1302,24 +1364,24 @@ def list_studios(
1302
1364
  status_display = "[yellow]" + studio["status"] + "[/yellow]"
1303
1365
  else:
1304
1366
  status_display = "[green]available[/green]"
1305
-
1367
+
1306
1368
  # Format attached engine info
1307
1369
  attached_to = "-"
1308
1370
  if studio.get("attached_vm_id"):
1309
1371
  vm_id = studio["attached_vm_id"]
1310
1372
  engine_name = engines.get(vm_id, "unknown")
1311
1373
  attached_to = f"{engine_name} ({vm_id})"
1312
-
1374
+
1313
1375
  # Format creation date (remove microseconds and timezone info)
1314
1376
  created = studio["creation_date"]
1315
1377
  try:
1316
1378
  # Parse and reformat to just show date and time
1317
- if 'T' in created:
1318
- created_dt = datetime.fromisoformat(created.replace('Z', '+00:00'))
1379
+ if "T" in created:
1380
+ created_dt = datetime.fromisoformat(created.replace("Z", "+00:00"))
1319
1381
  created = created_dt.strftime("%Y-%m-%d %H:%M")
1320
1382
  except:
1321
1383
  # If parsing fails, just truncate
1322
- created = created.split('T')[0] if 'T' in created else created[:16]
1384
+ created = created.split("T")[0] if "T" in created else created[:16]
1323
1385
 
1324
1386
  table.add_row(
1325
1387
  studio["studio_id"],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: dayhoff-tools
3
- Version: 1.3.7
3
+ Version: 1.3.8
4
4
  Summary: Common tools for all the repos at Dayhoff Labs
5
5
  Author: Daniel Martin-Alarcon
6
6
  Author-email: dma@dayhofflabs.com
@@ -3,7 +3,7 @@ dayhoff_tools/chemistry/standardizer.py,sha256=uMn7VwHnx02nc404eO6fRuS4rsl4dvSPf
3
3
  dayhoff_tools/chemistry/utils.py,sha256=jt-7JgF-GeeVC421acX-bobKbLU_X94KNOW24p_P-_M,2257
4
4
  dayhoff_tools/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  dayhoff_tools/cli/cloud_commands.py,sha256=33qcWLmq-FwEXMdL3F0OHm-5Stlh2r65CldyEZgQ1no,40904
6
- dayhoff_tools/cli/engine_commands.py,sha256=FBRNwuJ3Q7leRZrUCHn_4FHTA06QU-bqE3GrG87QLt8,50442
6
+ dayhoff_tools/cli/engine_commands.py,sha256=BkCyL_K1fxkc9hCrpt38m3GgvwNj68QNe6Gjh0Xabyw,51068
7
7
  dayhoff_tools/cli/main.py,sha256=rgeEHD9lJ8SBCR34BTLb7gVInHUUdmEBNXAJnq5yEU4,4795
8
8
  dayhoff_tools/cli/swarm_commands.py,sha256=5EyKj8yietvT5lfoz8Zx0iQvVaNgc3SJX1z2zQR6o6M,5614
9
9
  dayhoff_tools/cli/utility_commands.py,sha256=qs8vH9TBFHsOPC3X8cU3qZigM3dDn-2Ytq4o_F2WubU,27874
@@ -27,7 +27,7 @@ dayhoff_tools/intake/uniprot.py,sha256=BZYJQF63OtPcBBnQ7_P9gulxzJtqyorgyuDiPeOJq
27
27
  dayhoff_tools/logs.py,sha256=DKdeP0k0kliRcilwvX0mUB2eipO5BdWUeHwh-VnsICs,838
28
28
  dayhoff_tools/sqlite.py,sha256=jV55ikF8VpTfeQqqlHSbY8OgfyfHj8zgHNpZjBLos_E,18672
29
29
  dayhoff_tools/warehouse.py,sha256=8YbnQ--usrEgDQGfvpV4MrMji55A0rq2hZaOgFGh6ag,15896
30
- dayhoff_tools-1.3.7.dist-info/METADATA,sha256=UzFsOWGLYUgKx_soBtZVUgEhc_4sNoK8DmD6ZWh0_78,2842
31
- dayhoff_tools-1.3.7.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
32
- dayhoff_tools-1.3.7.dist-info/entry_points.txt,sha256=iAf4jteNqW3cJm6CO6czLxjW3vxYKsyGLZ8WGmxamSc,49
33
- dayhoff_tools-1.3.7.dist-info/RECORD,,
30
+ dayhoff_tools-1.3.8.dist-info/METADATA,sha256=LabiewPKusX8GH6vvxEq_UjXTTDT3q_X8Ym9ba0RsA4,2842
31
+ dayhoff_tools-1.3.8.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
32
+ dayhoff_tools-1.3.8.dist-info/entry_points.txt,sha256=iAf4jteNqW3cJm6CO6czLxjW3vxYKsyGLZ8WGmxamSc,49
33
+ dayhoff_tools-1.3.8.dist-info/RECORD,,