Skip to content

Commit d6f1468

Browse files
Jw/snow 2111885 pupr readiness (#2318)
* feat: [SNOW-1890085] add dbt command group; add dbt list command * feat: [SNOW-1890085] add dbt execute command * refactor: [SNOW-1890085] reimplement dbt execute as pass-through command group * feat: [SNOW-1890085] implement dbt deploy command * feat: [SNOW-1890085] implement StdoutExecutionMixin for demo purposes * fix: [SNOW-1890085] rename sql object DBT -> DBT PROJECT * feat: [SNOW-1890085] dbt deploy: add support for dbt-version flag * feat: [SNOW-1890085] dbt deploy: add support for dbt-adapter-version flag * feat: [SNOW-1890085] dbt deploy: provide main file param * feat: [SNOW-1890085] dbt deploy: use --force to create or replace * feat: [SNOW-1890085] dbt deploy: add execute_in_warehouse flag * refactor: [SNOW-1890085] hide dbt app behind feature flag * feat: [SNOW-1966187] update commands according to backend implementation * feat: [SNOW-1966187] remove StdoutExecutionMixin * feat: [SNOW-1890085] update command help texts * feat: [SNOW-1890085] add run_async option * feat: [SNOW-1890085] use SecurePath * feat: [SNOW-1890085] check if dbt project exists before deploying * refactor: [SNOW-1890085] use object plugin for listing dbt projects * refactor: [SNOW-1890085] set dbt execute subcommand metavar * feat: [SNOW-1890085] remove execute_in_warehouse option * feat: [SNOW-1890085] add spinner to dbt execute * feat: [SNOW-1890085] limit supported dbt commands * feat: [SNOW-1890085] remove options for dbt_version and dbt_adapter_version * fix: [SNOW-1890085] restore lost changes during rebasing * test: [SNOW-1890085] unlock dbt help snapshots * chore: [SNOW-1890085] cleanup * test: [SNOW-1890085] add integration test for simple dbt workflow * chore: [SNOW-1890085] rename feature flag * refactor: [SNOW-1890085] post code review fixes * feat: [SNOW-1890085] wip: capture success code from server * feat: [SNOW-1890085] process data from server * chore: [SNOW-1890085] update snapshots * Jw/snow 2036324 include dbt profile (#2196) * feat: [SNOW-2036324] validate dbt profile * refactor: [SNOW-2036324] PR fixes * Jw/snow 2045471 add flag for selecting profile directory (#2210) feat: [SNOW-2045471] add --profile-dir flag and more strict validations for profiles.yml * feat: [SNOW-2048251] enable dbt run-operation (#2261) * feat: [SNOW-2102782] support threads in profiles.yaml (#2291) * feat: [SNOW-2103792] support altering existing dbt objects in deploy command (#2294) * feat: [SNOW-2103792] support altering existing dbt objects in deploy command * refactor: [SNOW-2103792] tiny reformat * chore: [SNOW-2111885] update snapshots * cleanup: [SNOW-2111885] remove old feature flag * feat: [SNOW-2111885] CR fixes
1 parent 5abff9b commit d6f1468

File tree

25 files changed

+3545
-7
lines changed

25 files changed

+3545
-7
lines changed

src/snowflake/cli/_app/commands_registration/builtin_plugins.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from snowflake.cli._plugins.auth.keypair import plugin_spec as auth_plugin_spec
1616
from snowflake.cli._plugins.connection import plugin_spec as connection_plugin_spec
1717
from snowflake.cli._plugins.cortex import plugin_spec as cortex_plugin_spec
18+
from snowflake.cli._plugins.dbt import plugin_spec as dbt_plugin_spec
1819
from snowflake.cli._plugins.git import plugin_spec as git_plugin_spec
1920
from snowflake.cli._plugins.helpers import plugin_spec as migrate_plugin_spec
2021
from snowflake.cli._plugins.init import plugin_spec as init_plugin_spec
@@ -52,6 +53,7 @@ def get_builtin_plugin_name_to_plugin_spec():
5253
"init": init_plugin_spec,
5354
"workspace": workspace_plugin_spec,
5455
"plugin": plugin_plugin_spec,
56+
"dbt": dbt_plugin_spec,
5557
"logs": logs_plugin_spec,
5658
}
5759

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Copyright (c) 2025 Snowflake Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
# Copyright (c) 2025 Snowflake Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from __future__ import annotations
16+
17+
import logging
18+
from typing import Optional
19+
20+
import typer
21+
from click import types
22+
from rich.progress import Progress, SpinnerColumn, TextColumn
23+
from snowflake.cli._plugins.dbt.constants import (
24+
DBT_COMMANDS,
25+
OUTPUT_COLUMN_NAME,
26+
RESULT_COLUMN_NAME,
27+
)
28+
from snowflake.cli._plugins.dbt.manager import DBTManager
29+
from snowflake.cli._plugins.object.command_aliases import add_object_command_aliases
30+
from snowflake.cli._plugins.object.commands import scope_option
31+
from snowflake.cli.api.commands.decorators import global_options_with_connection
32+
from snowflake.cli.api.commands.flags import identifier_argument, like_option
33+
from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
34+
from snowflake.cli.api.constants import ObjectType
35+
from snowflake.cli.api.exceptions import CliError
36+
from snowflake.cli.api.feature_flags import FeatureFlag
37+
from snowflake.cli.api.identifiers import FQN
38+
from snowflake.cli.api.output.types import (
39+
CommandResult,
40+
MessageResult,
41+
QueryResult,
42+
)
43+
from snowflake.cli.api.secure_path import SecurePath
44+
45+
app = SnowTyperFactory(
46+
name="dbt",
47+
help="Manages dbt on Snowflake projects",
48+
is_hidden=FeatureFlag.ENABLE_DBT.is_disabled,
49+
)
50+
log = logging.getLogger(__name__)
51+
52+
53+
DBTNameArgument = identifier_argument(sf_object="DBT Project", example="my_pipeline")
54+
55+
# in passthrough commands we need to support that user would either provide the name of dbt object or name of dbt
56+
# command, in which case FQN validation could fail
57+
DBTNameOrCommandArgument = identifier_argument(
58+
sf_object="DBT Project", example="my_pipeline", click_type=types.StringParamType()
59+
)
60+
61+
add_object_command_aliases(
62+
app=app,
63+
object_type=ObjectType.DBT_PROJECT,
64+
name_argument=DBTNameArgument,
65+
like_option=like_option(
66+
help_example='`list --like "my%"` lists all dbt projects that begin with “my”'
67+
),
68+
scope_option=scope_option(help_example="`list --in database my_db`"),
69+
ommit_commands=["drop", "create", "describe"],
70+
)
71+
72+
73+
@app.command(
74+
"deploy",
75+
requires_connection=True,
76+
)
77+
def deploy_dbt(
78+
name: FQN = DBTNameArgument,
79+
source: Optional[str] = typer.Option(
80+
help="Path to directory containing dbt files to deploy. Defaults to current working directory.",
81+
show_default=False,
82+
default=None,
83+
),
84+
profiles_dir: Optional[str] = typer.Option(
85+
help="Path to directory containing profiles.yml. Defaults to directory provided in --source or current working directory",
86+
show_default=False,
87+
default=None,
88+
),
89+
force: Optional[bool] = typer.Option(
90+
False,
91+
help="Overwrites conflicting files in the project, if any.",
92+
),
93+
**options,
94+
) -> CommandResult:
95+
"""
96+
Copy dbt files and either recreate dbt on Snowflake if `--force` flag is
97+
provided; or create a new one if it doesn't exist; or update files and
98+
create a new version if it exists.
99+
"""
100+
project_path = SecurePath(source) if source is not None else SecurePath.cwd()
101+
profiles_dir_path = SecurePath(profiles_dir) if profiles_dir else project_path
102+
return QueryResult(
103+
DBTManager().deploy(
104+
name,
105+
project_path.resolve(),
106+
profiles_dir_path.resolve(),
107+
force=force,
108+
)
109+
)
110+
111+
112+
dbt_execute_app = SnowTyperFactory(
113+
name="execute",
114+
help="Execute a dbt command on Snowflake. Subcommand name and all "
115+
"parameters following it will be passed over to dbt.",
116+
subcommand_metavar="DBT_COMMAND",
117+
)
118+
app.add_typer(dbt_execute_app)
119+
120+
121+
@dbt_execute_app.callback()
122+
@global_options_with_connection
123+
def before_callback(
124+
name: str = DBTNameOrCommandArgument,
125+
run_async: Optional[bool] = typer.Option(
126+
False, help="Run dbt command asynchronously and check it's result later."
127+
),
128+
**options,
129+
):
130+
"""Handles global options passed before the command and takes pipeline name to be accessed through child context later"""
131+
pass
132+
133+
134+
for cmd in DBT_COMMANDS:
135+
136+
@dbt_execute_app.command(
137+
name=cmd,
138+
requires_connection=False,
139+
requires_global_options=False,
140+
context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
141+
help=f"Execute {cmd} command on Snowflake. Command name and all parameters following it will be passed over to dbt.",
142+
add_help_option=False,
143+
)
144+
def _dbt_execute(
145+
ctx: typer.Context,
146+
) -> CommandResult:
147+
dbt_cli_args = ctx.args
148+
dbt_command = ctx.command.name
149+
name = FQN.from_string(ctx.parent.params["name"])
150+
run_async = ctx.parent.params["run_async"]
151+
execute_args = (dbt_command, name, run_async, *dbt_cli_args)
152+
dbt_manager = DBTManager()
153+
154+
if run_async is True:
155+
result = dbt_manager.execute(*execute_args)
156+
return MessageResult(
157+
f"Command submitted. You can check the result with `snow sql -q \"select execution_status from table(information_schema.query_history_by_user()) where query_id in ('{result.sfqid}');\"`"
158+
)
159+
160+
with Progress(
161+
SpinnerColumn(),
162+
TextColumn("[progress.description]{task.description}"),
163+
transient=True,
164+
) as progress:
165+
progress.add_task(description=f"Executing 'dbt {dbt_command}'", total=None)
166+
167+
result = dbt_manager.execute(*execute_args)
168+
169+
try:
170+
columns = [column.name for column in result.description]
171+
success_column_index = columns.index(RESULT_COLUMN_NAME)
172+
stdout_column_index = columns.index(OUTPUT_COLUMN_NAME)
173+
except ValueError:
174+
raise CliError("Malformed server response")
175+
try:
176+
is_success, output = [
177+
(row[success_column_index], row[stdout_column_index])
178+
for row in result
179+
][-1]
180+
except IndexError:
181+
raise CliError("No data returned from server")
182+
183+
if is_success is True:
184+
return MessageResult(output)
185+
else:
186+
raise CliError(output)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Copyright (c) 2025 Snowflake Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
RESULT_COLUMN_NAME = "SUCCESS"
16+
OUTPUT_COLUMN_NAME = "STDOUT"
17+
18+
DBT_COMMANDS = [
19+
"build",
20+
"compile",
21+
"deps",
22+
"list",
23+
"parse",
24+
"run",
25+
"run-operation",
26+
"seed",
27+
"show",
28+
"snapshot",
29+
"test",
30+
]
31+
32+
UNSUPPORTED_COMMANDS = [
33+
"clean",
34+
"clone",
35+
"debug",
36+
"docs",
37+
"init",
38+
"retry",
39+
"source",
40+
]

0 commit comments

Comments
 (0)