Skip to content

Commit 8732257

Browse files
Fix: Add support for args syntax in dbt config (#5299)
1 parent 442c4a6 commit 8732257

File tree

3 files changed

+116
-7
lines changed

3 files changed

+116
-7
lines changed

sqlmesh/dbt/builtin.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,24 @@ class Config:
170170
def __init__(self, config_dict: t.Dict[str, t.Any]) -> None:
171171
self._config = config_dict
172172

173-
def __call__(self, **kwargs: t.Any) -> str:
174-
self._config.update(**kwargs)
173+
def __call__(self, *args: t.Any, **kwargs: t.Any) -> str:
174+
if args and kwargs:
175+
raise ConfigError(
176+
"Invalid inline model config: cannot mix positional and keyword arguments"
177+
)
178+
179+
if args:
180+
if len(args) == 1 and isinstance(args[0], dict):
181+
# Single dict argument: config({"materialized": "table"})
182+
self._config.update(args[0])
183+
else:
184+
raise ConfigError(
185+
f"Invalid inline model config: expected a single dictionary, got {len(args)} arguments"
186+
)
187+
elif kwargs:
188+
# Keyword arguments: config(materialized="table")
189+
self._config.update(kwargs)
190+
175191
return ""
176192

177193
def set(self, name: str, value: t.Any) -> str:

tests/dbt/test_transformation.py

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import pytest
1212
from dbt.adapters.base import BaseRelation
13+
from jinja2 import Template
1314

1415
if DBT_VERSION >= (1, 4, 0):
1516
from dbt.exceptions import CompilationError
@@ -42,7 +43,7 @@
4243
OnAdditiveChange,
4344
)
4445
from sqlmesh.core.state_sync.db.snapshot import _snapshot_to_json
45-
from sqlmesh.dbt.builtin import _relation_info_to_relation
46+
from sqlmesh.dbt.builtin import _relation_info_to_relation, Config
4647
from sqlmesh.dbt.common import Dependencies
4748
from sqlmesh.dbt.column import (
4849
ColumnConfig,
@@ -1052,6 +1053,97 @@ def test_config_jinja(sushi_test_project: Project):
10521053
assert model.render_pre_statements()[0].sql() == '"bar"'
10531054

10541055

1056+
@pytest.mark.xdist_group("dbt_manifest")
1057+
def test_config_dict_syntax():
1058+
# Test dictionary syntax
1059+
config = Config({})
1060+
result = config({"materialized": "table", "alias": "dict_table"})
1061+
assert result == ""
1062+
assert config._config["materialized"] == "table"
1063+
assert config._config["alias"] == "dict_table"
1064+
1065+
# Test kwargs syntax still works
1066+
config2 = Config({})
1067+
result = config2(materialized="view", alias="kwargs_table")
1068+
assert result == ""
1069+
assert config2._config["materialized"] == "view"
1070+
assert config2._config["alias"] == "kwargs_table"
1071+
1072+
# Test that mixing args and kwargs is rejected
1073+
config3 = Config({})
1074+
try:
1075+
config3({"materialized": "table"}, alias="mixed")
1076+
assert False, "Should have raised ConfigError"
1077+
except Exception as e:
1078+
assert "cannot mix positional and keyword arguments" in str(e)
1079+
1080+
# Test nested dicts
1081+
config4 = Config({})
1082+
config4({"meta": {"owner": "data_team", "priority": 1}, "tags": ["daily", "critical"]})
1083+
assert config4._config["meta"]["owner"] == "data_team"
1084+
assert config4._config["tags"] == ["daily", "critical"]
1085+
1086+
# Test multiple positional arguments are rejected
1087+
config4 = Config({})
1088+
try:
1089+
config4({"materialized": "table"}, {"alias": "test"})
1090+
assert False
1091+
except Exception as e:
1092+
assert "expected a single dictionary, got 2 arguments" in str(e)
1093+
1094+
1095+
def test_config_dict_in_jinja():
1096+
# Test dict syntax directly with Config class
1097+
config = Config({})
1098+
template = Template("{{ config({'materialized': 'table', 'unique_key': 'id'}) }}done")
1099+
result = template.render(config=config)
1100+
assert result == "done"
1101+
assert config._config["materialized"] == "table"
1102+
assert config._config["unique_key"] == "id"
1103+
1104+
# Test with nested dict and list values
1105+
config2 = Config({})
1106+
complex_template = Template("""{{ config({
1107+
'tags': ['test', 'dict'],
1108+
'meta': {'owner': 'data_team'}
1109+
}) }}result""")
1110+
result = complex_template.render(config=config2)
1111+
assert result == "result"
1112+
assert config2._config["tags"] == ["test", "dict"]
1113+
assert config2._config["meta"]["owner"] == "data_team"
1114+
1115+
# Test that kwargs still work
1116+
config3 = Config({})
1117+
kwargs_template = Template("{{ config(materialized='view', alias='my_view') }}done")
1118+
result = kwargs_template.render(config=config3)
1119+
assert result == "done"
1120+
assert config3._config["materialized"] == "view"
1121+
assert config3._config["alias"] == "my_view"
1122+
1123+
1124+
@pytest.mark.xdist_group("dbt_manifest")
1125+
def test_config_dict_syntax_in_sushi_project(sushi_test_project: Project):
1126+
assert sushi_test_project is not None
1127+
assert sushi_test_project.context is not None
1128+
1129+
sushi_package = sushi_test_project.packages.get("sushi")
1130+
assert sushi_package is not None
1131+
1132+
top_waiters_found = False
1133+
for model_config in sushi_package.models.values():
1134+
if model_config.name == "top_waiters":
1135+
# top_waiters model now uses dict config syntax with:
1136+
# config({'materialized': 'view', 'limit_value': var('top_waiters:limit'), 'meta': {...}})
1137+
top_waiters_found = True
1138+
assert model_config.materialized == "view"
1139+
assert model_config.meta is not None
1140+
assert model_config.meta.get("owner") == "analytics_team"
1141+
assert model_config.meta.get("priority") == "high"
1142+
break
1143+
1144+
assert top_waiters_found
1145+
1146+
10551147
@pytest.mark.xdist_group("dbt_manifest")
10561148
def test_config_jinja_get_methods(sushi_test_project: Project):
10571149
model_config = ModelConfig(

tests/fixtures/dbt/sushi_test/models/top_waiters.sql

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
{{
2-
config(
3-
materialized='view',
4-
limit_value=var('top_waiters:limit'),
5-
)
2+
config({
3+
'materialized': 'view',
4+
'limit_value': var('top_waiters:limit'),
5+
'meta': {'owner': 'analytics_team', 'priority': 'high'}
6+
})
67
}}
78

89
{% set columns = model.columns %}

0 commit comments

Comments
 (0)