dayhoff-tools 1.4.6__py3-none-any.whl → 1.4.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.
@@ -616,7 +616,6 @@ def engine_status(
616
616
  f"[bold]Instance:[/bold] {engine['instance_id']}",
617
617
  f"[bold]Type:[/bold] {engine['engine_type']} ({engine['instance_type']})",
618
618
  f"[bold]Status:[/bold] {format_status(engine['state'], engine.get('ready'))}",
619
- f"[bold]Bootstrap:[/bold] {_colour_stage(stage_val)}",
620
619
  f"[bold]User:[/bold] {engine['user']}",
621
620
  f"[bold]IP:[/bold] {engine.get('public_ip', 'N/A')}",
622
621
  f"[bold]Launched:[/bold] {launch_time.strftime('%Y-%m-%d %H:%M:%S')} ({format_duration(uptime)} ago)",
@@ -1619,33 +1618,12 @@ def attach_studio(
1619
1618
  console.print("[red]❌ Failed to start engine[/red]")
1620
1619
  raise typer.Exit(1)
1621
1620
  console.print("[green]✓ Engine started[/green]")
1622
- console.print("Waiting for engine to be ready...")
1623
- # Wait until the engine is fully running & marked ready (max 5 min)
1624
- console.print("Waiting for engine to be ready (this can take a couple of minutes)…")
1625
- # Progress classes already imported at module level – do not re-import to avoid UnboundLocalError
1626
-
1627
- with Progress(SpinnerColumn(), TimeElapsedColumn(), TextColumn("[progress.description]{task.description}"), transient=True) as prog:
1628
- prog.add_task("Awaiting engine readiness…", total=None)
1629
- max_attempts = 30 # 30 × 10 s ≈ 5 min
1630
- for _ in range(max_attempts):
1631
- time.sleep(10)
1632
-
1633
- status_resp = make_api_request("GET", f"/engines/{engine['instance_id']}")
1634
- if status_resp.status_code != 200:
1635
- continue
1636
-
1637
- detailed = status_resp.json().get("engine", {})
1638
- state_ok = detailed.get("state", "").lower() == "running"
1639
- ready_ok = detailed.get("ready") is True
1640
-
1641
- if state_ok and ready_ok:
1642
- break
1643
- else:
1644
- console.print("[yellow]Engine is still starting up. If attachment fails, wait a little longer and retry.[/yellow]")
1621
+ # No further waiting here – attachment attempts below handle retry logic while the
1622
+ # engine finishes booting.
1645
1623
  else:
1646
1624
  raise typer.Exit(1)
1647
1625
 
1648
- # Get SSH key
1626
+ # Retrieve SSH public key (required for authorised_keys provisioning)
1649
1627
  try:
1650
1628
  public_key = get_ssh_public_key()
1651
1629
  except FileNotFoundError as e:
@@ -1656,34 +1634,80 @@ def attach_studio(
1656
1634
 
1657
1635
  with Progress(
1658
1636
  SpinnerColumn(),
1637
+ TimeElapsedColumn(),
1659
1638
  TextColumn("[progress.description]{task.description}"),
1660
1639
  transient=True,
1661
- ) as progress:
1662
- task = progress.add_task("Attaching studio...", total=100)
1640
+ ) as prog:
1641
+ task = prog.add_task("Attaching studio (engine is still booting)…", total=None)
1642
+ ATTEMPT_LIMIT = 40 # ~6-7 min max (40 × 10 s)
1643
+ RETRY_DELAY = 10
1644
+ for attempt in range(ATTEMPT_LIMIT):
1645
+ success, error_msg = _attempt_studio_attach(studio, engine, target_user, public_key)
1663
1646
 
1664
- response = make_api_request(
1665
- "POST",
1666
- f"/studios/{studio['studio_id']}/attach",
1667
- json_data={
1668
- "vm_id": engine["instance_id"],
1669
- "user": target_user, # Use target_user instead of username
1670
- "public_key": public_key,
1671
- },
1672
- )
1647
+ if success:
1648
+ break # success!
1649
+
1650
+ # Update spinner description with attempt number
1651
+ prog.update(task, description=f"Attaching studio (engine is still booting)… {attempt+1}/{ATTEMPT_LIMIT}")
1652
+
1653
+ if error_msg:
1654
+ console.print(f"[red]❌ Failed to attach studio: {error_msg}[/red]")
1655
+ return
1656
+
1657
+ time.sleep(RETRY_DELAY)
1658
+
1659
+ else:
1660
+ console.print("[yellow]Engine is still starting up – please retry in a minute.[/yellow]")
1661
+ return
1662
+
1663
+ # Successful attach path
1664
+ console.print(f"[green]✓ Studio attached successfully![/green]")
1673
1665
 
1674
- progress.update(task, completed=100)
1666
+ # Update SSH config - use target_user for the connection
1667
+ update_ssh_config_entry(engine["name"], engine["instance_id"], target_user)
1668
+ console.print(f"[green]✓ SSH config updated[/green]")
1669
+ console.print(f"\nConnect with: [cyan]ssh {engine['name']}[/cyan]")
1670
+ console.print(f"Files are at: [cyan]/studios/{target_user}[/cyan]")
1671
+
1672
+
1673
+ def _attempt_studio_attach(studio, engine, target_user, public_key):
1674
+ response = make_api_request(
1675
+ "POST",
1676
+ f"/studios/{studio['studio_id']}/attach",
1677
+ json_data={
1678
+ "vm_id": engine["instance_id"],
1679
+ "user": target_user,
1680
+ "public_key": public_key,
1681
+ },
1682
+ )
1675
1683
 
1676
1684
  if response.status_code == 200:
1677
- console.print(f"[green]✓ Studio attached successfully![/green]")
1685
+ return True, None
1678
1686
 
1679
- # Update SSH config - use target_user for the connection
1680
- update_ssh_config_entry(engine["name"], engine["instance_id"], target_user)
1681
- console.print(f"[green]✓ SSH config updated[/green]")
1682
- console.print(f"\nConnect with: [cyan]ssh {engine['name']}[/cyan]")
1683
- console.print(f"Files are at: [cyan]/studios/{target_user}[/cyan]")
1687
+ # --- determine if we should retry ---
1688
+ recoverable = False
1689
+ if response.status_code in (409, 503):
1690
+ recoverable = True
1684
1691
  else:
1692
+ err_msg = response.json().get("error", "").lower()
1693
+ RECOVERABLE_PATTERNS = [
1694
+ "not ready",
1695
+ "still starting",
1696
+ "initializing",
1697
+ "failed to mount",
1698
+ "device busy",
1699
+ "not available",
1700
+ ]
1701
+ if any(p in err_msg for p in RECOVERABLE_PATTERNS):
1702
+ recoverable = True
1703
+
1704
+ if not recoverable:
1705
+ # fatal – abort immediately and show message
1685
1706
  error = response.json().get("error", "Unknown error")
1686
- console.print(f"[red]❌ Failed to attach studio: {error}[/red]")
1707
+ return False, error
1708
+
1709
+ # otherwise wait and retry
1710
+ return False, None
1687
1711
 
1688
1712
 
1689
1713
  @studio_app.command("detach")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: dayhoff-tools
3
- Version: 1.4.6
3
+ Version: 1.4.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=A1fs8_8eClQ24hRQSeVALZt-_ABvv_XNLx7KA0UtRSU,83443
6
+ dayhoff_tools/cli/engine_commands.py,sha256=rjS1Fmz3Z91R2hcZN3WOMf1_1g-ovHHSevy1_EDvxSc,83623
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.4.6.dist-info/METADATA,sha256=rAMwQNz7gAwDPqfHieIQ4-EiOaCzho3kIlnUVXq7Pdg,2824
31
- dayhoff_tools-1.4.6.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
32
- dayhoff_tools-1.4.6.dist-info/entry_points.txt,sha256=iAf4jteNqW3cJm6CO6czLxjW3vxYKsyGLZ8WGmxamSc,49
33
- dayhoff_tools-1.4.6.dist-info/RECORD,,
30
+ dayhoff_tools-1.4.8.dist-info/METADATA,sha256=1cbd0rQXE6kBHSeTr9WReY7AFzbLj7QBtLmurAttw1E,2824
31
+ dayhoff_tools-1.4.8.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
32
+ dayhoff_tools-1.4.8.dist-info/entry_points.txt,sha256=iAf4jteNqW3cJm6CO6czLxjW3vxYKsyGLZ8WGmxamSc,49
33
+ dayhoff_tools-1.4.8.dist-info/RECORD,,