diff --git a/.changes/unreleased/Fixes-20240627-154448.yaml b/.changes/unreleased/Fixes-20240627-154448.yaml new file mode 100644 index 00000000000..f2ea7dd739c --- /dev/null +++ b/.changes/unreleased/Fixes-20240627-154448.yaml @@ -0,0 +1,6 @@ +kind: Fixes +body: Limit data_tests deprecation to root_project +time: 2024-06-27T15:44:48.579869-04:00 +custom: + Author: gshank + Issue: "9835" diff --git a/core/dbt/contracts/graph/nodes.py b/core/dbt/contracts/graph/nodes.py index 328da2e04b0..338cb39b94e 100644 --- a/core/dbt/contracts/graph/nodes.py +++ b/core/dbt/contracts/graph/nodes.py @@ -1120,7 +1120,7 @@ def get_full_source_name(self): def get_source_representation(self): return f'source("{self.source.name}", "{self.table.name}")' - def validate_data_tests(self): + def validate_data_tests(self, is_root_project: bool): """ sources parse tests differently than models, so we need to do some validation here where it's done in the PatchParser for other nodes @@ -1131,11 +1131,12 @@ def validate_data_tests(self): "Invalid test config: cannot have both 'tests' and 'data_tests' defined" ) if self.tests: - deprecations.warn( - "project-test-config", - deprecated_path="tests", - exp_path="data_tests", - ) + if is_root_project: + deprecations.warn( + "project-test-config", + deprecated_path="tests", + exp_path="data_tests", + ) self.data_tests.extend(self.tests) self.tests.clear() @@ -1146,11 +1147,12 @@ def validate_data_tests(self): "Invalid test config: cannot have both 'tests' and 'data_tests' defined" ) if column.tests: - deprecations.warn( - "project-test-config", - deprecated_path="tests", - exp_path="data_tests", - ) + if is_root_project: + deprecations.warn( + "project-test-config", + deprecated_path="tests", + exp_path="data_tests", + ) column.data_tests.extend(column.tests) column.tests.clear() @@ -1168,7 +1170,6 @@ def columns(self) -> Sequence[UnparsedColumn]: return [] if self.table.columns is None else self.table.columns def get_tests(self) -> Iterator[Tuple[Dict[str, Any], Optional[UnparsedColumn]]]: - self.validate_data_tests() for data_test in self.data_tests: yield normalize_test(data_test), None diff --git a/core/dbt/parser/schemas.py b/core/dbt/parser/schemas.py index 6a16ca4903d..284a01fc58e 100644 --- a/core/dbt/parser/schemas.py +++ b/core/dbt/parser/schemas.py @@ -544,37 +544,44 @@ def normalize_contract_attribute(self, data, path): def normalize_access_attribute(self, data, path): return self.normalize_attribute(data, path, "access") + @property + def is_root_project(self): + if self.root_project.project_name == self.project.project_name: + return True + return False + def validate_data_tests(self, data): # Rename 'tests' -> 'data_tests' at both model-level and column-level # Raise a validation error if the user has defined both names - def validate_and_rename(data): + def validate_and_rename(data, is_root_project: bool): if data.get("tests"): if "tests" in data and "data_tests" in data: raise ValidationError( "Invalid test config: cannot have both 'tests' and 'data_tests' defined" ) - deprecations.warn( - "project-test-config", - deprecated_path="tests", - exp_path="data_tests", - ) + if is_root_project: + deprecations.warn( + "project-test-config", + deprecated_path="tests", + exp_path="data_tests", + ) data["data_tests"] = data.pop("tests") # model-level tests - validate_and_rename(data) + validate_and_rename(data, self.is_root_project) # column-level tests if data.get("columns"): for column in data["columns"]: - validate_and_rename(column) + validate_and_rename(column, self.is_root_project) # versioned models if data.get("versions"): for version in data["versions"]: - validate_and_rename(version) + validate_and_rename(version, self.is_root_project) if version.get("columns"): for column in version["columns"]: - validate_and_rename(column) + validate_and_rename(column, self.is_root_project) def patch_node_config(self, node, patch): if "access" in patch.config: diff --git a/core/dbt/parser/sources.py b/core/dbt/parser/sources.py index 5666a44da11..68dbed94ce5 100644 --- a/core/dbt/parser/sources.py +++ b/core/dbt/parser/sources.py @@ -226,6 +226,8 @@ def get_generic_test_parser_for(self, package_name: str) -> "SchemaGenericTestPa return generic_test_parser def get_source_tests(self, target: UnpatchedSourceDefinition) -> Iterable[GenericTestNode]: + is_root_project = True if self.root_project.project_name == target.package_name else False + target.validate_data_tests(is_root_project) for data_test, column in target.get_tests(): yield self.parse_source_test( target=target, diff --git a/tests/functional/dependencies/test_local_dependency.py b/tests/functional/dependencies/test_local_dependency.py index 28558bc725f..8e1a6d3cb17 100644 --- a/tests/functional/dependencies/test_local_dependency.py +++ b/tests/functional/dependencies/test_local_dependency.py @@ -11,7 +11,13 @@ import dbt.exceptions import dbt_common.exceptions import dbt_common.semver as semver -from dbt.tests.util import check_relations_equal, run_dbt, run_dbt_and_capture +from dbt import deprecations +from dbt.tests.util import ( + check_relations_equal, + run_dbt, + run_dbt_and_capture, + write_file, +) from tests.functional.utils import up_one # todo: make self.unique_schema to fixture @@ -353,3 +359,36 @@ def test_local_dependency_same_name_sneaky(self, prepare_dependencies, project): # needed to avoid compilation errors from duplicate package names in test autocleanup run_dbt(["clean"]) + + +source_with_tests = """ +sources: + - name: my_source + schema: invalid_schema + tables: + - name: my_table + - name: seed_source + schema: "{{ var('schema_override', target.schema) }}" + tables: + - name: "seed" + identifier: "seed_subpackage_generate_alias_name" + columns: + - name: id + tests: + - unique + - not_null +""" + + +class TestDependencyTestsConfig(BaseDependencyTest): + def test_dependency_tests_config(self, project): + run_dbt(["deps"]) + # Write a file to local_dependency with a "tests" config + write_file( + source_with_tests, project.project_root, "local_dependency", "models", "schema.yml" + ) + run_dbt(["parse"]) + # Check that project-test-config is NOT in active deprecations, since "tests" is only + # in a dependent project. + expected = set() + assert expected == deprecations.active_deprecations