snowflake-cli 3.13.0__py3-none-any.whl → 3.14.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/_plugins/dbt/manager.py +26 -2
- snowflake/cli/_plugins/dcm/commands.py +56 -4
- snowflake/cli/_plugins/dcm/manager.py +79 -33
- snowflake/cli/_plugins/snowpark/package/commands.py +1 -1
- snowflake/cli/_plugins/streamlit/commands.py +23 -4
- snowflake/cli/_plugins/streamlit/streamlit_entity.py +81 -45
- snowflake/cli/api/console/console.py +8 -3
- snowflake/cli/api/feature_flags.py +0 -3
- {snowflake_cli-3.13.0.dist-info → snowflake_cli-3.14.0.dist-info}/METADATA +2 -2
- {snowflake_cli-3.13.0.dist-info → snowflake_cli-3.14.0.dist-info}/RECORD +14 -14
- {snowflake_cli-3.13.0.dist-info → snowflake_cli-3.14.0.dist-info}/WHEEL +1 -1
- {snowflake_cli-3.13.0.dist-info → snowflake_cli-3.14.0.dist-info}/entry_points.txt +0 -0
- {snowflake_cli-3.13.0.dist-info → snowflake_cli-3.14.0.dist-info}/licenses/LICENSE +0 -0
snowflake/cli/__about__.py
CHANGED
|
@@ -382,6 +382,30 @@ class DBTManager(SqlExecutionMixin):
|
|
|
382
382
|
self, dbt_command: str, name: FQN, run_async: bool, *dbt_cli_args
|
|
383
383
|
) -> SnowflakeCursor:
|
|
384
384
|
if dbt_cli_args:
|
|
385
|
-
|
|
386
|
-
|
|
385
|
+
processed_args = self._process_dbt_args(dbt_cli_args)
|
|
386
|
+
dbt_command = f"{dbt_command} {processed_args}".strip()
|
|
387
|
+
dbt_command_escaped = dbt_command.replace("'", "\\'")
|
|
388
|
+
query = f"EXECUTE DBT PROJECT {name} args='{dbt_command_escaped}'"
|
|
387
389
|
return self.execute_query(query, _exec_async=run_async)
|
|
390
|
+
|
|
391
|
+
@staticmethod
|
|
392
|
+
def _process_dbt_args(dbt_cli_args: tuple) -> str:
|
|
393
|
+
"""
|
|
394
|
+
Process dbt CLI arguments, handling special cases like --vars flag.
|
|
395
|
+
"""
|
|
396
|
+
if not dbt_cli_args:
|
|
397
|
+
return ""
|
|
398
|
+
|
|
399
|
+
processed_args = []
|
|
400
|
+
i = 0
|
|
401
|
+
while i < len(dbt_cli_args):
|
|
402
|
+
arg = dbt_cli_args[i]
|
|
403
|
+
if arg == "--vars" and i + 1 < len(dbt_cli_args):
|
|
404
|
+
vars_value = dbt_cli_args[i + 1]
|
|
405
|
+
processed_args.append("--vars")
|
|
406
|
+
processed_args.append(f"'{vars_value}'")
|
|
407
|
+
i += 2
|
|
408
|
+
else:
|
|
409
|
+
processed_args.append(arg)
|
|
410
|
+
i += 1
|
|
411
|
+
return " ".join(processed_args)
|
|
@@ -19,6 +19,7 @@ from snowflake.cli._plugins.object.command_aliases import add_object_command_ali
|
|
|
19
19
|
from snowflake.cli._plugins.object.commands import scope_option
|
|
20
20
|
from snowflake.cli._plugins.object.manager import ObjectManager
|
|
21
21
|
from snowflake.cli.api.commands.flags import (
|
|
22
|
+
IdentifierType,
|
|
22
23
|
IfExistsOption,
|
|
23
24
|
IfNotExistsOption,
|
|
24
25
|
OverrideableOption,
|
|
@@ -112,6 +113,12 @@ def deploy(
|
|
|
112
113
|
variables: Optional[List[str]] = variables_flag,
|
|
113
114
|
configuration: Optional[str] = configuration_flag,
|
|
114
115
|
alias: Optional[str] = alias_option,
|
|
116
|
+
skip_plan: bool = typer.Option(
|
|
117
|
+
False,
|
|
118
|
+
"--skip-plan",
|
|
119
|
+
help="Skips planning step",
|
|
120
|
+
hidden=True,
|
|
121
|
+
),
|
|
115
122
|
**options,
|
|
116
123
|
):
|
|
117
124
|
"""
|
|
@@ -122,13 +129,15 @@ def deploy(
|
|
|
122
129
|
|
|
123
130
|
with cli_console.spinner() as spinner:
|
|
124
131
|
spinner.add_task(description=f"Deploying dcm project {identifier}", total=None)
|
|
125
|
-
|
|
132
|
+
if skip_plan:
|
|
133
|
+
cli_console.warning("Skipping planning step")
|
|
134
|
+
result = manager.deploy(
|
|
126
135
|
project_identifier=identifier,
|
|
127
136
|
configuration=configuration,
|
|
128
137
|
from_stage=effective_stage,
|
|
129
138
|
variables=variables,
|
|
130
139
|
alias=alias,
|
|
131
|
-
|
|
140
|
+
skip_plan=skip_plan,
|
|
132
141
|
)
|
|
133
142
|
return QueryJsonValueResult(result)
|
|
134
143
|
|
|
@@ -152,11 +161,10 @@ def plan(
|
|
|
152
161
|
|
|
153
162
|
with cli_console.spinner() as spinner:
|
|
154
163
|
spinner.add_task(description=f"Planning dcm project {identifier}", total=None)
|
|
155
|
-
result = manager.
|
|
164
|
+
result = manager.plan(
|
|
156
165
|
project_identifier=identifier,
|
|
157
166
|
configuration=configuration,
|
|
158
167
|
from_stage=effective_stage,
|
|
159
|
-
dry_run=True,
|
|
160
168
|
variables=variables,
|
|
161
169
|
output_path=output_path,
|
|
162
170
|
)
|
|
@@ -235,6 +243,50 @@ def drop_deployment(
|
|
|
235
243
|
)
|
|
236
244
|
|
|
237
245
|
|
|
246
|
+
@app.command(requires_connection=True)
|
|
247
|
+
def preview(
|
|
248
|
+
identifier: FQN = dcm_identifier,
|
|
249
|
+
object_identifier: FQN = typer.Option(
|
|
250
|
+
...,
|
|
251
|
+
"--object",
|
|
252
|
+
help="FQN of table/view/dynamic table to be previewed.",
|
|
253
|
+
show_default=False,
|
|
254
|
+
click_type=IdentifierType(),
|
|
255
|
+
),
|
|
256
|
+
from_location: Optional[str] = from_option,
|
|
257
|
+
variables: Optional[List[str]] = variables_flag,
|
|
258
|
+
configuration: Optional[str] = configuration_flag,
|
|
259
|
+
limit: Optional[int] = typer.Option(
|
|
260
|
+
None,
|
|
261
|
+
"--limit",
|
|
262
|
+
help="The maximum number of rows to be returned.",
|
|
263
|
+
show_default=False,
|
|
264
|
+
),
|
|
265
|
+
**options,
|
|
266
|
+
):
|
|
267
|
+
"""
|
|
268
|
+
Returns rows from any table, view, dynamic table.
|
|
269
|
+
"""
|
|
270
|
+
manager = DCMProjectManager()
|
|
271
|
+
effective_stage = _get_effective_stage(identifier, from_location)
|
|
272
|
+
|
|
273
|
+
with cli_console.spinner() as spinner:
|
|
274
|
+
spinner.add_task(
|
|
275
|
+
description=f"Previewing {object_identifier}.",
|
|
276
|
+
total=None,
|
|
277
|
+
)
|
|
278
|
+
result = manager.preview(
|
|
279
|
+
project_identifier=identifier,
|
|
280
|
+
object_identifier=object_identifier,
|
|
281
|
+
configuration=configuration,
|
|
282
|
+
from_stage=effective_stage,
|
|
283
|
+
variables=variables,
|
|
284
|
+
limit=limit,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
return QueryResult(result)
|
|
288
|
+
|
|
289
|
+
|
|
238
290
|
def _get_effective_stage(identifier: FQN, from_location: Optional[str]):
|
|
239
291
|
manager = DCMProjectManager()
|
|
240
292
|
if not from_location:
|
|
@@ -62,51 +62,59 @@ class DCMProjectManager(SqlExecutionMixin):
|
|
|
62
62
|
ObjectType.DCM_PROJECT, project_identifier, "OUTPUT_TMP_STAGE"
|
|
63
63
|
)
|
|
64
64
|
stage_manager.create(temp_stage_fqn, temporary=True)
|
|
65
|
-
effective_output_path = StagePath.from_stage_str(
|
|
65
|
+
effective_output_path = StagePath.from_stage_str(
|
|
66
|
+
temp_stage_fqn.identifier
|
|
67
|
+
).joinpath("/outputs")
|
|
66
68
|
temp_stage_for_local_output = (temp_stage_fqn.identifier, Path(output_path))
|
|
67
69
|
else:
|
|
68
70
|
effective_output_path = StagePath.from_stage_str(output_path)
|
|
69
71
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
72
|
+
try:
|
|
73
|
+
yield effective_output_path.absolute_path()
|
|
74
|
+
finally:
|
|
75
|
+
if should_download_files:
|
|
76
|
+
assert temp_stage_for_local_output is not None
|
|
77
|
+
stage_path, local_path = temp_stage_for_local_output
|
|
78
|
+
stage_manager.get_recursive(
|
|
79
|
+
stage_path=effective_output_path.absolute_path(),
|
|
80
|
+
dest_path=local_path,
|
|
81
|
+
)
|
|
82
|
+
cli_console.step(f"Plan output saved to: {local_path.resolve()}")
|
|
83
|
+
else:
|
|
84
|
+
cli_console.step(f"Plan output saved to: {output_path}")
|
|
79
85
|
|
|
80
|
-
def
|
|
86
|
+
def deploy(
|
|
81
87
|
self,
|
|
82
88
|
project_identifier: FQN,
|
|
83
89
|
from_stage: str,
|
|
84
90
|
configuration: str | None = None,
|
|
85
91
|
variables: List[str] | None = None,
|
|
86
|
-
dry_run: bool = False,
|
|
87
92
|
alias: str | None = None,
|
|
93
|
+
skip_plan: bool = False,
|
|
94
|
+
):
|
|
95
|
+
query = f"EXECUTE DCM PROJECT {project_identifier.sql_identifier} DEPLOY"
|
|
96
|
+
if alias:
|
|
97
|
+
query += f' AS "{alias}"'
|
|
98
|
+
query += self._get_configuration_and_variables_query(configuration, variables)
|
|
99
|
+
query += self._get_from_stage_query(from_stage)
|
|
100
|
+
if skip_plan:
|
|
101
|
+
query += f" SKIP PLAN"
|
|
102
|
+
return self.execute_query(query=query)
|
|
103
|
+
|
|
104
|
+
def plan(
|
|
105
|
+
self,
|
|
106
|
+
project_identifier: FQN,
|
|
107
|
+
from_stage: str,
|
|
108
|
+
configuration: str | None = None,
|
|
109
|
+
variables: List[str] | None = None,
|
|
88
110
|
output_path: str | None = None,
|
|
89
111
|
):
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
else:
|
|
97
|
-
query += " DEPLOY"
|
|
98
|
-
if alias:
|
|
99
|
-
query += f' AS "{alias}"'
|
|
100
|
-
if configuration or variables:
|
|
101
|
-
query += f" USING"
|
|
102
|
-
if configuration:
|
|
103
|
-
query += f" CONFIGURATION {configuration}"
|
|
104
|
-
if variables:
|
|
105
|
-
query += StageManager.parse_execute_variables(
|
|
106
|
-
parse_key_value_variables(variables)
|
|
107
|
-
).removeprefix(" using")
|
|
108
|
-
stage_path = StagePath.from_stage_str(from_stage)
|
|
109
|
-
query += f" FROM {stage_path.absolute_path()}"
|
|
112
|
+
query = f"EXECUTE DCM PROJECT {project_identifier.sql_identifier} PLAN"
|
|
113
|
+
query += self._get_configuration_and_variables_query(configuration, variables)
|
|
114
|
+
query += self._get_from_stage_query(from_stage)
|
|
115
|
+
with self._collect_output(
|
|
116
|
+
project_identifier, output_path
|
|
117
|
+
) if output_path else nullcontext() as output_stage:
|
|
110
118
|
if output_stage is not None:
|
|
111
119
|
query += f" OUTPUT_PATH {output_stage}"
|
|
112
120
|
result = self.execute_query(query=query)
|
|
@@ -136,6 +144,42 @@ class DCMProjectManager(SqlExecutionMixin):
|
|
|
136
144
|
query += f' "{deployment_name}"'
|
|
137
145
|
return self.execute_query(query=query)
|
|
138
146
|
|
|
147
|
+
def preview(
|
|
148
|
+
self,
|
|
149
|
+
project_identifier: FQN,
|
|
150
|
+
object_identifier: FQN,
|
|
151
|
+
from_stage: str,
|
|
152
|
+
configuration: str | None = None,
|
|
153
|
+
variables: List[str] | None = None,
|
|
154
|
+
limit: int | None = None,
|
|
155
|
+
):
|
|
156
|
+
query = f"EXECUTE DCM PROJECT {project_identifier.sql_identifier} PREVIEW {object_identifier.sql_identifier}"
|
|
157
|
+
query += self._get_configuration_and_variables_query(configuration, variables)
|
|
158
|
+
query += self._get_from_stage_query(from_stage)
|
|
159
|
+
if limit is not None:
|
|
160
|
+
query += f" LIMIT {limit}"
|
|
161
|
+
return self.execute_query(query=query)
|
|
162
|
+
|
|
163
|
+
@staticmethod
|
|
164
|
+
def _get_from_stage_query(from_stage: str) -> str:
|
|
165
|
+
stage_path = StagePath.from_stage_str(from_stage)
|
|
166
|
+
return f" FROM {stage_path.absolute_path()}"
|
|
167
|
+
|
|
168
|
+
@staticmethod
|
|
169
|
+
def _get_configuration_and_variables_query(
|
|
170
|
+
configuration: str | None, variables: List[str] | None
|
|
171
|
+
) -> str:
|
|
172
|
+
query = ""
|
|
173
|
+
if configuration or variables:
|
|
174
|
+
query += f" USING"
|
|
175
|
+
if configuration:
|
|
176
|
+
query += f" CONFIGURATION {configuration}"
|
|
177
|
+
if variables:
|
|
178
|
+
query += StageManager.parse_execute_variables(
|
|
179
|
+
parse_key_value_variables(variables)
|
|
180
|
+
).removeprefix(" using")
|
|
181
|
+
return query
|
|
182
|
+
|
|
139
183
|
@staticmethod
|
|
140
184
|
def sync_local_files(
|
|
141
185
|
project_identifier: FQN, source_directory: str | None = None
|
|
@@ -166,7 +210,9 @@ class DCMProjectManager(SqlExecutionMixin):
|
|
|
166
210
|
|
|
167
211
|
definitions = list(dcm_manifest.get("include_definitions", list()))
|
|
168
212
|
if MANIFEST_FILE_NAME not in definitions:
|
|
169
|
-
|
|
213
|
+
# append manifest file, but avoid sending it multiple times if
|
|
214
|
+
# there are manifests from previous runs stored in output path
|
|
215
|
+
definitions.append(rf"^{MANIFEST_FILE_NAME}")
|
|
170
216
|
|
|
171
217
|
with cli_console.phase(f"Uploading definition files"):
|
|
172
218
|
stage_fqn = FQN.from_resource(
|
|
@@ -183,7 +183,7 @@ def package_create(
|
|
|
183
183
|
f"""
|
|
184
184
|
The package {name} is successfully created, but depends on the following
|
|
185
185
|
Anaconda libraries. They need to be included in project requirements,
|
|
186
|
-
as
|
|
186
|
+
as they are not included in the .zip.
|
|
187
187
|
"""
|
|
188
188
|
)
|
|
189
189
|
message += "\n".join(
|
|
@@ -127,9 +127,17 @@ def _default_file_callback(param_name: str):
|
|
|
127
127
|
return _check_file_exists_if_not_default
|
|
128
128
|
|
|
129
129
|
|
|
130
|
+
LegacyOption = typer.Option(
|
|
131
|
+
False,
|
|
132
|
+
"--legacy",
|
|
133
|
+
help="Use legacy ROOT_LOCATION SQL syntax.",
|
|
134
|
+
is_flag=True,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
130
138
|
@app.command("deploy", requires_connection=True)
|
|
131
139
|
@with_project_definition()
|
|
132
|
-
@with_experimental_behaviour()
|
|
140
|
+
@with_experimental_behaviour() # Kept for backward compatibility
|
|
133
141
|
def streamlit_deploy(
|
|
134
142
|
replace: bool = ReplaceOption(
|
|
135
143
|
help="Replaces the Streamlit app if it already exists. It only uploads new and overwrites existing files, "
|
|
@@ -138,16 +146,27 @@ def streamlit_deploy(
|
|
|
138
146
|
prune: bool = PruneOption(),
|
|
139
147
|
entity_id: str = entity_argument("streamlit"),
|
|
140
148
|
open_: bool = OpenOption,
|
|
149
|
+
legacy: bool = LegacyOption,
|
|
141
150
|
**options,
|
|
142
151
|
) -> CommandResult:
|
|
143
152
|
"""
|
|
144
153
|
Deploys a Streamlit app defined in the project definition file (snowflake.yml). By default, the command uploads
|
|
145
|
-
environment.yml and any other pages or folders, if present. If you don
|
|
154
|
+
environment.yml and any other pages or folders, if present. If you don't specify a stage name, the `streamlit`
|
|
146
155
|
stage is used. If the specified stage does not exist, the command creates it. If multiple Streamlits are defined
|
|
147
156
|
in snowflake.yml and no entity_id is provided then command will raise an error.
|
|
148
157
|
"""
|
|
149
158
|
|
|
150
159
|
cli_context = get_cli_context()
|
|
160
|
+
workspace_ctx = _get_current_workspace_context()
|
|
161
|
+
|
|
162
|
+
# Handle deprecated --experimental flag for backward compatibility
|
|
163
|
+
if options.get("experimental"):
|
|
164
|
+
workspace_ctx.console.warning(
|
|
165
|
+
"[Deprecation] The --experimental flag is deprecated. "
|
|
166
|
+
"Versioned deployment is now the default behavior. "
|
|
167
|
+
"This flag will be removed in a future version."
|
|
168
|
+
)
|
|
169
|
+
|
|
151
170
|
pd = cli_context.project_definition
|
|
152
171
|
if not pd.meets_version_requirement("2"):
|
|
153
172
|
if not pd.streamlit:
|
|
@@ -163,7 +182,7 @@ def streamlit_deploy(
|
|
|
163
182
|
project_definition=pd,
|
|
164
183
|
entity_type=ObjectType.STREAMLIT.value.cli_name,
|
|
165
184
|
),
|
|
166
|
-
workspace_ctx=
|
|
185
|
+
workspace_ctx=workspace_ctx,
|
|
167
186
|
)
|
|
168
187
|
|
|
169
188
|
url = streamlit.perform(
|
|
@@ -173,7 +192,7 @@ def streamlit_deploy(
|
|
|
173
192
|
),
|
|
174
193
|
_open=open_,
|
|
175
194
|
replace=replace,
|
|
176
|
-
|
|
195
|
+
legacy=legacy,
|
|
177
196
|
prune=prune,
|
|
178
197
|
)
|
|
179
198
|
|
|
@@ -15,7 +15,7 @@ from snowflake.cli._plugins.workspace.context import ActionContext
|
|
|
15
15
|
from snowflake.cli.api.artifacts.bundle_map import BundleMap
|
|
16
16
|
from snowflake.cli.api.entities.common import EntityBase
|
|
17
17
|
from snowflake.cli.api.entities.utils import EntityActions, sync_deploy_root_with_stage
|
|
18
|
-
from snowflake.cli.api.
|
|
18
|
+
from snowflake.cli.api.exceptions import CliError
|
|
19
19
|
from snowflake.cli.api.identifiers import FQN
|
|
20
20
|
from snowflake.cli.api.project.project_paths import bundle_root
|
|
21
21
|
from snowflake.cli.api.project.schemas.entities.common import Identifier, PathMapping
|
|
@@ -66,12 +66,10 @@ class StreamlitEntity(EntityBase[StreamlitEntityModel]):
|
|
|
66
66
|
self._conn, f"/#/streamlit-apps/{name.url_identifier}"
|
|
67
67
|
)
|
|
68
68
|
|
|
69
|
-
def _is_spcs_runtime_v2_mode(self
|
|
69
|
+
def _is_spcs_runtime_v2_mode(self) -> bool:
|
|
70
70
|
"""Check if SPCS runtime v2 mode is enabled."""
|
|
71
71
|
return (
|
|
72
|
-
|
|
73
|
-
and self.model.runtime_name == SPCS_RUNTIME_V2_NAME
|
|
74
|
-
and self.model.compute_pool
|
|
72
|
+
self.model.runtime_name == SPCS_RUNTIME_V2_NAME and self.model.compute_pool
|
|
75
73
|
)
|
|
76
74
|
|
|
77
75
|
def bundle(self, output_dir: Optional[Path] = None) -> BundleMap:
|
|
@@ -93,7 +91,7 @@ class StreamlitEntity(EntityBase[StreamlitEntityModel]):
|
|
|
93
91
|
replace: bool,
|
|
94
92
|
prune: bool = False,
|
|
95
93
|
bundle_map: Optional[BundleMap] = None,
|
|
96
|
-
|
|
94
|
+
legacy: bool = False,
|
|
97
95
|
*args,
|
|
98
96
|
**kwargs,
|
|
99
97
|
):
|
|
@@ -104,49 +102,40 @@ class StreamlitEntity(EntityBase[StreamlitEntityModel]):
|
|
|
104
102
|
|
|
105
103
|
console = self._workspace_ctx.console
|
|
106
104
|
console.step(f"Checking if object exists")
|
|
107
|
-
|
|
105
|
+
object_exists = self._object_exists()
|
|
106
|
+
|
|
107
|
+
if object_exists and not replace:
|
|
108
108
|
raise ClickException(
|
|
109
109
|
f"Streamlit {self.model.fqn.sql_identifier} already exists. Use 'replace' option to overwrite."
|
|
110
110
|
)
|
|
111
111
|
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
else:
|
|
118
|
-
console.step(f"Uploading artifacts to stage {self.model.stage}")
|
|
119
|
-
|
|
120
|
-
# We use a static method from StageManager here, but maybe this logic could be implemented elswhere, as we implement entities?
|
|
121
|
-
name = (
|
|
122
|
-
self.model.identifier.name
|
|
123
|
-
if isinstance(self.model.identifier, Identifier)
|
|
124
|
-
else self.model.identifier or self.entity_id
|
|
125
|
-
)
|
|
126
|
-
stage_root = StageManager.get_standard_stage_prefix(
|
|
127
|
-
f"{FQN.from_string(self.model.stage).using_connection(self._conn)}/{name}"
|
|
112
|
+
if legacy and self._is_spcs_runtime_v2_mode():
|
|
113
|
+
raise CliError(
|
|
114
|
+
"runtime_name and compute_pool are not compatible with --legacy flag. "
|
|
115
|
+
"Please remove the --legacy flag to use versioned deployment, or remove "
|
|
116
|
+
"runtime_name and compute_pool from your snowflake.yml to use legacy deployment."
|
|
128
117
|
)
|
|
129
|
-
sync_deploy_root_with_stage(
|
|
130
|
-
console=self._workspace_ctx.console,
|
|
131
|
-
deploy_root=bundle_map.deploy_root(),
|
|
132
|
-
bundle_map=bundle_map,
|
|
133
|
-
prune=prune,
|
|
134
|
-
recursive=True,
|
|
135
|
-
stage_path_parts=StageManager().stage_path_parts_from_str(stage_root),
|
|
136
|
-
print_diff=True,
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
console.step(f"Creating Streamlit object {self.model.fqn.sql_identifier}")
|
|
140
118
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
119
|
+
# Warn if replacing with a different deployment style
|
|
120
|
+
if object_exists and replace:
|
|
121
|
+
existing_is_legacy = self._is_legacy_deployment()
|
|
122
|
+
if existing_is_legacy and not legacy:
|
|
123
|
+
console.warning(
|
|
124
|
+
"Replacing legacy ROOT_LOCATION deployment with versioned deployment. "
|
|
125
|
+
"Files from the old stage location will not be automatically migrated. "
|
|
126
|
+
"The new deployment will use a separate versioned stage location."
|
|
127
|
+
)
|
|
128
|
+
elif not existing_is_legacy and legacy:
|
|
129
|
+
console.warning(
|
|
130
|
+
"Deployment style is changing from versioned to legacy. "
|
|
131
|
+
"Your existing files will remain in the versioned stage. "
|
|
132
|
+
"If needed, manually copy any additional files to the legacy stage after deployment."
|
|
146
133
|
)
|
|
147
|
-
)
|
|
148
134
|
|
|
149
|
-
|
|
135
|
+
if legacy:
|
|
136
|
+
self._deploy_legacy(bundle_map=bundle_map, replace=replace, prune=prune)
|
|
137
|
+
else:
|
|
138
|
+
self._deploy_versioned(bundle_map=bundle_map, replace=replace, prune=prune)
|
|
150
139
|
|
|
151
140
|
return self.perform(EntityActions.GET_URL, action_context, *args, **kwargs)
|
|
152
141
|
|
|
@@ -172,7 +161,7 @@ class StreamlitEntity(EntityBase[StreamlitEntityModel]):
|
|
|
172
161
|
artifacts_dir: Optional[Path] = None,
|
|
173
162
|
schema: Optional[str] = None,
|
|
174
163
|
database: Optional[str] = None,
|
|
175
|
-
|
|
164
|
+
legacy: bool = False,
|
|
176
165
|
*args,
|
|
177
166
|
**kwargs,
|
|
178
167
|
) -> str:
|
|
@@ -218,7 +207,7 @@ class StreamlitEntity(EntityBase[StreamlitEntityModel]):
|
|
|
218
207
|
|
|
219
208
|
# SPCS runtime fields are only supported for FBE/versioned streamlits (FROM syntax)
|
|
220
209
|
# Never add these fields for stage-based deployments (ROOT_LOCATION syntax)
|
|
221
|
-
if not from_stage_name and self._is_spcs_runtime_v2_mode(
|
|
210
|
+
if not from_stage_name and not legacy and self._is_spcs_runtime_v2_mode():
|
|
222
211
|
query += f"\nRUNTIME_NAME = '{self.model.runtime_name}'"
|
|
223
212
|
query += f"\nCOMPUTE_POOL = '{self.model.compute_pool}'"
|
|
224
213
|
|
|
@@ -249,14 +238,61 @@ class StreamlitEntity(EntityBase[StreamlitEntityModel]):
|
|
|
249
238
|
except ProgrammingError:
|
|
250
239
|
return False
|
|
251
240
|
|
|
252
|
-
def
|
|
241
|
+
def _is_legacy_deployment(self) -> bool:
|
|
242
|
+
"""Check if the existing streamlit uses legacy ROOT_LOCATION deployment."""
|
|
243
|
+
try:
|
|
244
|
+
result = self.describe().fetchone()
|
|
245
|
+
# Versioned deployments have live_version_location_uri, legacy ones don't
|
|
246
|
+
return result.get("live_version_location_uri") is None
|
|
247
|
+
except (ProgrammingError, AttributeError, KeyError):
|
|
248
|
+
# If we can't determine, assume it doesn't exist or is inaccessible
|
|
249
|
+
return False
|
|
250
|
+
|
|
251
|
+
def _deploy_legacy(
|
|
252
|
+
self, bundle_map: BundleMap, replace: bool = False, prune: bool = False
|
|
253
|
+
):
|
|
254
|
+
console = self._workspace_ctx.console
|
|
255
|
+
console.step(f"Uploading artifacts to stage {self.model.stage}")
|
|
256
|
+
|
|
257
|
+
# We use a static method from StageManager here, but maybe this logic could be implemented elswhere, as we implement entities?
|
|
258
|
+
name = (
|
|
259
|
+
self.model.identifier.name
|
|
260
|
+
if isinstance(self.model.identifier, Identifier)
|
|
261
|
+
else self.model.identifier or self.entity_id
|
|
262
|
+
)
|
|
263
|
+
stage_root = StageManager.get_standard_stage_prefix(
|
|
264
|
+
f"{FQN.from_string(self.model.stage).using_connection(self._conn)}/{name}"
|
|
265
|
+
)
|
|
266
|
+
sync_deploy_root_with_stage(
|
|
267
|
+
console=self._workspace_ctx.console,
|
|
268
|
+
deploy_root=bundle_map.deploy_root(),
|
|
269
|
+
bundle_map=bundle_map,
|
|
270
|
+
prune=prune,
|
|
271
|
+
recursive=True,
|
|
272
|
+
stage_path_parts=StageManager().stage_path_parts_from_str(stage_root),
|
|
273
|
+
print_diff=True,
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
console.step(f"Creating Streamlit object {self.model.fqn.sql_identifier}")
|
|
277
|
+
|
|
278
|
+
self._execute_query(
|
|
279
|
+
self.get_deploy_sql(
|
|
280
|
+
replace=replace,
|
|
281
|
+
from_stage_name=stage_root,
|
|
282
|
+
legacy=True,
|
|
283
|
+
)
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
StreamlitManager(connection=self._conn).grant_privileges(self.model)
|
|
287
|
+
|
|
288
|
+
def _deploy_versioned(
|
|
253
289
|
self, bundle_map: BundleMap, replace: bool = False, prune: bool = False
|
|
254
290
|
):
|
|
255
291
|
self._execute_query(
|
|
256
292
|
self.get_deploy_sql(
|
|
257
293
|
if_not_exists=True,
|
|
258
294
|
replace=replace,
|
|
259
|
-
|
|
295
|
+
legacy=False,
|
|
260
296
|
)
|
|
261
297
|
)
|
|
262
298
|
try:
|
|
@@ -112,11 +112,16 @@ class CliConsole(AbstractConsole):
|
|
|
112
112
|
result = some_operation()
|
|
113
113
|
"""
|
|
114
114
|
with Progress(
|
|
115
|
-
SpinnerColumn(),
|
|
115
|
+
SpinnerColumn(finished_text="✓"),
|
|
116
116
|
TextColumn("[progress.description]{task.description}", style=SPINNER_STYLE),
|
|
117
|
-
transient=
|
|
117
|
+
transient=False,
|
|
118
118
|
) as progress:
|
|
119
|
-
|
|
119
|
+
try:
|
|
120
|
+
yield progress
|
|
121
|
+
finally:
|
|
122
|
+
for task_id in progress.task_ids:
|
|
123
|
+
if not progress.tasks[task_id].finished:
|
|
124
|
+
progress.update(task_id, completed=1, total=1)
|
|
120
125
|
|
|
121
126
|
def step(self, message: str):
|
|
122
127
|
"""Displays a message to output.
|
|
@@ -56,9 +56,6 @@ class FeatureFlagMixin(Enum):
|
|
|
56
56
|
|
|
57
57
|
@unique
|
|
58
58
|
class FeatureFlag(FeatureFlagMixin):
|
|
59
|
-
ENABLE_STREAMLIT_VERSIONED_STAGE = BooleanFlag(
|
|
60
|
-
"ENABLE_STREAMLIT_VERSIONED_STAGE", False
|
|
61
|
-
)
|
|
62
59
|
ENABLE_SEPARATE_AUTHENTICATION_POLICY_ID = BooleanFlag(
|
|
63
60
|
"ENABLE_SEPARATE_AUTHENTICATION_POLICY_ID", False
|
|
64
61
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: snowflake-cli
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.14.0
|
|
4
4
|
Summary: Snowflake CLI
|
|
5
5
|
Project-URL: Source code, https://github.com/snowflakedb/snowflake-cli
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/snowflakedb/snowflake-cli/issues
|
|
@@ -233,7 +233,7 @@ Requires-Dist: rich==14.0.0
|
|
|
233
233
|
Requires-Dist: setuptools==80.8.0
|
|
234
234
|
Requires-Dist: snowflake-connector-python[secure-local-storage]==3.18.0
|
|
235
235
|
Requires-Dist: snowflake-core==1.7.0
|
|
236
|
-
Requires-Dist: snowflake-snowpark-python==1.
|
|
236
|
+
Requires-Dist: snowflake-snowpark-python==1.41.0
|
|
237
237
|
Requires-Dist: tomlkit==0.13.3
|
|
238
238
|
Requires-Dist: typer==0.17.3
|
|
239
239
|
Requires-Dist: urllib3<2.6,>=1.24.3
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
snowflake/cli/__about__.py,sha256=
|
|
1
|
+
snowflake/cli/__about__.py,sha256=uE3v7EaCH1sCTTR9PLMUay6EVS1C0kQBKSvYzReSzlQ,853
|
|
2
2
|
snowflake/cli/__init__.py,sha256=uGA_QRGW3iGwaegpFsLgOhup0zBliBSXh9ou8J439uU,578
|
|
3
3
|
snowflake/cli/_app/__init__.py,sha256=CR_uTgoqHnU1XdyRhm5iQsS86yWXGVx5Ht7aGSDNFmc,765
|
|
4
4
|
snowflake/cli/_app/__main__.py,sha256=ZmcFdFqAtk2mFMz-cqCFdGd0iYzc7UsLH1oT1U40S0k,858
|
|
@@ -52,11 +52,11 @@ snowflake/cli/_plugins/cortex/types.py,sha256=9KQPlQRkoR67ty8VoqsifJfaoeLJPXZzCJ
|
|
|
52
52
|
snowflake/cli/_plugins/dbt/__init__.py,sha256=JhO1yb1LCYqYx-Ya-MlhubtiqD82CuvWF09dDMafxRM,578
|
|
53
53
|
snowflake/cli/_plugins/dbt/commands.py,sha256=0CsTMMBz9cA5E9lz1CO-Vvl27liS1S7rMSGEWvZXxZc,7892
|
|
54
54
|
snowflake/cli/_plugins/dbt/constants.py,sha256=KKyi4Zwe3iuygiHVq3bNk1VYqavE9UVQdRQgrGb5R2U,962
|
|
55
|
-
snowflake/cli/_plugins/dbt/manager.py,sha256=
|
|
55
|
+
snowflake/cli/_plugins/dbt/manager.py,sha256=i4T3yxyaaNZoyvmgJ-0EGgtUk6b8F1jvOJb1tOrLFio,15974
|
|
56
56
|
snowflake/cli/_plugins/dbt/plugin_spec.py,sha256=7yEc3tLgvw3iUhALpmaVpS-iePdSMjFdFSZVybf5KTc,992
|
|
57
57
|
snowflake/cli/_plugins/dcm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
58
|
-
snowflake/cli/_plugins/dcm/commands.py,sha256=
|
|
59
|
-
snowflake/cli/_plugins/dcm/manager.py,sha256=
|
|
58
|
+
snowflake/cli/_plugins/dcm/commands.py,sha256=r57BSxgrwrAUJS8aw9ckvN3XlOLDY83VzRLJUYPBuN8,9391
|
|
59
|
+
snowflake/cli/_plugins/dcm/manager.py,sha256=G2Y3ylRNuEMCpdL7yajT1cwkjgSjPXf13jVAx7IYc0Y,9048
|
|
60
60
|
snowflake/cli/_plugins/dcm/plugin_spec.py,sha256=U-p1UrjS2QTkk6j5-XfMsehc6gzcFHXVDjI4qnm5aPs,992
|
|
61
61
|
snowflake/cli/_plugins/git/__init__.py,sha256=uGA_QRGW3iGwaegpFsLgOhup0zBliBSXh9ou8J439uU,578
|
|
62
62
|
snowflake/cli/_plugins/git/commands.py,sha256=87R8Fs_f6BUdfLv85QGlfTTH6K-Y_oDlqJyZ3jUpBVg,11320
|
|
@@ -144,7 +144,7 @@ snowflake/cli/_plugins/snowpark/snowpark_shared.py,sha256=bvKQa0FkB0UCoqIkxAJAYy
|
|
|
144
144
|
snowflake/cli/_plugins/snowpark/zipper.py,sha256=ZKB-SGM-onVEcwkpXV2hcCuuioL8PJGHEJccPwYLILM,3585
|
|
145
145
|
snowflake/cli/_plugins/snowpark/package/__init__.py,sha256=uGA_QRGW3iGwaegpFsLgOhup0zBliBSXh9ou8J439uU,578
|
|
146
146
|
snowflake/cli/_plugins/snowpark/package/anaconda_packages.py,sha256=C-iiuAUc_Uouv3ABm5jKK4Ns8_pXvnBTyB10bHLRlOo,8988
|
|
147
|
-
snowflake/cli/_plugins/snowpark/package/commands.py,sha256=
|
|
147
|
+
snowflake/cli/_plugins/snowpark/package/commands.py,sha256=vCItV2YvmAcJYWkONtYQfUSUzVSLNZ_b_OZRQBeVMQ0,6993
|
|
148
148
|
snowflake/cli/_plugins/snowpark/package/manager.py,sha256=sd-SQplvq7Y4mh3AKBGRCnndKn4ZEm6VVyZJllGJ9ro,1631
|
|
149
149
|
snowflake/cli/_plugins/snowpark/package/utils.py,sha256=NEvKsK_kxKt_38GVOoiq8Mq4Aq6A67rrdf1tGdP6mK8,1012
|
|
150
150
|
snowflake/cli/_plugins/spcs/__init__.py,sha256=WtfeiPqu_hLaMnc7twQRTs9Uy-207T8UpHWhEoJfRLY,1292
|
|
@@ -191,10 +191,10 @@ snowflake/cli/_plugins/stage/md5.py,sha256=9B9jt3DVbSL4XyL7-4j7Tf8sI_u5CAO2LILS9
|
|
|
191
191
|
snowflake/cli/_plugins/stage/plugin_spec.py,sha256=2APmhjF1Emtdx1Ir9vLwJ1PLgLbnu7Or8lijOi_AM2U,994
|
|
192
192
|
snowflake/cli/_plugins/stage/utils.py,sha256=RiJ7bDFx92U8ffqzS0djrT_KbtWy-nk-tRbXbkaq5qI,1760
|
|
193
193
|
snowflake/cli/_plugins/streamlit/__init__.py,sha256=uGA_QRGW3iGwaegpFsLgOhup0zBliBSXh9ou8J439uU,578
|
|
194
|
-
snowflake/cli/_plugins/streamlit/commands.py,sha256=
|
|
194
|
+
snowflake/cli/_plugins/streamlit/commands.py,sha256=iWW6-8EcPTR-bH7ii9HQpr-v2Kuf7P1WtVzWwPuDsE0,7173
|
|
195
195
|
snowflake/cli/_plugins/streamlit/manager.py,sha256=6Vz9WCW33_Mb8GgLGtQzgBbnLRMvllySG-3HBRIf6io,2110
|
|
196
196
|
snowflake/cli/_plugins/streamlit/plugin_spec.py,sha256=swcszE2JoJWs-DzgN02CxK3myIYexsnWijkFYyYf7Ws,998
|
|
197
|
-
snowflake/cli/_plugins/streamlit/streamlit_entity.py,sha256=
|
|
197
|
+
snowflake/cli/_plugins/streamlit/streamlit_entity.py,sha256=guHwN8Htj2ykkruZIGIQxn7A0SanOw8PWTwNzLBPMiU,12179
|
|
198
198
|
snowflake/cli/_plugins/streamlit/streamlit_entity_model.py,sha256=_PnladsEJRjICjyXZxw6HR4iC_Jl0kNym63_dQkJyS0,3049
|
|
199
199
|
snowflake/cli/_plugins/streamlit/streamlit_project_paths.py,sha256=-q7EobPKqDmXk5x_gMvHsCaPT_l41vrJoxIHaGDHui4,1007
|
|
200
200
|
snowflake/cli/_plugins/workspace/__init__.py,sha256=uGA_QRGW3iGwaegpFsLgOhup0zBliBSXh9ou8J439uU,578
|
|
@@ -208,7 +208,7 @@ snowflake/cli/api/connections.py,sha256=zItH6oAuLD6H2GL0I2AyGlFZkMKPmqe9RPyhlHOx
|
|
|
208
208
|
snowflake/cli/api/constants.py,sha256=v42SRgCIpYjxm5SiEwoQefOqBVMOhw1_9f8UHrJ-v9A,4032
|
|
209
209
|
snowflake/cli/api/errno.py,sha256=nVQ2kO9nPaA1uGB4yZiKTwtE2LiQmINHTutziA37c6s,3871
|
|
210
210
|
snowflake/cli/api/exceptions.py,sha256=3Esa8gL0D_dsbpjWpBFWt1fQW8u4BgU59kx1B5Vgw9A,9228
|
|
211
|
-
snowflake/cli/api/feature_flags.py,sha256
|
|
211
|
+
snowflake/cli/api/feature_flags.py,sha256=OaSGFQzidBsvbNYdTulalN6Yd8s4za6LJkT4s7_UYQQ,2425
|
|
212
212
|
snowflake/cli/api/identifiers.py,sha256=5h7_lTYJQvQy4_QnyPigduJyHye2_9U-1echPIgTmhk,7445
|
|
213
213
|
snowflake/cli/api/metrics.py,sha256=l-khpKWvRF8OB86OhJ2H61jrcTdMDGZe_QM1_-yqWT8,10694
|
|
214
214
|
snowflake/cli/api/rest_api.py,sha256=RUo4prPAGmi2iQt1o96co3pWfo2t5PLCVBB2m1jlrNA,7404
|
|
@@ -236,7 +236,7 @@ snowflake/cli/api/commands/snow_typer.py,sha256=Mgz6QbinT53vYoTO6_1LtpLY-MhnYoY5
|
|
|
236
236
|
snowflake/cli/api/commands/utils.py,sha256=vZcVtPZsuH312FPf9yw-JooNWE7Tli-zVWh4u-gQk7c,1605
|
|
237
237
|
snowflake/cli/api/console/__init__.py,sha256=jKSsXJDqyQZwJ--5eRzUqb2nNvq-lo_NC1pbqK5xROI,665
|
|
238
238
|
snowflake/cli/api/console/abc.py,sha256=GzoaGSfVT-Usws06DznwQJLMSidUMePgVEmAY6Izv6Y,3583
|
|
239
|
-
snowflake/cli/api/console/console.py,sha256=
|
|
239
|
+
snowflake/cli/api/console/console.py,sha256=RwEDrZ0vOigZldtju-wSJYxW2IOWhXnMnr57wROaPRE,5448
|
|
240
240
|
snowflake/cli/api/console/enum.py,sha256=0dhepH8DEmxLjij9XxFX3kEZ_WE267zsffzecEwA2fU,675
|
|
241
241
|
snowflake/cli/api/entities/common.py,sha256=_IaDpqneQdE6MRZ6B7-dLXP2XiFBvoaNjjki59GP-_c,6071
|
|
242
242
|
snowflake/cli/api/entities/resolver.py,sha256=yD1m71X7-JPQ6dy5mKyzVR_9d3Eji4z-nRfKd_lqXFo,5922
|
|
@@ -293,8 +293,8 @@ snowflake/cli/api/utils/path_utils.py,sha256=OgR7cwbHXqP875RgPJGrAvDC1RRTU-2-Yss
|
|
|
293
293
|
snowflake/cli/api/utils/python_api_utils.py,sha256=wTNxXrma78wPvBz-Jo-ixNtP8ZjDCDh4TvciEnhYIAM,300
|
|
294
294
|
snowflake/cli/api/utils/templating_functions.py,sha256=zu2oK1BEC9yyWtDx17Hr-VAYHvCtagaOdxIrm70JQys,4955
|
|
295
295
|
snowflake/cli/api/utils/types.py,sha256=fVKuls8axKSsBzPqWwrkwkwoXXmedqxNJKqfXrrGyBM,1190
|
|
296
|
-
snowflake_cli-3.
|
|
297
|
-
snowflake_cli-3.
|
|
298
|
-
snowflake_cli-3.
|
|
299
|
-
snowflake_cli-3.
|
|
300
|
-
snowflake_cli-3.
|
|
296
|
+
snowflake_cli-3.14.0.dist-info/METADATA,sha256=_MqI77CTPtDBrt8VHfoNfQB8SgPY1__k1J8CPjR6lq0,18476
|
|
297
|
+
snowflake_cli-3.14.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
298
|
+
snowflake_cli-3.14.0.dist-info/entry_points.txt,sha256=6QmSI0wUX6p7f-dGvrPdswlQyVAVGi1AtOUbE8X6bho,58
|
|
299
|
+
snowflake_cli-3.14.0.dist-info/licenses/LICENSE,sha256=mJMA3Uz2AbjU_kVggo1CAx01XhBsI7BSi2H7ggUg_-c,11344
|
|
300
|
+
snowflake_cli-3.14.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|