Skip to content

Commit

Permalink
CT 2494 check for project level dependency cycles (#7558)
Browse files Browse the repository at this point in the history
* Raise error if dependent project depends on current project

* Test for project dependency cycle

* Changie
  • Loading branch information
gshank authored May 10, 2023
1 parent 58312f1 commit 43d949c
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 2 deletions.
6 changes: 6 additions & 0 deletions .changes/unreleased/Features-20230509-094147.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: Features
body: Check for project dependency cycles
time: 2023-05-09T09:41:47.2-04:00
custom:
Author: gshank
Issue: "7468"
13 changes: 13 additions & 0 deletions core/dbt/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -871,6 +871,19 @@ def get_message(self) -> str:
return msg


class ProjectDependencyCycleError(ParsingError):
def __init__(self, pub_project_name, project_name):
self.pub_project_name = pub_project_name
self.project_name = project_name
super().__init__(msg=self.get_message())

def get_message(self) -> str:
return (
f"A project dependency cycle has been detected. The current project {self.project_name} "
f"depends on {self.pub_project_name} which also depends on the current project."
)


class MacroArgTypeError(CompilationError):
def __init__(self, method_name: str, arg_name: str, got_value: Any, expected_type):
self.method_name = method_name
Expand Down
11 changes: 10 additions & 1 deletion core/dbt/parser/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,12 @@
PublicModel,
ProjectDependencies,
)
from dbt.exceptions import TargetNotFoundError, AmbiguousAliasError, PublicationConfigNotFound
from dbt.exceptions import (
TargetNotFoundError,
AmbiguousAliasError,
PublicationConfigNotFound,
ProjectDependencyCycleError,
)
from dbt.parser.base import Parser
from dbt.parser.analysis import AnalysisParser
from dbt.parser.generic_test import GenericTestParser
Expand Down Expand Up @@ -1712,6 +1717,10 @@ def write_publication_artifact(root_project: RuntimeConfig, manifest: Manifest):
# Get dependencies from publication dependencies
for pub_project in manifest.publications.values():
for project_name in pub_project.dependencies:
if project_name == root_project.project_name:
raise ProjectDependencyCycleError(
pub_project_name=pub_project.project_name, project_name=project_name
)
if project_name not in dependencies:
dependencies.append(project_name)

Expand Down
24 changes: 23 additions & 1 deletion tests/functional/multi_project/test_publication.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@

from dbt.tests.util import run_dbt, get_artifact, write_file, copy_file
from dbt.contracts.publication import PublicationArtifact, PublicModel
from dbt.exceptions import PublicationConfigNotFound, TargetNotFoundError
from dbt.exceptions import (
PublicationConfigNotFound,
TargetNotFoundError,
ProjectDependencyCycleError,
)


model_one_sql = """
Expand Down Expand Up @@ -257,3 +261,21 @@ def test_multi_projects(self, project, project_alt):
write_file(dependencies_alt_yml, project.project_root, "dependencies.yml")
results = run_dbt(["run", "--project-dir", str(project.project_root)])
assert len(results) == 4


class TestProjectCycles:
@pytest.fixture(scope="class")
def models(self):
return {
"model_one.sql": model_one_sql,
}

def test_project_cycles(self, project):
write_file(dependencies_yml, "dependencies.yml")
# Create a project dependency that's the same as the current project
m_pub_json = marketing_pub_json.replace('"dependencies": []', '"dependencies": ["test"]')
(pathlib.Path(project.project_root) / "publications").mkdir(parents=True, exist_ok=True)
write_file(m_pub_json, project.project_root, "publications", "marketing_publication.json")

with pytest.raises(ProjectDependencyCycleError):
run_dbt(["parse"])

0 comments on commit 43d949c

Please sign in to comment.