dayhoff-tools 1.9.0__tar.gz → 1.9.1__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.
Files changed (32) hide show
  1. {dayhoff_tools-1.9.0 → dayhoff_tools-1.9.1}/PKG-INFO +1 -1
  2. {dayhoff_tools-1.9.0 → dayhoff_tools-1.9.1}/dayhoff_tools/cli/engine_commands.py +1 -146
  3. {dayhoff_tools-1.9.0 → dayhoff_tools-1.9.1}/pyproject.toml +1 -1
  4. {dayhoff_tools-1.9.0 → dayhoff_tools-1.9.1}/README.md +0 -0
  5. {dayhoff_tools-1.9.0 → dayhoff_tools-1.9.1}/dayhoff_tools/__init__.py +0 -0
  6. {dayhoff_tools-1.9.0 → dayhoff_tools-1.9.1}/dayhoff_tools/chemistry/standardizer.py +0 -0
  7. {dayhoff_tools-1.9.0 → dayhoff_tools-1.9.1}/dayhoff_tools/chemistry/utils.py +0 -0
  8. {dayhoff_tools-1.9.0 → dayhoff_tools-1.9.1}/dayhoff_tools/cli/__init__.py +0 -0
  9. {dayhoff_tools-1.9.0 → dayhoff_tools-1.9.1}/dayhoff_tools/cli/cloud_commands.py +0 -0
  10. {dayhoff_tools-1.9.0 → dayhoff_tools-1.9.1}/dayhoff_tools/cli/main.py +0 -0
  11. {dayhoff_tools-1.9.0 → dayhoff_tools-1.9.1}/dayhoff_tools/cli/swarm_commands.py +0 -0
  12. {dayhoff_tools-1.9.0 → dayhoff_tools-1.9.1}/dayhoff_tools/cli/utility_commands.py +0 -0
  13. {dayhoff_tools-1.9.0 → dayhoff_tools-1.9.1}/dayhoff_tools/deployment/base.py +0 -0
  14. {dayhoff_tools-1.9.0 → dayhoff_tools-1.9.1}/dayhoff_tools/deployment/deploy_aws.py +0 -0
  15. {dayhoff_tools-1.9.0 → dayhoff_tools-1.9.1}/dayhoff_tools/deployment/deploy_gcp.py +0 -0
  16. {dayhoff_tools-1.9.0 → dayhoff_tools-1.9.1}/dayhoff_tools/deployment/deploy_utils.py +0 -0
  17. {dayhoff_tools-1.9.0 → dayhoff_tools-1.9.1}/dayhoff_tools/deployment/job_runner.py +0 -0
  18. {dayhoff_tools-1.9.0 → dayhoff_tools-1.9.1}/dayhoff_tools/deployment/processors.py +0 -0
  19. {dayhoff_tools-1.9.0 → dayhoff_tools-1.9.1}/dayhoff_tools/deployment/swarm.py +0 -0
  20. {dayhoff_tools-1.9.0 → dayhoff_tools-1.9.1}/dayhoff_tools/embedders.py +0 -0
  21. {dayhoff_tools-1.9.0 → dayhoff_tools-1.9.1}/dayhoff_tools/fasta.py +0 -0
  22. {dayhoff_tools-1.9.0 → dayhoff_tools-1.9.1}/dayhoff_tools/file_ops.py +0 -0
  23. {dayhoff_tools-1.9.0 → dayhoff_tools-1.9.1}/dayhoff_tools/h5.py +0 -0
  24. {dayhoff_tools-1.9.0 → dayhoff_tools-1.9.1}/dayhoff_tools/intake/gcp.py +0 -0
  25. {dayhoff_tools-1.9.0 → dayhoff_tools-1.9.1}/dayhoff_tools/intake/gtdb.py +0 -0
  26. {dayhoff_tools-1.9.0 → dayhoff_tools-1.9.1}/dayhoff_tools/intake/kegg.py +0 -0
  27. {dayhoff_tools-1.9.0 → dayhoff_tools-1.9.1}/dayhoff_tools/intake/mmseqs.py +0 -0
  28. {dayhoff_tools-1.9.0 → dayhoff_tools-1.9.1}/dayhoff_tools/intake/structure.py +0 -0
  29. {dayhoff_tools-1.9.0 → dayhoff_tools-1.9.1}/dayhoff_tools/intake/uniprot.py +0 -0
  30. {dayhoff_tools-1.9.0 → dayhoff_tools-1.9.1}/dayhoff_tools/logs.py +0 -0
  31. {dayhoff_tools-1.9.0 → dayhoff_tools-1.9.1}/dayhoff_tools/sqlite.py +0 -0
  32. {dayhoff_tools-1.9.0 → dayhoff_tools-1.9.1}/dayhoff_tools/warehouse.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: dayhoff-tools
3
- Version: 1.9.0
3
+ Version: 1.9.1
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
@@ -319,87 +319,8 @@ def format_status(state: str, ready: Optional[bool]) -> str:
319
319
  return state
320
320
 
321
321
 
322
- # --------------------------------------------------------------------------------
323
- # Audit helpers (Phase 1 observability)
324
- # --------------------------------------------------------------------------------
325
-
326
-
327
- def _get_engine_audit_bucket() -> Optional[str]:
328
- """Return the engine audit bucket name from SSM Parameter Store, if configured."""
329
- try:
330
- ssm = boto3.client("ssm", region_name="us-east-1")
331
- resp = ssm.get_parameter(Name="/dev/studio-manager/engine-audit-bucket")
332
- return resp["Parameter"]["Value"]
333
- except ClientError:
334
- return None
335
-
336
-
337
- def _fetch_last_audit_via_ssm(instance_id: str) -> Optional[Dict]:
338
- """Fetch last shutdown attempt audit from the engine via SSM (fast best-effort)."""
339
- try:
340
- ssm = boto3.client("ssm", region_name="us-east-1")
341
- resp = ssm.send_command(
342
- InstanceIds=[instance_id],
343
- DocumentName="AWS-RunShellScript",
344
- Parameters={
345
- "commands": [
346
- "cat /var/log/idle-detector/last_shutdown_attempt.json 2>/dev/null || true",
347
- ],
348
- "executionTimeout": ["3"],
349
- },
350
- )
351
- cid = resp["Command"]["CommandId"]
352
- time.sleep(1)
353
- inv = ssm.get_command_invocation(CommandId=cid, InstanceId=instance_id)
354
- if inv["Status"] != "Success":
355
- return None
356
- content = inv["StandardOutputContent"].strip()
357
- if not content:
358
- return None
359
- return json.loads(content)
360
- except Exception:
361
- return None
362
-
363
-
364
- def _fetch_last_audit_via_s3(instance_id: str) -> Optional[Dict]:
365
- """Fetch the newest audit object from S3 if available."""
366
- bucket = _get_engine_audit_bucket()
367
- if not bucket:
368
- return None
369
- try:
370
- s3 = boto3.client("s3", region_name="us-east-1")
371
- paginator = s3.get_paginator("list_objects_v2")
372
- newest = None
373
- for page in paginator.paginate(
374
- Bucket=bucket, Prefix=f"{instance_id}/", MaxKeys=1000
375
- ):
376
- for obj in page.get("Contents", []):
377
- lm = obj.get("LastModified")
378
- if newest is None or (lm and lm > newest["LastModified"]):
379
- newest = obj
380
- if not newest:
381
- return None
382
- obj = s3.get_object(Bucket=bucket, Key=newest["Key"])
383
- data = obj["Body"].read().decode("utf-8")
384
- return json.loads(data)
385
- except Exception:
386
- return None
387
322
 
388
323
 
389
- def _summarize_audit(audit: Dict) -> str:
390
- """Return a compact one-line summary for status output."""
391
- ts = audit.get("ts", "?")
392
- shutdown = audit.get("shutdown", {})
393
- result = shutdown.get("result", "?")
394
- detach = audit.get("detach", {})
395
- num_detached = sum(
396
- 1 for r in detach.get("results", []) if r.get("status") == "success"
397
- )
398
- idle = audit.get("idle", {})
399
- elapsed = int(idle.get("elapsed_sec", 0))
400
- threshold = int(idle.get("threshold_sec", 0))
401
- return f"Last shutdown attempt: {ts} result={result} detach={num_detached} idle={elapsed//60}/{threshold//60}m"
402
-
403
324
 
404
325
  def resolve_engine(name_or_id: str, engines: List[Dict]) -> Dict:
405
326
  """Resolve engine by name or ID with interactive selection."""
@@ -1081,15 +1002,7 @@ def engine_status(
1081
1002
  status_lines.append(_sensor_line(" IDE ", "IDEConnectionSensor", "🖥"))
1082
1003
  status_lines.append(_sensor_line("Docker", "DockerWorkloadSensor", "🐳"))
1083
1004
 
1084
- # Audit one-liner (best-effort SSM fetch)
1085
- try:
1086
- last_audit = _fetch_last_audit_via_ssm(engine["instance_id"])
1087
- if last_audit:
1088
- status_lines.append("")
1089
- status_lines.append("[bold]Shutdown Audit:[/bold]")
1090
- status_lines.append(f" • {_summarize_audit(last_audit)}")
1091
- except Exception:
1092
- pass
1005
+
1093
1006
 
1094
1007
  # Combine top summary and details
1095
1008
  all_lines = top_lines + status_lines
@@ -1128,65 +1041,7 @@ def engine_status(
1128
1041
  console.print(f"[red]❌ Error fetching log: {e}[/red]")
1129
1042
 
1130
1043
 
1131
- @engine_app.command("why")
1132
- def engine_why(
1133
- name_or_id: str = typer.Argument(help="Engine name or instance ID"),
1134
- raw: bool = typer.Option(False, "--raw", help="Print raw audit JSON"),
1135
- ):
1136
- """Explain the last idle-detector shutdown attempt for an engine.
1137
-
1138
- Tries SSM (on-instance file) first, then falls back to S3 audit bucket.
1139
- """
1140
- check_aws_sso()
1141
1044
 
1142
- # Resolve engine
1143
- response = make_api_request("GET", "/engines")
1144
- if response.status_code != 200:
1145
- console.print("[red]❌ Failed to fetch engines[/red]")
1146
- raise typer.Exit(1)
1147
-
1148
- engines = response.json().get("engines", [])
1149
- engine = resolve_engine(name_or_id, engines)
1150
-
1151
- audit = _fetch_last_audit_via_ssm(
1152
- engine["instance_id"]
1153
- ) or _fetch_last_audit_via_s3(engine["instance_id"])
1154
- if not audit:
1155
- console.print("No audit found (engine may not have attempted shutdown yet).")
1156
- raise typer.Exit(0)
1157
-
1158
- if raw:
1159
- console.print_json(data=audit)
1160
- return
1161
-
1162
- # Pretty summary
1163
- status = _summarize_audit(audit)
1164
- panel_lines = [
1165
- f"[bold]{status}[/bold]",
1166
- "",
1167
- "[bold]Sensors:[/bold]",
1168
- ]
1169
- for r in audit.get("idle", {}).get("reason_snapshot", []):
1170
- active = "✓" if r.get("active") else "-"
1171
- reason = r.get("reason") or ""
1172
- sensor = r.get("sensor")
1173
- panel_lines.append(f" {active} {sensor}: {reason}")
1174
- panel_lines.append("")
1175
- panel_lines.append("[bold]Detach results:[/bold]")
1176
- for res in audit.get("detach", {}).get("results", []):
1177
- panel_lines.append(
1178
- f" - {res.get('studio_id')}: {res.get('status')} {res.get('error') or ''}"
1179
- )
1180
- s3_info = audit.get("s3", {})
1181
- if s3_info.get("uploaded"):
1182
- panel_lines.append("")
1183
- panel_lines.append(
1184
- f"[dim]S3: s3://{s3_info.get('bucket')}/{s3_info.get('key')}[/dim]"
1185
- )
1186
-
1187
- console.print(
1188
- Panel("\n".join(panel_lines), title="Idle Shutdown Audit", border_style="blue")
1189
- )
1190
1045
 
1191
1046
 
1192
1047
  @engine_app.command("stop")
@@ -5,7 +5,7 @@ build-backend = "poetry.core.masonry.api"
5
5
 
6
6
  [project]
7
7
  name = "dayhoff-tools"
8
- version = "1.9.0"
8
+ version = "1.9.1"
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