diff --git a/CHANGELOG.md b/CHANGELOG.md index c61d9d3813d..2f4718345d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## dbt next (release TBD) +### 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)) + ## dbt 0.16.0rc2 (March 4, 2020) ### Under the hood diff --git a/core/dbt/clients/jinja.py b/core/dbt/clients/jinja.py index 39feb17104d..cbef36f4459 100644 --- a/core/dbt/clients/jinja.py +++ b/core/dbt/clients/jinja.py @@ -311,38 +311,18 @@ def _is_dunder_name(name): return name.startswith('__') and name.endswith('__') -def create_macro_capture_env(node): - - class ParserMacroCapture(jinja2.Undefined): - """ - This class sets up the parser to capture macros. - """ +def create_undefined(node=None): + class Undefined(jinja2.Undefined): def __init__(self, hint=None, obj=None, name=None, exc=None): super().__init__(hint=hint, name=name) self.node = node self.name = name - self.package_name = node.package_name + self.hint = hint # jinja uses these for safety, so we have to override them. # see https://github.com/pallets/jinja/blob/master/jinja2/sandbox.py#L332-L339 # noqa self.unsafe_callable = False self.alters_data = False - def __deepcopy__(self, memo): - path = os.path.join(self.node.root_path, - self.node.original_file_path) - - logger.debug( - 'dbt encountered an undefined variable, "{}" in node {}.{} ' - '(source path: {})' - .format(self.name, self.node.package_name, - self.node.name, path)) - - # match jinja's message - raise_compiler_error( - "{!r} is undefined".format(self.name), - node=self.node - ) - def __getitem__(self, name): # Propagate the undefined value if a caller accesses this as if it # were a dictionary @@ -355,15 +335,17 @@ def __getattr__(self, name): .format(type(self).__name__, name) ) - self.package_name = self.name self.name = name - return self + return self.__class__(hint=self.hint, name=self.name) def __call__(self, *args, **kwargs): return self - return ParserMacroCapture + def __reduce__(self): + raise_compiler_error(f'{self.name} is undefined', node=node) + + return Undefined def get_environment(node=None, capture_macros=False): @@ -372,7 +354,7 @@ def get_environment(node=None, capture_macros=False): } if capture_macros: - args['undefined'] = create_macro_capture_env(node) + args['undefined'] = create_undefined(node) args['extensions'].append(MaterializationExtension) args['extensions'].append(DocumentationExtension) diff --git a/test/integration/003_simple_reference_test/invalid-models/descendant.sql b/test/integration/003_simple_reference_test/invalid-models/descendant.sql new file mode 100644 index 00000000000..50b4e3e559a --- /dev/null +++ b/test/integration/003_simple_reference_test/invalid-models/descendant.sql @@ -0,0 +1,2 @@ +-- should be ref('model') +select * from {{ ref(model) }} diff --git a/test/integration/003_simple_reference_test/invalid-models/model.sql b/test/integration/003_simple_reference_test/invalid-models/model.sql new file mode 100644 index 00000000000..43258a71464 --- /dev/null +++ b/test/integration/003_simple_reference_test/invalid-models/model.sql @@ -0,0 +1 @@ +select 1 as id diff --git a/test/integration/003_simple_reference_test/test_simple_reference.py b/test/integration/003_simple_reference_test/test_simple_reference.py index 5d39a6e641b..b2a4aae3efc 100644 --- a/test/integration/003_simple_reference_test/test_simple_reference.py +++ b/test/integration/003_simple_reference_test/test_simple_reference.py @@ -1,5 +1,10 @@ +import os + +from dbt.exceptions import CompilationException + from test.integration.base import DBTIntegrationTest, use_profile + class TestSimpleReference(DBTIntegrationTest): @property def schema(self): @@ -184,3 +189,20 @@ def test__snowflake__simple_reference_with_models_and_children(self): self.assertTrue('EPHEMERAL_SUMMARY' in created_models) self.assertEqual(created_models['EPHEMERAL_SUMMARY'], 'table') + + +class TestErrorReference(DBTIntegrationTest): + @property + def schema(self): + return "simple_reference_003" + + @property + def models(self): + return "invalid-models" + + @use_profile('postgres') + def test_postgres_undefined_value(self): + with self.assertRaises(CompilationException) as exc: + self.run_dbt(['compile']) + path = os.path.join('invalid-models', 'descendant.sql') + self.assertIn(path, str(exc.exception))