Skip to content

Commit af36b8a

Browse files
Jw/snow 2036324 include dbt profile (#2196)
* feat: [SNOW-2036324] validate dbt profile * refactor: [SNOW-2036324] PR fixes
1 parent a928c36 commit af36b8a

File tree

4 files changed

+117
-36
lines changed

4 files changed

+117
-36
lines changed

src/snowflake/cli/_plugins/dbt/manager.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@
1414

1515
from __future__ import annotations
1616

17-
from click import ClickException
17+
import yaml
1818
from snowflake.cli._plugins.object.manager import ObjectManager
1919
from snowflake.cli._plugins.stage.manager import StageManager
2020
from snowflake.cli.api.console import cli_console
21-
from snowflake.cli.api.constants import ObjectType
21+
from snowflake.cli.api.constants import DEFAULT_SIZE_LIMIT_MB, ObjectType
22+
from snowflake.cli.api.exceptions import CliError
2223
from snowflake.cli.api.identifiers import FQN
2324
from snowflake.cli.api.secure_path import SecurePath
2425
from snowflake.cli.api.sql_execution import SqlExecutionMixin
@@ -44,12 +45,30 @@ def deploy(
4445
) -> SnowflakeCursor:
4546
dbt_project_path = path / "dbt_project.yml"
4647
if not dbt_project_path.exists():
47-
raise ClickException(
48+
raise CliError(
4849
f"dbt_project.yml does not exist in directory {path.path.absolute()}."
4950
)
5051

52+
with dbt_project_path.open(read_file_limit_mb=DEFAULT_SIZE_LIMIT_MB) as fd:
53+
dbt_project = yaml.safe_load(fd)
54+
try:
55+
profile = dbt_project["profile"]
56+
except KeyError:
57+
raise CliError("`profile` is not defined in dbt_project.yml")
58+
59+
dbt_profiles_path = path / "profiles.yml"
60+
if not dbt_profiles_path.exists():
61+
raise CliError(
62+
f"profiles.yml does not exist in directory {path.path.absolute()}."
63+
)
64+
65+
with dbt_profiles_path.open(read_file_limit_mb=DEFAULT_SIZE_LIMIT_MB) as fd:
66+
profiles = yaml.safe_load(fd)
67+
if profile not in profiles:
68+
raise CliError(f"profile {profile} is not defined in profiles.yml")
69+
5170
if self.exists(name=name) and force is not True:
52-
raise ClickException(
71+
raise CliError(
5372
f"DBT project {name} already exists. Use --force flag to overwrite"
5473
)
5574

tests/dbt/test_dbt_commands.py

Lines changed: 72 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from unittest import mock
1818

1919
import pytest
20+
import yaml
2021
from snowflake.cli._plugins.dbt.constants import OUTPUT_COLUMN_NAME, RESULT_COLUMN_NAME
2122
from snowflake.cli.api.identifiers import FQN
2223

@@ -56,8 +57,20 @@ class TestDBTDeploy:
5657
@pytest.fixture
5758
def dbt_project_path(self, tmp_path_factory):
5859
source_path = tmp_path_factory.mktemp("dbt_project")
59-
dbt_file = source_path / "dbt_project.yml"
60-
dbt_file.touch()
60+
dbt_project_file = source_path / "dbt_project.yml"
61+
dbt_project_file.write_text(yaml.dump({"profile": "dev"}))
62+
dbt_profiles_file = source_path / "profiles.yml"
63+
dbt_profiles_file.write_text(
64+
yaml.dump(
65+
{
66+
"dev": {
67+
"outputs": {
68+
"local": {"account": "test_account", "database": "testdb"}
69+
}
70+
}
71+
},
72+
)
73+
)
6174
yield source_path
6275

6376
@pytest.fixture
@@ -105,68 +118,78 @@ def test_deploys_project_from_source(
105118
dbt_project_path, "@MockDatabase.MockSchema.dbt_TEST_PIPELINE_stage"
106119
)
107120

121+
@pytest.mark.parametrize("exists", (True, False))
108122
@mock.patch("snowflake.cli._plugins.dbt.manager.StageManager.put_recursive")
109123
@mock.patch("snowflake.cli._plugins.dbt.manager.StageManager.create")
110-
def test_dbt_version_from_option_has_precedence_over_file(
124+
def test_force_flag_uses_create_or_replace(
111125
self,
112126
_mock_create,
113127
_mock_put_recursive,
128+
exists,
114129
mock_connect,
115130
runner,
116131
dbt_project_path,
117132
mock_exists,
118133
):
134+
mock_exists.return_value = exists
135+
119136
result = runner.invoke(
120137
[
121138
"dbt",
122139
"deploy",
123140
"TEST_PIPELINE",
124141
f"--source={dbt_project_path}",
142+
"--force",
125143
]
126144
)
127145

128146
assert result.exit_code == 0, result.output
129-
assert (
130-
mock_connect.mocked_ctx.get_query()
131-
== """CREATE DBT PROJECT TEST_PIPELINE
132-
FROM @MockDatabase.MockSchema.dbt_TEST_PIPELINE_stage"""
147+
assert mock_connect.mocked_ctx.get_query().startswith(
148+
"CREATE OR REPLACE DBT PROJECT"
133149
)
134150

135-
@pytest.mark.parametrize("exists", (True, False))
136-
@mock.patch("snowflake.cli._plugins.dbt.manager.StageManager.put_recursive")
137-
@mock.patch("snowflake.cli._plugins.dbt.manager.StageManager.create")
138-
def test_force_flag_uses_create_or_replace(
139-
self,
140-
_mock_create,
141-
_mock_put_recursive,
142-
exists,
143-
mock_connect,
144-
runner,
145-
dbt_project_path,
146-
mock_exists,
151+
def test_raises_when_dbt_project_yml_is_not_available(
152+
self, dbt_project_path, mock_connect, runner
147153
):
148-
mock_exists.return_value = exists
154+
dbt_file = dbt_project_path / "dbt_project.yml"
155+
dbt_file.unlink()
149156

150157
result = runner.invoke(
151158
[
152159
"dbt",
153160
"deploy",
154161
"TEST_PIPELINE",
155162
f"--source={dbt_project_path}",
156-
"--force",
157-
]
163+
],
158164
)
159165

160-
assert result.exit_code == 0, result.output
161-
assert mock_connect.mocked_ctx.get_query().startswith(
162-
"CREATE OR REPLACE DBT PROJECT"
166+
assert result.exit_code == 1, result.output
167+
assert f"dbt_project.yml does not exist in directory" in result.output
168+
assert mock_connect.mocked_ctx.get_query() == ""
169+
170+
def test_raises_when_dbt_project_yml_does_not_specify_profile(
171+
self, dbt_project_path, mock_connect, runner
172+
):
173+
with open((dbt_project_path / "dbt_project.yml"), "w") as f:
174+
yaml.dump({}, f)
175+
176+
result = runner.invoke(
177+
[
178+
"dbt",
179+
"deploy",
180+
"TEST_PIPELINE",
181+
f"--source={dbt_project_path}",
182+
],
163183
)
164184

165-
def test_raises_when_dbt_project_is_not_available(
185+
assert result.exit_code == 1, result.output
186+
assert "`profile` is not defined in dbt_project.yml" in result.output
187+
assert mock_connect.mocked_ctx.get_query() == ""
188+
189+
def test_raises_when_profiles_yml_is_not_available(
166190
self, dbt_project_path, mock_connect, runner
167191
):
168-
dbt_file = dbt_project_path / "dbt_project.yml"
169-
dbt_file.unlink()
192+
(dbt_project_path / "profiles.yml").unlink()
170193

171194
result = runner.invoke(
172195
[
@@ -178,10 +201,29 @@ def test_raises_when_dbt_project_is_not_available(
178201
)
179202

180203
assert result.exit_code == 1, result.output
181-
assert f"dbt_project.yml does not exist in directory" in result.output
204+
assert f"profiles.yml does not exist in directory" in result.output
205+
assert mock_connect.mocked_ctx.get_query() == ""
206+
207+
def test_raises_when_profiles_yml_does_not_contain_selected_profile(
208+
self, dbt_project_path, mock_connect, runner
209+
):
210+
with open((dbt_project_path / "profiles.yml"), "w") as f:
211+
yaml.dump({}, f)
212+
213+
result = runner.invoke(
214+
[
215+
"dbt",
216+
"deploy",
217+
"TEST_PIPELINE",
218+
f"--source={dbt_project_path}",
219+
],
220+
)
221+
222+
assert result.exit_code == 1, result.output
223+
assert "profile dev is not defined in profiles.yml" in result.output
182224
assert mock_connect.mocked_ctx.get_query() == ""
183225

184-
def test_raises_when_dbt_project_exists_and_is_not_force(
226+
def test_raises_when_dbt_object_exists_and_is_not_force(
185227
self, dbt_project_path, mock_connect, runner, mock_exists
186228
):
187229
mock_exists.return_value = True
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
dbt_integration_project:
2+
target: dev
3+
outputs:
4+
dev:
5+
type: snowflake

tests_integration/test_dbt.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,22 @@
1414
import datetime
1515

1616
import pytest
17+
import yaml
1718

1819

1920
@pytest.mark.integration
2021
@pytest.mark.qa_only
21-
def test_dbt_deploy(
22+
def test_dbt(
2223
runner,
2324
snowflake_session,
2425
test_database,
2526
project_directory,
2627
):
27-
with project_directory("dbt_project"):
28+
with project_directory("dbt_project") as root_dir:
2829
# Given a local dbt project
2930
ts = int(datetime.datetime.now().timestamp())
3031
name = f"dbt_project_{ts}"
32+
_setup_dbt_profile(root_dir, snowflake_session)
3133

3234
# When it's deployed
3335
result = runner.invoke_with_connection_json(["dbt", "deploy", name])
@@ -65,3 +67,16 @@ def test_dbt_deploy(
6567
)
6668
assert len(result.json) == 1, result.json
6769
assert result.json[0]["COUNT"] == 1, result.json[0]
70+
71+
72+
def _setup_dbt_profile(root_dir, snowflake_session):
73+
with open((root_dir / "profiles.yml"), "r") as f:
74+
profiles = yaml.safe_load(f)
75+
dev_profile = profiles["dbt_integration_project"]["outputs"]["dev"]
76+
dev_profile["database"] = snowflake_session.database
77+
dev_profile["account"] = snowflake_session.account
78+
dev_profile["user"] = snowflake_session.user
79+
dev_profile["role"] = snowflake_session.role
80+
dev_profile["warehouse"] = snowflake_session.warehouse
81+
dev_profile["schema"] = snowflake_session.schema
82+
(root_dir / "profiles.yml").write_text(yaml.dump(profiles))

0 commit comments

Comments
 (0)