diff --git a/pipeline/models.py b/pipeline/models.py index d1daf00..65fc257 100644 --- a/pipeline/models.py +++ b/pipeline/models.py @@ -14,6 +14,7 @@ validate_cohortextractor_outputs, validate_databuilder_outputs, validate_glob_pattern, + validate_no_kwargs, validate_type, ) @@ -58,6 +59,7 @@ def build( population_size: Any = None, **kwargs: Any, ) -> Expectations: + validate_no_kwargs(kwargs, "project `expectations` section") try: population_size = int(population_size) except (TypeError, ValueError): @@ -91,6 +93,8 @@ def build( f"must specify at least one output of: {', '.join(['highly_sensitive', 'moderately_sensitive', 'minimally_sensitive'])}" ) + validate_no_kwargs(kwargs, f"`outputs` section for action {action_id}") + cls.validate_output_filenames_are_valid( action_id, "highly_sensitive", highly_sensitive ) @@ -175,6 +179,7 @@ def build( dummy_data_file: Any = None, **kwargs: Any, ) -> Action: + validate_no_kwargs(kwargs, f"action {action_id}") validate_type(outputs, dict, f"`outputs` section for action {action_id}") validate_type(run, str, f"`run` section for action {action_id}") validate_type( @@ -243,6 +248,7 @@ def build( expectations: Any = None, **kwargs: Any, ) -> Pipeline: + validate_no_kwargs(kwargs, "project") if version is None: raise ValidationError( f"Project file must have a `version` attribute specifying which " diff --git a/pipeline/validation.py b/pipeline/validation.py index e8bd3de..d1cb2f2 100644 --- a/pipeline/validation.py +++ b/pipeline/validation.py @@ -25,6 +25,11 @@ def validate_type(val: Any, exp_type: type, loc: str, optional: bool = False) -> raise ValidationError(f"{loc} must be a {type_lookup[exp_type]}") +def validate_no_kwargs(kwargs: dict[str, Any], loc: str) -> None: + if kwargs: + raise ValidationError(f"Unexpected parameters ({', '.join(kwargs)}) in {loc}") + + def validate_glob_pattern(pattern: str, privacy_level: str) -> None: """ These patterns get converted into regular expressions and matched diff --git a/tests/test_type_validation.py b/tests/test_type_validation.py index c35e662..2ae5720 100644 --- a/tests/test_type_validation.py +++ b/tests/test_type_validation.py @@ -1,3 +1,5 @@ +import re + import pytest from pipeline.exceptions import ValidationError @@ -114,3 +116,61 @@ def test_output_filename_incorrect_type(): }, expectations={"population_size": 10}, ) + + +def test_project_extra_parameters(): + with pytest.raises( + ValidationError, match=re.escape("Unexpected parameters (extra) in project") + ): + Pipeline.build(extra=123) + + +def test_action_extra_parameters(): + with pytest.raises( + ValidationError, + match=re.escape("Unexpected parameters (extra) in action action1"), + ): + Pipeline.build( + version=3, + actions={ + "action1": { + "outputs": {}, + "run": "test:v1", + "extra": 123, + } + }, + expectations={"population_size": 10}, + ) + + +def test_outputs_extra_parameters(): + with pytest.raises( + ValidationError, + match=re.escape( + "Unexpected parameters (extra) in `outputs` section for action action1" + ), + ): + Pipeline.build( + version=3, + actions={ + "action1": { + "outputs": {"highly_sensitive": {"dataset": {}}, "extra": 123}, + "run": "test:v1", + } + }, + expectations={"population_size": 10}, + ) + + +def test_expectations_extra_parameters(): + with pytest.raises( + ValidationError, + match=re.escape( + "Unexpected parameters (extra) in project `expectations` section" + ), + ): + Pipeline.build( + version=3, + actions={}, + expectations={"population_size": 10, "extra": 123}, + )