dayhoff-tools 1.9.5__tar.gz → 1.9.7__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.7}/PKG-INFO +1 -1
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.7}/dayhoff_tools/cli/engine_commands.py +125 -89
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.7}/pyproject.toml +1 -1
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.7}/README.md +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.7}/dayhoff_tools/__init__.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.7}/dayhoff_tools/chemistry/standardizer.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.7}/dayhoff_tools/chemistry/utils.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.7}/dayhoff_tools/cli/__init__.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.7}/dayhoff_tools/cli/cloud_commands.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.7}/dayhoff_tools/cli/main.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.7}/dayhoff_tools/cli/swarm_commands.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.7}/dayhoff_tools/cli/utility_commands.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.7}/dayhoff_tools/deployment/base.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.7}/dayhoff_tools/deployment/deploy_aws.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.7}/dayhoff_tools/deployment/deploy_gcp.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.7}/dayhoff_tools/deployment/deploy_utils.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.7}/dayhoff_tools/deployment/job_runner.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.7}/dayhoff_tools/deployment/processors.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.7}/dayhoff_tools/deployment/swarm.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.7}/dayhoff_tools/embedders.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.7}/dayhoff_tools/fasta.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.7}/dayhoff_tools/file_ops.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.7}/dayhoff_tools/h5.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.7}/dayhoff_tools/intake/gcp.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.7}/dayhoff_tools/intake/gtdb.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.7}/dayhoff_tools/intake/kegg.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.7}/dayhoff_tools/intake/mmseqs.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.7}/dayhoff_tools/intake/structure.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.7}/dayhoff_tools/intake/uniprot.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.7}/dayhoff_tools/logs.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.7}/dayhoff_tools/sqlite.py +0 -0
- {dayhoff_tools-1.9.5 → dayhoff_tools-1.9.7}/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,122 @@ 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
|
+
# Skip the API call for studios - use basic info we already have
|
714
|
+
attached_studios = []
|
715
|
+
studio_user = engine.get("user") # Use the engine's user as studio owner
|
716
|
+
|
717
|
+
# Fetch idle status via SSM with longer timeout
|
718
|
+
ssm = boto3.client("ssm", region_name="us-east-1")
|
719
|
+
idle_data = None # Use None to indicate no data received
|
720
|
+
|
721
|
+
if engine["state"].lower() == "running":
|
722
|
+
try:
|
723
|
+
resp = ssm.send_command(
|
724
|
+
InstanceIds=[engine["instance_id"]],
|
725
|
+
DocumentName="AWS-RunShellScript",
|
726
|
+
Parameters={
|
727
|
+
"commands": [
|
728
|
+
"cat /var/run/idle-detector/last_state.json 2>/dev/null || echo '{}'"
|
729
|
+
],
|
730
|
+
"executionTimeout": ["10"],
|
731
|
+
},
|
732
|
+
)
|
733
|
+
cid = resp["Command"]["CommandId"]
|
734
|
+
|
735
|
+
# Wait up to 3 seconds for result
|
736
|
+
for _ in range(6): # 6 * 0.5 = 3 seconds
|
737
|
+
time.sleep(0.5)
|
738
|
+
inv = ssm.get_command_invocation(
|
739
|
+
CommandId=cid, InstanceId=engine["instance_id"]
|
740
|
+
)
|
741
|
+
if inv["Status"] in ["Success", "Failed"]:
|
742
|
+
break
|
743
|
+
|
744
|
+
if inv["Status"] == "Success":
|
745
|
+
content = inv["StandardOutputContent"].strip()
|
746
|
+
if content and content != "{}":
|
747
|
+
idle_data = json.loads(content)
|
748
|
+
else:
|
749
|
+
idle_data = {} # Empty response but SSM worked
|
750
|
+
except Exception:
|
751
|
+
idle_data = None # SSM failed
|
752
|
+
|
753
|
+
# Determine running state display
|
754
|
+
running_state = engine["state"].lower()
|
755
|
+
if running_state == "running":
|
756
|
+
run_disp = "[green]Running[/green]"
|
757
|
+
elif running_state == "pending":
|
758
|
+
run_disp = "[yellow]Starting...[/yellow]"
|
759
|
+
elif running_state == "stopping":
|
760
|
+
run_disp = "[yellow]Stopping...[/yellow]"
|
761
|
+
elif running_state == "stopped":
|
762
|
+
run_disp = "[dim]Stopped[/dim]"
|
763
|
+
else:
|
764
|
+
run_disp = engine["state"].capitalize()
|
765
|
+
|
766
|
+
# Determine idle/active status
|
767
|
+
idle_disp = ""
|
768
|
+
if running_state == "running":
|
769
|
+
if idle_data is None:
|
770
|
+
# SSM failed - we don't know the status
|
771
|
+
idle_disp = " [dim]N/A[/dim]"
|
772
|
+
elif not idle_data:
|
773
|
+
# Empty data - likely very early in boot
|
774
|
+
idle_disp = " [dim]N/A[/dim]"
|
775
|
+
else:
|
776
|
+
# We have data
|
777
|
+
is_idle = idle_data.get("idle", False)
|
778
|
+
timeout_sec = idle_data.get("timeout_sec")
|
779
|
+
idle_seconds = idle_data.get("idle_seconds", 0) if is_idle else 0
|
780
|
+
|
781
|
+
if is_idle:
|
782
|
+
if isinstance(timeout_sec, int) and isinstance(idle_seconds, int):
|
783
|
+
remaining = max(0, timeout_sec - idle_seconds)
|
784
|
+
idle_disp = f" [yellow]Idle {idle_seconds//60}m/{timeout_sec//60}m: [red]{remaining//60}m {remaining%60}s[/red] left[/yellow]"
|
785
|
+
else:
|
786
|
+
idle_disp = " [yellow]Idle ?/?[/yellow]"
|
787
|
+
else:
|
788
|
+
# Actively not idle
|
789
|
+
idle_disp = " [green]Active[/green]"
|
790
|
+
|
791
|
+
# Build status lines - minimal info for fast view
|
792
|
+
status_lines = [
|
793
|
+
f"[blue]{engine['name']}[/blue] {run_disp}{idle_disp}\n",
|
794
|
+
]
|
795
|
+
|
796
|
+
# Add studio owner if known
|
797
|
+
if studio_user:
|
798
|
+
status_lines.append(f"Studio: [magenta]{studio_user}[/magenta]")
|
799
|
+
|
800
|
+
# Add activity sensors if we have idle data
|
801
|
+
if idle_data and idle_data.get("reasons"):
|
802
|
+
status_lines.append("")
|
803
|
+
status_lines.append("[bold]Activity:[/bold]")
|
804
|
+
|
805
|
+
sensor_map = {
|
806
|
+
"CoffeeLockSensor": ("☕", "Coffee"),
|
807
|
+
"ActiveLoginSensor": ("🐚", "SSH"),
|
808
|
+
"IDEConnectionSensor": ("🖥 ", "IDE"),
|
809
|
+
"DockerWorkloadSensor": ("🐳", "Docker"),
|
810
|
+
}
|
811
|
+
|
812
|
+
for r in idle_data.get("reasons", []):
|
813
|
+
sensor = r.get("sensor", "Unknown")
|
814
|
+
active = r.get("active", False)
|
815
|
+
icon, label = sensor_map.get(sensor, ("?", sensor))
|
816
|
+
status_str = "[green]YES[/green]" if active else "[dim]nope[/dim]"
|
817
|
+
status_lines.append(f" {icon} {label:6} {status_str}")
|
818
|
+
|
819
|
+
# Display in a nice panel
|
820
|
+
console.print(
|
821
|
+
Panel("\n".join(status_lines), title="Engine Status", border_style="blue")
|
822
|
+
)
|
823
|
+
return # Exit early for fast status
|
709
824
|
|
710
|
-
# Get detailed engine status including idle detector info
|
825
|
+
# Get detailed engine status including idle detector info (for --detailed mode)
|
711
826
|
response = make_api_request("GET", f"/engines/{engine['instance_id']}")
|
712
827
|
if response.status_code != 200:
|
713
828
|
console.print("[red]❌ Failed to fetch engine details[/red]")
|
@@ -783,12 +898,13 @@ def engine_status(
|
|
783
898
|
if not idle_info.get("available"):
|
784
899
|
return "[dim]N/A[/dim]"
|
785
900
|
|
786
|
-
# If idle, show time/threshold
|
901
|
+
# If idle, show time/threshold with time remaining if available
|
787
902
|
if idle_info.get("status") == "idle":
|
788
903
|
idle_seconds_v = idle_info.get("idle_seconds")
|
789
904
|
thresh_v = idle_info.get("idle_threshold")
|
790
905
|
if isinstance(idle_seconds_v, (int, float)) and isinstance(thresh_v, (int, float)):
|
791
|
-
|
906
|
+
remaining = max(0, int(thresh_v) - int(idle_seconds_v))
|
907
|
+
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
908
|
elif isinstance(thresh_v, (int, float)):
|
793
909
|
return f"[yellow]Idle ?/{int(thresh_v)//60}m[/yellow]"
|
794
910
|
else:
|
@@ -1062,6 +1178,9 @@ def engine_status(
|
|
1062
1178
|
)
|
1063
1179
|
|
1064
1180
|
if show_log:
|
1181
|
+
if not detailed:
|
1182
|
+
console.print("[yellow]Note: --show-log requires --detailed flag[/yellow]")
|
1183
|
+
return
|
1065
1184
|
console.print("\n[bold]Bootstrap Log:[/bold]")
|
1066
1185
|
try:
|
1067
1186
|
ssm = boto3.client("ssm", region_name="us-east-1")
|
@@ -2631,9 +2750,7 @@ def idle_timeout_cmd(
|
|
2631
2750
|
set: Optional[str] = typer.Option(
|
2632
2751
|
None, "--set", "-s", help="New timeout (e.g., 2h30m, 45m)"
|
2633
2752
|
),
|
2634
|
-
|
2635
|
-
False, "--status", help="Show detailed idle status (faster than full engine status)"
|
2636
|
-
),
|
2753
|
+
|
2637
2754
|
):
|
2638
2755
|
"""Show or set the engine idle-detector timeout."""
|
2639
2756
|
check_aws_sso()
|
@@ -2648,87 +2765,6 @@ def idle_timeout_cmd(
|
|
2648
2765
|
engine = resolve_engine(name_or_id, engines)
|
2649
2766
|
|
2650
2767
|
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
2768
|
|
2733
2769
|
if set is None:
|
2734
2770
|
# 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.7"
|
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
|