From 6a1e3a6db85f90c682ba5cd124ec28770ae9969a Mon Sep 17 00:00:00 2001 From: Doug Beatty <44704949+dbeatty10@users.noreply.github.com> Date: Tue, 27 Jun 2023 12:55:38 -0600 Subject: [PATCH] Fix macro namespace search packages (#5804) --- .../unreleased/Fixes-20220909-164413.yaml | 7 ++++ core/dbt/context/providers.py | 36 +++++++++------- tests/functional/macros/fixtures.py | 25 +++++++++++ tests/functional/macros/test_macros.py | 41 +++++++++++++++++++ 4 files changed, 94 insertions(+), 15 deletions(-) create mode 100644 .changes/unreleased/Fixes-20220909-164413.yaml diff --git a/.changes/unreleased/Fixes-20220909-164413.yaml b/.changes/unreleased/Fixes-20220909-164413.yaml new file mode 100644 index 00000000000..8050d603cea --- /dev/null +++ b/.changes/unreleased/Fixes-20220909-164413.yaml @@ -0,0 +1,7 @@ +kind: Fixes +body: Raise better error message when dispatching a package that is not installed +time: 2022-09-09T16:44:13.382685-06:00 +custom: + Author: "dbeatty10" + Issue: "5801" + PR: "5804" diff --git a/core/dbt/context/providers.py b/core/dbt/context/providers.py index 4a1fca0c353..28c6681be7e 100644 --- a/core/dbt/context/providers.py +++ b/core/dbt/context/providers.py @@ -133,6 +133,25 @@ def _get_adapter_macro_prefixes(self) -> List[str]: search_prefixes = get_adapter_type_names(self._adapter.type()) + ["default"] return search_prefixes + def _get_search_packages(self, namespace: Optional[str] = None) -> List[Optional[str]]: + search_packages: List[Optional[str]] = [None] + + if namespace is None: + search_packages = [None] + elif isinstance(namespace, str): + macro_search_order = self._adapter.config.get_macro_search_order(namespace) + if macro_search_order: + search_packages = macro_search_order + elif not macro_search_order and namespace in self._adapter.config.dependencies: + search_packages = [self.config.project_name, namespace] + else: + raise CompilationError( + f"In adapter.dispatch, got a {type(namespace)} macro_namespace argument " + f'("{namespace}"), but macro_namespace should be None or a string.' + ) + + return search_packages + def dispatch( self, macro_name: str, @@ -154,20 +173,7 @@ def dispatch( if packages is not None: raise MacroDispatchArgError(macro_name) - namespace = macro_namespace - - if namespace is None: - search_packages = [None] - elif isinstance(namespace, str): - search_packages = self._adapter.config.get_macro_search_order(namespace) - if not search_packages and namespace in self._adapter.config.dependencies: - search_packages = [self.config.project_name, namespace] - else: - # Not a string and not None so must be a list - raise CompilationError( - f"In adapter.dispatch, got a list macro_namespace argument " - f'("{macro_namespace}"), but macro_namespace should be None or a string.' - ) + search_packages = self._get_search_packages(macro_namespace) attempts = [] @@ -191,7 +197,7 @@ def dispatch( return macro searched = ", ".join(repr(a) for a in attempts) - msg = f"In dispatch: No macro named '{macro_name}' found\n Searched for: {searched}" + msg = f"In dispatch: No macro named '{macro_name}' found within namespace: '{macro_namespace}'\n Searched for: {searched}" raise CompilationError(msg) diff --git a/tests/functional/macros/fixtures.py b/tests/functional/macros/fixtures.py index f1e7841f58e..500859ec1f3 100644 --- a/tests/functional/macros/fixtures.py +++ b/tests/functional/macros/fixtures.py @@ -129,3 +129,28 @@ {{ adapter_macro('some_macro', arg1, arg2) }} {%- endmacro %} """ + +macros__incorrect_dispatch = """ +{% macro cowsay() %} + {{ return(adapter.dispatch('cowsay', 'farm_utils')()) }} +{%- endmacro %} + +{% macro default__cowsay() %} + 'moo' +{% endmacro %} +""" + +# Note the difference between `test_utils` below and `farm_utils` above +models__incorrect_dispatch = """ +select {{ test_utils.cowsay() }} as cowsay +""" + +dbt_project__incorrect_dispatch = """ +name: 'test_utils' +version: '1.0' +config-version: 2 + +profile: 'default' + +macro-paths: ["macros"] +""" diff --git a/tests/functional/macros/test_macros.py b/tests/functional/macros/test_macros.py index e7f25acab3a..31f7647434d 100644 --- a/tests/functional/macros/test_macros.py +++ b/tests/functional/macros/test_macros.py @@ -10,18 +10,22 @@ check_relations_equal, ) +from dbt.tests.fixtures.project import write_project_files from tests.functional.macros.fixtures import ( + dbt_project__incorrect_dispatch, models__dep_macro, models__with_undefined_macro, models__local_macro, models__ref_macro, models__override_get_columns_macros, models__deprecated_adapter_macro_model, + models__incorrect_dispatch, macros__my_macros, macros__no_default_macros, macros__override_get_columns_macros, macros__package_override_get_columns_macros, macros__deprecated_adapter_macro, + macros__incorrect_dispatch, ) @@ -203,6 +207,43 @@ def test_overrides(self, project): run_dbt() +class TestMisnamedMacroNamespace: + @pytest.fixture(scope="class", autouse=True) + def setUp(self, project_root): + test_utils_files = { + "dbt_project.yml": dbt_project__incorrect_dispatch, + "macros": { + "cowsay.sql": macros__incorrect_dispatch, + }, + } + write_project_files(project_root, "test_utils", test_utils_files) + + @pytest.fixture(scope="class") + def models(self): + return { + "my_model.sql": models__incorrect_dispatch, + } + + @pytest.fixture(scope="class") + def packages(self): + return { + "packages": [ + {"local": "test_utils"}, + ] + } + + def test_misnamed_macro_namespace( + self, + project, + ): + run_dbt(["deps"]) + + with pytest.raises(dbt.exceptions.CompilationError) as exc: + run_dbt() + + assert "In dispatch: No macro named 'cowsay' found" in str(exc.value) + + class TestAdapterMacroDeprecated: @pytest.fixture(scope="class") def models(self):