snowflake-cli 3.14.0__py3-none-any.whl → 3.15.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- snowflake/cli/__about__.py +1 -1
- snowflake/cli/_app/dev/docs/project_definition_generate_json_schema.py +2 -2
- snowflake/cli/_app/printing.py +14 -12
- snowflake/cli/_app/snow_connector.py +59 -9
- snowflake/cli/_plugins/dbt/commands.py +37 -7
- snowflake/cli/_plugins/dbt/manager.py +81 -53
- snowflake/cli/_plugins/dcm/commands.py +38 -0
- snowflake/cli/_plugins/dcm/manager.py +8 -0
- snowflake/cli/_plugins/dcm/reporters.py +462 -0
- snowflake/cli/_plugins/dcm/styles.py +26 -0
- snowflake/cli/_plugins/dcm/utils.py +88 -0
- snowflake/cli/_plugins/git/manager.py +24 -22
- snowflake/cli/_plugins/object/command_aliases.py +7 -1
- snowflake/cli/_plugins/object/commands.py +12 -2
- snowflake/cli/_plugins/object/manager.py +7 -2
- snowflake/cli/_plugins/snowpark/commands.py +8 -1
- snowflake/cli/_plugins/streamlit/streamlit_entity.py +8 -1
- snowflake/cli/api/commands/decorators.py +1 -1
- snowflake/cli/api/commands/flags.py +30 -5
- snowflake/cli/api/console/abc.py +7 -3
- snowflake/cli/api/console/console.py +10 -3
- snowflake/cli/api/exceptions.py +1 -1
- snowflake/cli/api/feature_flags.py +1 -0
- snowflake/cli/api/output/types.py +6 -0
- snowflake/cli/api/utils/types.py +20 -1
- {snowflake_cli-3.14.0.dist-info → snowflake_cli-3.15.0.dist-info}/METADATA +9 -4
- {snowflake_cli-3.14.0.dist-info → snowflake_cli-3.15.0.dist-info}/RECORD +30 -27
- {snowflake_cli-3.14.0.dist-info → snowflake_cli-3.15.0.dist-info}/WHEEL +0 -0
- {snowflake_cli-3.14.0.dist-info → snowflake_cli-3.15.0.dist-info}/entry_points.txt +0 -0
- {snowflake_cli-3.14.0.dist-info → snowflake_cli-3.15.0.dist-info}/licenses/LICENSE +0 -0
snowflake/cli/__about__.py
CHANGED
|
@@ -38,8 +38,8 @@ class ProjectDefinitionProperty:
|
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
class ProjectDefinitionGenerateJsonSchema(GenerateJsonSchema):
|
|
41
|
-
def __init__(self, by_alias: bool = False, ref_template: str = ""):
|
|
42
|
-
super().__init__(by_alias, "{model}")
|
|
41
|
+
def __init__(self, by_alias: bool = False, ref_template: str = "", **kwargs):
|
|
42
|
+
super().__init__(by_alias, "{model}", **kwargs)
|
|
43
43
|
self._remapped_definitions: Dict[str, Any] = {}
|
|
44
44
|
|
|
45
45
|
def generate(self, schema, mode="validation"):
|
snowflake/cli/_app/printing.py
CHANGED
|
@@ -33,6 +33,7 @@ from snowflake.cli.api.output.formats import OutputFormat
|
|
|
33
33
|
from snowflake.cli.api.output.types import (
|
|
34
34
|
CollectionResult,
|
|
35
35
|
CommandResult,
|
|
36
|
+
EmptyResult,
|
|
36
37
|
MessageResult,
|
|
37
38
|
MultipleResults,
|
|
38
39
|
ObjectResult,
|
|
@@ -341,15 +342,16 @@ def _print_single_table(obj):
|
|
|
341
342
|
|
|
342
343
|
def print_result(cmd_result: CommandResult, output_format: OutputFormat | None = None):
|
|
343
344
|
output_format = output_format or _get_format_type()
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
345
|
+
|
|
346
|
+
match cmd_result:
|
|
347
|
+
case EmptyResult():
|
|
348
|
+
return
|
|
349
|
+
case _ if is_structured_format(output_format):
|
|
350
|
+
print_structured(cmd_result, output_format)
|
|
351
|
+
case MultipleResults() | StreamResult():
|
|
352
|
+
for res in cmd_result.result:
|
|
353
|
+
print_result(res)
|
|
354
|
+
case MessageResult() | ObjectResult() | CollectionResult() | None:
|
|
355
|
+
print_unstructured(cmd_result)
|
|
356
|
+
case _:
|
|
357
|
+
raise ValueError(f"Unexpected type {type(cmd_result)}")
|
|
@@ -15,9 +15,11 @@
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
17
|
import contextlib
|
|
18
|
+
import io
|
|
18
19
|
import logging
|
|
19
20
|
import os
|
|
20
|
-
|
|
21
|
+
import sys
|
|
22
|
+
from typing import Dict, Literal, Optional, TextIO
|
|
21
23
|
|
|
22
24
|
import snowflake.connector
|
|
23
25
|
from click.exceptions import ClickException
|
|
@@ -29,9 +31,7 @@ from snowflake.cli._app.constants import (
|
|
|
29
31
|
PARAM_APPLICATION_NAME,
|
|
30
32
|
)
|
|
31
33
|
from snowflake.cli._app.telemetry import command_info
|
|
32
|
-
from snowflake.cli._plugins.auth.oidc.manager import
|
|
33
|
-
OidcManager,
|
|
34
|
-
)
|
|
34
|
+
from snowflake.cli._plugins.auth.oidc.manager import OidcManager
|
|
35
35
|
from snowflake.cli.api.config import (
|
|
36
36
|
get_connection_dict,
|
|
37
37
|
get_env_value,
|
|
@@ -53,6 +53,8 @@ log = logging.getLogger(__name__)
|
|
|
53
53
|
|
|
54
54
|
ENCRYPTED_PKCS8_PK_HEADER = b"-----BEGIN ENCRYPTED PRIVATE KEY-----"
|
|
55
55
|
UNENCRYPTED_PKCS8_PK_HEADER = b"-----BEGIN PRIVATE KEY-----"
|
|
56
|
+
AUTHENTICATOR_EXTERNAL_BROWSER: Literal["externalbrowser"] = "externalbrowser"
|
|
57
|
+
AUTHENTICATOR_PARAM: Literal["authenticator"] = "authenticator"
|
|
56
58
|
|
|
57
59
|
# connection keys that can be set using SNOWFLAKE_* env vars
|
|
58
60
|
SUPPORTED_ENV_OVERRIDES = [
|
|
@@ -88,6 +90,32 @@ SUPPORTED_ENV_OVERRIDES = [
|
|
|
88
90
|
CONNECTION_KEY_ALIASES = {"private_key_path": "private_key_file"}
|
|
89
91
|
|
|
90
92
|
|
|
93
|
+
class _BufferedMirrorStream(io.StringIO):
|
|
94
|
+
"""Buffer connector chatter while optionally mirroring to another stream."""
|
|
95
|
+
|
|
96
|
+
def __init__(self, mirror: Optional[TextIO] = None) -> None:
|
|
97
|
+
super().__init__()
|
|
98
|
+
self._mirror = mirror
|
|
99
|
+
|
|
100
|
+
def write(self, text: str) -> int:
|
|
101
|
+
text = text or ""
|
|
102
|
+
written = super().write(text)
|
|
103
|
+
if self._mirror is not None:
|
|
104
|
+
self._mirror.write(text)
|
|
105
|
+
self._mirror.flush()
|
|
106
|
+
return written
|
|
107
|
+
|
|
108
|
+
def flush(self) -> None:
|
|
109
|
+
super().flush()
|
|
110
|
+
if self._mirror is not None:
|
|
111
|
+
self._mirror.flush()
|
|
112
|
+
|
|
113
|
+
def isatty(self) -> bool:
|
|
114
|
+
if self._mirror is not None and hasattr(self._mirror, "isatty"):
|
|
115
|
+
return bool(self._mirror.isatty())
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
|
|
91
119
|
def _resolve_alias(key_or_alias: str):
|
|
92
120
|
"""
|
|
93
121
|
Given the key of an override / env var, what key should it be set as in the connection parameters?
|
|
@@ -95,6 +123,24 @@ def _resolve_alias(key_or_alias: str):
|
|
|
95
123
|
return CONNECTION_KEY_ALIASES.get(key_or_alias, key_or_alias)
|
|
96
124
|
|
|
97
125
|
|
|
126
|
+
def _build_silent_streams(
|
|
127
|
+
connection_parameters: Dict,
|
|
128
|
+
) -> tuple[_BufferedMirrorStream, _BufferedMirrorStream]:
|
|
129
|
+
"""
|
|
130
|
+
Build stdout/stderr silent streams.
|
|
131
|
+
|
|
132
|
+
We must provide a writable stdout for authenticators (notably externalbrowser)
|
|
133
|
+
that prompt via input(); redirecting to None breaks sys.stdout.
|
|
134
|
+
For externalbrowser we mirror stdout to stderr so prompts remain visible
|
|
135
|
+
without polluting structured stdout (e.g., JSON output).
|
|
136
|
+
"""
|
|
137
|
+
authenticator = str(connection_parameters.get(AUTHENTICATOR_PARAM, "")).lower()
|
|
138
|
+
mirror_stdout: Optional[TextIO] = (
|
|
139
|
+
sys.stderr if authenticator == AUTHENTICATOR_EXTERNAL_BROWSER else None
|
|
140
|
+
)
|
|
141
|
+
return _BufferedMirrorStream(mirror_stdout), _BufferedMirrorStream()
|
|
142
|
+
|
|
143
|
+
|
|
98
144
|
def connect_to_snowflake(
|
|
99
145
|
temporary_connection: bool = False,
|
|
100
146
|
mfa_passcode: Optional[str] = None,
|
|
@@ -186,12 +232,16 @@ def connect_to_snowflake(
|
|
|
186
232
|
|
|
187
233
|
_update_internal_application_info(connection_parameters)
|
|
188
234
|
|
|
235
|
+
silent_stdout, silent_stderr = _build_silent_streams(connection_parameters)
|
|
236
|
+
|
|
189
237
|
try:
|
|
190
|
-
#
|
|
191
|
-
#
|
|
192
|
-
#
|
|
193
|
-
|
|
194
|
-
|
|
238
|
+
# The output is redirected to silent stream for reuse
|
|
239
|
+
# in cases like externalbrowser auth not to pollute output
|
|
240
|
+
# in formats like JSON
|
|
241
|
+
with (
|
|
242
|
+
contextlib.redirect_stdout(silent_stdout),
|
|
243
|
+
contextlib.redirect_stderr(silent_stderr),
|
|
244
|
+
):
|
|
195
245
|
return snowflake.connector.connect(
|
|
196
246
|
application=command_info(),
|
|
197
247
|
**connection_parameters,
|
|
@@ -25,7 +25,11 @@ from snowflake.cli._plugins.dbt.constants import (
|
|
|
25
25
|
PROFILES_FILENAME,
|
|
26
26
|
RESULT_COLUMN_NAME,
|
|
27
27
|
)
|
|
28
|
-
from snowflake.cli._plugins.dbt.manager import
|
|
28
|
+
from snowflake.cli._plugins.dbt.manager import (
|
|
29
|
+
DBTDeployAttributes,
|
|
30
|
+
DBTManager,
|
|
31
|
+
SemanticVersionType,
|
|
32
|
+
)
|
|
29
33
|
from snowflake.cli._plugins.object.command_aliases import add_object_command_aliases
|
|
30
34
|
from snowflake.cli._plugins.object.commands import scope_option
|
|
31
35
|
from snowflake.cli.api.commands.decorators import global_options_with_connection
|
|
@@ -35,6 +39,7 @@ from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
|
|
|
35
39
|
from snowflake.cli.api.console.console import cli_console
|
|
36
40
|
from snowflake.cli.api.constants import ObjectType
|
|
37
41
|
from snowflake.cli.api.exceptions import CliError
|
|
42
|
+
from snowflake.cli.api.feature_flags import FeatureFlag
|
|
38
43
|
from snowflake.cli.api.identifiers import FQN
|
|
39
44
|
from snowflake.cli.api.output.types import (
|
|
40
45
|
CommandResult,
|
|
@@ -118,6 +123,14 @@ def deploy_dbt(
|
|
|
118
123
|
show_default=False,
|
|
119
124
|
help="Installs local dependencies from project that don't require external access.",
|
|
120
125
|
),
|
|
126
|
+
dbt_version: Optional[str] = typer.Option(
|
|
127
|
+
None,
|
|
128
|
+
"--dbt-version",
|
|
129
|
+
click_type=SemanticVersionType(),
|
|
130
|
+
show_default=False,
|
|
131
|
+
hidden=not FeatureFlag.ENABLE_DBT_VERSION.is_enabled(),
|
|
132
|
+
help="dbt version to use for the project, for example '1.9.4'.",
|
|
133
|
+
),
|
|
121
134
|
**options,
|
|
122
135
|
) -> CommandResult:
|
|
123
136
|
"""
|
|
@@ -127,18 +140,24 @@ def deploy_dbt(
|
|
|
127
140
|
snow dbt deploy PROJECT
|
|
128
141
|
snow dbt deploy PROJECT --source=/Users/jdoe/project --force
|
|
129
142
|
"""
|
|
143
|
+
if not FeatureFlag.ENABLE_DBT_VERSION.is_enabled():
|
|
144
|
+
dbt_version = None
|
|
130
145
|
project_path = SecurePath(source) if source is not None else SecurePath.cwd()
|
|
131
146
|
profiles_dir_path = SecurePath(profiles_dir) if profiles_dir else project_path
|
|
147
|
+
attrs = DBTDeployAttributes(
|
|
148
|
+
default_target=default_target,
|
|
149
|
+
unset_default_target=unset_default_target,
|
|
150
|
+
external_access_integrations=external_access_integrations,
|
|
151
|
+
install_local_deps=install_local_deps,
|
|
152
|
+
dbt_version=dbt_version,
|
|
153
|
+
)
|
|
132
154
|
return QueryResult(
|
|
133
155
|
DBTManager().deploy(
|
|
134
156
|
name,
|
|
135
157
|
path=project_path.resolve(),
|
|
136
158
|
profiles_path=profiles_dir_path.resolve(),
|
|
137
159
|
force=force,
|
|
138
|
-
|
|
139
|
-
unset_default_target=unset_default_target,
|
|
140
|
-
external_access_integrations=external_access_integrations,
|
|
141
|
-
install_local_deps=install_local_deps,
|
|
160
|
+
attrs=attrs,
|
|
142
161
|
)
|
|
143
162
|
)
|
|
144
163
|
|
|
@@ -159,9 +178,17 @@ def before_callback(
|
|
|
159
178
|
run_async: Optional[bool] = typer.Option(
|
|
160
179
|
False, help="Run dbt command asynchronously and check it's result later."
|
|
161
180
|
),
|
|
181
|
+
dbt_version: Optional[str] = typer.Option(
|
|
182
|
+
None,
|
|
183
|
+
"--dbt-version",
|
|
184
|
+
click_type=SemanticVersionType(),
|
|
185
|
+
show_default=False,
|
|
186
|
+
hidden=not FeatureFlag.ENABLE_DBT_VERSION.is_enabled(),
|
|
187
|
+
help="dbt version to use for execution (ephemeral, does not change project configuration).",
|
|
188
|
+
),
|
|
162
189
|
**options,
|
|
163
190
|
):
|
|
164
|
-
"""Handles global options passed before the command and takes pipeline name to be accessed through child context later"""
|
|
191
|
+
"""Handles global options passed before the command and takes pipeline name to be accessed through child context later."""
|
|
165
192
|
pass
|
|
166
193
|
|
|
167
194
|
|
|
@@ -182,7 +209,10 @@ for cmd in DBT_COMMANDS:
|
|
|
182
209
|
dbt_command = ctx.command.name
|
|
183
210
|
name = FQN.from_string(ctx.parent.params["name"])
|
|
184
211
|
run_async = ctx.parent.params["run_async"]
|
|
185
|
-
|
|
212
|
+
dbt_version = ctx.parent.params.get("dbt_version")
|
|
213
|
+
if not FeatureFlag.ENABLE_DBT_VERSION.is_enabled():
|
|
214
|
+
dbt_version = None
|
|
215
|
+
execute_args = (dbt_command, name, run_async, dbt_version, *dbt_cli_args)
|
|
186
216
|
dbt_manager = DBTManager()
|
|
187
217
|
|
|
188
218
|
if run_async is True:
|
|
@@ -14,11 +14,14 @@
|
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
+
import re
|
|
17
18
|
from collections import defaultdict
|
|
19
|
+
from dataclasses import dataclass
|
|
18
20
|
from pathlib import Path
|
|
19
21
|
from tempfile import TemporaryDirectory
|
|
20
22
|
from typing import Dict, List, Optional, TypedDict
|
|
21
23
|
|
|
24
|
+
import click
|
|
22
25
|
import yaml
|
|
23
26
|
from snowflake.cli._plugins.dbt.constants import PROFILES_FILENAME
|
|
24
27
|
from snowflake.cli._plugins.object.manager import ObjectManager
|
|
@@ -32,10 +35,43 @@ from snowflake.cli.api.sql_execution import SqlExecutionMixin
|
|
|
32
35
|
from snowflake.connector.cursor import SnowflakeCursor
|
|
33
36
|
from snowflake.connector.errors import ProgrammingError
|
|
34
37
|
|
|
38
|
+
SEMANTIC_VERSION_PATTERN = re.compile(r"^\d+\.\d+\.\d+$")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class SemanticVersionType(click.ParamType):
|
|
42
|
+
"""Custom Click type that validates semantic version format (major.minor.patch)."""
|
|
43
|
+
|
|
44
|
+
name = "VERSION"
|
|
45
|
+
|
|
46
|
+
def convert(self, value, param, ctx):
|
|
47
|
+
if value is None:
|
|
48
|
+
return None
|
|
49
|
+
if not isinstance(value, str):
|
|
50
|
+
self.fail(f"Expected string, got {type(value).__name__}.", param, ctx)
|
|
51
|
+
if not SEMANTIC_VERSION_PATTERN.match(value):
|
|
52
|
+
self.fail(
|
|
53
|
+
f"Invalid version format '{value}'. Expected format: major.minor.patch (e.g., '1.9.4').",
|
|
54
|
+
param,
|
|
55
|
+
ctx,
|
|
56
|
+
)
|
|
57
|
+
return value
|
|
58
|
+
|
|
35
59
|
|
|
36
60
|
class DBTObjectEditableAttributes(TypedDict):
|
|
37
61
|
default_target: Optional[str]
|
|
38
62
|
external_access_integrations: Optional[List[str]]
|
|
63
|
+
dbt_version: Optional[str]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class DBTDeployAttributes:
|
|
68
|
+
"""Attributes for deploying a DBT project."""
|
|
69
|
+
|
|
70
|
+
default_target: Optional[str] = None
|
|
71
|
+
unset_default_target: bool = False
|
|
72
|
+
external_access_integrations: Optional[List[str]] = None
|
|
73
|
+
install_local_deps: bool = False
|
|
74
|
+
dbt_version: Optional[str] = None
|
|
39
75
|
|
|
40
76
|
|
|
41
77
|
class DBTManager(SqlExecutionMixin):
|
|
@@ -90,6 +126,7 @@ class DBTManager(SqlExecutionMixin):
|
|
|
90
126
|
return DBTObjectEditableAttributes(
|
|
91
127
|
default_target=row_dict.get("default_target"),
|
|
92
128
|
external_access_integrations=external_access_integrations,
|
|
129
|
+
dbt_version=row_dict.get("dbt_version"),
|
|
93
130
|
)
|
|
94
131
|
|
|
95
132
|
def deploy(
|
|
@@ -98,10 +135,7 @@ class DBTManager(SqlExecutionMixin):
|
|
|
98
135
|
path: SecurePath,
|
|
99
136
|
profiles_path: SecurePath,
|
|
100
137
|
force: bool,
|
|
101
|
-
|
|
102
|
-
unset_default_target: bool = False,
|
|
103
|
-
external_access_integrations: Optional[List[str]] = None,
|
|
104
|
-
install_local_deps: bool = False,
|
|
138
|
+
attrs: DBTDeployAttributes,
|
|
105
139
|
) -> SnowflakeCursor:
|
|
106
140
|
dbt_project_path = path / "dbt_project.yml"
|
|
107
141
|
if not dbt_project_path.exists():
|
|
@@ -116,7 +150,7 @@ class DBTManager(SqlExecutionMixin):
|
|
|
116
150
|
except KeyError:
|
|
117
151
|
raise CliError("`profile` is not defined in dbt_project.yml")
|
|
118
152
|
|
|
119
|
-
self._validate_profiles(profiles_path, profile, default_target)
|
|
153
|
+
self._validate_profiles(profiles_path, profile, attrs.default_target)
|
|
120
154
|
|
|
121
155
|
with cli_console.phase("Creating temporary stage"):
|
|
122
156
|
stage_manager = StageManager()
|
|
@@ -140,43 +174,22 @@ class DBTManager(SqlExecutionMixin):
|
|
|
140
174
|
|
|
141
175
|
with cli_console.phase("Creating DBT project"):
|
|
142
176
|
if force is True:
|
|
143
|
-
return self._deploy_create_or_replace(
|
|
144
|
-
fqn,
|
|
145
|
-
stage_name,
|
|
146
|
-
default_target,
|
|
147
|
-
external_access_integrations,
|
|
148
|
-
install_local_deps,
|
|
149
|
-
)
|
|
177
|
+
return self._deploy_create_or_replace(fqn, stage_name, attrs)
|
|
150
178
|
else:
|
|
151
179
|
dbt_object_attributes = self.get_dbt_object_attributes(fqn)
|
|
152
180
|
if dbt_object_attributes is not None:
|
|
153
181
|
return self._deploy_alter(
|
|
154
|
-
fqn,
|
|
155
|
-
stage_name,
|
|
156
|
-
dbt_object_attributes,
|
|
157
|
-
default_target,
|
|
158
|
-
unset_default_target,
|
|
159
|
-
external_access_integrations,
|
|
160
|
-
install_local_deps,
|
|
182
|
+
fqn, stage_name, dbt_object_attributes, attrs
|
|
161
183
|
)
|
|
162
184
|
else:
|
|
163
|
-
return self._deploy_create(
|
|
164
|
-
fqn,
|
|
165
|
-
stage_name,
|
|
166
|
-
default_target,
|
|
167
|
-
external_access_integrations,
|
|
168
|
-
install_local_deps,
|
|
169
|
-
)
|
|
185
|
+
return self._deploy_create(fqn, stage_name, attrs)
|
|
170
186
|
|
|
171
187
|
def _deploy_alter(
|
|
172
188
|
self,
|
|
173
189
|
fqn: FQN,
|
|
174
190
|
stage_name: str,
|
|
175
191
|
dbt_object_attributes: DBTObjectEditableAttributes,
|
|
176
|
-
|
|
177
|
-
unset_default_target: bool,
|
|
178
|
-
external_access_integrations: Optional[List[str]],
|
|
179
|
-
install_local_deps: bool,
|
|
192
|
+
attrs: DBTDeployAttributes,
|
|
180
193
|
) -> SnowflakeCursor:
|
|
181
194
|
query = f"ALTER DBT PROJECT {fqn} ADD VERSION"
|
|
182
195
|
query += f"\nFROM {stage_name}"
|
|
@@ -186,28 +199,35 @@ class DBTManager(SqlExecutionMixin):
|
|
|
186
199
|
unset_properties = []
|
|
187
200
|
|
|
188
201
|
current_default_target = dbt_object_attributes.get("default_target")
|
|
189
|
-
if unset_default_target and current_default_target is not None:
|
|
202
|
+
if attrs.unset_default_target and current_default_target is not None:
|
|
190
203
|
unset_properties.append("DEFAULT_TARGET")
|
|
191
|
-
elif default_target and (
|
|
204
|
+
elif attrs.default_target and (
|
|
192
205
|
current_default_target is None
|
|
193
|
-
or current_default_target.lower() != default_target.lower()
|
|
206
|
+
or current_default_target.lower() != attrs.default_target.lower()
|
|
194
207
|
):
|
|
195
|
-
set_properties.append(f"DEFAULT_TARGET='{default_target}'")
|
|
208
|
+
set_properties.append(f"DEFAULT_TARGET='{attrs.default_target}'")
|
|
209
|
+
|
|
210
|
+
# Comparing dbt_version to existing project's dbt_version might be ambiguous
|
|
211
|
+
# if previously project was locked to just minor version and now user wants to
|
|
212
|
+
# lock it to a patch as well. If target version is provided, it's better to just
|
|
213
|
+
# apply it.
|
|
214
|
+
if attrs.dbt_version:
|
|
215
|
+
set_properties.append(f"DBT_VERSION='{attrs.dbt_version}'")
|
|
196
216
|
|
|
197
217
|
current_external_access_integrations = dbt_object_attributes.get(
|
|
198
218
|
"external_access_integrations"
|
|
199
219
|
)
|
|
200
220
|
if self._should_update_external_access_integrations(
|
|
201
221
|
current_external_access_integrations,
|
|
202
|
-
external_access_integrations,
|
|
203
|
-
install_local_deps,
|
|
222
|
+
attrs.external_access_integrations,
|
|
223
|
+
attrs.install_local_deps,
|
|
204
224
|
):
|
|
205
|
-
if external_access_integrations:
|
|
206
|
-
integrations_str = ", ".join(sorted(external_access_integrations))
|
|
225
|
+
if attrs.external_access_integrations:
|
|
226
|
+
integrations_str = ", ".join(sorted(attrs.external_access_integrations))
|
|
207
227
|
set_properties.append(
|
|
208
228
|
f"EXTERNAL_ACCESS_INTEGRATIONS=({integrations_str})"
|
|
209
229
|
)
|
|
210
|
-
elif install_local_deps:
|
|
230
|
+
elif attrs.install_local_deps:
|
|
211
231
|
set_properties.append("EXTERNAL_ACCESS_INTEGRATIONS=()")
|
|
212
232
|
|
|
213
233
|
if set_properties or unset_properties:
|
|
@@ -245,16 +265,16 @@ class DBTManager(SqlExecutionMixin):
|
|
|
245
265
|
self,
|
|
246
266
|
fqn: FQN,
|
|
247
267
|
stage_name: str,
|
|
248
|
-
|
|
249
|
-
external_access_integrations: Optional[List[str]],
|
|
250
|
-
install_local_deps: bool,
|
|
268
|
+
attrs: DBTDeployAttributes,
|
|
251
269
|
) -> SnowflakeCursor:
|
|
252
270
|
query = f"CREATE DBT PROJECT {fqn}"
|
|
253
271
|
query += f"\nFROM {stage_name}"
|
|
254
|
-
if default_target:
|
|
255
|
-
query += f" DEFAULT_TARGET='{default_target}'"
|
|
272
|
+
if attrs.default_target:
|
|
273
|
+
query += f" DEFAULT_TARGET='{attrs.default_target}'"
|
|
274
|
+
if attrs.dbt_version:
|
|
275
|
+
query += f" DBT_VERSION='{attrs.dbt_version}'"
|
|
256
276
|
query = self._handle_external_access_integrations_query(
|
|
257
|
-
query, external_access_integrations, install_local_deps
|
|
277
|
+
query, attrs.external_access_integrations, attrs.install_local_deps
|
|
258
278
|
)
|
|
259
279
|
return self.execute_query(query)
|
|
260
280
|
|
|
@@ -276,16 +296,16 @@ class DBTManager(SqlExecutionMixin):
|
|
|
276
296
|
self,
|
|
277
297
|
fqn: FQN,
|
|
278
298
|
stage_name: str,
|
|
279
|
-
|
|
280
|
-
external_access_integrations: Optional[List[str]],
|
|
281
|
-
install_local_deps: bool,
|
|
299
|
+
attrs: DBTDeployAttributes,
|
|
282
300
|
) -> SnowflakeCursor:
|
|
283
301
|
query = f"CREATE OR REPLACE DBT PROJECT {fqn}"
|
|
284
302
|
query += f"\nFROM {stage_name}"
|
|
285
|
-
if default_target:
|
|
286
|
-
query += f" DEFAULT_TARGET='{default_target}'"
|
|
303
|
+
if attrs.default_target:
|
|
304
|
+
query += f" DEFAULT_TARGET='{attrs.default_target}'"
|
|
305
|
+
if attrs.dbt_version:
|
|
306
|
+
query += f" DBT_VERSION='{attrs.dbt_version}'"
|
|
287
307
|
query = self._handle_external_access_integrations_query(
|
|
288
|
-
query, external_access_integrations, install_local_deps
|
|
308
|
+
query, attrs.external_access_integrations, attrs.install_local_deps
|
|
289
309
|
)
|
|
290
310
|
return self.execute_query(query)
|
|
291
311
|
|
|
@@ -379,13 +399,21 @@ class DBTManager(SqlExecutionMixin):
|
|
|
379
399
|
yaml.safe_dump(yaml.safe_load(sfd), tfd)
|
|
380
400
|
|
|
381
401
|
def execute(
|
|
382
|
-
self,
|
|
402
|
+
self,
|
|
403
|
+
dbt_command: str,
|
|
404
|
+
name: FQN,
|
|
405
|
+
run_async: bool,
|
|
406
|
+
dbt_version: Optional[str] = None,
|
|
407
|
+
*dbt_cli_args,
|
|
383
408
|
) -> SnowflakeCursor:
|
|
384
409
|
if dbt_cli_args:
|
|
385
410
|
processed_args = self._process_dbt_args(dbt_cli_args)
|
|
386
411
|
dbt_command = f"{dbt_command} {processed_args}".strip()
|
|
387
412
|
dbt_command_escaped = dbt_command.replace("'", "\\'")
|
|
388
|
-
query = f"EXECUTE DBT PROJECT {name}
|
|
413
|
+
query = f"EXECUTE DBT PROJECT {name}"
|
|
414
|
+
if dbt_version:
|
|
415
|
+
query += f" dbt_version='{dbt_version}'"
|
|
416
|
+
query += f" args='{dbt_command_escaped}'"
|
|
389
417
|
return self.execute_query(query, _exec_async=run_async)
|
|
390
418
|
|
|
391
419
|
@staticmethod
|
|
@@ -15,6 +15,8 @@ from typing import List, Optional
|
|
|
15
15
|
|
|
16
16
|
import typer
|
|
17
17
|
from snowflake.cli._plugins.dcm.manager import DCMProjectManager
|
|
18
|
+
from snowflake.cli._plugins.dcm.reporters import RefreshReporter, TestReporter
|
|
19
|
+
from snowflake.cli._plugins.dcm.utils import mock_dcm_response
|
|
18
20
|
from snowflake.cli._plugins.object.command_aliases import add_object_command_aliases
|
|
19
21
|
from snowflake.cli._plugins.object.commands import scope_option
|
|
20
22
|
from snowflake.cli._plugins.object.manager import ObjectManager
|
|
@@ -36,6 +38,7 @@ from snowflake.cli.api.exceptions import CliError
|
|
|
36
38
|
from snowflake.cli.api.feature_flags import FeatureFlag
|
|
37
39
|
from snowflake.cli.api.identifiers import FQN
|
|
38
40
|
from snowflake.cli.api.output.types import (
|
|
41
|
+
EmptyResult,
|
|
39
42
|
MessageResult,
|
|
40
43
|
QueryJsonValueResult,
|
|
41
44
|
QueryResult,
|
|
@@ -287,6 +290,41 @@ def preview(
|
|
|
287
290
|
return QueryResult(result)
|
|
288
291
|
|
|
289
292
|
|
|
293
|
+
@app.command(requires_connection=True)
|
|
294
|
+
@mock_dcm_response("refresh")
|
|
295
|
+
def refresh(
|
|
296
|
+
identifier: FQN = dcm_identifier,
|
|
297
|
+
**options,
|
|
298
|
+
):
|
|
299
|
+
"""
|
|
300
|
+
Refreshes dynamic tables defined in DCM project.
|
|
301
|
+
"""
|
|
302
|
+
with cli_console.spinner() as spinner:
|
|
303
|
+
spinner.add_task(description=f"Refreshing dcm project {identifier}", total=None)
|
|
304
|
+
result = DCMProjectManager().refresh(project_identifier=identifier)
|
|
305
|
+
|
|
306
|
+
RefreshReporter().process(result)
|
|
307
|
+
return EmptyResult()
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
@app.command(requires_connection=True)
|
|
311
|
+
@mock_dcm_response("test")
|
|
312
|
+
def test(
|
|
313
|
+
identifier: FQN = dcm_identifier,
|
|
314
|
+
**options,
|
|
315
|
+
):
|
|
316
|
+
"""
|
|
317
|
+
Tests all expectations defined in DCM project.
|
|
318
|
+
"""
|
|
319
|
+
with cli_console.spinner() as spinner:
|
|
320
|
+
spinner.add_task(description=f"Testing dcm project {identifier}", total=None)
|
|
321
|
+
result = DCMProjectManager().test(project_identifier=identifier)
|
|
322
|
+
|
|
323
|
+
reporter = TestReporter()
|
|
324
|
+
reporter.process(result)
|
|
325
|
+
return EmptyResult()
|
|
326
|
+
|
|
327
|
+
|
|
290
328
|
def _get_effective_stage(identifier: FQN, from_location: Optional[str]):
|
|
291
329
|
manager = DCMProjectManager()
|
|
292
330
|
if not from_location:
|
|
@@ -160,6 +160,14 @@ class DCMProjectManager(SqlExecutionMixin):
|
|
|
160
160
|
query += f" LIMIT {limit}"
|
|
161
161
|
return self.execute_query(query=query)
|
|
162
162
|
|
|
163
|
+
def refresh(self, project_identifier: FQN):
|
|
164
|
+
query = f"EXECUTE DCM PROJECT {project_identifier.sql_identifier} REFRESH ALL"
|
|
165
|
+
return self.execute_query(query=query)
|
|
166
|
+
|
|
167
|
+
def test(self, project_identifier: FQN):
|
|
168
|
+
query = f"EXECUTE DCM PROJECT {project_identifier.sql_identifier} TEST ALL"
|
|
169
|
+
return self.execute_query(query=query)
|
|
170
|
+
|
|
163
171
|
@staticmethod
|
|
164
172
|
def _get_from_stage_query(from_stage: str) -> str:
|
|
165
173
|
stage_path = StagePath.from_stage_str(from_stage)
|