diff --git a/CHANGELOG.md b/CHANGELOG.md index bf9d3e22972..d0389253262 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### Fixes - When a jinja value is undefined, give a helpful error instead of failing with cryptic "cannot pickle ParserMacroCapture" errors ([#2110](https://github.com/fishtown-analytics/dbt/issues/2110), [#2184](https://github.com/fishtown-analytics/dbt/pull/2184)) - Added timeout to registry download call ([#2195](https://github.com/fishtown-analytics/dbt/issues/2195), [#2228](https://github.com/fishtown-analytics/dbt/pull/2228)) +- When a macro is called with invalid arguments, include the calling model in the output ([#2073](https://github.com/fishtown-analytics/dbt/issues/2073), [#2238](https://github.com/fishtown-analytics/dbt/pull/2238)) Contributors: - [@raalsky](https://github.com/Raalsky) ([#2224](https://github.com/fishtown-analytics/dbt/pull/2224), [#2228](https://github.com/fishtown-analytics/dbt/pull/2228)) diff --git a/core/dbt/clients/jinja.py b/core/dbt/clients/jinja.py index cbef36f4459..40e4d16ebac 100644 --- a/core/dbt/clients/jinja.py +++ b/core/dbt/clients/jinja.py @@ -371,6 +371,9 @@ def catch_jinja(node=None) -> Iterator[None]: raise CompilationException(str(e), node) from e except jinja2.exceptions.UndefinedError as e: raise CompilationException(str(e), node) from e + except CompilationException as exc: + exc.add_node(node) + raise def parse(string): diff --git a/core/dbt/exceptions.py b/core/dbt/exceptions.py index 2f3edd2df6f..e7e0cae9445 100644 --- a/core/dbt/exceptions.py +++ b/core/dbt/exceptions.py @@ -53,6 +53,12 @@ def __init__(self, msg, node=None): self.node = node self.msg = msg + def add_node(self, node=None): + if node is not None and node is not self.node: + if self.node is not None: + self.stack.append(self.node) + self.node = node + @property def type(self): return 'Runtime' @@ -849,8 +855,7 @@ def inner(*args, **kwargs): try: return func(*args, **kwargs) except RuntimeException as exc: - if exc.node is None: - exc.node = model + exc.add_node(model) raise exc return inner return wrap diff --git a/core/dbt/node_runners.py b/core/dbt/node_runners.py index f2fbfbca711..3594396da64 100644 --- a/core/dbt/node_runners.py +++ b/core/dbt/node_runners.py @@ -172,7 +172,7 @@ def compile_and_execute(self, manifest, ctx): def _handle_catchable_exception(self, e, ctx): if e.node is None: - e.node = ctx.node + e.add_node(ctx.node) logger.debug(str(e), exc_info=True) return str(e) diff --git a/core/dbt/parser/docs.py b/core/dbt/parser/docs.py index eb43a737854..6d75f4b5c80 100644 --- a/core/dbt/parser/docs.py +++ b/core/dbt/parser/docs.py @@ -71,7 +71,7 @@ def parse_block(self, block: FullBlock) -> Iterable[ParsedDocumentation]: try: template = get_template(block.contents, {}) except CompilationException as e: - e.node = base_node + e.add_node(base_node) raise all_docs = list(self._parse_template_docs(template, base_node)) if len(all_docs) != 1: diff --git a/core/dbt/parser/macros.py b/core/dbt/parser/macros.py index 3ffbbf4c243..a35dc8d2d2b 100644 --- a/core/dbt/parser/macros.py +++ b/core/dbt/parser/macros.py @@ -63,7 +63,7 @@ def parse_unparsed_macros( try: ast = jinja.parse(block.full_block) except CompilationException as e: - e.node = base_node + e.add_node(base_node) raise e macro_nodes = list(ast.find_all(jinja2.nodes.Macro)) diff --git a/core/dbt/parser/search.py b/core/dbt/parser/search.py index a5d8b5fe441..db41626cc6c 100644 --- a/core/dbt/parser/search.py +++ b/core/dbt/parser/search.py @@ -116,7 +116,7 @@ def extract_blocks(self, source_file: FileBlock) -> Iterable[BlockTag]: except CompilationException as exc: if exc.node is None: - exc.node = source_file + exc.add_node(source_file) raise def __iter__(self) -> Iterator[BlockSearchResult]: diff --git a/core/dbt/rpc/node_runners.py b/core/dbt/rpc/node_runners.py index 39108ff3a47..21bf3c0a1ee 100644 --- a/core/dbt/rpc/node_runners.py +++ b/core/dbt/rpc/node_runners.py @@ -24,7 +24,7 @@ def handle_exception(self, e, ctx): logger.debug('Got an exception: {}'.format(e), exc_info=True) if isinstance(e, dbt.exceptions.Exception): if isinstance(e, dbt.exceptions.RuntimeException): - e.node = ctx.node + e.add_node(ctx.node) return dbt_error(e) elif isinstance(e, RPCException): return e diff --git a/test/integration/011_invalid_model_tests/bad-macros/macros.sql b/test/integration/011_invalid_model_tests/bad-macros/macros.sql new file mode 100644 index 00000000000..bff4eb9d9ae --- /dev/null +++ b/test/integration/011_invalid_model_tests/bad-macros/macros.sql @@ -0,0 +1,3 @@ +{% macro some_macro(arg) %} + {{ arg }} +{% endmacro %} diff --git a/test/integration/011_invalid_model_tests/models-4/bad_macro.sql b/test/integration/011_invalid_model_tests/models-4/bad_macro.sql new file mode 100644 index 00000000000..7fe652d70bc --- /dev/null +++ b/test/integration/011_invalid_model_tests/models-4/bad_macro.sql @@ -0,0 +1,2 @@ +{{ some_macro(invalid='test') }} +select 1 as id diff --git a/test/integration/011_invalid_model_tests/test_invalid_models.py b/test/integration/011_invalid_model_tests/test_invalid_models.py index bcec8f49eab..cc1bc93404c 100644 --- a/test/integration/011_invalid_model_tests/test_invalid_models.py +++ b/test/integration/011_invalid_model_tests/test_invalid_models.py @@ -1,4 +1,5 @@ from test.integration.base import DBTIntegrationTest, use_profile +import os class TestInvalidDisabledModels(DBTIntegrationTest): @@ -45,3 +46,35 @@ def test_postgres_view_with_incremental_attributes(self): self.run_dbt() self.assertIn('which was not found', str(exc.exception)) + + +class TestInvalidMacroCall(DBTIntegrationTest): + @property + def schema(self): + return "invalid_models_011" + + @property + def models(self): + return "models-4" + + @staticmethod + def dir(path): + return path.lstrip("/") + + @property + def project_config(self): + return { + 'macro-paths': [self.dir('bad-macros')], + } + + @use_profile('postgres') + def test_postgres_call_invalid(self): + with self.assertRaises(Exception) as exc: + self.run_dbt(['compile']) + + + macro_path = os.path.join('bad-macros', 'macros.sql') + model_path = os.path.join('models-4', 'bad_macro.sql') + + self.assertIn(f'> in macro some_macro ({macro_path})', str(exc.exception)) + self.assertIn(f'> called by model bad_macro ({model_path})', str(exc.exception))