Coverage for scripts / example_job_validation / models.py: 100.00%
39 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-10-28 16:19 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-10-28 16:19 +0000
1"""Run settings for the example-job validation harness."""
3from __future__ import annotations
5from dataclasses import dataclass
6from typing import Any
8from scripts.live_release_validation.checks.eks_capabilities import (
9 build_eks_capabilities_overrides,
10 overrides_json,
11)
12from scripts.live_release_validation.models import RunSettings
14from .specs import (
15 EXAMPLE_SPECS,
16 required_capability_overrides,
17 required_capability_settings,
18 required_feature_overrides,
19 required_helm_overrides,
20)
23@dataclass(frozen=True)
24class ExampleRunSettings(RunSettings):
25 """Operator inputs for one example-validation run.
27 Extends the live-release-validation settings with the example selection;
28 the required helm/feature/EKS Capability enablement is DERIVED from the
29 selection so the deployed graph always matches exactly what the selected
30 examples need.
31 """
33 #: Example names (file stems) to validate this run, in registry order.
34 selected_examples: tuple[str, ...] = ()
36 #: Maximum examples running concurrently inside the examples action.
37 #: 0 means "all selected at once". Deliberately NOT part of identity():
38 #: parallelism changes execution pacing, not what is validated, so a
39 #: checkpointed run may resume with a different setting.
40 max_parallel_examples: int = 0
42 def __post_init__(self) -> None:
43 super().__post_init__()
44 unknown = sorted(set(self.selected_examples) - set(EXAMPLE_SPECS))
45 if unknown:
46 raise ValueError(
47 f"Unknown example name(s): {', '.join(unknown)}. "
48 f"Valid: {', '.join(sorted(EXAMPLE_SPECS))}"
49 )
50 if self.max_parallel_examples < 0:
51 raise ValueError("max_parallel_examples must be >= 0 (0 = no limit)")
52 # The scheduler charts the selection needs are threaded through the
53 # same optional_schedulers field the base class already carries so
54 # identity(), resume checks, and extra_cdk_context() stay coherent.
55 derived = required_helm_overrides(list(self.selected_examples))
56 object.__setattr__(
57 self, "optional_schedulers", tuple(sorted({*self.optional_schedulers, *derived}))
58 )
59 # EKS Capabilities travel as the base class's eks_capabilities_overrides
60 # JSON (the same context the live harness's --eks-capabilities builds),
61 # so extra_cdk_context() and the resume identity carry them unchanged.
62 capability_types = self.capability_overrides
63 if capability_types:
64 object.__setattr__(
65 self,
66 "eks_capabilities_overrides_json",
67 overrides_json(
68 build_eks_capabilities_overrides(
69 types=capability_types,
70 extra=required_capability_settings(list(self.selected_examples)),
71 )
72 ),
73 )
75 @property
76 def capability_overrides(self) -> tuple[str, ...]:
77 return required_capability_overrides(list(self.selected_examples))
79 @property
80 def feature_overrides(self) -> tuple[str, ...]:
81 return required_feature_overrides(list(self.selected_examples))
83 def extra_cdk_context(self) -> dict[str, str]:
84 context = super().extra_cdk_context()
85 if self.feature_overrides:
86 context["feature_enabled_overrides"] = ",".join(self.feature_overrides)
87 return context
89 def identity(self) -> dict[str, Any]:
90 identity = super().identity()
91 identity["selected_examples"] = list(self.selected_examples)
92 identity["feature_overrides"] = list(self.feature_overrides)
93 identity["capability_overrides"] = list(self.capability_overrides)
94 return identity