dayhoff-tools 1.12.32__py3-none-any.whl → 1.13.0__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/engines_studios/engine-studio-cli.md +24 -12
- dayhoff_tools/cli/engines_studios/engine_commands.py +15 -4
- dayhoff_tools/cli/engines_studios/simulators/engine_list_simulator.py +57 -9
- dayhoff_tools/cli/engines_studios/simulators/studio_list_simulator.py +325 -0
- dayhoff_tools/cli/engines_studios/studio_commands.py +48 -16
- {dayhoff_tools-1.12.32.dist-info → dayhoff_tools-1.13.0.dist-info}/METADATA +1 -1
- {dayhoff_tools-1.12.32.dist-info → dayhoff_tools-1.13.0.dist-info}/RECORD +9 -8
- {dayhoff_tools-1.12.32.dist-info → dayhoff_tools-1.13.0.dist-info}/WHEEL +0 -0
- {dayhoff_tools-1.12.32.dist-info → dayhoff_tools-1.13.0.dist-info}/entry_points.txt +0 -0
|
@@ -323,18 +323,22 @@ dh engine2 list --env sand
|
|
|
323
323
|
```
|
|
324
324
|
Engines for AWS Account dev
|
|
325
325
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
326
|
+
┌──────────────┬─────────────┬─────────────┬─────────────┬─────────────────────┐
|
|
327
|
+
│ Name │ State │ User │ Type │ Instance ID │
|
|
328
|
+
├──────────────┼─────────────┼─────────────┼─────────────┼─────────────────────┤
|
|
329
|
+
│ alice-work │ running │ alice │ cpu │ i-0123456789abcdef0 │
|
|
330
|
+
│ bob-training │ running │ bob │ a10g │ i-0fedcba987654321 │
|
|
331
|
+
│ batch-worker │ stopped │ charlie │ cpumax │ i-0abc123def456789 │
|
|
332
|
+
└──────────────┴─────────────┴─────────────┴─────────────┴─────────────────────┘
|
|
331
333
|
|
|
332
334
|
Total: 3 engine(s)
|
|
333
335
|
```
|
|
334
336
|
|
|
335
337
|
**Formatting:**
|
|
338
|
+
- Full table borders with Unicode box-drawing characters
|
|
336
339
|
- Engine names are displayed in blue
|
|
337
340
|
- State is color-coded: green for "running", yellow for "starting/stopping", grey for "stopped"
|
|
341
|
+
- Instance IDs are displayed in grey
|
|
338
342
|
- Name column width adjusts dynamically to fit the longest engine name
|
|
339
343
|
|
|
340
344
|
---
|
|
@@ -751,20 +755,28 @@ dh studio2 list
|
|
|
751
755
|
|
|
752
756
|
**Output:**
|
|
753
757
|
```
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
758
|
+
Studios for AWS Account dev
|
|
759
|
+
|
|
760
|
+
┌────────┬──────────────┬──────────────┬───────────┬───────────────────────────┐
|
|
761
|
+
│ User │ Status │ Attached To │ Size │ Studio ID │
|
|
762
|
+
├────────┼──────────────┼──────────────┼───────────┼───────────────────────────┤
|
|
763
|
+
│ alice │ attached │ alice-work │ 100GB │ vol-0123456789abcdef0 │
|
|
764
|
+
│ bob │ available │ - │ 200GB │ vol-0fedcba987654321 │
|
|
765
|
+
│ carol │ attaching │ carol-gpu │ 150GB │ vol-0abc123def456789 │
|
|
766
|
+
└────────┴──────────────┴──────────────┴───────────┴───────────────────────────┘
|
|
759
767
|
|
|
760
768
|
Total: 3 studio(s)
|
|
761
769
|
```
|
|
762
770
|
|
|
763
771
|
**Formatting:**
|
|
772
|
+
- Full table borders with Unicode box-drawing characters
|
|
764
773
|
- User names are displayed in blue
|
|
765
|
-
- Status is color-coded: green for "available
|
|
774
|
+
- Status is color-coded: purple for "attached", green for "available", yellow for "attaching/detaching", red for "error"
|
|
775
|
+
- "Attached To" shows engine name, or "-" if not attached
|
|
776
|
+
- Studio IDs are displayed in grey
|
|
766
777
|
- User column width adjusts dynamically to fit the longest username
|
|
767
|
-
-
|
|
778
|
+
- Attached To column width adjusts dynamically to fit the longest engine name
|
|
779
|
+
- Columns are ordered: User, Status, Attached To, Size, Studio ID
|
|
768
780
|
|
|
769
781
|
---
|
|
770
782
|
|
|
@@ -598,13 +598,18 @@ def list_engines(env: Optional[str]):
|
|
|
598
598
|
id_width = 20
|
|
599
599
|
|
|
600
600
|
# Calculate total width for separator line
|
|
601
|
-
total_width = name_width + state_width + user_width + type_width + id_width +
|
|
601
|
+
total_width = name_width + state_width + user_width + type_width + id_width + 9 # +9 for separators and spaces
|
|
602
|
+
|
|
603
|
+
# Table top border
|
|
604
|
+
click.echo("┌" + "─" * (name_width + 1) + "┬" + "─" * (state_width + 1) + "┬" + "─" * (user_width + 1) + "┬" + "─" * (type_width + 1) + "┬" + "─" * (id_width + 1) + "┐")
|
|
602
605
|
|
|
603
606
|
# Table header
|
|
604
607
|
click.echo(
|
|
605
|
-
f"{'Name':<{name_width}} {'State':<{state_width}} {'User':<{user_width}} {'Type':<{type_width}} {'Instance ID':<{id_width}}"
|
|
608
|
+
f"│ {'Name':<{name_width}}│ {'State':<{state_width}}│ {'User':<{user_width}}│ {'Type':<{type_width}}│ {'Instance ID':<{id_width}}│"
|
|
606
609
|
)
|
|
607
|
-
|
|
610
|
+
|
|
611
|
+
# Header separator
|
|
612
|
+
click.echo("├" + "─" * (name_width + 1) + "┼" + "─" * (state_width + 1) + "┼" + "─" * (user_width + 1) + "┼" + "─" * (type_width + 1) + "┼" + "─" * (id_width + 1) + "┤")
|
|
608
613
|
|
|
609
614
|
# Table rows
|
|
610
615
|
for engine in engines:
|
|
@@ -635,10 +640,16 @@ def list_engines(env: Optional[str]):
|
|
|
635
640
|
else:
|
|
636
641
|
state_display = f"{state:<{state_width}}" # No color for other states
|
|
637
642
|
|
|
643
|
+
# Color the instance ID (grey)
|
|
644
|
+
instance_id_display = f"\033[90m{instance_id:<{id_width}}\033[0m"
|
|
645
|
+
|
|
638
646
|
click.echo(
|
|
639
|
-
f"{name_display} {state_display} {user:<{user_width}} {engine_type:<{type_width}} {
|
|
647
|
+
f"│ {name_display}│ {state_display}│ {user:<{user_width}}│ {engine_type:<{type_width}}│ {instance_id_display}│"
|
|
640
648
|
)
|
|
641
649
|
|
|
650
|
+
# Table bottom border
|
|
651
|
+
click.echo("└" + "─" * (name_width + 1) + "┴" + "─" * (state_width + 1) + "┴" + "─" * (user_width + 1) + "┴" + "─" * (type_width + 1) + "┴" + "─" * (id_width + 1) + "┘")
|
|
652
|
+
|
|
642
653
|
click.echo(f"\nTotal: {len(engines)} engine(s)")
|
|
643
654
|
|
|
644
655
|
except Exception as e:
|
|
@@ -31,7 +31,9 @@ def format_list_output(engines: list[dict[str, Any]], env: str = "dev") -> None:
|
|
|
31
31
|
return
|
|
32
32
|
|
|
33
33
|
# Calculate dynamic width for Name column (longest name + 2 for padding)
|
|
34
|
-
max_name_len = max(
|
|
34
|
+
max_name_len = max(
|
|
35
|
+
(len(engine.get("name", "unknown")) for engine in engines), default=4
|
|
36
|
+
)
|
|
35
37
|
name_width = max(max_name_len + 2, len("Name") + 2)
|
|
36
38
|
|
|
37
39
|
# Fixed widths for other columns
|
|
@@ -40,12 +42,40 @@ def format_list_output(engines: list[dict[str, Any]], env: str = "dev") -> None:
|
|
|
40
42
|
type_width = 12
|
|
41
43
|
id_width = 20
|
|
42
44
|
|
|
43
|
-
#
|
|
44
|
-
|
|
45
|
+
# Table top border
|
|
46
|
+
print(
|
|
47
|
+
"┌"
|
|
48
|
+
+ "─" * (name_width + 1)
|
|
49
|
+
+ "┬"
|
|
50
|
+
+ "─" * (state_width + 1)
|
|
51
|
+
+ "┬"
|
|
52
|
+
+ "─" * (user_width + 1)
|
|
53
|
+
+ "┬"
|
|
54
|
+
+ "─" * (type_width + 1)
|
|
55
|
+
+ "┬"
|
|
56
|
+
+ "─" * (id_width + 1)
|
|
57
|
+
+ "┐"
|
|
58
|
+
)
|
|
45
59
|
|
|
46
60
|
# Table header
|
|
47
|
-
print(
|
|
48
|
-
|
|
61
|
+
print(
|
|
62
|
+
f"│ {'Name':<{name_width}}│ {'State':<{state_width}}│ {'User':<{user_width}}│ {'Type':<{type_width}}│ {'Instance ID':<{id_width}}│"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Header separator
|
|
66
|
+
print(
|
|
67
|
+
"├"
|
|
68
|
+
+ "─" * (name_width + 1)
|
|
69
|
+
+ "┼"
|
|
70
|
+
+ "─" * (state_width + 1)
|
|
71
|
+
+ "┼"
|
|
72
|
+
+ "─" * (user_width + 1)
|
|
73
|
+
+ "┼"
|
|
74
|
+
+ "─" * (type_width + 1)
|
|
75
|
+
+ "┼"
|
|
76
|
+
+ "─" * (id_width + 1)
|
|
77
|
+
+ "┤"
|
|
78
|
+
)
|
|
49
79
|
|
|
50
80
|
# Table rows
|
|
51
81
|
for engine in engines:
|
|
@@ -57,11 +87,11 @@ def format_list_output(engines: list[dict[str, Any]], env: str = "dev") -> None:
|
|
|
57
87
|
|
|
58
88
|
# Truncate if needed
|
|
59
89
|
if len(name) > name_width - 1:
|
|
60
|
-
name = name[:name_width - 1]
|
|
90
|
+
name = name[: name_width - 1]
|
|
61
91
|
if len(user) > user_width - 1:
|
|
62
|
-
user = user[:user_width - 1]
|
|
92
|
+
user = user[: user_width - 1]
|
|
63
93
|
if len(engine_type) > type_width - 1:
|
|
64
|
-
engine_type = engine_type[:type_width - 1]
|
|
94
|
+
engine_type = engine_type[: type_width - 1]
|
|
65
95
|
|
|
66
96
|
# Color the name (blue)
|
|
67
97
|
name_display = colorize(f"{name:<{name_width}}", "34")
|
|
@@ -76,10 +106,28 @@ def format_list_output(engines: list[dict[str, Any]], env: str = "dev") -> None:
|
|
|
76
106
|
else:
|
|
77
107
|
state_display = f"{state:<{state_width}}" # No color for other states
|
|
78
108
|
|
|
109
|
+
# Color the instance ID (grey)
|
|
110
|
+
instance_id_display = colorize(f"{instance_id:<{id_width}}", "90")
|
|
111
|
+
|
|
79
112
|
print(
|
|
80
|
-
f"{name_display} {state_display} {user:<{user_width}} {engine_type:<{type_width}} {
|
|
113
|
+
f"│ {name_display}│ {state_display}│ {user:<{user_width}}│ {engine_type:<{type_width}}│ {instance_id_display}│"
|
|
81
114
|
)
|
|
82
115
|
|
|
116
|
+
# Table bottom border
|
|
117
|
+
print(
|
|
118
|
+
"└"
|
|
119
|
+
+ "─" * (name_width + 1)
|
|
120
|
+
+ "┴"
|
|
121
|
+
+ "─" * (state_width + 1)
|
|
122
|
+
+ "┴"
|
|
123
|
+
+ "─" * (user_width + 1)
|
|
124
|
+
+ "┴"
|
|
125
|
+
+ "─" * (type_width + 1)
|
|
126
|
+
+ "┴"
|
|
127
|
+
+ "─" * (id_width + 1)
|
|
128
|
+
+ "┘"
|
|
129
|
+
)
|
|
130
|
+
|
|
83
131
|
print(f"\nTotal: {len(engines)} engine(s)")
|
|
84
132
|
|
|
85
133
|
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Simulator for studio list output - iterate on design locally without AWS.
|
|
3
|
+
|
|
4
|
+
This lets you quickly see how the list command output looks with different
|
|
5
|
+
studio states and configurations.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
python dayhoff_tools/cli/engines_studios/simulators/studio_list_simulator.py # Show all scenarios
|
|
9
|
+
python dayhoff_tools/cli/engines_studios/simulators/studio_list_simulator.py --scenario few # Show specific scenario
|
|
10
|
+
python dayhoff_tools/cli/engines_studios/simulators/studio_list_simulator.py --env prod # Simulate different environment
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import sys
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def colorize(text: str, color_code: str) -> str:
|
|
19
|
+
"""Apply ANSI color code to text."""
|
|
20
|
+
return f"\033[{color_code}m{text}\033[0m"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def format_list_output(studios: list[dict[str, Any]], engines_map: dict[str, str], env: str = "dev") -> None:
|
|
24
|
+
"""Format and print studio list output matching the actual CLI."""
|
|
25
|
+
|
|
26
|
+
# Header with blue account name
|
|
27
|
+
print(f"Studios for AWS Account {colorize(env, '34')}\n")
|
|
28
|
+
|
|
29
|
+
if not studios:
|
|
30
|
+
print("No studios found")
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
# Calculate dynamic width for User column (longest user + 2 for padding)
|
|
34
|
+
max_user_len = max((len(studio.get("user", "unknown")) for studio in studios), default=4)
|
|
35
|
+
user_width = max(max_user_len + 2, len("User") + 2)
|
|
36
|
+
|
|
37
|
+
# Calculate dynamic width for Attached To column
|
|
38
|
+
max_attached_len = 0
|
|
39
|
+
for studio in studios:
|
|
40
|
+
if studio.get("attached_to"):
|
|
41
|
+
instance_id = studio["attached_to"]
|
|
42
|
+
engine_name = engines_map.get(instance_id, "unknown")
|
|
43
|
+
max_attached_len = max(max_attached_len, len(engine_name))
|
|
44
|
+
attached_width = max(max_attached_len + 2, len("Attached To") + 2, 3) # At least 3 for "-"
|
|
45
|
+
|
|
46
|
+
# Fixed widths for other columns
|
|
47
|
+
status_width = 12
|
|
48
|
+
size_width = 10
|
|
49
|
+
id_width = 25
|
|
50
|
+
|
|
51
|
+
# Table top border
|
|
52
|
+
print("┌" + "─" * (user_width + 1) + "┬" + "─" * (status_width + 1) + "┬" + "─" * (attached_width + 1) + "┬" + "─" * (size_width + 1) + "┬" + "─" * (id_width + 1) + "┐")
|
|
53
|
+
|
|
54
|
+
# Table header
|
|
55
|
+
print(f"│ {'User':<{user_width}}│ {'Status':<{status_width}}│ {'Attached To':<{attached_width}}│ {'Size':<{size_width}}│ {'Studio ID':<{id_width}}│")
|
|
56
|
+
|
|
57
|
+
# Header separator
|
|
58
|
+
print("├" + "─" * (user_width + 1) + "┼" + "─" * (status_width + 1) + "┼" + "─" * (attached_width + 1) + "┼" + "─" * (size_width + 1) + "┼" + "─" * (id_width + 1) + "┤")
|
|
59
|
+
|
|
60
|
+
# Table rows
|
|
61
|
+
for studio in studios:
|
|
62
|
+
user = studio.get("user", "unknown")
|
|
63
|
+
status = studio.get("status", "unknown")
|
|
64
|
+
size = f"{studio.get('size_gb', 0)}GB"
|
|
65
|
+
studio_id = studio.get("studio_id", "unknown")
|
|
66
|
+
attached_to = studio.get("attached_to")
|
|
67
|
+
|
|
68
|
+
# Truncate if needed
|
|
69
|
+
if len(user) > user_width - 1:
|
|
70
|
+
user = user[:user_width - 1]
|
|
71
|
+
|
|
72
|
+
# Color the user (blue)
|
|
73
|
+
user_display = colorize(f"{user:<{user_width}}", "34")
|
|
74
|
+
|
|
75
|
+
# Format status - display "in-use" as "attached" in purple
|
|
76
|
+
if status == "in-use":
|
|
77
|
+
display_status = "attached"
|
|
78
|
+
status_display = colorize(f"{display_status:<{status_width}}", "35") # Purple
|
|
79
|
+
elif status == "available":
|
|
80
|
+
status_display = colorize(f"{status:<{status_width}}", "32") # Green
|
|
81
|
+
elif status in ["attaching", "detaching"]:
|
|
82
|
+
status_display = colorize(f"{status:<{status_width}}", "33") # Yellow
|
|
83
|
+
elif status == "attached":
|
|
84
|
+
status_display = colorize(f"{status:<{status_width}}", "35") # Purple
|
|
85
|
+
elif status == "error":
|
|
86
|
+
status_display = colorize(f"{status:<{status_width}}", "31") # Red
|
|
87
|
+
else:
|
|
88
|
+
status_display = f"{status:<{status_width}}" # No color for other states
|
|
89
|
+
|
|
90
|
+
# Format Attached To column
|
|
91
|
+
if attached_to:
|
|
92
|
+
instance_id = attached_to
|
|
93
|
+
engine_name = engines_map.get(instance_id, "unknown")
|
|
94
|
+
# Engine name in white (no color)
|
|
95
|
+
attached_display = f"{engine_name:<{attached_width}}"
|
|
96
|
+
else:
|
|
97
|
+
attached_display = f"{'-':<{attached_width}}"
|
|
98
|
+
|
|
99
|
+
# Color the studio ID (grey)
|
|
100
|
+
studio_id_display = colorize(f"{studio_id:<{id_width}}", "90")
|
|
101
|
+
|
|
102
|
+
print(f"│ {user_display}│ {status_display}│ {attached_display}│ {size:<{size_width}}│ {studio_id_display}│")
|
|
103
|
+
|
|
104
|
+
# Table bottom border
|
|
105
|
+
print("└" + "─" * (user_width + 1) + "┴" + "─" * (status_width + 1) + "┴" + "─" * (attached_width + 1) + "┴" + "─" * (size_width + 1) + "┴" + "─" * (id_width + 1) + "┘")
|
|
106
|
+
|
|
107
|
+
print(f"\nTotal: {len(studios)} studio(s)")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def generate_scenarios() -> dict[str, dict[str, Any]]:
|
|
111
|
+
"""Generate various test scenarios for studio list output."""
|
|
112
|
+
|
|
113
|
+
scenarios = {}
|
|
114
|
+
|
|
115
|
+
# Create a consistent engines map for all scenarios
|
|
116
|
+
engines_map = {
|
|
117
|
+
"i-0123456789abcdef0": "alice-gpu",
|
|
118
|
+
"i-1234567890abcdef1": "bob-cpu",
|
|
119
|
+
"i-2345678901abcdef2": "charlie-work",
|
|
120
|
+
"i-3456789012abcdef3": "diana-dev",
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
# Scenario 1: Single available studio
|
|
124
|
+
scenarios["single"] = {
|
|
125
|
+
"name": "Single Available Studio",
|
|
126
|
+
"studios": [
|
|
127
|
+
{
|
|
128
|
+
"user": "alice",
|
|
129
|
+
"status": "available",
|
|
130
|
+
"size_gb": 100,
|
|
131
|
+
"studio_id": "vol-0abc123def456789a",
|
|
132
|
+
"attached_to": None,
|
|
133
|
+
}
|
|
134
|
+
],
|
|
135
|
+
"engines_map": engines_map,
|
|
136
|
+
"env": "dev",
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# Scenario 2: Few studios with various states
|
|
140
|
+
scenarios["few"] = {
|
|
141
|
+
"name": "Few Studios - Mixed States",
|
|
142
|
+
"studios": [
|
|
143
|
+
{
|
|
144
|
+
"user": "alice",
|
|
145
|
+
"status": "in-use", # Will be displayed as "attached" in purple
|
|
146
|
+
"size_gb": 100,
|
|
147
|
+
"studio_id": "vol-0abc123def456789a",
|
|
148
|
+
"attached_to": "i-0123456789abcdef0",
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"user": "bob",
|
|
152
|
+
"status": "available",
|
|
153
|
+
"size_gb": 200,
|
|
154
|
+
"studio_id": "vol-0abc123def456789b",
|
|
155
|
+
"attached_to": None,
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"user": "charlie",
|
|
159
|
+
"status": "attaching",
|
|
160
|
+
"size_gb": 150,
|
|
161
|
+
"studio_id": "vol-0abc123def456789c",
|
|
162
|
+
"attached_to": "i-2345678901abcdef2",
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
"engines_map": engines_map,
|
|
166
|
+
"env": "sand",
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
# Scenario 3: Many studios (production-like)
|
|
170
|
+
scenarios["many"] = {
|
|
171
|
+
"name": "Many Studios - Production",
|
|
172
|
+
"studios": [
|
|
173
|
+
{
|
|
174
|
+
"user": "alice",
|
|
175
|
+
"status": "attached",
|
|
176
|
+
"size_gb": 100,
|
|
177
|
+
"studio_id": "vol-0abc123def456789a",
|
|
178
|
+
"attached_to": "i-0123456789abcdef0",
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
"user": "bob",
|
|
182
|
+
"status": "attached",
|
|
183
|
+
"size_gb": 200,
|
|
184
|
+
"studio_id": "vol-0abc123def456789b",
|
|
185
|
+
"attached_to": "i-1234567890abcdef1",
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"user": "charlie",
|
|
189
|
+
"status": "available",
|
|
190
|
+
"size_gb": 150,
|
|
191
|
+
"studio_id": "vol-0abc123def456789c",
|
|
192
|
+
"attached_to": None,
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"user": "diana",
|
|
196
|
+
"status": "attached",
|
|
197
|
+
"size_gb": 250,
|
|
198
|
+
"studio_id": "vol-0abc123def456789d",
|
|
199
|
+
"attached_to": "i-3456789012abcdef3",
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"user": "eve",
|
|
203
|
+
"status": "available",
|
|
204
|
+
"size_gb": 100,
|
|
205
|
+
"studio_id": "vol-0abc123def456789e",
|
|
206
|
+
"attached_to": None,
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
"user": "frank",
|
|
210
|
+
"status": "detaching",
|
|
211
|
+
"size_gb": 300,
|
|
212
|
+
"studio_id": "vol-0abc123def456789f",
|
|
213
|
+
"attached_to": None,
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
"engines_map": engines_map,
|
|
217
|
+
"env": "prod",
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
# Scenario 4: Empty list
|
|
221
|
+
scenarios["empty"] = {
|
|
222
|
+
"name": "No Studios",
|
|
223
|
+
"studios": [],
|
|
224
|
+
"engines_map": engines_map,
|
|
225
|
+
"env": "dev",
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
# Scenario 5: All transitional states
|
|
229
|
+
scenarios["transitions"] = {
|
|
230
|
+
"name": "Transitional States",
|
|
231
|
+
"studios": [
|
|
232
|
+
{
|
|
233
|
+
"user": "alice",
|
|
234
|
+
"status": "attaching",
|
|
235
|
+
"size_gb": 100,
|
|
236
|
+
"studio_id": "vol-0abc123def456789a",
|
|
237
|
+
"attached_to": "i-0123456789abcdef0",
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
"user": "bob",
|
|
241
|
+
"status": "detaching",
|
|
242
|
+
"size_gb": 200,
|
|
243
|
+
"studio_id": "vol-0abc123def456789b",
|
|
244
|
+
"attached_to": None,
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
"user": "charlie",
|
|
248
|
+
"status": "error",
|
|
249
|
+
"size_gb": 150,
|
|
250
|
+
"studio_id": "vol-0abc123def456789c",
|
|
251
|
+
"attached_to": None,
|
|
252
|
+
},
|
|
253
|
+
],
|
|
254
|
+
"engines_map": engines_map,
|
|
255
|
+
"env": "sand",
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
# Scenario 6: Long names
|
|
259
|
+
scenarios["long_names"] = {
|
|
260
|
+
"name": "Long User Names",
|
|
261
|
+
"studios": [
|
|
262
|
+
{
|
|
263
|
+
"user": "alice-with-very-long-username",
|
|
264
|
+
"status": "attached",
|
|
265
|
+
"size_gb": 100,
|
|
266
|
+
"studio_id": "vol-0abc123def456789a",
|
|
267
|
+
"attached_to": "i-0123456789abcdef0",
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
"user": "bob",
|
|
271
|
+
"status": "available",
|
|
272
|
+
"size_gb": 200,
|
|
273
|
+
"studio_id": "vol-0abc123def456789b",
|
|
274
|
+
"attached_to": None,
|
|
275
|
+
},
|
|
276
|
+
],
|
|
277
|
+
"engines_map": engines_map,
|
|
278
|
+
"env": "dev",
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return scenarios
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def main():
|
|
285
|
+
parser = argparse.ArgumentParser(
|
|
286
|
+
description="Simulate studio list output for design iteration"
|
|
287
|
+
)
|
|
288
|
+
parser.add_argument(
|
|
289
|
+
"--scenario",
|
|
290
|
+
choices=["single", "few", "many", "empty", "transitions", "long_names", "all"],
|
|
291
|
+
default="all",
|
|
292
|
+
help="Which scenario to display (default: all)",
|
|
293
|
+
)
|
|
294
|
+
parser.add_argument(
|
|
295
|
+
"--env",
|
|
296
|
+
choices=["dev", "sand", "prod"],
|
|
297
|
+
help="Override environment for display",
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
args = parser.parse_args()
|
|
301
|
+
|
|
302
|
+
scenarios = generate_scenarios()
|
|
303
|
+
|
|
304
|
+
if args.scenario == "all":
|
|
305
|
+
# Show all scenarios
|
|
306
|
+
for _, scenario_data in scenarios.items():
|
|
307
|
+
print("\n" + "=" * 80)
|
|
308
|
+
print(f"SCENARIO: {scenario_data['name']}")
|
|
309
|
+
print("=" * 80 + "\n")
|
|
310
|
+
|
|
311
|
+
env = args.env if args.env else scenario_data["env"]
|
|
312
|
+
format_list_output(scenario_data["studios"], scenario_data["engines_map"], env)
|
|
313
|
+
print() # Extra newline between scenarios
|
|
314
|
+
else:
|
|
315
|
+
# Show specific scenario
|
|
316
|
+
scenario_data = scenarios[args.scenario]
|
|
317
|
+
print(f"\nSCENARIO: {scenario_data['name']}\n")
|
|
318
|
+
|
|
319
|
+
env = args.env if args.env else scenario_data["env"]
|
|
320
|
+
format_list_output(scenario_data["studios"], scenario_data["engines_map"], env)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
if __name__ == "__main__":
|
|
324
|
+
main()
|
|
325
|
+
|
|
@@ -262,23 +262,40 @@ def list_studios(env: Optional[str]):
|
|
|
262
262
|
click.echo("No studios found")
|
|
263
263
|
return
|
|
264
264
|
|
|
265
|
+
# Get all engines to map instance IDs to names
|
|
266
|
+
engines_result = client.list_engines()
|
|
267
|
+
engines_map = {}
|
|
268
|
+
for engine in engines_result.get("engines", []):
|
|
269
|
+
engines_map[engine["instance_id"]] = engine["name"]
|
|
270
|
+
|
|
265
271
|
# Calculate dynamic width for User column (longest user + 2 for padding)
|
|
266
272
|
max_user_len = max((len(studio.get("user", "unknown")) for studio in studios), default=4)
|
|
267
273
|
user_width = max(max_user_len + 2, len("User") + 2)
|
|
268
274
|
|
|
269
|
-
#
|
|
275
|
+
# Calculate dynamic width for Attached To column
|
|
276
|
+
max_attached_len = 0
|
|
277
|
+
for studio in studios:
|
|
278
|
+
if studio.get("attached_to"):
|
|
279
|
+
instance_id = studio["attached_to"]
|
|
280
|
+
engine_name = engines_map.get(instance_id, "unknown")
|
|
281
|
+
max_attached_len = max(max_attached_len, len(engine_name))
|
|
282
|
+
attached_width = max(max_attached_len + 2, len("Attached To") + 2, 3) # At least 3 for "-"
|
|
283
|
+
|
|
284
|
+
# Fixed widths for other columns - reordered to [User, Status, Attached To, Size, Studio ID]
|
|
270
285
|
status_width = 12
|
|
271
286
|
size_width = 10
|
|
272
287
|
id_width = 25
|
|
273
288
|
|
|
274
|
-
#
|
|
275
|
-
|
|
289
|
+
# Table top border
|
|
290
|
+
click.echo("┌" + "─" * (user_width + 1) + "┬" + "─" * (status_width + 1) + "┬" + "─" * (attached_width + 1) + "┬" + "─" * (size_width + 1) + "┬" + "─" * (id_width + 1) + "┐")
|
|
276
291
|
|
|
277
|
-
# Table header - reordered to [User, Status, Size, Studio ID]
|
|
292
|
+
# Table header - reordered to [User, Status, Attached To, Size, Studio ID]
|
|
278
293
|
click.echo(
|
|
279
|
-
f"{'User':<{user_width}} {'Status':<{status_width}} {'Size':<{size_width}} {'Studio ID':<{id_width}}"
|
|
294
|
+
f"│ {'User':<{user_width}}│ {'Status':<{status_width}}│ {'Attached To':<{attached_width}}│ {'Size':<{size_width}}│ {'Studio ID':<{id_width}}│"
|
|
280
295
|
)
|
|
281
|
-
|
|
296
|
+
|
|
297
|
+
# Header separator
|
|
298
|
+
click.echo("├" + "─" * (user_width + 1) + "┼" + "─" * (status_width + 1) + "┼" + "─" * (attached_width + 1) + "┼" + "─" * (size_width + 1) + "┼" + "─" * (id_width + 1) + "┤")
|
|
282
299
|
|
|
283
300
|
# Table rows
|
|
284
301
|
for studio in studios:
|
|
@@ -286,6 +303,7 @@ def list_studios(env: Optional[str]):
|
|
|
286
303
|
status = studio.get("status", "unknown")
|
|
287
304
|
size = f"{studio.get('size_gb', 0)}GB"
|
|
288
305
|
studio_id = studio.get("studio_id", "unknown")
|
|
306
|
+
attached_to = studio.get("attached_to")
|
|
289
307
|
|
|
290
308
|
# Truncate if needed
|
|
291
309
|
if len(user) > user_width - 1:
|
|
@@ -294,26 +312,40 @@ def list_studios(env: Optional[str]):
|
|
|
294
312
|
# Color the user (blue)
|
|
295
313
|
user_display = f"\033[34m{user:<{user_width}}\033[0m"
|
|
296
314
|
|
|
297
|
-
#
|
|
298
|
-
if status == "
|
|
315
|
+
# Format status - display "in-use" as "attached" in purple
|
|
316
|
+
if status == "in-use":
|
|
317
|
+
display_status = "attached"
|
|
318
|
+
status_display = f"\033[35m{display_status:<{status_width}}\033[0m" # Purple
|
|
319
|
+
elif status == "available":
|
|
299
320
|
status_display = f"\033[32m{status:<{status_width}}\033[0m" # Green
|
|
300
321
|
elif status in ["attaching", "detaching"]:
|
|
301
322
|
status_display = f"\033[33m{status:<{status_width}}\033[0m" # Yellow
|
|
302
323
|
elif status == "attached":
|
|
303
|
-
status_display = f"\033[
|
|
304
|
-
elif status
|
|
305
|
-
|
|
306
|
-
if status == "in-use":
|
|
307
|
-
status_display = f"\033[32m{status:<{status_width}}\033[0m" # Green
|
|
308
|
-
else:
|
|
309
|
-
status_display = f"\033[31m{status:<{status_width}}\033[0m" # Red for error
|
|
324
|
+
status_display = f"\033[35m{status:<{status_width}}\033[0m" # Purple
|
|
325
|
+
elif status == "error":
|
|
326
|
+
status_display = f"\033[31m{status:<{status_width}}\033[0m" # Red for error
|
|
310
327
|
else:
|
|
311
328
|
status_display = f"{status:<{status_width}}" # No color for other states
|
|
312
329
|
|
|
330
|
+
# Format Attached To column
|
|
331
|
+
if attached_to:
|
|
332
|
+
instance_id = attached_to
|
|
333
|
+
engine_name = engines_map.get(instance_id, "unknown")
|
|
334
|
+
# Engine name in white (no color)
|
|
335
|
+
attached_display = f"{engine_name:<{attached_width}}"
|
|
336
|
+
else:
|
|
337
|
+
attached_display = f"{'-':<{attached_width}}"
|
|
338
|
+
|
|
339
|
+
# Color the studio ID (grey)
|
|
340
|
+
studio_id_display = f"\033[90m{studio_id:<{id_width}}\033[0m"
|
|
341
|
+
|
|
313
342
|
click.echo(
|
|
314
|
-
f"{user_display} {status_display} {size:<{size_width}} {
|
|
343
|
+
f"│ {user_display}│ {status_display}│ {attached_display}│ {size:<{size_width}}│ {studio_id_display}│"
|
|
315
344
|
)
|
|
316
345
|
|
|
346
|
+
# Table bottom border
|
|
347
|
+
click.echo("└" + "─" * (user_width + 1) + "┴" + "─" * (status_width + 1) + "┴" + "─" * (attached_width + 1) + "┴" + "─" * (size_width + 1) + "┴" + "─" * (id_width + 1) + "┘")
|
|
348
|
+
|
|
317
349
|
click.echo(f"\nTotal: {len(studios)} studio(s)")
|
|
318
350
|
|
|
319
351
|
except Exception as e:
|
|
@@ -13,17 +13,18 @@ dayhoff_tools/cli/engine1/studio_commands.py,sha256=VwTQujz32-uMcYusDRE73SdzRpgv
|
|
|
13
13
|
dayhoff_tools/cli/engines_studios/__init__.py,sha256=E6aG0C6qjJnJuClemSKRFlYvLUL49MQZOvfqNQ7SDKs,159
|
|
14
14
|
dayhoff_tools/cli/engines_studios/api_client.py,sha256=9I55_Ns8VHxndGjvSt_c5ZohSqMOeywQlLjyuoDEqCQ,13039
|
|
15
15
|
dayhoff_tools/cli/engines_studios/auth.py,sha256=rwetV5hp4jSvK8FyvKgXCnezLOZx1aW8oiSDc6U83iE,5189
|
|
16
|
-
dayhoff_tools/cli/engines_studios/engine-studio-cli.md,sha256=
|
|
17
|
-
dayhoff_tools/cli/engines_studios/engine_commands.py,sha256=
|
|
16
|
+
dayhoff_tools/cli/engines_studios/engine-studio-cli.md,sha256=TYnoJUxuTsnsvUAbn0oxlp0nCru9eYElL1_LLCvtqw4,29944
|
|
17
|
+
dayhoff_tools/cli/engines_studios/engine_commands.py,sha256=Kmt9lo5bEmLDbIqNX6ivqWIgoTH9XrbOsXmHpqeVaqs,34561
|
|
18
18
|
dayhoff_tools/cli/engines_studios/progress.py,sha256=SMahdG2YmO5bEPSONrfAXVTdS6m_69Ep02t3hc2DdKQ,9264
|
|
19
19
|
dayhoff_tools/cli/engines_studios/simulators/cli-simulators.md,sha256=FZJl6nehdr2Duht2cx3yijcak0yKyOaHTrTzvFTAfZs,4976
|
|
20
20
|
dayhoff_tools/cli/engines_studios/simulators/demo.sh,sha256=8tYABSCxLNXqGs-4r071V9mpKNZ5DTQ34WZ-v3d5s94,5364
|
|
21
|
-
dayhoff_tools/cli/engines_studios/simulators/engine_list_simulator.py,sha256=
|
|
21
|
+
dayhoff_tools/cli/engines_studios/simulators/engine_list_simulator.py,sha256=ldPA6D4EqnCn6zjcbDX2AWRtVZ3SXCyUTfFbVStabvc,9533
|
|
22
22
|
dayhoff_tools/cli/engines_studios/simulators/engine_status_simulator.py,sha256=KUm3gA2MiRgGrQV7KURhb5zabM18-30z_ugRjiq5iso,13024
|
|
23
23
|
dayhoff_tools/cli/engines_studios/simulators/idle_status_simulator.py,sha256=F_MfEXdPKNVDCKgJV72QyU2oMG8hLt-Bwic4yFadRXE,17570
|
|
24
24
|
dayhoff_tools/cli/engines_studios/simulators/simulator_utils.py,sha256=HA08pIMJWV3OFrWj3Ca8GldvgJZfFoTOloyLK0UWMgA,6729
|
|
25
|
+
dayhoff_tools/cli/engines_studios/simulators/studio_list_simulator.py,sha256=kOoON4_V5DwPRBlCuFqOEEjH535a_SyDAFoK5wKlYaY,11205
|
|
25
26
|
dayhoff_tools/cli/engines_studios/simulators/studio_status_simulator.py,sha256=6WvpnRawJVaQf_H81zuR1_66igRRVxPxjAt8e69xjp4,5394
|
|
26
|
-
dayhoff_tools/cli/engines_studios/studio_commands.py,sha256=
|
|
27
|
+
dayhoff_tools/cli/engines_studios/studio_commands.py,sha256=V7yltLuwN67xGOCj6S8DyfyQDQtjJbANq7G6gY5P08s,24275
|
|
27
28
|
dayhoff_tools/cli/main.py,sha256=Nz_jtbppmvWKHZydQ0nkt_eejccJE90ces8xCGrerdY,7086
|
|
28
29
|
dayhoff_tools/cli/swarm_commands.py,sha256=5EyKj8yietvT5lfoz8Zx0iQvVaNgc3SJX1z2zQR6o6M,5614
|
|
29
30
|
dayhoff_tools/cli/utility_commands.py,sha256=e2P4dCCtoqMUGNyb0lFBZ6GZpl5Zslm1qqE5qIvsy38,50765
|
|
@@ -47,7 +48,7 @@ dayhoff_tools/intake/uniprot.py,sha256=BZYJQF63OtPcBBnQ7_P9gulxzJtqyorgyuDiPeOJq
|
|
|
47
48
|
dayhoff_tools/logs.py,sha256=DKdeP0k0kliRcilwvX0mUB2eipO5BdWUeHwh-VnsICs,838
|
|
48
49
|
dayhoff_tools/sqlite.py,sha256=jV55ikF8VpTfeQqqlHSbY8OgfyfHj8zgHNpZjBLos_E,18672
|
|
49
50
|
dayhoff_tools/warehouse.py,sha256=UETBtZD3r7WgvURqfGbyHlT7cxoiVq8isjzMuerKw8I,24475
|
|
50
|
-
dayhoff_tools-1.
|
|
51
|
-
dayhoff_tools-1.
|
|
52
|
-
dayhoff_tools-1.
|
|
53
|
-
dayhoff_tools-1.
|
|
51
|
+
dayhoff_tools-1.13.0.dist-info/METADATA,sha256=J7tfJUuPi4-ncz_TgTx4tZqbkpEey8KxwdSM8M3mvCg,2980
|
|
52
|
+
dayhoff_tools-1.13.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
53
|
+
dayhoff_tools-1.13.0.dist-info/entry_points.txt,sha256=iAf4jteNqW3cJm6CO6czLxjW3vxYKsyGLZ8WGmxamSc,49
|
|
54
|
+
dayhoff_tools-1.13.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|