44from abc import abstractmethod
55from enum import Enum
66from pathlib import Path
7+ import logging
78
89from pydantic import Field
910from sqlglot .helper import ensure_list
3839BMC = t .TypeVar ("BMC" , bound = "BaseModelConfig" )
3940
4041
42+ logger = logging .getLogger (__name__ )
43+
44+
4145class Materialization (str , Enum ):
4246 """DBT model materializations"""
4347
@@ -261,37 +265,32 @@ def remove_tests_with_invalid_refs(self, context: DbtContext) -> None:
261265 if all (ref in context .refs for ref in test .dependencies .refs )
262266 ]
263267
264- def check_for_circular_test_refs (self , context : DbtContext ) -> None :
268+ def fix_circular_test_refs (self , context : DbtContext ) -> None :
265269 """
266- Checks for direct circular references between two models and raises an exception if found.
267- This addresses the most common circular reference seen when importing a dbt project -
268- relationship tests in both directions. In the future, we may want to increase coverage by
269- checking for indirect circular references.
270+ Checks for direct circular references between two models and moves the test to the downstream
271+ model if found. This addresses the most common circular reference - relationship tests in both
272+ directions. In the future, we may want to increase coverage by checking for indirect circular references.
270273
271274 Args:
272275 context: The dbt context this model resides within.
273276
274277 Returns:
275278 None
276279 """
277- for test in self .tests :
280+ for test in self .tests . copy () :
278281 for ref in test .dependencies .refs :
279- model = context .refs [ref ]
280282 if ref == self .name or ref in self .dependencies .refs :
281283 continue
282- elif self .name in model .dependencies .refs :
283- raise ConfigError (
284- f"Test '{ test .name } ' for model '{ self .name } ' depends on downstream model '{ model .name } '."
285- " Move the test to the downstream model to avoid circular references."
286- )
287- elif self .name in model .tests_ref_source_dependencies .refs :
288- circular_test = next (
289- test .name for test in model .tests if ref in test .dependencies .refs
290- )
291- raise ConfigError (
292- f"Circular reference detected between tests for models '{ self .name } ' and '{ model .name } ':"
293- f" '{ test .name } ' ({ self .name } ), '{ circular_test } ' ({ model .name } )."
284+ model = context .refs [ref ]
285+ if (
286+ self .name in model .dependencies .refs
287+ or self .name in model .tests_ref_source_dependencies .refs
288+ ):
289+ logger .info (
290+ f"Moving test '{ test .name } ' from model '{ self .name } ' to '{ model .name } ' to avoid circular reference."
294291 )
292+ model .tests .append (test )
293+ self .tests .remove (test )
295294
296295 @property
297296 def sqlmesh_config_fields (self ) -> t .Set [str ]:
@@ -312,7 +311,7 @@ def sqlmesh_model_kwargs(
312311 ) -> t .Dict [str , t .Any ]:
313312 """Get common sqlmesh model parameters"""
314313 self .remove_tests_with_invalid_refs (context )
315- self .check_for_circular_test_refs (context )
314+ self .fix_circular_test_refs (context )
316315
317316 dependencies = self .dependencies .copy ()
318317 if dependencies .has_dynamic_var_names :
0 commit comments