Skip to content

Commit c7e6718

Browse files
committed
Fix: Add workaround to ignore source dependency if fqn matches model
1 parent 223422f commit c7e6718

File tree

4 files changed

+56
-5
lines changed

4 files changed

+56
-5
lines changed

sqlmesh/dbt/basemodel.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ class BaseModelConfig(GeneralConfig):
130130
unique_id: str = ""
131131
name: str = ""
132132
package_name: str = ""
133-
fqn: t.List[str] = []
133+
fqn_: t.List[str] = Field(default_factory=list, alias="fqn")
134134
schema_: str = Field("", alias="schema")
135135
database: t.Optional[str] = None
136136
alias: t.Optional[str] = None
@@ -281,15 +281,17 @@ def remove_tests_with_invalid_refs(self, context: DbtContext) -> None:
281281
and all(source in context.sources for source in test.dependencies.sources)
282282
]
283283

284+
@property
285+
def fqn(self) -> str:
286+
return ".".join(self.fqn_)
287+
284288
@property
285289
def sqlmesh_config_fields(self) -> t.Set[str]:
286290
return {"description", "owner", "stamp", "storage_format"}
287291

288292
@property
289293
def node_info(self) -> DbtNodeInfo:
290-
return DbtNodeInfo(
291-
unique_id=self.unique_id, name=self.name, fqn=".".join(self.fqn), alias=self.alias
292-
)
294+
return DbtNodeInfo(unique_id=self.unique_id, name=self.name, fqn=self.fqn, alias=self.alias)
293295

294296
def sqlmesh_model_kwargs(
295297
self,
@@ -327,7 +329,14 @@ def sqlmesh_model_kwargs(
327329
"column_descriptions": column_descriptions_to_sqlmesh(self.columns) or None,
328330
"depends_on": {
329331
model.canonical_name(context) for model in model_context.refs.values()
330-
}.union({source.canonical_name(context) for source in model_context.sources.values()}),
332+
}.union(
333+
{
334+
source.canonical_name(context)
335+
for source in model_context.sources.values()
336+
if source.fqn not in context.models_by_fqn
337+
# Allow dbt projects to reference a model as a source without causing a cycle
338+
},
339+
),
331340
"jinja_macros": jinja_macros,
332341
"path": self.path,
333342
"pre_statements": [d.jinja_statement(hook.sql) for hook in self.pre_hook],

sqlmesh/dbt/context.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ class DbtContext:
5151
_project_name: t.Optional[str] = None
5252
_variables: t.Dict[str, t.Any] = field(default_factory=dict)
5353
_models: t.Dict[str, ModelConfig] = field(default_factory=dict)
54+
_models_by_fqn: t.Dict[str, ModelConfig] = field(default_factory=dict)
5455
_seeds: t.Dict[str, SeedConfig] = field(default_factory=dict)
5556
_sources: t.Dict[str, SourceConfig] = field(default_factory=dict)
5657
_refs: t.Dict[str, t.Union[ModelConfig, SeedConfig]] = field(default_factory=dict)
@@ -144,13 +145,20 @@ def models(self) -> t.Dict[str, ModelConfig]:
144145
def models(self, models: t.Dict[str, ModelConfig]) -> None:
145146
self._models = {}
146147
self._refs = {}
148+
self._models_by_fqn = {}
147149
self.add_models(models)
148150

149151
def add_models(self, models: t.Dict[str, ModelConfig]) -> None:
150152
self._refs = {}
151153
self._models.update(models)
152154
self._jinja_environment = None
153155

156+
@property
157+
def models_by_fqn(self) -> t.Dict[str, ModelConfig]:
158+
if not self._models_by_fqn:
159+
self._models_by_fqn = {model.fqn: model for model in self._models.values()}
160+
return self._models_by_fqn
161+
154162
@property
155163
def seeds(self) -> t.Dict[str, SeedConfig]:
156164
return self._seeds

sqlmesh/dbt/source.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class SourceConfig(GeneralConfig):
3636
# DBT configuration fields
3737
name: str = ""
3838
source_name_: str = Field("", alias="source_name")
39+
fqn_: t.List[str] = Field(default_factory=list, alias="fqn")
3940
database: t.Optional[str] = None
4041
schema_: t.Optional[str] = Field(None, alias="schema")
4142
identifier: t.Optional[str] = None
@@ -64,6 +65,10 @@ def table_name(self) -> t.Optional[str]:
6465
def config_name(self) -> str:
6566
return f"{self.source_name_}.{self.name}"
6667

68+
@property
69+
def fqn(self) -> str:
70+
return ".".join(self.fqn_)
71+
6772
def canonical_name(self, context: DbtContext) -> str:
6873
if self._canonical_name is None:
6974
source = context.get_callable_macro("source")

tests/dbt/test_transformation.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
)
5454
from sqlmesh.dbt.context import DbtContext
5555
from sqlmesh.dbt.model import Materialization, ModelConfig
56+
from sqlmesh.dbt.source import SourceConfig
5657
from sqlmesh.dbt.project import Project
5758
from sqlmesh.dbt.relation import Policy
5859
from sqlmesh.dbt.seed import SeedConfig
@@ -2678,3 +2679,31 @@ def test_selected_resources_context_variable(
26782679

26792680
result = context.render(test_condition, selected_resources=selected_resources)
26802681
assert result.strip() == "has_resources"
2682+
2683+
2684+
def test_ignore_source_depends_on_when_also_model(dbt_dummy_postgres_config: PostgresConfig):
2685+
context = DbtContext()
2686+
context._target = dbt_dummy_postgres_config
2687+
2688+
source_a = SourceConfig(
2689+
name="source_a",
2690+
fqn=["package", "schema", "model_a"],
2691+
)
2692+
source_a._canonical_name = "schema.source_a"
2693+
source_b = SourceConfig(
2694+
name="source_b",
2695+
fqn=["package", "schema", "source_b"],
2696+
)
2697+
source_b._canonical_name = "schema.source_b"
2698+
context.sources = {"source_a": source_a, "source_b": source_b}
2699+
2700+
model = ModelConfig(
2701+
dependencies=Dependencies(sources={"source_a", "source_b"}),
2702+
fqn=["package", "schema", "test_model"],
2703+
)
2704+
context.models = {
2705+
"test_model": model,
2706+
"model_a": ModelConfig(name="model_a", fqn=["package", "schema", "model_a"]),
2707+
}
2708+
2709+
assert model.sqlmesh_model_kwargs(context)["depends_on"] == {"schema.source_b"}

0 commit comments

Comments
 (0)