diff --git a/core/dbt/config/selectors.py b/core/dbt/config/selectors.py index 74c85aae344..6995c6a1554 100644 --- a/core/dbt/config/selectors.py +++ b/core/dbt/config/selectors.py @@ -1,5 +1,6 @@ from pathlib import Path from typing import Dict, Any +import yaml from hologram import ValidationError @@ -33,7 +34,17 @@ def from_dict(cls, data: Dict[str, Any]) -> 'SelectorConfig': try: selector_file = SelectorFile.from_dict(data) selectors = parse_from_selectors_definition(selector_file) - except (ValidationError, RuntimeException) as exc: + except ValidationError as exc: + yaml_sel_cfg = yaml.dump(exc.instance) + raise DbtSelectorsError( + f"Could not parse selector file data: \n{yaml_sel_cfg}\n" + f"Valid selector description definitions: " + f"union, intersection, string, dictionary. No lists. " + f"\nhttps://docs.getdbt.com/reference/node-selection/" + f"yaml-selectors", + result_type='invalid_selector' + ) from exc + except RuntimeException as exc: raise DbtSelectorsError( f'Could not read selector file data: {exc}', result_type='invalid_selector', diff --git a/core/dbt/graph/cli.py b/core/dbt/graph/cli.py index b743022472c..bf8954200a0 100644 --- a/core/dbt/graph/cli.py +++ b/core/dbt/graph/cli.py @@ -1,5 +1,6 @@ # special support for CLI argument parsing. import itertools +import yaml from typing import ( Dict, List, Optional, Tuple, Any, Union @@ -165,9 +166,10 @@ def _parse_include_exclude_subdefs( if isinstance(definition, dict) and 'exclude' in definition: # do not allow multiple exclude: defs at the same level if diff_arg is not None: + yaml_sel_cfg = yaml.dump(definitions) raise ValidationException( - f'Got multiple exclusion definitions in definition list ' - f'{definitions}' + f"You cannot provide multiple exclude arguments to the " + f"same selector set operator:\n{yaml_sel_cfg}" ) diff_arg = _parse_exclusions(definition) else: @@ -251,10 +253,15 @@ def parse_from_definition(definition: RawDefinition) -> SelectionSpec: return parse_intersection_definition(definition) elif isinstance(definition, dict): return parse_dict_definition(definition) + elif isinstance(definition, list): + raise ValidationException( + f'Selector definition root level list not allowed. Use Union to ' + f'contain list. {definition}' + ) else: raise ValidationException( - f'Expected to find str or dict, instead found ' - f'{type(definition)}: {definition}' + f'Expected to find union, intersection, str or dict, instead ' + f'found {type(definition)}: {definition}' ) diff --git a/test/unit/test_selector_errors.py b/test/unit/test_selector_errors.py new file mode 100644 index 00000000000..95c4dbe0344 --- /dev/null +++ b/test/unit/test_selector_errors.py @@ -0,0 +1,71 @@ +from dbt.graph import cli +import dbt.exceptions +import textwrap +import yaml +import unittest +from pprint import pprint +from dbt.config.selectors import ( + selector_config_from_data +) + +from dbt.contracts.selection import SelectorFile + + +def get_selector_dict(txt: str) -> dict: + txt = textwrap.dedent(txt) + dct = yaml.safe_load(txt) + return dct + + + +class SelectorUnitTest(unittest.TestCase): + + def test_parse_multiple_excludes(self): + dct = get_selector_dict('''\ + selectors: + - name: mult_excl + definition: + union: + - method: tag + value: nightly + - exclude: + - method: tag + value: hourly + - exclude: + - method: tag + value: daily + ''') + with self.assertRaises(dbt.exceptions.DbtSelectorsError): + selectors = selector_config_from_data(dct) + + def test_parse_set_op_plus(self): + dct = get_selector_dict('''\ + selectors: + - name: union_plus + definition: + - union: + - method: tag + value: nightly + - exclude: + - method: tag + value: hourly + - method: tag + value: foo + ''') + with self.assertRaises(dbt.exceptions.DbtSelectorsError): + selectors = selector_config_from_data(dct) + + def test_parse_multiple_methods(self): + dct = get_selector_dict('''\ + selectors: + - name: mult_methods + definition: + - tag:hourly + - tag:nightly + - fqn:start + ''') + with self.assertRaises(dbt.exceptions.DbtSelectorsError): + selectors = selector_config_from_data(dct) + + +