diff --git a/core/dbt/cli/option_types.py b/core/dbt/cli/option_types.py index 6c94116eb67..2d0efedbb9e 100644 --- a/core/dbt/cli/option_types.py +++ b/core/dbt/cli/option_types.py @@ -1,6 +1,6 @@ from click import Choice, ParamType -from dbt.config.utils import exclusive_primary_alt_value_setting, parse_cli_yaml_string +from dbt.config.utils import normalize_warn_error_options, parse_cli_yaml_string from dbt.events import ALL_EVENT_NAMES from dbt.exceptions import OptionNotYamlDictError, ValidationError from dbt_common.exceptions import DbtValidationError @@ -51,12 +51,7 @@ class WarnErrorOptionsType(YAML): def convert(self, value, param, ctx): # this function is being used by param in click include_exclude = super().convert(value, param, ctx) - exclusive_primary_alt_value_setting( - include_exclude, "include", "error", "warn_error_options" - ) - exclusive_primary_alt_value_setting( - include_exclude, "exclude", "warn", "warn_error_options" - ) + normalize_warn_error_options(include_exclude) return WarnErrorOptions( include=include_exclude.get("include", []), diff --git a/core/dbt/config/project.py b/core/dbt/config/project.py index 7f396477fd6..e1dfac4e10c 100644 --- a/core/dbt/config/project.py +++ b/core/dbt/config/project.py @@ -10,7 +10,7 @@ from dbt.adapters.contracts.connection import QueryComment from dbt.clients.yaml_helper import load_yaml_text from dbt.config.selectors import SelectorDict -from dbt.config.utils import exclusive_primary_alt_value_setting +from dbt.config.utils import normalize_warn_error_options from dbt.constants import ( DBT_PROJECT_FILE_NAME, DEPENDENCIES_FILE_NAME, @@ -828,13 +828,8 @@ def read_project_flags(project_dir: str, profiles_dir: str) -> ProjectFlags: if project_flags is not None: # handle collapsing `include` and `error` as well as collapsing `exclude` and `warn` # for warn_error_options - warn_error_options = project_flags.get("warn_error_options") - exclusive_primary_alt_value_setting( - warn_error_options, "include", "error", "warn_error_options" - ) - exclusive_primary_alt_value_setting( - warn_error_options, "exclude", "warn", "warn_error_options" - ) + warn_error_options = project_flags.get("warn_error_options", {}) + normalize_warn_error_options(warn_error_options) ProjectFlags.validate(project_flags) return ProjectFlags.from_dict(project_flags) diff --git a/core/dbt/config/utils.py b/core/dbt/config/utils.py index c37854c477b..df554f1c461 100644 --- a/core/dbt/config/utils.py +++ b/core/dbt/config/utils.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Optional +from typing import Any, Dict from dbt.clients import yaml_helper from dbt.events.types import InvalidOptionYAML @@ -24,33 +24,36 @@ def parse_cli_yaml_string(var_string: str, cli_option_name: str) -> Dict[str, An raise -def exclusive_primary_alt_value_setting( - dictionary: Optional[Dict[str, Any]], - primary: str, - alt: str, - parent_config: Optional[str] = None, +def normalize_warn_error_options( + dictionary: Dict[str, Any], ) -> None: - """Munges in place under the primary the options for the primary and alt values + """Fixes fields for warn_error_options from yaml format to fields + expected by the WarnErrorOptions class. + 'error' => 'include', 'warn' => 'exclude' - Sometimes we allow setting something via TWO keys, but not at the same time. If both the primary - key and alt key have values, an error gets raised. If the alt key has values, then we update - the dictionary to ensure the primary key contains the values. If neither are set, nothing happens. + Also validates that two different forms of accepted keys are not + both provided. """ - if dictionary is None: - return - - primary_options = dictionary.get(primary) - alt_options = dictionary.get(alt) + if "include" in dictionary and "error" in dictionary: + raise DbtExclusivePropertyUseError( + "Only `error` or `include` can be specified in `warn_error_options`, not both" + ) - if primary_options and alt_options: - where = f" in `{parent_config}`" if parent_config is not None else "" + if "warn" in dictionary and "exclude" in dictionary: raise DbtExclusivePropertyUseError( - f"Only `{alt}` or `{primary}` can be specified{where}, not both" + "Only `warn` or `exclude` can be specified in `warn_error_options`, not both" ) - if alt in dictionary: - alt_value = dictionary.pop(alt) - if alt_value is None: - alt_value = [] - dictionary[primary] = alt_value + convert = { + "error": "include", + "warn": "exclude", + } + for key in list(convert.keys()): + if key in dictionary: + value = dictionary.pop(key) + if value is None: + value = [] + dictionary[convert[key]] = value + if "silence" in dictionary and dictionary["silence"] is None: + dictionary["silence"] = [] diff --git a/tests/unit/config/test_utils.py b/tests/unit/config/test_utils.py index 41e7dfddd43..0f1dfe36d0c 100644 --- a/tests/unit/config/test_utils.py +++ b/tests/unit/config/test_utils.py @@ -1,40 +1,35 @@ import pytest -from dbt.config.utils import exclusive_primary_alt_value_setting +from dbt.config.utils import normalize_warn_error_options from dbt.exceptions import DbtExclusivePropertyUseError -class TestExclusivePrimaryAltValueSetting: - @pytest.fixture(scope="class") - def primary_key(self) -> str: - return "key_a" - - @pytest.fixture(scope="class") - def alt_key(self) -> str: - return "key_b" - - @pytest.fixture(scope="class") - def value(self) -> str: - return "I LIKE CATS" - - def test_primary_set(self, primary_key: str, alt_key: str, value: str): - test_dict = {primary_key: value} - exclusive_primary_alt_value_setting(test_dict, primary_key, alt_key) - assert test_dict.get(primary_key) == value - assert test_dict.get(alt_key) is None - - def test_alt_set(self, primary_key: str, alt_key: str, value: str): - test_dict = {alt_key: value} - exclusive_primary_alt_value_setting(test_dict, primary_key, alt_key) - assert test_dict.get(primary_key) == value - - def test_primary_and_alt_set(self, primary_key: str, alt_key: str, value: str): - test_dict = {primary_key: value, alt_key: value} +class TestNormalizeWarnErrorOptions: + def test_primary_set(self): + test_dict = { + "error": ["SomeWarning"], + } + normalize_warn_error_options(test_dict) + assert len(test_dict) == 1 + assert test_dict["include"] == ["SomeWarning"] + + def test_convert(self): + test_dict = {"warn": None, "silence": None, "include": ["SomeWarning"]} + normalize_warn_error_options(test_dict) + assert test_dict["exclude"] == [] + assert test_dict["include"] == ["SomeWarning"] + assert test_dict["silence"] == [] + + def test_both_keys_set(self): + test_dict = { + "warn": ["SomeWarning"], + "exclude": ["SomeWarning"], + } with pytest.raises(DbtExclusivePropertyUseError): - exclusive_primary_alt_value_setting(test_dict, primary_key, alt_key) + normalize_warn_error_options(test_dict) - def test_neither_primary_nor_alt_set(self, primary_key: str, alt_key: str): + def test_empty_dict(self): test_dict = {} - exclusive_primary_alt_value_setting(test_dict, primary_key, alt_key) - assert test_dict.get(primary_key) is None - assert test_dict.get(alt_key) is None + normalize_warn_error_options(test_dict) + assert test_dict.get("include") is None + assert test_dict.get("exclude") is None