dayhoff-tools 1.3.3__py3-none-any.whl → 1.3.5__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.
@@ -3,7 +3,7 @@
3
3
  import json
4
4
  import subprocess
5
5
  import sys
6
- from datetime import datetime, timedelta
6
+ from datetime import datetime, timedelta, timezone
7
7
  from pathlib import Path
8
8
  from typing import Dict, List, Optional, Tuple
9
9
 
@@ -130,15 +130,31 @@ def parse_launch_time(launch_time_str: str) -> datetime:
130
130
  formats = [
131
131
  "%Y-%m-%dT%H:%M:%S.%fZ",
132
132
  "%Y-%m-%dT%H:%M:%SZ",
133
+ "%Y-%m-%dT%H:%M:%S%z", # ISO format with timezone
134
+ "%Y-%m-%dT%H:%M:%S+00:00", # Explicit UTC offset
133
135
  "%Y-%m-%d %H:%M:%S",
134
136
  ]
137
+
138
+ # First try parsing with fromisoformat for better timezone handling
139
+ try:
140
+ # Handle the ISO format properly
141
+ return datetime.fromisoformat(launch_time_str.replace('Z', '+00:00'))
142
+ except (ValueError, AttributeError):
143
+ pass
144
+
145
+ # Fallback to manual format parsing
135
146
  for fmt in formats:
136
147
  try:
137
- return datetime.strptime(launch_time_str, fmt)
148
+ parsed = datetime.strptime(launch_time_str, fmt)
149
+ # If no timezone info, assume UTC
150
+ if parsed.tzinfo is None:
151
+ parsed = parsed.replace(tzinfo=timezone.utc)
152
+ return parsed
138
153
  except ValueError:
139
154
  continue
155
+
140
156
  # Fallback: assume it's recent
141
- return datetime.utcnow()
157
+ return datetime.now(timezone.utc)
142
158
 
143
159
 
144
160
  def format_status(state: str, ready: Optional[bool]) -> str:
@@ -363,7 +379,7 @@ def list_engines(
363
379
  total_cost = 0.0
364
380
  for engine in engines:
365
381
  launch_time = parse_launch_time(engine["launch_time"])
366
- uptime = datetime.utcnow() - launch_time
382
+ uptime = datetime.now(timezone.utc) - launch_time
367
383
  hourly_cost = HOURLY_COSTS.get(engine["engine_type"], 0)
368
384
 
369
385
  if engine["state"].lower() == "running":
@@ -418,7 +434,7 @@ def engine_status(
418
434
 
419
435
  # Calculate costs
420
436
  launch_time = parse_launch_time(engine["launch_time"])
421
- uptime = datetime.utcnow() - launch_time
437
+ uptime = datetime.now(timezone.utc) - launch_time
422
438
  hourly_cost = HOURLY_COSTS.get(engine["engine_type"], 0)
423
439
  total_cost = hourly_cost * (uptime.total_seconds() / 3600)
424
440
 
@@ -550,7 +566,7 @@ def terminate_engine(
550
566
 
551
567
  # Calculate cost
552
568
  launch_time = parse_launch_time(engine["launch_time"])
553
- uptime = datetime.utcnow() - launch_time
569
+ uptime = datetime.now(timezone.utc) - launch_time
554
570
  hourly_cost = HOURLY_COSTS.get(engine["engine_type"], 0)
555
571
  total_cost = hourly_cost * (uptime.total_seconds() / 3600)
556
572
 
@@ -1001,10 +1017,19 @@ def studio_status():
1001
1017
  return
1002
1018
 
1003
1019
  # Create status panel
1020
+ # Format status with colors
1021
+ status = studio['status']
1022
+ if status == "in-use":
1023
+ status_display = "[magenta]attached[/magenta]"
1024
+ elif status in ["attaching", "detaching"]:
1025
+ status_display = f"[yellow]{status}[/yellow]"
1026
+ else:
1027
+ status_display = f"[green]{status}[/green]"
1028
+
1004
1029
  status_lines = [
1005
1030
  f"[bold]Studio ID:[/bold] {studio['studio_id']}",
1006
1031
  f"[bold]User:[/bold] {studio['user']}",
1007
- f"[bold]Status:[/bold] {studio['status']}",
1032
+ f"[bold]Status:[/bold] {status_display}",
1008
1033
  f"[bold]Size:[/bold] {studio['size_gb']}GB",
1009
1034
  f"[bold]Created:[/bold] {studio['creation_date']}",
1010
1035
  ]
@@ -1214,6 +1239,13 @@ def list_studios(
1214
1239
  console.print("No studios found.")
1215
1240
  return
1216
1241
 
1242
+ # Get all engines to map instance IDs to names
1243
+ engines_response = make_api_request("GET", "/engines")
1244
+ engines = {}
1245
+ if engines_response.status_code == 200:
1246
+ for engine in engines_response.json().get("engines", []):
1247
+ engines[engine["instance_id"]] = engine["name"]
1248
+
1217
1249
  # Create table
1218
1250
  table = Table(title="Studios", box=box.ROUNDED)
1219
1251
  table.add_column("Studio ID", style="cyan")
@@ -1224,14 +1256,39 @@ def list_studios(
1224
1256
  table.add_column("Created")
1225
1257
 
1226
1258
  for studio in studios:
1227
- status_color = "green" if studio["status"] == "available" else "yellow"
1259
+ # Change status display
1260
+ if studio["status"] == "in-use":
1261
+ status_display = "[magenta]attached[/magenta]"
1262
+ elif studio["status"] in ["attaching", "detaching"]:
1263
+ status_display = "[yellow]" + studio["status"] + "[/yellow]"
1264
+ else:
1265
+ status_display = "[green]available[/green]"
1266
+
1267
+ # Format attached engine info
1268
+ attached_to = "-"
1269
+ if studio.get("attached_vm_id"):
1270
+ vm_id = studio["attached_vm_id"]
1271
+ engine_name = engines.get(vm_id, "unknown")
1272
+ attached_to = f"{engine_name} ({vm_id})"
1273
+
1274
+ # Format creation date (remove microseconds and timezone info)
1275
+ created = studio["creation_date"]
1276
+ try:
1277
+ # Parse and reformat to just show date and time
1278
+ if 'T' in created:
1279
+ created_dt = datetime.fromisoformat(created.replace('Z', '+00:00'))
1280
+ created = created_dt.strftime("%Y-%m-%d %H:%M")
1281
+ except:
1282
+ # If parsing fails, just truncate
1283
+ created = created.split('T')[0] if 'T' in created else created[:16]
1284
+
1228
1285
  table.add_row(
1229
1286
  studio["studio_id"],
1230
1287
  studio["user"],
1231
- f"[{status_color}]{studio['status']}[/{status_color}]",
1288
+ status_display,
1232
1289
  f"{studio['size_gb']}GB",
1233
- studio.get("attached_vm_id", "-"),
1234
- studio["creation_date"],
1290
+ attached_to,
1291
+ created,
1235
1292
  )
1236
1293
 
1237
1294
  console.print(table)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: dayhoff-tools
3
- Version: 1.3.3
3
+ Version: 1.3.5
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
@@ -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=uqS46dGMaF5UGNbIJ3G1oY3QxD38jUNHiBAfYMBpmf4,46268
6
+ dayhoff_tools/cli/engine_commands.py,sha256=BU5mi4Q_UTsg2CQLkn2XQaw0anUKwIp_GhMa_Zjlcns,48604
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.3.3.dist-info/METADATA,sha256=mWVT1xFladhd-RuJOeESPAoFxe1FDKsJP-G9tTlO1yk,2842
31
- dayhoff_tools-1.3.3.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
32
- dayhoff_tools-1.3.3.dist-info/entry_points.txt,sha256=iAf4jteNqW3cJm6CO6czLxjW3vxYKsyGLZ8WGmxamSc,49
33
- dayhoff_tools-1.3.3.dist-info/RECORD,,
30
+ dayhoff_tools-1.3.5.dist-info/METADATA,sha256=LmjC7aGDDP-Pj-LII3xRjc_G8CELn7KJEozkjMtGeTU,2842
31
+ dayhoff_tools-1.3.5.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
32
+ dayhoff_tools-1.3.5.dist-info/entry_points.txt,sha256=iAf4jteNqW3cJm6CO6czLxjW3vxYKsyGLZ8WGmxamSc,49
33
+ dayhoff_tools-1.3.5.dist-info/RECORD,,