-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add better error messages for yaml selectors #2781
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
import dbt.exceptions | ||
import textwrap | ||
import yaml | ||
import unittest | ||
from dbt.config.selectors import ( | ||
selector_config_from_data | ||
) | ||
|
||
from dbt.config.selectors import SelectorConfig | ||
|
||
|
||
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.assertRaisesRegex( | ||
dbt.exceptions.DbtSelectorsError, | ||
'cannot provide multiple exclude arguments' | ||
): | ||
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 | ||
Comment on lines
+44
to
+53
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you also add an exception + test for quite similar case, which I've seen a few folks run into: selectors:
- name: mixed_syntaxes
definition:
key: value
method: tag
value: foo
union:
- method: tag
value: nightly
- exclude:
- method: tag
value: hourly The issue here is that contradictory dict arguments are being passed to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we talking about the "key: value" piece? or having a union in a dictionary without a 'union' above it? I was under the impression that the above should be: selectors:
- name: mixed_syntaxes
definition:
union:
- method: tag
value: foo
- method: tag
value: nightly
- exclude:
method: tag
value: hourly So having a 'union' in a single dictionary should be an error, and having an invalid keyword 'key' should be an error? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right on—the way you've defined it is how it should be. I've seen users write the version I put above, which neither throws an error nor works the way they'd expect. I think we should check to see that
and throw an exception if it doesn't meet one of those three criteria. Does that sound reasonable? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think there are quite a few other arguments that you can put in a dict, such as 'children', 'parents', etc. What I did and pushed was to check that if there is a union or intersection operator in the root level, there's not also a 'method' key. The way the code worked was that it would just. ignore additional dict keys if there was a union or intersection, so that takes the "way it works" and creates an error. Let me know if you see any other situations that are problematic. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, that's a really good point about That sounds about right to me. Would it be possible to generalize this:
To be: "if there is a union or intersection operator in the root level, there should be no other dict arguments." OR: "There should be only one dict argument, unless I think this would also avoid the weirdness of something like: selectors:
- name: multiple_sets
definition:
intersection:
- method: tag
value: foo
- method: tag
value: bar
union:
- method: tag
value: nightly
- exclude:
- method: tag
value: hourly If I understand correctly, the current behavior would be to take the first set and ignore the second set entirely. Really we should throw an error. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that would cover more situations. Will change. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like this now raises a much better error message: selectors:
- name: intersection_and_method
definition:
intersection:
- method: tag
value: foo
- method: tag
value: bar
method: tag
value: bar
Nice! The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Strange. For the multiple_set example I get: Only a single 'union' or 'intersection' key is allowed in a root level selector definition; found intersection,union. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This now works wonderfully!
I'm sure this was on me for not pulling the latest remote changes. |
||
''') | ||
with self.assertRaisesRegex( | ||
dbt.exceptions.DbtSelectorsError, | ||
'Valid root-level selector definitions' | ||
): | ||
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.assertRaisesRegex( | ||
dbt.exceptions.DbtSelectorsError, | ||
'Valid root-level selector definitions' | ||
): | ||
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.assertRaisesRegex( | ||
dbt.exceptions.DbtSelectorsError, | ||
"Only a single 'union' or 'intersection' key is allowed" | ||
): | ||
selector_config_from_data(dct) | ||
|
||
def test_complex_sector(self): | ||
dct = get_selector_dict('''\ | ||
selectors: | ||
- name: nightly_diet_snowplow | ||
definition: | ||
union: | ||
- intersection: | ||
- method: source | ||
value: snowplow | ||
childrens_parents: true | ||
- method: tag | ||
value: nightly | ||
- method: path | ||
value: models/export | ||
- exclude: | ||
- intersection: | ||
- method: package | ||
value: snowplow | ||
- method: config.materialized | ||
value: incremental | ||
- method: fqn | ||
value: export_performance_timing | ||
''') | ||
selectors = selector_config_from_data(dct) | ||
assert(isinstance(selectors, SelectorConfig)) | ||
|
||
def test_exclude_not_list(self): | ||
dct = get_selector_dict('''\ | ||
selectors: | ||
- name: summa_exclude | ||
definition: | ||
union: | ||
- method: tag | ||
value: nightly | ||
- exclude: | ||
method: tag | ||
value: daily | ||
''') | ||
with self.assertRaisesRegex( | ||
dbt.exceptions.DbtSelectorsError, | ||
"Expected a list" | ||
): | ||
selector_config_from_data(dct) | ||
|
||
def test_invalid_key(self): | ||
dct = get_selector_dict('''\ | ||
selectors: | ||
- name: summa_nothing | ||
definition: | ||
method: tag | ||
key: nightly | ||
''') | ||
with self.assertRaisesRegex( | ||
dbt.exceptions.DbtSelectorsError, | ||
"Expected either 1 key" | ||
): | ||
selector_config_from_data(dct) | ||
|
||
def test_invalid_single_def(self): | ||
dct = get_selector_dict('''\ | ||
selectors: | ||
- name: summa_nothing | ||
definition: | ||
fubar: tag | ||
''') | ||
with self.assertRaisesRegex( | ||
dbt.exceptions.DbtSelectorsError, | ||
"not a valid method name" | ||
): | ||
selector_config_from_data(dct) | ||
|
||
def test_method_no_value(self): | ||
dct = get_selector_dict('''\ | ||
selectors: | ||
- name: summa_nothing | ||
definition: | ||
method: tag | ||
''') | ||
with self.assertRaisesRegex( | ||
dbt.exceptions.DbtSelectorsError, | ||
"not a valid method name" | ||
): | ||
selector_config_from_data(dct) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incredibly nit-picky and totally up to you: