dayhoff-tools 1.1.10__py3-none-any.whl → 1.13.12__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/__init__.py +10 -0
- dayhoff_tools/cli/cloud_commands.py +179 -43
- dayhoff_tools/cli/engine1/__init__.py +323 -0
- dayhoff_tools/cli/engine1/engine_core.py +703 -0
- dayhoff_tools/cli/engine1/engine_lifecycle.py +136 -0
- dayhoff_tools/cli/engine1/engine_maintenance.py +431 -0
- dayhoff_tools/cli/engine1/engine_management.py +505 -0
- dayhoff_tools/cli/engine1/shared.py +501 -0
- dayhoff_tools/cli/engine1/studio_commands.py +825 -0
- dayhoff_tools/cli/engines_studios/__init__.py +6 -0
- dayhoff_tools/cli/engines_studios/api_client.py +351 -0
- dayhoff_tools/cli/engines_studios/auth.py +144 -0
- dayhoff_tools/cli/engines_studios/engine-studio-cli.md +1230 -0
- dayhoff_tools/cli/engines_studios/engine_commands.py +1151 -0
- dayhoff_tools/cli/engines_studios/progress.py +260 -0
- dayhoff_tools/cli/engines_studios/simulators/cli-simulators.md +151 -0
- dayhoff_tools/cli/engines_studios/simulators/demo.sh +75 -0
- dayhoff_tools/cli/engines_studios/simulators/engine_list_simulator.py +319 -0
- dayhoff_tools/cli/engines_studios/simulators/engine_status_simulator.py +369 -0
- dayhoff_tools/cli/engines_studios/simulators/idle_status_simulator.py +476 -0
- dayhoff_tools/cli/engines_studios/simulators/simulator_utils.py +180 -0
- dayhoff_tools/cli/engines_studios/simulators/studio_list_simulator.py +374 -0
- dayhoff_tools/cli/engines_studios/simulators/studio_status_simulator.py +164 -0
- dayhoff_tools/cli/engines_studios/studio_commands.py +755 -0
- dayhoff_tools/cli/main.py +106 -7
- dayhoff_tools/cli/utility_commands.py +896 -179
- dayhoff_tools/deployment/base.py +70 -6
- dayhoff_tools/deployment/deploy_aws.py +165 -25
- dayhoff_tools/deployment/deploy_gcp.py +78 -5
- dayhoff_tools/deployment/deploy_utils.py +20 -7
- dayhoff_tools/deployment/job_runner.py +9 -4
- dayhoff_tools/deployment/processors.py +230 -418
- dayhoff_tools/deployment/swarm.py +47 -12
- dayhoff_tools/embedders.py +28 -26
- dayhoff_tools/fasta.py +181 -64
- dayhoff_tools/warehouse.py +268 -1
- {dayhoff_tools-1.1.10.dist-info → dayhoff_tools-1.13.12.dist-info}/METADATA +20 -5
- dayhoff_tools-1.13.12.dist-info/RECORD +54 -0
- {dayhoff_tools-1.1.10.dist-info → dayhoff_tools-1.13.12.dist-info}/WHEEL +1 -1
- dayhoff_tools-1.1.10.dist-info/RECORD +0 -32
- {dayhoff_tools-1.1.10.dist-info → dayhoff_tools-1.13.12.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,374 @@
|
|
|
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(
|
|
24
|
+
studios: list[dict[str, Any]], engines_map: dict[str, str], env: str = "dev"
|
|
25
|
+
) -> None:
|
|
26
|
+
"""Format and print studio list output matching the actual CLI."""
|
|
27
|
+
|
|
28
|
+
# Header with blue account name
|
|
29
|
+
print(f"\nStudios for AWS Account {colorize(env, '34')}")
|
|
30
|
+
|
|
31
|
+
if not studios:
|
|
32
|
+
print("No studios found\n")
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
# Calculate dynamic width for User column (longest user + 2 for padding)
|
|
36
|
+
max_user_len = max(
|
|
37
|
+
(len(studio.get("user", "unknown")) for studio in studios), default=4
|
|
38
|
+
)
|
|
39
|
+
user_width = max(max_user_len + 2, len("User") + 2)
|
|
40
|
+
|
|
41
|
+
# Calculate dynamic width for Attached To column
|
|
42
|
+
max_attached_len = 0
|
|
43
|
+
for studio in studios:
|
|
44
|
+
if studio.get("attached_to"):
|
|
45
|
+
instance_id = studio["attached_to"]
|
|
46
|
+
engine_name = engines_map.get(instance_id, "unknown")
|
|
47
|
+
max_attached_len = max(max_attached_len, len(engine_name))
|
|
48
|
+
attached_width = max(
|
|
49
|
+
max_attached_len + 2, len("Attached To") + 2, 3
|
|
50
|
+
) # At least 3 for "-"
|
|
51
|
+
|
|
52
|
+
# Fixed widths for other columns
|
|
53
|
+
status_width = 12
|
|
54
|
+
size_width = 10
|
|
55
|
+
id_width = 25
|
|
56
|
+
|
|
57
|
+
# Table top border
|
|
58
|
+
print(
|
|
59
|
+
"╭"
|
|
60
|
+
+ "─" * (user_width + 1)
|
|
61
|
+
+ "┬"
|
|
62
|
+
+ "─" * (status_width + 1)
|
|
63
|
+
+ "┬"
|
|
64
|
+
+ "─" * (attached_width + 1)
|
|
65
|
+
+ "┬"
|
|
66
|
+
+ "─" * (size_width + 1)
|
|
67
|
+
+ "┬"
|
|
68
|
+
+ "─" * (id_width + 1)
|
|
69
|
+
+ "╮"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Table header
|
|
73
|
+
print(
|
|
74
|
+
f"│ {'User':<{user_width}}│ {'Status':<{status_width}}│ {'Attached To':<{attached_width}}│ {'Size':<{size_width}}│ {'Studio ID':<{id_width}}│"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Header separator
|
|
78
|
+
print(
|
|
79
|
+
"├"
|
|
80
|
+
+ "─" * (user_width + 1)
|
|
81
|
+
+ "┼"
|
|
82
|
+
+ "─" * (status_width + 1)
|
|
83
|
+
+ "┼"
|
|
84
|
+
+ "─" * (attached_width + 1)
|
|
85
|
+
+ "┼"
|
|
86
|
+
+ "─" * (size_width + 1)
|
|
87
|
+
+ "┼"
|
|
88
|
+
+ "─" * (id_width + 1)
|
|
89
|
+
+ "┤"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Table rows
|
|
93
|
+
for studio in studios:
|
|
94
|
+
user = studio.get("user", "unknown")
|
|
95
|
+
status = studio.get("status", "unknown")
|
|
96
|
+
size = f"{studio.get('size_gb', 0)}GB"
|
|
97
|
+
studio_id = studio.get("studio_id", "unknown")
|
|
98
|
+
attached_to = studio.get("attached_to")
|
|
99
|
+
|
|
100
|
+
# Truncate if needed
|
|
101
|
+
if len(user) > user_width - 1:
|
|
102
|
+
user = user[: user_width - 1]
|
|
103
|
+
|
|
104
|
+
# Color the user (blue)
|
|
105
|
+
user_display = colorize(f"{user:<{user_width}}", "34")
|
|
106
|
+
|
|
107
|
+
# Format status - display "in-use" as "attached" in purple
|
|
108
|
+
if status == "in-use":
|
|
109
|
+
display_status = "attached"
|
|
110
|
+
status_display = colorize(
|
|
111
|
+
f"{display_status:<{status_width}}", "35"
|
|
112
|
+
) # Purple
|
|
113
|
+
elif status == "available":
|
|
114
|
+
status_display = colorize(f"{status:<{status_width}}", "32") # Green
|
|
115
|
+
elif status in ["attaching", "detaching"]:
|
|
116
|
+
status_display = colorize(f"{status:<{status_width}}", "33") # Yellow
|
|
117
|
+
elif status == "attached":
|
|
118
|
+
status_display = colorize(f"{status:<{status_width}}", "35") # Purple
|
|
119
|
+
elif status == "error":
|
|
120
|
+
status_display = colorize(f"{status:<{status_width}}", "31") # Red
|
|
121
|
+
else:
|
|
122
|
+
status_display = f"{status:<{status_width}}" # No color for other states
|
|
123
|
+
|
|
124
|
+
# Format Attached To column
|
|
125
|
+
if attached_to:
|
|
126
|
+
instance_id = attached_to
|
|
127
|
+
engine_name = engines_map.get(instance_id, "unknown")
|
|
128
|
+
# Engine name in white (no color)
|
|
129
|
+
attached_display = f"{engine_name:<{attached_width}}"
|
|
130
|
+
else:
|
|
131
|
+
attached_display = f"{'-':<{attached_width}}"
|
|
132
|
+
|
|
133
|
+
# Color the studio ID (grey)
|
|
134
|
+
studio_id_display = colorize(f"{studio_id:<{id_width}}", "90")
|
|
135
|
+
|
|
136
|
+
print(
|
|
137
|
+
f"│ {user_display}│ {status_display}│ {attached_display}│ {size:<{size_width}}│ {studio_id_display}│"
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Table bottom border
|
|
141
|
+
print(
|
|
142
|
+
"╰"
|
|
143
|
+
+ "─" * (user_width + 1)
|
|
144
|
+
+ "┴"
|
|
145
|
+
+ "─" * (status_width + 1)
|
|
146
|
+
+ "┴"
|
|
147
|
+
+ "─" * (attached_width + 1)
|
|
148
|
+
+ "┴"
|
|
149
|
+
+ "─" * (size_width + 1)
|
|
150
|
+
+ "┴"
|
|
151
|
+
+ "─" * (id_width + 1)
|
|
152
|
+
+ "╯"
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
print(f"Total: {len(studios)}\n")
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def generate_scenarios() -> dict[str, dict[str, Any]]:
|
|
159
|
+
"""Generate various test scenarios for studio list output."""
|
|
160
|
+
|
|
161
|
+
scenarios = {}
|
|
162
|
+
|
|
163
|
+
# Create a consistent engines map for all scenarios
|
|
164
|
+
engines_map = {
|
|
165
|
+
"i-0123456789abcdef0": "alice-gpu",
|
|
166
|
+
"i-1234567890abcdef1": "bob-cpu",
|
|
167
|
+
"i-2345678901abcdef2": "charlie-work",
|
|
168
|
+
"i-3456789012abcdef3": "diana-dev",
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
# Scenario 1: Single available studio
|
|
172
|
+
scenarios["single"] = {
|
|
173
|
+
"name": "Single Available Studio",
|
|
174
|
+
"studios": [
|
|
175
|
+
{
|
|
176
|
+
"user": "alice",
|
|
177
|
+
"status": "available",
|
|
178
|
+
"size_gb": 100,
|
|
179
|
+
"studio_id": "vol-0abc123def456789a",
|
|
180
|
+
"attached_to": None,
|
|
181
|
+
}
|
|
182
|
+
],
|
|
183
|
+
"engines_map": engines_map,
|
|
184
|
+
"env": "dev",
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
# Scenario 2: Few studios with various states
|
|
188
|
+
scenarios["few"] = {
|
|
189
|
+
"name": "Few Studios - Mixed States",
|
|
190
|
+
"studios": [
|
|
191
|
+
{
|
|
192
|
+
"user": "alice",
|
|
193
|
+
"status": "in-use", # Will be displayed as "attached" in purple
|
|
194
|
+
"size_gb": 100,
|
|
195
|
+
"studio_id": "vol-0abc123def456789a",
|
|
196
|
+
"attached_to": "i-0123456789abcdef0",
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"user": "bob",
|
|
200
|
+
"status": "available",
|
|
201
|
+
"size_gb": 200,
|
|
202
|
+
"studio_id": "vol-0abc123def456789b",
|
|
203
|
+
"attached_to": None,
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"user": "charlie",
|
|
207
|
+
"status": "attaching",
|
|
208
|
+
"size_gb": 150,
|
|
209
|
+
"studio_id": "vol-0abc123def456789c",
|
|
210
|
+
"attached_to": "i-2345678901abcdef2",
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
"engines_map": engines_map,
|
|
214
|
+
"env": "sand",
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
# Scenario 3: Many studios (production-like)
|
|
218
|
+
scenarios["many"] = {
|
|
219
|
+
"name": "Many Studios - Production",
|
|
220
|
+
"studios": [
|
|
221
|
+
{
|
|
222
|
+
"user": "alice",
|
|
223
|
+
"status": "attached",
|
|
224
|
+
"size_gb": 100,
|
|
225
|
+
"studio_id": "vol-0abc123def456789a",
|
|
226
|
+
"attached_to": "i-0123456789abcdef0",
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
"user": "bob",
|
|
230
|
+
"status": "attached",
|
|
231
|
+
"size_gb": 200,
|
|
232
|
+
"studio_id": "vol-0abc123def456789b",
|
|
233
|
+
"attached_to": "i-1234567890abcdef1",
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
"user": "charlie",
|
|
237
|
+
"status": "available",
|
|
238
|
+
"size_gb": 150,
|
|
239
|
+
"studio_id": "vol-0abc123def456789c",
|
|
240
|
+
"attached_to": None,
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
"user": "diana",
|
|
244
|
+
"status": "attached",
|
|
245
|
+
"size_gb": 250,
|
|
246
|
+
"studio_id": "vol-0abc123def456789d",
|
|
247
|
+
"attached_to": "i-3456789012abcdef3",
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
"user": "eve",
|
|
251
|
+
"status": "available",
|
|
252
|
+
"size_gb": 100,
|
|
253
|
+
"studio_id": "vol-0abc123def456789e",
|
|
254
|
+
"attached_to": None,
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
"user": "frank",
|
|
258
|
+
"status": "detaching",
|
|
259
|
+
"size_gb": 300,
|
|
260
|
+
"studio_id": "vol-0abc123def456789f",
|
|
261
|
+
"attached_to": None,
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
"engines_map": engines_map,
|
|
265
|
+
"env": "prod",
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
# Scenario 4: Empty list
|
|
269
|
+
scenarios["empty"] = {
|
|
270
|
+
"name": "No Studios",
|
|
271
|
+
"studios": [],
|
|
272
|
+
"engines_map": engines_map,
|
|
273
|
+
"env": "dev",
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
# Scenario 5: All transitional states
|
|
277
|
+
scenarios["transitions"] = {
|
|
278
|
+
"name": "Transitional States",
|
|
279
|
+
"studios": [
|
|
280
|
+
{
|
|
281
|
+
"user": "alice",
|
|
282
|
+
"status": "attaching",
|
|
283
|
+
"size_gb": 100,
|
|
284
|
+
"studio_id": "vol-0abc123def456789a",
|
|
285
|
+
"attached_to": "i-0123456789abcdef0",
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
"user": "bob",
|
|
289
|
+
"status": "detaching",
|
|
290
|
+
"size_gb": 200,
|
|
291
|
+
"studio_id": "vol-0abc123def456789b",
|
|
292
|
+
"attached_to": None,
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
"user": "charlie",
|
|
296
|
+
"status": "error",
|
|
297
|
+
"size_gb": 150,
|
|
298
|
+
"studio_id": "vol-0abc123def456789c",
|
|
299
|
+
"attached_to": None,
|
|
300
|
+
},
|
|
301
|
+
],
|
|
302
|
+
"engines_map": engines_map,
|
|
303
|
+
"env": "sand",
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
# Scenario 6: Long names
|
|
307
|
+
scenarios["long_names"] = {
|
|
308
|
+
"name": "Long User Names",
|
|
309
|
+
"studios": [
|
|
310
|
+
{
|
|
311
|
+
"user": "alice-with-very-long-username",
|
|
312
|
+
"status": "attached",
|
|
313
|
+
"size_gb": 100,
|
|
314
|
+
"studio_id": "vol-0abc123def456789a",
|
|
315
|
+
"attached_to": "i-0123456789abcdef0",
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
"user": "bob",
|
|
319
|
+
"status": "available",
|
|
320
|
+
"size_gb": 200,
|
|
321
|
+
"studio_id": "vol-0abc123def456789b",
|
|
322
|
+
"attached_to": None,
|
|
323
|
+
},
|
|
324
|
+
],
|
|
325
|
+
"engines_map": engines_map,
|
|
326
|
+
"env": "dev",
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return scenarios
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def main():
|
|
333
|
+
parser = argparse.ArgumentParser(
|
|
334
|
+
description="Simulate studio list output for design iteration"
|
|
335
|
+
)
|
|
336
|
+
parser.add_argument(
|
|
337
|
+
"--scenario",
|
|
338
|
+
choices=["single", "few", "many", "empty", "transitions", "long_names", "all"],
|
|
339
|
+
default="all",
|
|
340
|
+
help="Which scenario to display (default: all)",
|
|
341
|
+
)
|
|
342
|
+
parser.add_argument(
|
|
343
|
+
"--env",
|
|
344
|
+
choices=["dev", "sand", "prod"],
|
|
345
|
+
help="Override environment for display",
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
args = parser.parse_args()
|
|
349
|
+
|
|
350
|
+
scenarios = generate_scenarios()
|
|
351
|
+
|
|
352
|
+
if args.scenario == "all":
|
|
353
|
+
# Show all scenarios
|
|
354
|
+
for _, scenario_data in scenarios.items():
|
|
355
|
+
print("\n" + "=" * 80)
|
|
356
|
+
print(f"SCENARIO: {scenario_data['name']}")
|
|
357
|
+
print("=" * 80 + "\n")
|
|
358
|
+
|
|
359
|
+
env = args.env if args.env else scenario_data["env"]
|
|
360
|
+
format_list_output(
|
|
361
|
+
scenario_data["studios"], scenario_data["engines_map"], env
|
|
362
|
+
)
|
|
363
|
+
print() # Extra newline between scenarios
|
|
364
|
+
else:
|
|
365
|
+
# Show specific scenario
|
|
366
|
+
scenario_data = scenarios[args.scenario]
|
|
367
|
+
print(f"\nSCENARIO: {scenario_data['name']}\n")
|
|
368
|
+
|
|
369
|
+
env = args.env if args.env else scenario_data["env"]
|
|
370
|
+
format_list_output(scenario_data["studios"], scenario_data["engines_map"], env)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
if __name__ == "__main__":
|
|
374
|
+
main()
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Simulator for studio status output - iterate on design locally without AWS.
|
|
3
|
+
|
|
4
|
+
This lets you quickly see how the studio status command output looks for different
|
|
5
|
+
studio states and configurations.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
python dayhoff_tools/cli/engines_studios/simulators/studio_status_simulator.py # Show all scenarios
|
|
9
|
+
python dayhoff_tools/cli/engines_studios/simulators/studio_status_simulator.py --scenario attached # Show specific scenario
|
|
10
|
+
python dayhoff_tools/cli/engines_studios/simulators/studio_status_simulator.py --env prod # Override environment
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import sys
|
|
15
|
+
from datetime import datetime, timedelta, timezone
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
# Import standalone utilities
|
|
19
|
+
simulator_dir = Path(__file__).parent
|
|
20
|
+
sys.path.insert(0, str(simulator_dir))
|
|
21
|
+
from simulator_utils import format_time_ago
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def colorize(text: str, color_code: str) -> str:
|
|
25
|
+
"""Apply ANSI color code to text."""
|
|
26
|
+
return f"\033[{color_code}m{text}\033[0m"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def format_studio_status(studio_data: dict, env: str = "dev") -> None:
|
|
30
|
+
"""Format and print studio status output matching the actual CLI."""
|
|
31
|
+
|
|
32
|
+
# Reordered output: User, Status, Attached to, Account, Size, Created, Studio ID
|
|
33
|
+
print(f"User: {studio_data['user']}")
|
|
34
|
+
# Status in blue
|
|
35
|
+
print(f"Status: {colorize(studio_data['status'], '34')}")
|
|
36
|
+
# Attached to in blue (if present)
|
|
37
|
+
if studio_data.get("attached_to"):
|
|
38
|
+
print(f"Attached to: {colorize(studio_data['attached_to'], '34')}")
|
|
39
|
+
print(f"Account: {env}")
|
|
40
|
+
print(f"Size: {studio_data['size_gb']}GB")
|
|
41
|
+
if studio_data.get("created_at"):
|
|
42
|
+
print(f"Created: {format_time_ago(studio_data['created_at'])}")
|
|
43
|
+
print(f"Studio ID: {studio_data['studio_id']}")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def generate_scenarios() -> dict:
|
|
47
|
+
"""Generate various test scenarios for studio status output."""
|
|
48
|
+
|
|
49
|
+
scenarios = {}
|
|
50
|
+
|
|
51
|
+
# Scenario 1: Available studio (not attached)
|
|
52
|
+
scenarios["available"] = {
|
|
53
|
+
"name": "Available Studio - Not Attached",
|
|
54
|
+
"studio_data": {
|
|
55
|
+
"user": "alice",
|
|
56
|
+
"size_gb": 100,
|
|
57
|
+
"status": "available",
|
|
58
|
+
"created_at": (datetime.now(timezone.utc) - timedelta(days=5)).isoformat(),
|
|
59
|
+
"studio_id": "vol-0123456789abcdef0",
|
|
60
|
+
},
|
|
61
|
+
"env": "dev",
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# Scenario 2: Studio attached to engine
|
|
65
|
+
scenarios["attached"] = {
|
|
66
|
+
"name": "Studio Attached to Engine",
|
|
67
|
+
"studio_data": {
|
|
68
|
+
"user": "bob",
|
|
69
|
+
"size_gb": 150,
|
|
70
|
+
"status": "attached",
|
|
71
|
+
"created_at": (
|
|
72
|
+
datetime.now(timezone.utc) - timedelta(days=21, hours=3)
|
|
73
|
+
).isoformat(),
|
|
74
|
+
"studio_id": "vol-1234567890abcdef1",
|
|
75
|
+
"attached_to": "bob-main (i-1234567890abcdef1)",
|
|
76
|
+
},
|
|
77
|
+
"env": "sand",
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# Scenario 3: Large studio in production
|
|
81
|
+
scenarios["large"] = {
|
|
82
|
+
"name": "Large Production Studio",
|
|
83
|
+
"studio_data": {
|
|
84
|
+
"user": "charlie",
|
|
85
|
+
"size_gb": 500,
|
|
86
|
+
"status": "available",
|
|
87
|
+
"created_at": (
|
|
88
|
+
datetime.now(timezone.utc) - timedelta(days=120)
|
|
89
|
+
).isoformat(),
|
|
90
|
+
"studio_id": "vol-2345678901abcdef2",
|
|
91
|
+
},
|
|
92
|
+
"env": "prod",
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# Scenario 4: Recently created studio
|
|
96
|
+
scenarios["new"] = {
|
|
97
|
+
"name": "Newly Created Studio",
|
|
98
|
+
"studio_data": {
|
|
99
|
+
"user": "diana",
|
|
100
|
+
"size_gb": 50,
|
|
101
|
+
"status": "available",
|
|
102
|
+
"created_at": (datetime.now(timezone.utc) - timedelta(hours=2)).isoformat(),
|
|
103
|
+
"studio_id": "vol-3456789012abcdef3",
|
|
104
|
+
},
|
|
105
|
+
"env": "dev",
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# Scenario 5: Studio being modified
|
|
109
|
+
scenarios["modifying"] = {
|
|
110
|
+
"name": "Studio Being Modified",
|
|
111
|
+
"studio_data": {
|
|
112
|
+
"user": "eve",
|
|
113
|
+
"size_gb": 200,
|
|
114
|
+
"status": "modifying",
|
|
115
|
+
"created_at": (datetime.now(timezone.utc) - timedelta(days=30)).isoformat(),
|
|
116
|
+
"studio_id": "vol-4567890123abcdef4",
|
|
117
|
+
},
|
|
118
|
+
"env": "sand",
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return scenarios
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def main():
|
|
125
|
+
parser = argparse.ArgumentParser(
|
|
126
|
+
description="Simulate studio status output for design iteration"
|
|
127
|
+
)
|
|
128
|
+
parser.add_argument(
|
|
129
|
+
"--scenario",
|
|
130
|
+
choices=["available", "attached", "large", "new", "modifying", "all"],
|
|
131
|
+
default="all",
|
|
132
|
+
help="Which scenario to display (default: all)",
|
|
133
|
+
)
|
|
134
|
+
parser.add_argument(
|
|
135
|
+
"--env",
|
|
136
|
+
choices=["dev", "sand", "prod"],
|
|
137
|
+
help="Override environment for display",
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
args = parser.parse_args()
|
|
141
|
+
|
|
142
|
+
scenarios = generate_scenarios()
|
|
143
|
+
|
|
144
|
+
if args.scenario == "all":
|
|
145
|
+
# Show all scenarios
|
|
146
|
+
for scenario_key, scenario_data in scenarios.items():
|
|
147
|
+
print("\n" + "=" * 80)
|
|
148
|
+
print(f"SCENARIO: {scenario_data['name']}")
|
|
149
|
+
print("=" * 80 + "\n")
|
|
150
|
+
|
|
151
|
+
env = args.env if args.env else scenario_data["env"]
|
|
152
|
+
format_studio_status(scenario_data["studio_data"], env)
|
|
153
|
+
print() # Extra newline between scenarios
|
|
154
|
+
else:
|
|
155
|
+
# Show specific scenario
|
|
156
|
+
scenario_data = scenarios[args.scenario]
|
|
157
|
+
print(f"\nSCENARIO: {scenario_data['name']}\n")
|
|
158
|
+
|
|
159
|
+
env = args.env if args.env else scenario_data["env"]
|
|
160
|
+
format_studio_status(scenario_data["studio_data"], env)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
if __name__ == "__main__":
|
|
164
|
+
main()
|