dayhoff-tools 1.4.6__py3-none-any.whl → 1.4.12__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.
- dayhoff_tools/cli/engine_commands.py +108 -48
- {dayhoff_tools-1.4.6.dist-info → dayhoff_tools-1.4.12.dist-info}/METADATA +1 -1
- {dayhoff_tools-1.4.6.dist-info → dayhoff_tools-1.4.12.dist-info}/RECORD +5 -5
- {dayhoff_tools-1.4.6.dist-info → dayhoff_tools-1.4.12.dist-info}/WHEEL +0 -0
- {dayhoff_tools-1.4.6.dist-info → dayhoff_tools-1.4.12.dist-info}/entry_points.txt +0 -0
@@ -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)",
|
@@ -1607,6 +1606,9 @@ def attach_studio(
|
|
1607
1606
|
engines = response.json().get("engines", [])
|
1608
1607
|
engine = resolve_engine(engine_name_or_id, engines)
|
1609
1608
|
|
1609
|
+
# Flag to track if we started the engine in this command (affects retry length)
|
1610
|
+
engine_started_now: bool = False
|
1611
|
+
|
1610
1612
|
if engine["state"].lower() != "running":
|
1611
1613
|
console.print(f"[yellow]⚠️ Engine is {engine['state']}[/yellow]")
|
1612
1614
|
if engine["state"].lower() == "stopped" and Confirm.ask(
|
@@ -1619,33 +1621,14 @@ def attach_studio(
|
|
1619
1621
|
console.print("[red]❌ Failed to start engine[/red]")
|
1620
1622
|
raise typer.Exit(1)
|
1621
1623
|
console.print("[green]✓ Engine started[/green]")
|
1622
|
-
|
1623
|
-
|
1624
|
-
|
1625
|
-
#
|
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]")
|
1624
|
+
# Mark that we booted the engine so attach loop gets extended retries
|
1625
|
+
engine_started_now = True
|
1626
|
+
# No further waiting here – attachment attempts below handle retry logic while the
|
1627
|
+
# engine finishes booting.
|
1645
1628
|
else:
|
1646
1629
|
raise typer.Exit(1)
|
1647
1630
|
|
1648
|
-
#
|
1631
|
+
# Retrieve SSH public key (required for authorised_keys provisioning)
|
1649
1632
|
try:
|
1650
1633
|
public_key = get_ssh_public_key()
|
1651
1634
|
except FileNotFoundError as e:
|
@@ -1654,36 +1637,113 @@ def attach_studio(
|
|
1654
1637
|
|
1655
1638
|
console.print(f"Attaching studio to engine [cyan]{engine['name']}[/cyan]...")
|
1656
1639
|
|
1657
|
-
|
1658
|
-
|
1659
|
-
|
1660
|
-
transient=True,
|
1661
|
-
) as progress:
|
1662
|
-
task = progress.add_task("Attaching studio...", total=100)
|
1640
|
+
# Determine retry strategy
|
1641
|
+
max_attempts = 40 if engine_started_now else 3
|
1642
|
+
retry_delay = 10 if engine_started_now else 3
|
1663
1643
|
|
1664
|
-
|
1665
|
-
|
1666
|
-
|
1667
|
-
|
1668
|
-
|
1669
|
-
|
1670
|
-
|
1671
|
-
|
1672
|
-
|
1644
|
+
if engine_started_now:
|
1645
|
+
# Long spinner-based loop while the freshly started engine finishes booting
|
1646
|
+
with Progress(
|
1647
|
+
SpinnerColumn(),
|
1648
|
+
TimeElapsedColumn(),
|
1649
|
+
TextColumn("[progress.description]{task.description}"),
|
1650
|
+
transient=True,
|
1651
|
+
) as prog:
|
1652
|
+
task = prog.add_task("Attaching studio (engine is still booting)…", total=None)
|
1653
|
+
|
1654
|
+
for attempt in range(max_attempts):
|
1655
|
+
success, error_msg = _attempt_studio_attach(studio, engine, target_user, public_key)
|
1656
|
+
|
1657
|
+
if success:
|
1658
|
+
break # success!
|
1659
|
+
|
1660
|
+
# Update spinner every 3rd try to avoid log spam
|
1661
|
+
if attempt % 3 == 0:
|
1662
|
+
prog.update(task, description=f"Attaching studio (engine is still booting)… {attempt+1}/{max_attempts}")
|
1663
|
+
|
1664
|
+
if error_msg:
|
1665
|
+
console.print(f"[red]❌ Failed to attach studio: {error_msg}[/red]")
|
1666
|
+
return
|
1667
|
+
|
1668
|
+
time.sleep(retry_delay)
|
1673
1669
|
|
1674
|
-
|
1670
|
+
else:
|
1671
|
+
console.print("[yellow]Engine is still starting up – please retry in a minute.[/yellow]")
|
1672
|
+
return
|
1673
|
+
else:
|
1674
|
+
# Quick path: engine is already running – try a couple of times then fail fast
|
1675
|
+
for attempt in range(max_attempts):
|
1676
|
+
success, error_msg = _attempt_studio_attach(studio, engine, target_user, public_key)
|
1677
|
+
|
1678
|
+
if success:
|
1679
|
+
break # good to go
|
1680
|
+
|
1681
|
+
if error_msg:
|
1682
|
+
console.print(f"[red]❌ Failed to attach studio: {error_msg}[/red]")
|
1683
|
+
return
|
1684
|
+
|
1685
|
+
time.sleep(retry_delay)
|
1686
|
+
|
1687
|
+
else:
|
1688
|
+
console.print("[yellow]Could not attach studio – please retry in a moment.[/yellow]")
|
1689
|
+
return
|
1690
|
+
|
1691
|
+
# Successful attach path
|
1692
|
+
console.print(f"[green]✓ Studio attached successfully![/green]")
|
1693
|
+
|
1694
|
+
# Update SSH config - use target_user for the connection
|
1695
|
+
update_ssh_config_entry(engine["name"], engine["instance_id"], target_user)
|
1696
|
+
console.print(f"[green]✓ SSH config updated[/green]")
|
1697
|
+
console.print(f"\nConnect with: [cyan]ssh {engine['name']}[/cyan]")
|
1698
|
+
console.print(f"Files are at: [cyan]/studios/{target_user}[/cyan]")
|
1699
|
+
|
1700
|
+
|
1701
|
+
def _attempt_studio_attach(studio, engine, target_user, public_key):
|
1702
|
+
response = make_api_request(
|
1703
|
+
"POST",
|
1704
|
+
f"/studios/{studio['studio_id']}/attach",
|
1705
|
+
json_data={
|
1706
|
+
"vm_id": engine["instance_id"],
|
1707
|
+
"user": target_user,
|
1708
|
+
"public_key": public_key,
|
1709
|
+
},
|
1710
|
+
)
|
1675
1711
|
|
1676
1712
|
if response.status_code == 200:
|
1677
|
-
|
1713
|
+
return True, None
|
1678
1714
|
|
1679
|
-
|
1680
|
-
|
1681
|
-
|
1682
|
-
|
1683
|
-
console.print(f"Files are at: [cyan]/studios/{target_user}[/cyan]")
|
1715
|
+
# --- determine if we should retry ---
|
1716
|
+
recoverable = False
|
1717
|
+
if response.status_code in (409, 503):
|
1718
|
+
recoverable = True
|
1684
1719
|
else:
|
1720
|
+
err_msg = response.json().get("error", "").lower()
|
1721
|
+
RECOVERABLE_PATTERNS = [
|
1722
|
+
"not ready",
|
1723
|
+
"still starting",
|
1724
|
+
"initializing",
|
1725
|
+
"failed to mount",
|
1726
|
+
"device busy",
|
1727
|
+
"not available",
|
1728
|
+
"pending", # VM state pending
|
1729
|
+
]
|
1730
|
+
FATAL_PATTERNS = [
|
1731
|
+
"in-use",
|
1732
|
+
"already attached",
|
1733
|
+
"permission",
|
1734
|
+
]
|
1735
|
+
if any(p in err_msg for p in FATAL_PATTERNS):
|
1736
|
+
recoverable = False
|
1737
|
+
elif any(p in err_msg for p in RECOVERABLE_PATTERNS):
|
1738
|
+
recoverable = True
|
1739
|
+
|
1740
|
+
if not recoverable:
|
1741
|
+
# fatal – abort immediately and show message
|
1685
1742
|
error = response.json().get("error", "Unknown error")
|
1686
|
-
|
1743
|
+
return False, error
|
1744
|
+
|
1745
|
+
# otherwise wait and retry
|
1746
|
+
return False, None
|
1687
1747
|
|
1688
1748
|
|
1689
1749
|
@studio_app.command("detach")
|
@@ -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=
|
6
|
+
dayhoff_tools/cli/engine_commands.py,sha256=enbpFSLCsmPACL1EOarxCcF8L-TkpiNqq4DB4vhv6X0,85005
|
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.
|
31
|
-
dayhoff_tools-1.4.
|
32
|
-
dayhoff_tools-1.4.
|
33
|
-
dayhoff_tools-1.4.
|
30
|
+
dayhoff_tools-1.4.12.dist-info/METADATA,sha256=GjDgKBeFXN1cwgetohVhR8Ix8SD_bTxHAQqd7HrqX_4,2825
|
31
|
+
dayhoff_tools-1.4.12.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
32
|
+
dayhoff_tools-1.4.12.dist-info/entry_points.txt,sha256=iAf4jteNqW3cJm6CO6czLxjW3vxYKsyGLZ8WGmxamSc,49
|
33
|
+
dayhoff_tools-1.4.12.dist-info/RECORD,,
|
File without changes
|
File without changes
|