Skip to content

Commit cf32dd1

Browse files
feat: [SNOW-1890085] check if dbt project exists before deploying
1 parent a362b69 commit cf32dd1

File tree

4 files changed

+76
-6
lines changed

4 files changed

+76
-6
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
log = logging.getLogger(__name__)
3737

3838

39-
DBTNameArgument = identifier_argument(sf_object="DBT Object", example="my_pipeline")
39+
DBTNameArgument = identifier_argument(sf_object="DBT Project", example="my_pipeline")
4040

4141

4242
@app.command(

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@
1818

1919
import yaml
2020
from click import ClickException
21+
from snowflake.cli._plugins.object.manager import ObjectManager
2122
from snowflake.cli._plugins.stage.manager import StageManager
2223
from snowflake.cli.api.console import cli_console
23-
from snowflake.cli.api.constants import DEFAULT_SIZE_LIMIT_MB
24+
from snowflake.cli.api.constants import DEFAULT_SIZE_LIMIT_MB, ObjectType
2425
from snowflake.cli.api.identifiers import FQN
2526
from snowflake.cli.api.secure_path import SecurePath
2627
from snowflake.cli.api.sql_execution import SqlExecutionMixin
@@ -32,6 +33,12 @@ def list(self) -> SnowflakeCursor: # noqa: A003
3233
query = "SHOW DBT PROJECTS"
3334
return self.execute_query(query)
3435

36+
@staticmethod
37+
def exists(name: FQN) -> bool:
38+
return ObjectManager().object_exists(
39+
object_type=ObjectType.DBT_PROJECT.value.cli_name, fqn=name
40+
)
41+
3542
def deploy(
3643
self,
3744
path: SecurePath,
@@ -55,6 +62,11 @@ def deploy(
5562
f"dbt-version was not provided and is not available in dbt_project.yml"
5663
)
5764

65+
if self.exists(name=name) and force is not True:
66+
raise ClickException(
67+
f"DBT project {name} already exists. Use --force flag to overwrite"
68+
)
69+
5870
with cli_console.phase("Creating temporary stage"):
5971
stage_manager = StageManager()
6072
stage_fqn = FQN.from_string(f"dbt_{name}_stage").using_context()

src/snowflake/cli/api/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def __str__(self):
3535

3636
class ObjectType(Enum):
3737
COMPUTE_POOL = ObjectNames("compute-pool", "compute pool", "compute pools")
38+
DBT_PROJECT = ObjectNames("dbt-project", "DBT project", "DBT projects")
3839
DATABASE = ObjectNames("database", "database", "databases")
3940
FUNCTION = ObjectNames("function", "function", "functions")
4041
INTEGRATION = ObjectNames("integration", "integration", "integrations")
@@ -79,6 +80,7 @@ def __str__(self):
7980
ObjectType.APPLICATION.value.cli_name,
8081
ObjectType.APPLICATION_PACKAGE.value.cli_name,
8182
ObjectType.PROJECT.value.cli_name,
83+
ObjectType.DBT_PROJECT.value.cli_name,
8284
}
8385
SUPPORTED_OBJECTS = sorted(OBJECT_TO_NAMES.keys() - UNSUPPORTED_OBJECTS)
8486

tests/dbt/test_dbt_commands.py

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,23 @@ def mock_cli_console(self):
5555
with mock.patch("snowflake.cli.api.console") as _fixture:
5656
yield _fixture
5757

58+
@pytest.fixture
59+
def mock_exists(self):
60+
with mock.patch(
61+
"snowflake.cli._plugins.dbt.manager.DBTManager.exists", return_value=False
62+
) as _fixture:
63+
yield _fixture
64+
5865
@mock.patch("snowflake.cli._plugins.dbt.manager.StageManager.put_recursive")
5966
@mock.patch("snowflake.cli._plugins.dbt.manager.StageManager.create")
6067
def test_deploys_project_from_source(
61-
self, mock_create, mock_put_recursive, mock_connect, runner, dbt_project_path
68+
self,
69+
mock_create,
70+
mock_put_recursive,
71+
mock_connect,
72+
runner,
73+
dbt_project_path,
74+
mock_exists,
6275
):
6376

6477
result = runner.invoke(
@@ -88,7 +101,13 @@ def test_deploys_project_from_source(
88101
@mock.patch("snowflake.cli._plugins.dbt.manager.StageManager.put_recursive")
89102
@mock.patch("snowflake.cli._plugins.dbt.manager.StageManager.create")
90103
def test_dbt_version_from_option_has_precedence_over_file(
91-
self, _mock_create, _mock_put_recursive, mock_connect, runner, dbt_project_path
104+
self,
105+
_mock_create,
106+
_mock_put_recursive,
107+
mock_connect,
108+
runner,
109+
dbt_project_path,
110+
mock_exists,
92111
):
93112
result = runner.invoke(
94113
[
@@ -110,11 +129,21 @@ def test_dbt_version_from_option_has_precedence_over_file(
110129
DBT_ADAPTER_VERSION='3.4.5'"""
111130
)
112131

132+
@pytest.mark.parametrize("exists", (True, False))
113133
@mock.patch("snowflake.cli._plugins.dbt.manager.StageManager.put_recursive")
114134
@mock.patch("snowflake.cli._plugins.dbt.manager.StageManager.create")
115135
def test_force_flag_uses_create_or_replace(
116-
self, _mock_create, _mock_put_recursive, mock_connect, runner, dbt_project_path
136+
self,
137+
_mock_create,
138+
_mock_put_recursive,
139+
exists,
140+
mock_connect,
141+
runner,
142+
dbt_project_path,
143+
mock_exists,
117144
):
145+
mock_exists.return_value = exists
146+
118147
result = runner.invoke(
119148
[
120149
"dbt",
@@ -134,7 +163,13 @@ def test_force_flag_uses_create_or_replace(
134163
@mock.patch("snowflake.cli._plugins.dbt.manager.StageManager.put_recursive")
135164
@mock.patch("snowflake.cli._plugins.dbt.manager.StageManager.create")
136165
def test_execute_in_warehouse(
137-
self, _mock_create, _mock_put_recursive, mock_connect, runner, dbt_project_path
166+
self,
167+
_mock_create,
168+
_mock_put_recursive,
169+
mock_connect,
170+
runner,
171+
dbt_project_path,
172+
mock_exists,
138173
):
139174

140175
result = runner.invoke(
@@ -201,6 +236,27 @@ def test_raises_when_dbt_project_version_is_not_specified(
201236
)
202237
assert mock_connect.mocked_ctx.get_query() == ""
203238

239+
def test_raises_when_dbt_project_exists_and_is_not_force(
240+
self, dbt_project_path, mock_connect, runner, mock_exists
241+
):
242+
mock_exists.return_value = True
243+
244+
result = runner.invoke(
245+
[
246+
"dbt",
247+
"deploy",
248+
"TEST_PIPELINE",
249+
f"--source={dbt_project_path}",
250+
],
251+
)
252+
253+
assert result.exit_code == 1, result.output
254+
assert (
255+
"DBT project TEST_PIPELINE already exists. Use --force flag to overwrite"
256+
in result.output
257+
)
258+
assert mock_connect.mocked_ctx.get_query() == ""
259+
204260

205261
class TestDBTExecute:
206262
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)