Skip to content

Commit a238d1e

Browse files
added a tracker to let know what validations are done
1 parent 336f90d commit a238d1e

File tree

11 files changed

+75
-16
lines changed

11 files changed

+75
-16
lines changed

src/eligibility_signposting_api/model/campaign_config.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,12 @@ class Iteration(BaseModel):
269269

270270
model_config = {"populate_by_name": True, "arbitrary_types_allowed": True, "extra": "ignore"}
271271

272+
def __init__(self, **data: dict[str, typing.Any]) -> None:
273+
super().__init__(**data)
274+
# Ensure each rule knows its parent iteration
275+
for rule in self.iteration_rules:
276+
rule.set_parent(self)
277+
272278
@field_validator("iteration_date", mode="before")
273279
@classmethod
274280
def parse_dates(cls, v: str | date) -> date:
@@ -292,12 +298,6 @@ def parse_dates(cls, v: str | date) -> date:
292298
def serialize_dates(v: date, _info: SerializationInfo) -> str:
293299
return v.strftime("%Y%m%d")
294300

295-
@model_validator(mode="after")
296-
def attach_rule_parents(self) -> Iteration:
297-
for rule in self.iteration_rules:
298-
rule.set_parent(self)
299-
return self
300-
301301
def __str__(self) -> str:
302302
return json.dumps(self.model_dump(by_alias=True), indent=2)
303303

src/rules_validation_api/app.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,30 @@
22
import json
33
import logging
44
import sys
5+
from collections import defaultdict
56
from pathlib import Path
67

8+
from rules_validation_api.decorators.tracker import VALIDATORS_CALLED
79
from rules_validation_api.validators.rules_validator import RulesValidation
810

911
logging.basicConfig(
10-
level=logging.INFO, # or DEBUG for more detail
12+
level=logging.INFO,
1113
format="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
1214
force=True,
1315
)
1416

15-
GREEN = "\033[92m" # pragma: no cover
16-
RESET = "\033[0m" # pragma: no cover
17-
YELLOW = "\033[93m" # pragma: no cover
18-
RED = "\033[91m" # pragma: no cover
17+
GREEN = "\033[92m"
18+
RESET = "\033[0m"
19+
YELLOW = "\033[93m"
20+
RED = "\033[91m"
1921

22+
# ANSI color codes
23+
LEFT_COLOR = "\033[34m" # Blue for class name
24+
COLON_COLOR = "\033[33m" # Yellow for colon
25+
RIGHT_COLOR = "\033[92m" # Milk green for validator
2026

21-
def main() -> None: # pragma: no cover
27+
28+
def main() -> None:
2229
parser = argparse.ArgumentParser(description="Validate campaign configuration.")
2330
parser.add_argument("--config_path", required=True, help="Path to the campaign config JSON file")
2431
args = parser.parse_args()
@@ -28,9 +35,22 @@ def main() -> None: # pragma: no cover
2835
json_data = json.load(file)
2936
RulesValidation(**json_data)
3037
sys.stdout.write(f"{GREEN}Valid Config{RESET}\n")
38+
39+
# --- Group by class ---
40+
grouped = defaultdict(list)
41+
for v in VALIDATORS_CALLED:
42+
cls, method = v.split(":", 1)
43+
grouped[cls].append(method.strip())
44+
45+
# --- Sort classes descending, methods ascending ---
46+
for cls_name in sorted(grouped.keys(), reverse=True):
47+
for method_name in sorted(grouped[cls_name]):
48+
colored = f"{LEFT_COLOR}{cls_name}{RESET}{COLON_COLOR}:{RESET}{RIGHT_COLOR}{method_name}{RESET}"
49+
sys.stdout.write(f"{colored}\n")
50+
3151
except ValueError as e:
3252
sys.stderr.write(f"{YELLOW}Validation Error:{RESET} {RED}{e}{RESET}\n")
3353

3454

35-
if __name__ == "__main__": # pragma: no cover
55+
if __name__ == "__main__":
3656
main()

src/rules_validation_api/decorators/__init__.py

Whitespace-only changes.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from typing import Self
2+
3+
from pydantic import model_validator
4+
5+
VALIDATORS_CALLED: list[str] = []
6+
7+
8+
# --- Mixin and decorator to track validators ---
9+
class TrackValidatorsMixin:
10+
"""
11+
Mixin to track all validator names in a Pydantic model.
12+
"""
13+
14+
@model_validator(mode="after")
15+
def _track_validators(self) -> Self:
16+
for name in dir(self):
17+
if name.startswith(("validate_", "check_")) and callable(getattr(self, name)):
18+
full_name = f"{self.__class__.__name__}:{name}"
19+
if full_name not in VALIDATORS_CALLED:
20+
VALIDATORS_CALLED.append(full_name)
21+
return self
22+
23+
24+
def track_validators(cls) -> type: # noqa:ANN001
25+
"""
26+
Decorator to add the tracking mixin to a Pydantic model.
27+
"""
28+
return type(cls.__name__, (TrackValidatorsMixin, cls), {})

src/rules_validation_api/validators/actions_mapper_validator.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from pydantic import ValidationError, model_validator
22

33
from eligibility_signposting_api.model.campaign_config import ActionsMapper
4+
from rules_validation_api.decorators.tracker import track_validators
45
from rules_validation_api.validators.available_action_validator import AvailableActionValidation
56

67

8+
@track_validators
79
class ActionsMapperValidation(ActionsMapper):
810
@model_validator(mode="after")
911
def validate_keys(self) -> "ActionsMapperValidation":
@@ -16,17 +18,14 @@ def validate_keys(self) -> "ActionsMapperValidation":
1618
@model_validator(mode="after")
1719
def validate_values(self) -> "ActionsMapperValidation":
1820
error_report = []
19-
2021
for key, value in self.root.items():
2122
try:
2223
AvailableActionValidation.model_validate(value.model_dump())
2324
except ValidationError as e:
2425
for err in e.errors():
2526
msg = err.get("msg", "Unknown error").replace("Value error, ", "")
2627
error_report.append(f"\n Action '{key}': {msg}")
27-
2828
if error_report:
2929
final_msg = "Markdown Validation Issues:".join(error_report)
3030
raise ValueError(final_msg)
31-
3231
return self

src/rules_validation_api/validators/available_action_validator.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
from pydantic import field_validator
44

55
from eligibility_signposting_api.model.campaign_config import AvailableAction
6+
from rules_validation_api.decorators.tracker import track_validators
67

78

9+
@track_validators
810
class AvailableActionValidation(AvailableAction):
911
@field_validator("action_description")
1012
@classmethod

src/rules_validation_api/validators/campaign_config_validator.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
from pydantic import field_validator, model_validator
66

77
from eligibility_signposting_api.model.campaign_config import CampaignConfig, Iteration
8+
from rules_validation_api.decorators.tracker import track_validators
89
from rules_validation_api.validators.iteration_validator import IterationValidation
910

1011

12+
@track_validators
1113
class CampaignConfigValidation(CampaignConfig):
1214
@field_validator("iterations")
1315
@classmethod
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from eligibility_signposting_api.model.campaign_config import IterationCohort
2+
from rules_validation_api.decorators.tracker import track_validators
23

34

5+
@track_validators
46
class IterationCohortValidation(IterationCohort):
57
pass

src/rules_validation_api/validators/iteration_rules_validator.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
RuleAttributeName,
99
RuleType,
1010
)
11+
from rules_validation_api.decorators.tracker import track_validators
1112

1213

14+
@track_validators
1315
class IterationRuleValidation(IterationRule):
1416
@model_validator(mode="after")
1517
def check_cohort_attribute_name(self) -> typing.Self:

src/rules_validation_api/validators/iteration_validator.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@
1111
IterationRule,
1212
RuleType,
1313
)
14+
from rules_validation_api.decorators.tracker import track_validators
1415
from rules_validation_api.validators.actions_mapper_validator import ActionsMapperValidation
1516
from rules_validation_api.validators.available_action_validator import AvailableActionValidation
1617
from rules_validation_api.validators.iteration_cohort_validator import IterationCohortValidation
1718
from rules_validation_api.validators.iteration_rules_validator import IterationRuleValidation
1819

1920

21+
@track_validators
2022
class IterationValidation(Iteration):
2123
@field_validator("iteration_rules")
2224
@classmethod

0 commit comments

Comments
 (0)