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.
- dayhoff_tools/cli/engine_commands.py +68 -44
- {dayhoff_tools-1.4.6.dist-info → dayhoff_tools-1.4.8.dist-info}/METADATA +1 -1
- {dayhoff_tools-1.4.6.dist-info → dayhoff_tools-1.4.8.dist-info}/RECORD +5 -5
- {dayhoff_tools-1.4.6.dist-info → dayhoff_tools-1.4.8.dist-info}/WHEEL +0 -0
- {dayhoff_tools-1.4.6.dist-info → dayhoff_tools-1.4.8.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)",
|
@@ -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
|
-
|
1623
|
-
#
|
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
|
-
#
|
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
|
1662
|
-
task =
|
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
|
-
|
1665
|
-
|
1666
|
-
|
1667
|
-
|
1668
|
-
|
1669
|
-
|
1670
|
-
|
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
|
-
|
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
|
-
|
1685
|
+
return True, None
|
1678
1686
|
|
1679
|
-
|
1680
|
-
|
1681
|
-
|
1682
|
-
|
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
|
-
|
1707
|
+
return False, error
|
1708
|
+
|
1709
|
+
# otherwise wait and retry
|
1710
|
+
return False, None
|
1687
1711
|
|
1688
1712
|
|
1689
1713
|
@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=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.
|
31
|
-
dayhoff_tools-1.4.
|
32
|
-
dayhoff_tools-1.4.
|
33
|
-
dayhoff_tools-1.4.
|
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,,
|
File without changes
|
File without changes
|