dh-cli 0.4.2__tar.gz → 0.4.4__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 (51) hide show
  1. {dh_cli-0.4.2 → dh_cli-0.4.4}/PKG-INFO +1 -1
  2. {dh_cli-0.4.2 → dh_cli-0.4.4}/pyproject.toml +1 -1
  3. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/hz/deploy.py +15 -3
  4. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/hz/tf.py +18 -3
  5. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/hz/users.py +50 -9
  6. {dh_cli-0.4.2 → dh_cli-0.4.4}/tests/hz/test_users.py +48 -1
  7. {dh_cli-0.4.2 → dh_cli-0.4.4}/.gitignore +0 -0
  8. {dh_cli-0.4.2 → dh_cli-0.4.4}/LICENSE +0 -0
  9. {dh_cli-0.4.2 → dh_cli-0.4.4}/README.md +0 -0
  10. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/__init__.py +0 -0
  11. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/__init__.py +0 -0
  12. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/aws_batch.py +0 -0
  13. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/commands/__init__.py +0 -0
  14. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/commands/boltz.py +0 -0
  15. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/commands/cancel.py +0 -0
  16. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/commands/clean.py +0 -0
  17. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/commands/embed_t5.py +0 -0
  18. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/commands/finalize.py +0 -0
  19. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/commands/list_jobs.py +0 -0
  20. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/commands/local.py +0 -0
  21. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/commands/logs.py +0 -0
  22. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/commands/protmpnn.py +0 -0
  23. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/commands/protmpnn_to_boltz.py +0 -0
  24. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/commands/retry.py +0 -0
  25. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/commands/status.py +0 -0
  26. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/commands/submit.py +0 -0
  27. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/commands/train.py +0 -0
  28. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/commands/wait_for.py +0 -0
  29. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/fasta_utils.py +0 -0
  30. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/h5_utils.py +0 -0
  31. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/job_id.py +0 -0
  32. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/manifest.py +0 -0
  33. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/batch/s3_transport.py +0 -0
  34. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/cloud_commands.py +0 -0
  35. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/codeartifact.py +0 -0
  36. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/engines_studios/__init__.py +0 -0
  37. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/engines_studios/api_client.py +0 -0
  38. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/engines_studios/auth.py +0 -0
  39. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/engines_studios/engine_commands.py +0 -0
  40. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/engines_studios/progress.py +0 -0
  41. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/engines_studios/ssh_config.py +0 -0
  42. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/engines_studios/studio_commands.py +0 -0
  43. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/github_commands.py +0 -0
  44. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/hz/__init__.py +0 -0
  45. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/hz/local.py +0 -0
  46. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/hz/test.py +0 -0
  47. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/main.py +0 -0
  48. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/utility_commands.py +0 -0
  49. {dh_cli-0.4.2 → dh_cli-0.4.4}/src/dh_cli/warehouse.py +0 -0
  50. {dh_cli-0.4.2 → dh_cli-0.4.4}/tests/hz/test_init.py +0 -0
  51. {dh_cli-0.4.2 → dh_cli-0.4.4}/tests/hz/test_suites.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dh-cli
3
- Version: 0.4.2
3
+ Version: 0.4.4
4
4
  Summary: Dayhoff Labs developer CLI
5
5
  Author-email: Dayhoff Labs <dev@dayhofflabs.com>
6
6
  License: # PolyForm Noncommercial License 1.0.0
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "dh-cli"
7
- version = "0.4.2"
7
+ version = "0.4.4"
8
8
  description = "Dayhoff Labs developer CLI"
9
9
  requires-python = ">=3.11"
10
10
  readme = "README.md"
@@ -7,11 +7,21 @@ from dh_cli.hz import require_repo, run_script
7
7
  deploy_app = typer.Typer(context_settings={"help_option_names": ["-h", "--help"]})
8
8
 
9
9
 
10
+ def _confirm_deploy(target: str, env: str) -> None:
11
+ color = typer.colors.RED if env == "prod" else typer.colors.BRIGHT_GREEN
12
+ env_styled = typer.style(env, fg=color, bold=True)
13
+ typer.echo(f"\n Deploying {target} to {env_styled}\n")
14
+ if not typer.confirm(" Continue?"):
15
+ raise typer.Abort()
16
+ typer.echo()
17
+
18
+
10
19
  @deploy_app.command("api")
11
20
  def deploy_api(
12
- env: str = typer.Option("prod", "--env", "-e", help="Environment: prod or dev."),
21
+ env: str = typer.Option("dev", "--env", "-e", help="Environment: prod or dev."),
13
22
  ):
14
23
  """Build and deploy the Horizyn API server."""
24
+ _confirm_deploy("api", env)
15
25
  repo = require_repo("horizyn-api")
16
26
  script = repo / f"server/build-and-push-{env}.sh"
17
27
  run_script(script, cwd=repo)
@@ -19,9 +29,10 @@ def deploy_api(
19
29
 
20
30
  @deploy_app.command("docking")
21
31
  def deploy_docking(
22
- env: str = typer.Option("prod", "--env", "-e", help="Environment: prod or dev."),
32
+ env: str = typer.Option("dev", "--env", "-e", help="Environment: prod or dev."),
23
33
  ):
24
34
  """Build and deploy the docking service."""
35
+ _confirm_deploy("docking", env)
25
36
  repo = require_repo("horizyn-api")
26
37
  script = repo / f"docking_service/build-and-push-{env}.sh"
27
38
  run_script(script, cwd=repo)
@@ -29,9 +40,10 @@ def deploy_docking(
29
40
 
30
41
  @deploy_app.command("frontend")
31
42
  def deploy_frontend(
32
- env: str = typer.Option("prod", "--env", "-e", help="Environment: prod or dev."),
43
+ env: str = typer.Option("dev", "--env", "-e", help="Environment: prod or dev."),
33
44
  ):
34
45
  """Build and deploy the Horizyn frontend."""
46
+ _confirm_deploy("frontend", env)
35
47
  repo = require_repo("horizyn-frontend")
36
48
  script = repo / "scripts/deploy.sh"
37
49
  run_script(script, args=[env], cwd=repo)
@@ -16,6 +16,15 @@ TF_MODULES = {
16
16
  }
17
17
 
18
18
 
19
+ def _confirm_tf(target: str, env: str) -> None:
20
+ color = typer.colors.RED if env == "prod" else typer.colors.BRIGHT_GREEN
21
+ env_styled = typer.style(env, fg=color, bold=True)
22
+ typer.echo(f"\n Terraform apply: {target} in {env_styled}\n")
23
+ if not typer.confirm(" Continue?"):
24
+ raise typer.Abort()
25
+ typer.echo()
26
+
27
+
19
28
  def _run_tf(target: str, env: str, action: str) -> None:
20
29
  repo = require_repo("blueprints")
21
30
  module_path = TF_MODULES.get((target, env))
@@ -28,26 +37,32 @@ def _run_tf(target: str, env: str, action: str) -> None:
28
37
 
29
38
  @tf_app.command("api")
30
39
  def tf_api(
31
- env: str = typer.Option("prod", "--env", "-e", help="Environment: prod or dev."),
40
+ env: str = typer.Option("dev", "--env", "-e", help="Environment: prod or dev."),
32
41
  yolo: bool = typer.Option(False, "--yolo", help="Auto-approve (no confirmation prompt)."),
33
42
  ):
34
43
  """Terraform apply for the Horizyn API server."""
44
+ if not yolo:
45
+ _confirm_tf("api", env)
35
46
  _run_tf("api", env, "yolo" if yolo else "plan")
36
47
 
37
48
 
38
49
  @tf_app.command("frontend")
39
50
  def tf_frontend(
40
- env: str = typer.Option("prod", "--env", "-e", help="Environment: prod or dev."),
51
+ env: str = typer.Option("dev", "--env", "-e", help="Environment: prod or dev."),
41
52
  yolo: bool = typer.Option(False, "--yolo", help="Auto-approve (no confirmation prompt)."),
42
53
  ):
43
54
  """Terraform apply for the Horizyn frontend."""
55
+ if not yolo:
56
+ _confirm_tf("frontend", env)
44
57
  _run_tf("frontend", env, "yolo" if yolo else "plan")
45
58
 
46
59
 
47
60
  @tf_app.command("docking")
48
61
  def tf_docking(
49
- env: str = typer.Option("prod", "--env", "-e", help="Environment: prod or dev."),
62
+ env: str = typer.Option("dev", "--env", "-e", help="Environment: prod or dev."),
50
63
  yolo: bool = typer.Option(False, "--yolo", help="Auto-approve (no confirmation prompt)."),
51
64
  ):
52
65
  """Terraform apply for the Boltz docking service."""
66
+ if not yolo:
67
+ _confirm_tf("docking", env)
53
68
  _run_tf("docking", env, "yolo" if yolo else "plan")
@@ -40,9 +40,26 @@ def _format_csv(emails: set[str]) -> str:
40
40
  return ",".join(sorted(emails))
41
41
 
42
42
 
43
+ def compute_user_tiers(
44
+ allowed_emails: set[str],
45
+ alpha_emails: set[str],
46
+ admin_emails: set[str],
47
+ ) -> tuple[set[str], set[str], set[str]]:
48
+ """Derive beta set and find orphaned emails not on the allowed list.
49
+
50
+ Returns (beta_emails, alpha_only, orphaned_emails) where orphaned
51
+ means present in alpha or admin but missing from allowed_emails.
52
+ """
53
+ orphaned = (alpha_emails | admin_emails) - allowed_emails
54
+ alpha_only = alpha_emails - admin_emails - orphaned
55
+ beta_emails = allowed_emails - alpha_emails - admin_emails
56
+ return beta_emails, alpha_only, orphaned
57
+
58
+
43
59
  @users_app.command("list")
44
60
  def list_users(
45
61
  env: str = typer.Option("prod", "--env", "-e", help="Environment: prod or dev."),
62
+ detailed: bool = typer.Option(False, "-d", "--detailed", help="Show raw SSM parameter lists."),
46
63
  ):
47
64
  """Show all authorized users and their tiers."""
48
65
  ssm = _ssm_client()
@@ -53,7 +70,7 @@ def list_users(
53
70
  allowed_domains = _parse_csv(_get_param(ssm, SSM_PARAMS["allowed_domains"].format(env=env)))
54
71
  allowed_emails = _parse_csv(_get_param(ssm, SSM_PARAMS["allowed_emails"].format(env=env)))
55
72
 
56
- typer.echo(f"\n Horizyn users ({env})\n")
73
+ env_colored = typer.style(env, fg=typer.colors.BRIGHT_GREEN, bold=True)
57
74
 
58
75
  def _print_section(label: str, items: set[str]) -> None:
59
76
  typer.echo(f" {label}:")
@@ -62,13 +79,36 @@ def list_users(
62
79
  else:
63
80
  for item in sorted(items):
64
81
  typer.echo(f" {item}")
82
+ typer.echo()
83
+
84
+ if detailed:
85
+ typer.echo(f"\n Horizyn users ({env_colored}) — detailed")
86
+ typer.echo()
87
+ typer.echo(" Access requires allowed_emails or allowed_domains match.")
88
+ typer.echo(" Allowed users default to beta tier unless listed in alpha or admin.")
89
+ typer.echo()
90
+ _print_section("Admin domains", admin_domains)
91
+ _print_section("Admin emails", admin_emails)
92
+ _print_section("Alpha emails", alpha_emails)
93
+ _print_section("Allowed domains", allowed_domains)
94
+ _print_section("Allowed emails", allowed_emails)
95
+ else:
96
+ beta, alpha_only, orphaned = compute_user_tiers(
97
+ allowed_emails, alpha_emails, admin_emails,
98
+ )
65
99
 
66
- _print_section("Admin domains", admin_domains)
67
- _print_section("Admin emails", admin_emails)
68
- _print_section("Alpha emails", alpha_emails)
69
- _print_section("Allowed domains", allowed_domains)
70
- _print_section("Allowed emails", allowed_emails)
71
- typer.echo()
100
+ typer.echo(f"\n Horizyn users ({env_colored})")
101
+ typer.echo()
102
+ _print_section("Alpha", alpha_only)
103
+ _print_section("Beta", beta)
104
+
105
+ if orphaned:
106
+ for email in sorted(orphaned):
107
+ typer.secho(
108
+ f" ⚠ {email} is in alpha/admin but NOT in allowed_emails",
109
+ fg=typer.colors.RED,
110
+ )
111
+ typer.echo()
72
112
 
73
113
 
74
114
  @users_app.command("add")
@@ -169,11 +209,12 @@ def find_user(
169
209
  if email in values:
170
210
  matches.append(param_key)
171
211
 
212
+ env_colored = typer.style(env, fg=typer.colors.BRIGHT_GREEN, bold=True)
172
213
  if matches:
173
- typer.echo(f" {env}: found")
214
+ typer.echo(f" {env_colored}: found")
174
215
  for m in matches:
175
216
  typer.echo(f" - {m}")
176
217
  else:
177
- typer.echo(f" {env}: not found")
218
+ typer.echo(f" {env_colored}: not found")
178
219
 
179
220
  typer.echo()
@@ -5,7 +5,7 @@ from unittest.mock import MagicMock, patch
5
5
  import pytest
6
6
  from click.exceptions import Exit
7
7
 
8
- from dh_cli.hz.users import _format_csv, _parse_csv
8
+ from dh_cli.hz.users import _format_csv, _parse_csv, compute_user_tiers
9
9
 
10
10
 
11
11
  class TestParseCSV:
@@ -206,3 +206,50 @@ class TestPromoteUser:
206
206
  from dh_cli.hz.users import promote_user
207
207
 
208
208
  promote_user("alice@x.com", tier="superadmin", env="prod")
209
+
210
+
211
+ class TestComputeUserTiers:
212
+ def test_beta_is_allowed_minus_alpha_minus_admin(self):
213
+ beta, alpha_only, orphaned = compute_user_tiers(
214
+ allowed_emails={"a@x.com", "b@x.com", "c@x.com"},
215
+ alpha_emails={"b@x.com"},
216
+ admin_emails=set(),
217
+ )
218
+ assert beta == {"a@x.com", "c@x.com"}
219
+ assert alpha_only == {"b@x.com"}
220
+ assert orphaned == set()
221
+
222
+ def test_admin_emails_excluded_from_alpha_and_beta(self):
223
+ beta, alpha_only, orphaned = compute_user_tiers(
224
+ allowed_emails={"a@x.com", "b@x.com", "c@x.com"},
225
+ alpha_emails={"b@x.com", "c@x.com"},
226
+ admin_emails={"c@x.com"},
227
+ )
228
+ assert beta == {"a@x.com"}
229
+ assert alpha_only == {"b@x.com"}
230
+ assert orphaned == set()
231
+
232
+ def test_orphaned_alpha_not_in_allowed(self):
233
+ beta, alpha_only, orphaned = compute_user_tiers(
234
+ allowed_emails={"a@x.com"},
235
+ alpha_emails={"ghost@x.com"},
236
+ admin_emails=set(),
237
+ )
238
+ assert beta == {"a@x.com"}
239
+ assert alpha_only == set()
240
+ assert orphaned == {"ghost@x.com"}
241
+
242
+ def test_orphaned_admin_not_in_allowed(self):
243
+ beta, alpha_only, orphaned = compute_user_tiers(
244
+ allowed_emails={"a@x.com"},
245
+ alpha_emails=set(),
246
+ admin_emails={"ghost@x.com"},
247
+ )
248
+ assert beta == {"a@x.com"}
249
+ assert orphaned == {"ghost@x.com"}
250
+
251
+ def test_all_empty(self):
252
+ beta, alpha_only, orphaned = compute_user_tiers(set(), set(), set())
253
+ assert beta == set()
254
+ assert alpha_only == set()
255
+ assert orphaned == set()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes