dayhoff-tools 1.12.31__py3-none-any.whl → 1.12.32__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.
@@ -321,15 +321,22 @@ dh engine2 list --env sand
321
321
 
322
322
  **Output:**
323
323
  ```
324
- Name Instance ID Type State
325
- --------------------------------------------------------------------------------
326
- alice-work i-0123456789abcdef0 cpu running
327
- bob-training i-0fedcba987654321 a10g running
328
- batch-worker i-0abc123def456789 cpumax stopped
324
+ Engines for AWS Account dev
325
+
326
+ Name State User Type Instance ID
327
+ ───────────────────────────────────────────────────────────────────────────
328
+ alice-work running alice cpu i-0123456789abcdef0
329
+ bob-training running bob a10g i-0fedcba987654321
330
+ batch-worker stopped charlie cpumax i-0abc123def456789
329
331
 
330
332
  Total: 3 engine(s)
331
333
  ```
332
334
 
335
+ **Formatting:**
336
+ - Engine names are displayed in blue
337
+ - State is color-coded: green for "running", yellow for "starting/stopping", grey for "stopped"
338
+ - Name column width adjusts dynamically to fit the longest engine name
339
+
333
340
  ---
334
341
 
335
342
  ### Access
@@ -744,15 +751,21 @@ dh studio2 list
744
751
 
745
752
  **Output:**
746
753
  ```
747
- User Studio ID Size Status
748
- ---------------------------------------------------------------------------
749
- alice vol-0123456789abcdef0 100GB attached
750
- bob vol-0fedcba987654321 200GB available
751
- carol vol-0abc123def456789 150GB available
754
+ User Status Size Studio ID
755
+ ──────────────────────────────────────────────────────────────
756
+ alice attached 100GB vol-0123456789abcdef0
757
+ bob available 200GB vol-0fedcba987654321
758
+ carol available 150GB vol-0abc123def456789
752
759
 
753
760
  Total: 3 studio(s)
754
761
  ```
755
762
 
763
+ **Formatting:**
764
+ - User names are displayed in blue
765
+ - Status is color-coded: green for "available/attached", yellow for "attaching/detaching", red for "error"
766
+ - User column width adjusts dynamically to fit the longest username
767
+ - Columns are ordered: User, Status, Size, Studio ID
768
+
756
769
  ---
757
770
 
758
771
  ### Attachment
@@ -587,32 +587,56 @@ def list_engines(env: Optional[str]):
587
587
  click.echo("No engines found")
588
588
  return
589
589
 
590
- # Table header - reordered per user request: Name, State, User, Type, Instance ID
590
+ # Calculate dynamic width for Name column (longest name + 2 for padding)
591
+ max_name_len = max((len(engine.get("name", "unknown")) for engine in engines), default=4)
592
+ name_width = max(max_name_len + 2, len("Name") + 2)
593
+
594
+ # Fixed widths for other columns
595
+ state_width = 12
596
+ user_width = 12
597
+ type_width = 12
598
+ id_width = 20
599
+
600
+ # Calculate total width for separator line
601
+ total_width = name_width + state_width + user_width + type_width + id_width + 4 # +4 for spaces
602
+
603
+ # Table header
591
604
  click.echo(
592
- f"{'Name':<30} {'State':<12} {'User':<12} {'Type':<12} {'Instance ID':<20}"
605
+ f"{'Name':<{name_width}} {'State':<{state_width}} {'User':<{user_width}} {'Type':<{type_width}} {'Instance ID':<{id_width}}"
593
606
  )
594
- click.echo("-" * 90)
607
+ click.echo("" * total_width)
595
608
 
596
609
  # Table rows
597
610
  for engine in engines:
598
- name = engine.get("name", "unknown")[:29]
599
- state = engine.get("state", "unknown")[:11]
600
- user = engine.get("user", "unknown")[:11]
601
- engine_type = engine.get("engine_type", "unknown")[:11]
611
+ name = engine.get("name", "unknown")
612
+ state = engine.get("state", "unknown")
613
+ user = engine.get("user", "unknown")
614
+ engine_type = engine.get("engine_type", "unknown")
602
615
  instance_id = engine.get("instance_id", "unknown")
603
616
 
604
- # Color the state in the list view
617
+ # Truncate if needed
618
+ if len(name) > name_width - 1:
619
+ name = name[:name_width - 1]
620
+ if len(user) > user_width - 1:
621
+ user = user[:user_width - 1]
622
+ if len(engine_type) > type_width - 1:
623
+ engine_type = engine_type[:type_width - 1]
624
+
625
+ # Color the name (blue)
626
+ name_display = f"\033[34m{name:<{name_width}}\033[0m"
627
+
628
+ # Color the state
605
629
  if state == "running":
606
- state_display = f"\033[32m{state:<12}\033[0m" # Green
630
+ state_display = f"\033[32m{state:<{state_width}}\033[0m" # Green
607
631
  elif state in ["starting", "stopping", "pending"]:
608
- state_display = f"\033[33m{state:<12}\033[0m" # Yellow
632
+ state_display = f"\033[33m{state:<{state_width}}\033[0m" # Yellow
609
633
  elif state == "stopped":
610
- state_display = f"\033[37m{state:<12}\033[0m" # White
634
+ state_display = f"\033[90m{state:<{state_width}}\033[0m" # Grey (dim)
611
635
  else:
612
- state_display = f"{state:<12}" # No color for other states
636
+ state_display = f"{state:<{state_width}}" # No color for other states
613
637
 
614
638
  click.echo(
615
- f"{name:<30} {state_display} {user:<12} {engine_type:<12} {instance_id:<20}"
639
+ f"{name_display} {state_display} {user:<{user_width}} {engine_type:<{type_width}} {instance_id:<{id_width}}"
616
640
  )
617
641
 
618
642
  click.echo(f"\nTotal: {len(engines)} engine(s)")
@@ -30,30 +30,54 @@ def format_list_output(engines: list[dict[str, Any]], env: str = "dev") -> None:
30
30
  print("No engines found")
31
31
  return
32
32
 
33
+ # Calculate dynamic width for Name column (longest name + 2 for padding)
34
+ max_name_len = max((len(engine.get("name", "unknown")) for engine in engines), default=4)
35
+ name_width = max(max_name_len + 2, len("Name") + 2)
36
+
37
+ # Fixed widths for other columns
38
+ state_width = 12
39
+ user_width = 12
40
+ type_width = 12
41
+ id_width = 20
42
+
43
+ # Calculate total width for separator line
44
+ total_width = name_width + state_width + user_width + type_width + id_width + 4 # +4 for spaces
45
+
33
46
  # Table header
34
- print(f"{'Name':<12} {'State':<12} {'User':<12} {'Type':<12} {'Instance ID':<20}")
35
- print("-" * 72)
47
+ print(f"{'Name':<{name_width}} {'State':<{state_width}} {'User':<{user_width}} {'Type':<{type_width}} {'Instance ID':<{id_width}}")
48
+ print("" * total_width)
36
49
 
37
50
  # Table rows
38
51
  for engine in engines:
39
- name = engine.get("name", "unknown")[:11]
40
- state = engine.get("state", "unknown")[:11]
41
- user = engine.get("user", "unknown")[:11]
42
- engine_type = engine.get("engine_type", "unknown")[:11]
52
+ name = engine.get("name", "unknown")
53
+ state = engine.get("state", "unknown")
54
+ user = engine.get("user", "unknown")
55
+ engine_type = engine.get("engine_type", "unknown")
43
56
  instance_id = engine.get("instance_id", "unknown")
44
57
 
58
+ # Truncate if needed
59
+ if len(name) > name_width - 1:
60
+ name = name[:name_width - 1]
61
+ if len(user) > user_width - 1:
62
+ user = user[:user_width - 1]
63
+ if len(engine_type) > type_width - 1:
64
+ engine_type = engine_type[:type_width - 1]
65
+
66
+ # Color the name (blue)
67
+ name_display = colorize(f"{name:<{name_width}}", "34")
68
+
45
69
  # Color the state
46
70
  if state == "running":
47
- state_display = colorize(f"{state:<12}", "32") # Green
71
+ state_display = colorize(f"{state:<{state_width}}", "32") # Green
48
72
  elif state in ["starting", "stopping", "pending"]:
49
- state_display = colorize(f"{state:<12}", "33") # Yellow
73
+ state_display = colorize(f"{state:<{state_width}}", "33") # Yellow
50
74
  elif state == "stopped":
51
- state_display = colorize(f"{state:<12}", "37") # White
75
+ state_display = colorize(f"{state:<{state_width}}", "90") # Grey (dim)
52
76
  else:
53
- state_display = f"{state:<12}" # No color for other states
77
+ state_display = f"{state:<{state_width}}" # No color for other states
54
78
 
55
79
  print(
56
- f"{name:<12} {state_display} {user:<12} {engine_type:<12} {instance_id:<20}"
80
+ f"{name_display} {state_display} {user:<{user_width}} {engine_type:<{type_width}} {instance_id:<{id_width}}"
57
81
  )
58
82
 
59
83
  print(f"\nTotal: {len(engines)} engine(s)")
@@ -262,18 +262,57 @@ def list_studios(env: Optional[str]):
262
262
  click.echo("No studios found")
263
263
  return
264
264
 
265
- # Table header
266
- click.echo(f"{'User':<20} {'Studio ID':<25} {'Size':<10} {'Status':<15}")
267
- click.echo("-" * 75)
265
+ # Calculate dynamic width for User column (longest user + 2 for padding)
266
+ max_user_len = max((len(studio.get("user", "unknown")) for studio in studios), default=4)
267
+ user_width = max(max_user_len + 2, len("User") + 2)
268
+
269
+ # Fixed widths for other columns - reordered to [User, Status, Size, Studio ID]
270
+ status_width = 12
271
+ size_width = 10
272
+ id_width = 25
273
+
274
+ # Calculate total width for separator line
275
+ total_width = user_width + status_width + size_width + id_width + 3 # +3 for spaces
276
+
277
+ # Table header - reordered to [User, Status, Size, Studio ID]
278
+ click.echo(
279
+ f"{'User':<{user_width}} {'Status':<{status_width}} {'Size':<{size_width}} {'Studio ID':<{id_width}}"
280
+ )
281
+ click.echo("─" * total_width)
268
282
 
269
283
  # Table rows
270
284
  for studio in studios:
271
- user = studio.get("user", "unknown")[:19]
272
- studio_id = studio.get("studio_id", "unknown")
273
- size = f"{studio.get('size_gb', 0)}GB"
285
+ user = studio.get("user", "unknown")
274
286
  status = studio.get("status", "unknown")
287
+ size = f"{studio.get('size_gb', 0)}GB"
288
+ studio_id = studio.get("studio_id", "unknown")
289
+
290
+ # Truncate if needed
291
+ if len(user) > user_width - 1:
292
+ user = user[:user_width - 1]
293
+
294
+ # Color the user (blue)
295
+ user_display = f"\033[34m{user:<{user_width}}\033[0m"
296
+
297
+ # Color the status
298
+ if status == "available":
299
+ status_display = f"\033[32m{status:<{status_width}}\033[0m" # Green
300
+ elif status in ["attaching", "detaching"]:
301
+ status_display = f"\033[33m{status:<{status_width}}\033[0m" # Yellow
302
+ elif status == "attached":
303
+ status_display = f"\033[32m{status:<{status_width}}\033[0m" # Green
304
+ elif status in ["error", "in-use"]:
305
+ # "in-use" is the old status name, treat as attached (green)
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
310
+ else:
311
+ status_display = f"{status:<{status_width}}" # No color for other states
275
312
 
276
- click.echo(f"{user:<20} {studio_id:<25} {size:<10} {status:<15}")
313
+ click.echo(
314
+ f"{user_display} {status_display} {size:<{size_width}} {studio_id:<{id_width}}"
315
+ )
277
316
 
278
317
  click.echo(f"\nTotal: {len(studios)} studio(s)")
279
318
 
@@ -344,6 +344,47 @@ def create_or_update_job_definition(
344
344
  f"Adding mount points to job definition: {batch_job_config['mountPoints']}"
345
345
  )
346
346
 
347
+ # Auto-mount Primordial Drive if available
348
+ # This ensures all batch jobs have access to shared datasets at /primordial/
349
+ primordial_fs_id = get_primordial_fs_id(session)
350
+ if primordial_fs_id:
351
+ print(f"Adding Primordial Drive configuration (fs_id: {primordial_fs_id})")
352
+
353
+ # Add volume configuration
354
+ efs_volume = {
355
+ "name": "primordial",
356
+ "efsVolumeConfiguration": {
357
+ "fileSystemId": primordial_fs_id,
358
+ "rootDirectory": "/",
359
+ },
360
+ }
361
+
362
+ if "volumes" not in container_properties:
363
+ container_properties["volumes"] = []
364
+
365
+ # Check if already added to avoid duplicates
366
+ if not any(
367
+ v.get("name") == "primordial" for v in container_properties["volumes"]
368
+ ):
369
+ container_properties["volumes"].append(efs_volume)
370
+
371
+ # Add mount point
372
+ mount_point = {
373
+ "sourceVolume": "primordial",
374
+ "containerPath": "/primordial",
375
+ "readOnly": False,
376
+ }
377
+
378
+ if "mountPoints" not in container_properties:
379
+ container_properties["mountPoints"] = []
380
+
381
+ # Check if already added
382
+ if not any(
383
+ mp.get("containerPath") == "/primordial"
384
+ for mp in container_properties["mountPoints"]
385
+ ):
386
+ container_properties["mountPoints"].append(mount_point)
387
+
347
388
  # Check if job definition already exists using the session client
348
389
  try:
349
390
  existing = batch.describe_job_definitions(
@@ -385,6 +426,38 @@ def create_or_update_job_definition(
385
426
  return response["jobDefinitionName"]
386
427
 
387
428
 
429
+ def get_primordial_fs_id(session: boto3.Session) -> Optional[str]:
430
+ """Fetch Primordial Drive EFS ID from SSM.
431
+
432
+ Args:
433
+ session: Boto3 session
434
+
435
+ Returns:
436
+ FileSystemId if found, None otherwise
437
+ """
438
+ ssm = session.client("ssm")
439
+
440
+ # Determine environment from profile name
441
+ # Default to dev if cannot determine
442
+ env = "dev"
443
+ if session.profile_name and "sand" in session.profile_name:
444
+ env = "sand"
445
+
446
+ param_name = f"/{env}/primordial/fs_id"
447
+
448
+ try:
449
+ response = ssm.get_parameter(Name=param_name)
450
+ return response["Parameter"]["Value"]
451
+ except ClientError as e:
452
+ # Silently fail if not found - Primordial might not be deployed in this env
453
+ # or we might not have permissions
454
+ # ParameterNotFound is a ClientError with error code "ParameterNotFound"
455
+ return None
456
+ except Exception as e:
457
+ print(f"Warning: Failed to check for Primordial Drive: {e}")
458
+ return None
459
+
460
+
388
461
  def submit_aws_batch_job(
389
462
  image_uri: str,
390
463
  config: dict[str, Any],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dayhoff-tools
3
- Version: 1.12.31
3
+ Version: 1.12.32
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
@@ -13,22 +13,22 @@ 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=jwUoMrwp8OnNcOMw128NnQib9Yn2tWmupXcLLtyfass,27613
17
- dayhoff_tools/cli/engines_studios/engine_commands.py,sha256=x7fif4bnc0A8sDNHv3UuilUSurWv8Qhfrv9Jlp1s_QA,32750
16
+ dayhoff_tools/cli/engines_studios/engine-studio-cli.md,sha256=uWi6MMZwlIc1PiNQ8iRtRZSVTSOmBkwWY1s2wpuRbgk,28345
17
+ dayhoff_tools/cli/engines_studios/engine_commands.py,sha256=zhkKsXj6LzpfJ2MnNsmir-pByKOuGSXLeJU14pVJfaQ,33743
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=MrfcCpg8oKZ3fpr9-J8rb7pdJ7XptKuhLcVSF07xxxU,7601
21
+ dayhoff_tools/cli/engines_studios/simulators/engine_list_simulator.py,sha256=hkuZMkCfnAD8giMVS04TeqGWMiRKhjhCmKCPRWcdPZA,8603
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
25
  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=fXj0Oo8BTWrORplg3V0dxZo1i9xX6-h2WSsdtbJQqbc,20575
26
+ dayhoff_tools/cli/engines_studios/studio_commands.py,sha256=DX--3p15yrKC5eB86dc5m_MTb-5SB7b-kPsqW42ykaI,22417
27
27
  dayhoff_tools/cli/main.py,sha256=Nz_jtbppmvWKHZydQ0nkt_eejccJE90ces8xCGrerdY,7086
28
28
  dayhoff_tools/cli/swarm_commands.py,sha256=5EyKj8yietvT5lfoz8Zx0iQvVaNgc3SJX1z2zQR6o6M,5614
29
29
  dayhoff_tools/cli/utility_commands.py,sha256=e2P4dCCtoqMUGNyb0lFBZ6GZpl5Zslm1qqE5qIvsy38,50765
30
30
  dayhoff_tools/deployment/base.py,sha256=fM9zyhuRvIK8YqY6ooYg9j6wy_8touA_L-dkV7FA5q4,18058
31
- dayhoff_tools/deployment/deploy_aws.py,sha256=gfqh09hGbz0q3oPqVm0imd_CEjKF2k8moGNRIL26qqE,18614
31
+ dayhoff_tools/deployment/deploy_aws.py,sha256=3xNuGvwDKvICfiLXVeM4Oz8MXQ363voTM-6vXHtEHZY,20987
32
32
  dayhoff_tools/deployment/deploy_gcp.py,sha256=xgaOVsUDmP6wSEMYNkm1yRNcVskfdz80qJtCulkBIAM,8860
33
33
  dayhoff_tools/deployment/deploy_utils.py,sha256=KyUFZZWn8NGT9QpR0HGqkX-huOFubvYCabko9SlC5Gg,26516
34
34
  dayhoff_tools/deployment/job_runner.py,sha256=hljvFpH2Bw96uYyUup5Ths72PZRL_X27KxlYzBMgguo,5086
@@ -47,7 +47,7 @@ dayhoff_tools/intake/uniprot.py,sha256=BZYJQF63OtPcBBnQ7_P9gulxzJtqyorgyuDiPeOJq
47
47
  dayhoff_tools/logs.py,sha256=DKdeP0k0kliRcilwvX0mUB2eipO5BdWUeHwh-VnsICs,838
48
48
  dayhoff_tools/sqlite.py,sha256=jV55ikF8VpTfeQqqlHSbY8OgfyfHj8zgHNpZjBLos_E,18672
49
49
  dayhoff_tools/warehouse.py,sha256=UETBtZD3r7WgvURqfGbyHlT7cxoiVq8isjzMuerKw8I,24475
50
- dayhoff_tools-1.12.31.dist-info/METADATA,sha256=XXc2Zu9LH0HKmDqY-uj2MlRNYrm4sOtxIHVdzRozBgg,2981
51
- dayhoff_tools-1.12.31.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
52
- dayhoff_tools-1.12.31.dist-info/entry_points.txt,sha256=iAf4jteNqW3cJm6CO6czLxjW3vxYKsyGLZ8WGmxamSc,49
53
- dayhoff_tools-1.12.31.dist-info/RECORD,,
50
+ dayhoff_tools-1.12.32.dist-info/METADATA,sha256=wg00QqtusBsm7HGPud5mkObekKEuyJoa-IYpRRquZ5k,2981
51
+ dayhoff_tools-1.12.32.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
52
+ dayhoff_tools-1.12.32.dist-info/entry_points.txt,sha256=iAf4jteNqW3cJm6CO6czLxjW3vxYKsyGLZ8WGmxamSc,49
53
+ dayhoff_tools-1.12.32.dist-info/RECORD,,