Skip to content

Commit 669aba6

Browse files
easelclaude
andcommitted
hx-2c3c331f Migrate remaining consumers to read from ExpectationSuite
Migrate validator.py, excel_converter.py, and umf_diff.py to prefer umf.expectations (ExpectationSuite) over legacy umf.validation_rules, with backward-compatible fallback for UMF instances not loaded via UMFLoader. This completes ADR-005 Phase C: all runtime consumers now read from ExpectationSuite. Legacy quality_checks and validation_rules fields are retained for backward-compatible loading only. Governing: docs/helix/02-design/adr/ADR-005-unified-expectation-model.md Verification: 222 tests pass (validator, excel, diff, loader, migration, suite), pyright clean Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d2f6e92 commit 669aba6

3 files changed

Lines changed: 31 additions & 11 deletions

File tree

src/tablespec/excel_converter.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,7 @@ def convert(self, umf: UMF) -> openpyxl.Workbook:
416416
self._create_columns_sheet(umf)
417417
self._create_survivorship_sheet(umf)
418418

419-
if umf.validation_rules:
419+
if (umf.expectations and umf.expectations.expectations) or umf.validation_rules:
420420
self._create_validation_sheet(umf)
421421

422422
if umf.relationships:
@@ -899,6 +899,15 @@ def _create_survivorship_sheet(self, umf: UMF) -> None:
899899
ws.column_dimensions["H"].width = 30 # Source
900900
ws.column_dimensions["I"].width = 100 # Explanation/Reason (very wide for readability)
901901

902+
@staticmethod
903+
def _get_expectation_dicts_for_export(umf: "UMF") -> list[dict[str, Any]]:
904+
"""Get expectation dicts for Excel export, preferring ExpectationSuite."""
905+
if umf.expectations and umf.expectations.expectations:
906+
return [exp.to_gx_dict() for exp in umf.expectations.expectations]
907+
if umf.validation_rules and umf.validation_rules.expectations:
908+
return umf.validation_rules.expectations
909+
return []
910+
902911
def _prepare_validation_expectations_with_index(
903912
self, expectations: list[dict[str, Any]]
904913
) -> tuple[list[tuple[str, str, str, int, dict[str, Any]]], list[str]]:
@@ -1026,9 +1035,10 @@ def _create_validation_sheet(self, umf: UMF) -> None:
10261035
]
10271036
self._add_header_row(ws, headers)
10281037

1029-
if umf.validation_rules and umf.validation_rules.expectations:
1038+
exp_list = self._get_expectation_dicts_for_export(umf)
1039+
if exp_list:
10301040
expectations_with_index, _ = self._prepare_validation_expectations_with_index(
1031-
umf.validation_rules.expectations
1041+
exp_list
10321042
)
10331043

10341044
default_font = self._get_default_font()

src/tablespec/umf_diff.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -334,12 +334,14 @@ def _get_expectations(self, umf: UMF) -> list[dict]:
334334
List of expectation dicts
335335
336336
"""
337+
# Prefer unified ExpectationSuite (ADR-005)
338+
if umf.expectations and umf.expectations.expectations:
339+
return [exp.to_gx_dict() for exp in umf.expectations.expectations]
340+
# Fallback to legacy validation_rules
337341
if not umf.validation_rules:
338342
return []
339-
# Support expectations attribute (used in pulseflow UMF format)
340343
if hasattr(umf.validation_rules, "expectations"):
341344
return list(getattr(umf.validation_rules, "expectations", None) or [])
342-
# Support dict-based validation_rules
343345
if isinstance(umf.validation_rules, dict):
344346
return umf.validation_rules.get("expectations", [])
345347
return []

src/tablespec/validator.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,15 @@
3838
_UMF_JSON_SCHEMA: dict[str, Any] = UMF.model_json_schema()
3939

4040

41+
def _get_expectation_dicts(umf: UMF) -> list[dict[str, Any]]:
42+
"""Get expectation dicts from ExpectationSuite, falling back to legacy fields."""
43+
if umf.expectations and umf.expectations.expectations:
44+
return [exp.to_gx_dict() for exp in umf.expectations.expectations]
45+
if umf.validation_rules and umf.validation_rules.expectations:
46+
return umf.validation_rules.expectations
47+
return []
48+
49+
4150
@dataclass
4251
class ValidationContext:
4352
"""Context for validation operations with process-lifetime UMF caching.
@@ -183,7 +192,8 @@ def validate_table(
183192
table_names.update(a.lower() for a in t.aliases)
184193

185194
# 5. Validate expectations if present
186-
if umf.validation_rules and umf.validation_rules.expectations:
195+
exp_dicts = _get_expectation_dicts(umf)
196+
if exp_dicts:
187197
# Validate expectation configuration through the consolidated GX executor.
188198
gx_executor = None
189199
if GXSuiteExecutor is not None:
@@ -194,7 +204,7 @@ def validate_table(
194204

195205
column_names = {col.name for col in umf.columns}
196206

197-
for i, exp in enumerate(umf.validation_rules.expectations):
207+
for i, exp in enumerate(exp_dicts):
198208
exp_type = exp.get("type")
199209
if not exp_type:
200210
errors.append(f"Expectation {i}: Missing 'type' field")
@@ -398,10 +408,8 @@ def show_table_info(
398408
],
399409
},
400410
"validation": {
401-
"has_rules": bool(umf.validation_rules and umf.validation_rules.expectations),
402-
"expectation_count": len(umf.validation_rules.expectations)
403-
if umf.validation_rules and umf.validation_rules.expectations
404-
else 0,
411+
"has_rules": bool(_get_expectation_dicts(umf)),
412+
"expectation_count": len(_get_expectation_dicts(umf)),
405413
},
406414
"relationships": {
407415
"foreign_keys": len(umf.relationships.foreign_keys or []) if umf.relationships else 0,

0 commit comments

Comments
 (0)