Skip to content

Commit

Permalink
Add better error messages for yaml selectors
Browse files Browse the repository at this point in the history
  • Loading branch information
gshank committed Sep 23, 2020
1 parent 8ee490b commit bbb9acc
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- Added state and defer arguments to the RPC client, matching the CLI ([#2678](https://github.com/fishtown-analytics/dbt/issues/2678), [#2736](https://github.com/fishtown-analytics/dbt/pull/2736))
- Added schema and dbt versions to JSON artifacts ([#2670](https://github.com/fishtown-analytics/dbt/issues/2670), [#2767](https://github.com/fishtown-analytics/dbt/pull/2767))
- Added ability to snapshot hard-deleted records (opt-in with `invalidate_hard_deletes` config option). ([#249](https://github.com/fishtown-analytics/dbt/issues/249), [#2749](https://github.com/fishtown-analytics/dbt/pull/2749))
- Improved error messages for YAML selectors ([#2700](https://github.com/fishtown-analytics/dbt/issues/2700), [#2781](https://github.com/fishtown-analytics/dbt/pull/2781))

Contributors:
- [@joelluijmes](https://github.com/joelluijmes) ([#2749](https://github.com/fishtown-analytics/dbt/pull/2749))
Expand Down
13 changes: 12 additions & 1 deletion core/dbt/config/selectors.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from pathlib import Path
from typing import Dict, Any
import yaml

from hologram import ValidationError

Expand Down Expand Up @@ -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',
Expand Down
33 changes: 27 additions & 6 deletions core/dbt/graph/cli.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# special support for CLI argument parsing.
import itertools
import yaml

from typing import (
Dict, List, Optional, Tuple, Any, Union
Expand Down Expand Up @@ -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(definition)
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:
Expand All @@ -182,6 +184,13 @@ def parse_union_definition(definition: Dict[str, Any]) -> SelectionSpec:

union = SelectionUnion(components=include)

if 'method' in definition:
yaml_sel_cfg = yaml.dump(definition)
raise ValidationException(
f"You cannot have both method and union keys in a root level "
f"selector definition:\n{yaml_sel_cfg}"
)

if exclude is None:
union.raw = definition
return union
Expand All @@ -198,6 +207,14 @@ def parse_intersection_definition(
intersection_def_parts = _get_list_dicts(definition, 'intersection')
include, exclude = _parse_include_exclude_subdefs(intersection_def_parts)
intersection = SelectionIntersection(components=include)

if 'method' in definition:
yaml_sel_cfg = yaml.dump(definition)
raise ValidationException(
f"You cannot have both method and intersection keys in a root level "
f"selector definition:\n{yaml_sel_cfg}"
)

if exclude is None:
intersection.raw = definition
return intersection
Expand All @@ -210,7 +227,6 @@ def parse_intersection_definition(

def parse_dict_definition(definition: Dict[str, Any]) -> SelectionSpec:
diff_arg: Optional[SelectionSpec] = None

if len(definition) == 1:
key = list(definition)[0]
value = definition[key]
Expand All @@ -229,7 +245,7 @@ def parse_dict_definition(definition: Dict[str, Any]) -> SelectionSpec:
diff_arg = _parse_exclusions(definition)
dct = {k: v for k, v in dct.items() if k != 'exclude'}
else:
raise ValidationException(
raise ValidationError(
f'Expected exactly 1 key in the selection definition or "method" '
f'and "value" keys, but got {list(definition)}'
)
Expand All @@ -251,10 +267,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}'
)


Expand Down
88 changes: 88 additions & 0 deletions test/unit/test_selector_errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
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)

def test_parse_set_with_method(self):
dct = get_selector_dict('''\
selectors:
- name: mixed_syntaxes
definition:
key: value
method: tag
value: foo
union:
- method: tag
value: m1234
- exclude:
- method: tag
value: m5678
''')
with self.assertRaises(dbt.exceptions.DbtSelectorsError):
selectors = selector_config_from_data(dct)


0 comments on commit bbb9acc

Please sign in to comment.