dayhoff-tools 1.9.5__tar.gz → 1.9.6__tar.gz
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-1.9.5 → dayhoff_tools-1.9.6}/PKG-INFO +1 -1
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/cli/engine_commands.py +134 -89
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/pyproject.toml +1 -1
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/README.md +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/__init__.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/chemistry/standardizer.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/chemistry/utils.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/cli/__init__.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/cli/cloud_commands.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/cli/main.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/cli/swarm_commands.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/cli/utility_commands.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/deployment/base.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/deployment/deploy_aws.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/deployment/deploy_gcp.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/deployment/deploy_utils.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/deployment/job_runner.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/deployment/processors.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/deployment/swarm.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/embedders.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/fasta.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/file_ops.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/h5.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/intake/gcp.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/intake/gtdb.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/intake/kegg.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/intake/mmseqs.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/intake/structure.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/intake/uniprot.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/logs.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/sqlite.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.6}/dayhoff_tools/warehouse.py +0 -0
@@ -693,9 +693,10 @@ def list_engines(
|
|
693
693
|
@engine_app.command("status")
|
694
694
|
def engine_status(
|
695
695
|
name_or_id: str = typer.Argument(help="Engine name or instance ID"),
|
696
|
-
|
696
|
+
detailed: bool = typer.Option(False, "--detailed", "-d", help="Show detailed status (slower)"),
|
697
|
+
show_log: bool = typer.Option(False, "--show-log", help="Show bootstrap log (requires --detailed)"),
|
697
698
|
):
|
698
|
-
"""Show
|
699
|
+
"""Show engine status and information."""
|
699
700
|
check_aws_sso()
|
700
701
|
|
701
702
|
# Get all engines to resolve name
|
@@ -706,8 +707,131 @@ def engine_status(
|
|
706
707
|
|
707
708
|
engines = response.json().get("engines", [])
|
708
709
|
engine = resolve_engine(name_or_id, engines)
|
710
|
+
|
711
|
+
# Fast status display (default)
|
712
|
+
if not detailed:
|
713
|
+
# Get basic engine info and studios from API
|
714
|
+
response = make_api_request("GET", f"/engines/{engine['instance_id']}")
|
715
|
+
if response.status_code != 200:
|
716
|
+
console.print("[red]❌ Failed to fetch engine details[/red]")
|
717
|
+
raise typer.Exit(1)
|
718
|
+
|
719
|
+
engine_details = response.json()
|
720
|
+
engine = engine_details.get("engine", engine)
|
721
|
+
attached_studios = engine_details.get("attached_studios", [])
|
722
|
+
|
723
|
+
# Fetch idle status via SSM with longer timeout
|
724
|
+
ssm = boto3.client("ssm", region_name="us-east-1")
|
725
|
+
idle_data = {}
|
726
|
+
|
727
|
+
if engine["state"].lower() == "running":
|
728
|
+
try:
|
729
|
+
resp = ssm.send_command(
|
730
|
+
InstanceIds=[engine["instance_id"]],
|
731
|
+
DocumentName="AWS-RunShellScript",
|
732
|
+
Parameters={
|
733
|
+
"commands": [
|
734
|
+
"cat /var/run/idle-detector/last_state.json 2>/dev/null || echo '{}'"
|
735
|
+
],
|
736
|
+
"executionTimeout": ["10"],
|
737
|
+
},
|
738
|
+
)
|
739
|
+
cid = resp["Command"]["CommandId"]
|
740
|
+
|
741
|
+
# Wait up to 3 seconds for result
|
742
|
+
for _ in range(6): # 6 * 0.5 = 3 seconds
|
743
|
+
time.sleep(0.5)
|
744
|
+
inv = ssm.get_command_invocation(
|
745
|
+
CommandId=cid, InstanceId=engine["instance_id"]
|
746
|
+
)
|
747
|
+
if inv["Status"] in ["Success", "Failed"]:
|
748
|
+
break
|
749
|
+
|
750
|
+
if inv["Status"] == "Success":
|
751
|
+
content = inv["StandardOutputContent"].strip()
|
752
|
+
if content and content != "{}":
|
753
|
+
idle_data = json.loads(content)
|
754
|
+
except Exception:
|
755
|
+
pass # Best effort
|
756
|
+
|
757
|
+
# Build status display
|
758
|
+
hourly_cost = HOURLY_COSTS.get(engine["engine_type"], 0)
|
759
|
+
|
760
|
+
# Determine running state display
|
761
|
+
running_state = engine["state"].lower()
|
762
|
+
if running_state == "running":
|
763
|
+
run_disp = "[green]Running[/green]"
|
764
|
+
elif running_state == "pending":
|
765
|
+
run_disp = "[yellow]Starting...[/yellow]"
|
766
|
+
elif running_state == "stopping":
|
767
|
+
run_disp = "[yellow]Stopping...[/yellow]"
|
768
|
+
elif running_state == "stopped":
|
769
|
+
run_disp = "[dim]Stopped[/dim]"
|
770
|
+
else:
|
771
|
+
run_disp = engine["state"].capitalize()
|
772
|
+
|
773
|
+
# Determine idle/active status
|
774
|
+
idle_disp = ""
|
775
|
+
if running_state == "running":
|
776
|
+
is_idle = idle_data.get("idle", False)
|
777
|
+
timeout_sec = idle_data.get("timeout_sec")
|
778
|
+
idle_seconds = idle_data.get("idle_seconds", 0) if is_idle else 0
|
779
|
+
|
780
|
+
if is_idle:
|
781
|
+
if isinstance(timeout_sec, int) and isinstance(idle_seconds, int):
|
782
|
+
remaining = max(0, timeout_sec - idle_seconds)
|
783
|
+
idle_disp = f" [yellow]Idle {idle_seconds//60}m/{timeout_sec//60}m: [red]{remaining//60}m {remaining%60}s[/red] left[/yellow]"
|
784
|
+
else:
|
785
|
+
idle_disp = " [yellow]Idle ?/?[/yellow]"
|
786
|
+
else:
|
787
|
+
idle_disp = " [green]Active[/green]"
|
788
|
+
|
789
|
+
# Build status lines
|
790
|
+
status_lines = [
|
791
|
+
f"[blue]{engine['name']}[/blue] {run_disp}{idle_disp}\n",
|
792
|
+
]
|
793
|
+
|
794
|
+
# Add studios if attached
|
795
|
+
if attached_studios:
|
796
|
+
studio_names = [f"[magenta]{s.get('user', 'studio')}[/magenta]" for s in attached_studios]
|
797
|
+
status_lines.append(f"Studios: {', '.join(studio_names)}")
|
798
|
+
|
799
|
+
status_lines.append("") # blank line
|
800
|
+
|
801
|
+
# Basic info
|
802
|
+
status_lines.extend([
|
803
|
+
f"Type: {engine['engine_type']} ({engine['instance_type']})",
|
804
|
+
f"User: {engine['user']}",
|
805
|
+
f"IP: {engine.get('public_ip', 'N/A')}",
|
806
|
+
f"$/hour: ${hourly_cost:.2f}",
|
807
|
+
])
|
808
|
+
|
809
|
+
# Add activity sensors if we have idle data
|
810
|
+
if idle_data.get("reasons"):
|
811
|
+
status_lines.append("")
|
812
|
+
status_lines.append("[bold]Activity:[/bold]")
|
813
|
+
|
814
|
+
sensor_map = {
|
815
|
+
"CoffeeLockSensor": ("☕", "Coffee"),
|
816
|
+
"ActiveLoginSensor": ("🐚", "SSH"),
|
817
|
+
"IDEConnectionSensor": ("🖥", "IDE"),
|
818
|
+
"DockerWorkloadSensor": ("🐳", "Docker"),
|
819
|
+
}
|
820
|
+
|
821
|
+
for r in idle_data.get("reasons", []):
|
822
|
+
sensor = r.get("sensor", "Unknown")
|
823
|
+
active = r.get("active", False)
|
824
|
+
icon, label = sensor_map.get(sensor, ("?", sensor))
|
825
|
+
status_str = "[green]YES[/green]" if active else "[dim]no[/dim]"
|
826
|
+
status_lines.append(f" {icon} {label:6} {status_str}")
|
827
|
+
|
828
|
+
# Display in a nice panel
|
829
|
+
console.print(
|
830
|
+
Panel("\n".join(status_lines), title="Engine Status", border_style="blue")
|
831
|
+
)
|
832
|
+
return # Exit early for fast status
|
709
833
|
|
710
|
-
# Get detailed engine status including idle detector info
|
834
|
+
# Get detailed engine status including idle detector info (for --detailed mode)
|
711
835
|
response = make_api_request("GET", f"/engines/{engine['instance_id']}")
|
712
836
|
if response.status_code != 200:
|
713
837
|
console.print("[red]❌ Failed to fetch engine details[/red]")
|
@@ -783,12 +907,13 @@ def engine_status(
|
|
783
907
|
if not idle_info.get("available"):
|
784
908
|
return "[dim]N/A[/dim]"
|
785
909
|
|
786
|
-
# If idle, show time/threshold
|
910
|
+
# If idle, show time/threshold with time remaining if available
|
787
911
|
if idle_info.get("status") == "idle":
|
788
912
|
idle_seconds_v = idle_info.get("idle_seconds")
|
789
913
|
thresh_v = idle_info.get("idle_threshold")
|
790
914
|
if isinstance(idle_seconds_v, (int, float)) and isinstance(thresh_v, (int, float)):
|
791
|
-
|
915
|
+
remaining = max(0, int(thresh_v) - int(idle_seconds_v))
|
916
|
+
return f"[yellow]Idle {int(idle_seconds_v)//60}m/{int(thresh_v)//60}m: [red]{remaining//60}m {remaining%60}s[/red] left[/yellow]"
|
792
917
|
elif isinstance(thresh_v, (int, float)):
|
793
918
|
return f"[yellow]Idle ?/{int(thresh_v)//60}m[/yellow]"
|
794
919
|
else:
|
@@ -1062,6 +1187,9 @@ def engine_status(
|
|
1062
1187
|
)
|
1063
1188
|
|
1064
1189
|
if show_log:
|
1190
|
+
if not detailed:
|
1191
|
+
console.print("[yellow]Note: --show-log requires --detailed flag[/yellow]")
|
1192
|
+
return
|
1065
1193
|
console.print("\n[bold]Bootstrap Log:[/bold]")
|
1066
1194
|
try:
|
1067
1195
|
ssm = boto3.client("ssm", region_name="us-east-1")
|
@@ -2631,9 +2759,7 @@ def idle_timeout_cmd(
|
|
2631
2759
|
set: Optional[str] = typer.Option(
|
2632
2760
|
None, "--set", "-s", help="New timeout (e.g., 2h30m, 45m)"
|
2633
2761
|
),
|
2634
|
-
|
2635
|
-
False, "--status", help="Show detailed idle status (faster than full engine status)"
|
2636
|
-
),
|
2762
|
+
|
2637
2763
|
):
|
2638
2764
|
"""Show or set the engine idle-detector timeout."""
|
2639
2765
|
check_aws_sso()
|
@@ -2648,87 +2774,6 @@ def idle_timeout_cmd(
|
|
2648
2774
|
engine = resolve_engine(name_or_id, engines)
|
2649
2775
|
|
2650
2776
|
ssm = boto3.client("ssm", region_name="us-east-1")
|
2651
|
-
|
2652
|
-
# Handle --status flag for quick idle status check
|
2653
|
-
if status:
|
2654
|
-
console.print(f"[bold]Idle Status for {engine['name']}:[/bold]")
|
2655
|
-
|
2656
|
-
# Fetch state file with longer timeout for reliability
|
2657
|
-
try:
|
2658
|
-
resp = ssm.send_command(
|
2659
|
-
InstanceIds=[engine["instance_id"]],
|
2660
|
-
DocumentName="AWS-RunShellScript",
|
2661
|
-
Parameters={
|
2662
|
-
"commands": [
|
2663
|
-
"cat /var/run/idle-detector/last_state.json 2>/dev/null || echo '{}'"
|
2664
|
-
],
|
2665
|
-
"executionTimeout": ["10"],
|
2666
|
-
},
|
2667
|
-
)
|
2668
|
-
cid = resp["Command"]["CommandId"]
|
2669
|
-
|
2670
|
-
# Wait up to 3 seconds for result
|
2671
|
-
for _ in range(6): # 6 * 0.5 = 3 seconds
|
2672
|
-
time.sleep(0.5)
|
2673
|
-
inv = ssm.get_command_invocation(
|
2674
|
-
CommandId=cid, InstanceId=engine["instance_id"]
|
2675
|
-
)
|
2676
|
-
if inv["Status"] in ["Success", "Failed"]:
|
2677
|
-
break
|
2678
|
-
|
2679
|
-
if inv["Status"] == "Success":
|
2680
|
-
import json
|
2681
|
-
content = inv["StandardOutputContent"].strip()
|
2682
|
-
if content and content != "{}":
|
2683
|
-
data = json.loads(content)
|
2684
|
-
|
2685
|
-
# Extract key values
|
2686
|
-
is_idle = data.get("idle", False)
|
2687
|
-
timeout_sec = data.get("timeout_sec", "?")
|
2688
|
-
idle_seconds = data.get("idle_seconds", 0) if is_idle else 0
|
2689
|
-
|
2690
|
-
# Display status
|
2691
|
-
if is_idle:
|
2692
|
-
if isinstance(timeout_sec, int) and isinstance(idle_seconds, int):
|
2693
|
-
console.print(f" Status: [yellow]Idle {idle_seconds//60}m/{timeout_sec//60}m[/yellow]")
|
2694
|
-
remaining = max(0, timeout_sec - idle_seconds)
|
2695
|
-
console.print(f" Time until shutdown: [red]{remaining//60}m {remaining%60}s[/red]")
|
2696
|
-
else:
|
2697
|
-
console.print(f" Status: [yellow]Idle (timing unavailable)[/yellow]")
|
2698
|
-
else:
|
2699
|
-
console.print(f" Status: [green]Active[/green]")
|
2700
|
-
if isinstance(timeout_sec, int):
|
2701
|
-
console.print(f" Timeout: {timeout_sec//60}m ({timeout_sec}s)")
|
2702
|
-
|
2703
|
-
# Show activity sensors
|
2704
|
-
console.print("\n[bold]Activity Sensors:[/bold]")
|
2705
|
-
reasons = data.get("reasons", [])
|
2706
|
-
for r in reasons:
|
2707
|
-
sensor = r.get("sensor", "Unknown")
|
2708
|
-
active = r.get("active", False)
|
2709
|
-
reason = r.get("reason", "")
|
2710
|
-
|
2711
|
-
# Map sensor names to icons and labels
|
2712
|
-
sensor_map = {
|
2713
|
-
"CoffeeLockSensor": ("☕", "Coffee"),
|
2714
|
-
"ActiveLoginSensor": ("🐚", "SSH"),
|
2715
|
-
"IDEConnectionSensor": ("🖥", "IDE"),
|
2716
|
-
"DockerWorkloadSensor": ("🐳", "Docker"),
|
2717
|
-
}
|
2718
|
-
|
2719
|
-
icon, label = sensor_map.get(sensor, ("?", sensor))
|
2720
|
-
status_str = "[green]YES[/green]" if active else "[dim]no[/dim]"
|
2721
|
-
console.print(f" {icon} {label:8} {status_str} {reason if active else ''}")
|
2722
|
-
|
2723
|
-
else:
|
2724
|
-
console.print("[yellow]⚠️ Could not read idle detector state[/yellow]")
|
2725
|
-
else:
|
2726
|
-
console.print(f"[red]❌ Failed to get status: {inv.get('StandardErrorContent', 'Unknown error')}[/red]")
|
2727
|
-
|
2728
|
-
except Exception as e:
|
2729
|
-
console.print(f"[red]❌ Error fetching idle status: {e}[/red]")
|
2730
|
-
|
2731
|
-
return
|
2732
2777
|
|
2733
2778
|
if set is None:
|
2734
2779
|
# Show current timeout setting
|
@@ -5,7 +5,7 @@ build-backend = "poetry.core.masonry.api"
|
|
5
5
|
|
6
6
|
[project]
|
7
7
|
name = "dayhoff-tools"
|
8
|
-
version = "1.9.
|
8
|
+
version = "1.9.6"
|
9
9
|
description = "Common tools for all the repos at Dayhoff Labs"
|
10
10
|
authors = [
|
11
11
|
{name = "Daniel Martin-Alarcon", email = "dma@dayhofflabs.com"}
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|