snowflake-cli 3.9.0__py3-none-any.whl → 3.10.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/printing.py +53 -13
- snowflake/cli/_app/snow_connector.py +1 -0
- snowflake/cli/_app/telemetry.py +2 -0
- snowflake/cli/_app/version_check.py +73 -6
- snowflake/cli/_plugins/cortex/commands.py +8 -3
- snowflake/cli/_plugins/cortex/manager.py +24 -20
- snowflake/cli/_plugins/dbt/commands.py +5 -2
- snowflake/cli/_plugins/git/manager.py +1 -11
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +4 -0
- snowflake/cli/_plugins/nativeapp/commands.py +3 -4
- snowflake/cli/_plugins/nativeapp/entities/application_package.py +1 -1
- snowflake/cli/_plugins/nativeapp/release_channel/commands.py +1 -2
- snowflake/cli/_plugins/nativeapp/version/commands.py +1 -2
- snowflake/cli/_plugins/project/commands.py +61 -10
- snowflake/cli/_plugins/project/manager.py +20 -1
- snowflake/cli/_plugins/snowpark/common.py +23 -11
- snowflake/cli/_plugins/snowpark/snowpark_entity.py +13 -5
- snowflake/cli/_plugins/snowpark/snowpark_entity_model.py +10 -2
- snowflake/cli/_plugins/spcs/image_registry/commands.py +2 -2
- snowflake/cli/_plugins/spcs/image_registry/manager.py +2 -2
- snowflake/cli/_plugins/sql/commands.py +49 -1
- snowflake/cli/_plugins/sql/manager.py +14 -4
- snowflake/cli/_plugins/sql/repl.py +4 -0
- snowflake/cli/_plugins/stage/commands.py +30 -11
- snowflake/cli/_plugins/stage/diff.py +2 -0
- snowflake/cli/_plugins/stage/manager.py +79 -55
- snowflake/cli/_plugins/streamlit/streamlit_entity.py +17 -30
- snowflake/cli/api/artifacts/upload.py +1 -1
- snowflake/cli/api/cli_global_context.py +5 -14
- snowflake/cli/api/commands/decorators.py +7 -0
- snowflake/cli/api/commands/flags.py +12 -0
- snowflake/cli/api/commands/snow_typer.py +23 -2
- snowflake/cli/api/config.py +9 -5
- snowflake/cli/api/connections.py +1 -0
- snowflake/cli/api/entities/common.py +16 -13
- snowflake/cli/api/entities/utils.py +15 -9
- snowflake/cli/api/feature_flags.py +2 -5
- snowflake/cli/api/output/formats.py +6 -0
- snowflake/cli/api/output/types.py +48 -2
- snowflake/cli/api/rendering/sql_templates.py +67 -11
- snowflake/cli/api/stage_path.py +37 -5
- {snowflake_cli-3.9.0.dist-info → snowflake_cli-3.10.0.dist-info}/METADATA +45 -12
- {snowflake_cli-3.9.0.dist-info → snowflake_cli-3.10.0.dist-info}/RECORD +47 -48
- snowflake/cli/_plugins/project/feature_flags.py +0 -22
- {snowflake_cli-3.9.0.dist-info → snowflake_cli-3.10.0.dist-info}/WHEEL +0 -0
- {snowflake_cli-3.9.0.dist-info → snowflake_cli-3.10.0.dist-info}/entry_points.txt +0 -0
- {snowflake_cli-3.9.0.dist-info → snowflake_cli-3.10.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -18,7 +18,6 @@ import typer
|
|
|
18
18
|
from snowflake.cli._plugins.object.command_aliases import add_object_command_aliases
|
|
19
19
|
from snowflake.cli._plugins.object.commands import scope_option
|
|
20
20
|
from snowflake.cli._plugins.object.manager import ObjectManager
|
|
21
|
-
from snowflake.cli._plugins.project.feature_flags import FeatureFlag
|
|
22
21
|
from snowflake.cli._plugins.project.manager import ProjectManager
|
|
23
22
|
from snowflake.cli._plugins.project.project_entity_model import (
|
|
24
23
|
ProjectEntityModel,
|
|
@@ -26,6 +25,7 @@ from snowflake.cli._plugins.project.project_entity_model import (
|
|
|
26
25
|
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
27
26
|
from snowflake.cli.api.commands.decorators import with_project_definition
|
|
28
27
|
from snowflake.cli.api.commands.flags import (
|
|
28
|
+
IfExistsOption,
|
|
29
29
|
IfNotExistsOption,
|
|
30
30
|
OverrideableOption,
|
|
31
31
|
PruneOption,
|
|
@@ -39,8 +39,13 @@ from snowflake.cli.api.commands.utils import get_entity_for_operation
|
|
|
39
39
|
from snowflake.cli.api.console.console import cli_console
|
|
40
40
|
from snowflake.cli.api.constants import ObjectType
|
|
41
41
|
from snowflake.cli.api.exceptions import CliError
|
|
42
|
+
from snowflake.cli.api.feature_flags import FeatureFlag
|
|
42
43
|
from snowflake.cli.api.identifiers import FQN
|
|
43
|
-
from snowflake.cli.api.output.types import
|
|
44
|
+
from snowflake.cli.api.output.types import (
|
|
45
|
+
MessageResult,
|
|
46
|
+
QueryJsonValueResult,
|
|
47
|
+
QueryResult,
|
|
48
|
+
)
|
|
44
49
|
|
|
45
50
|
app = SnowTyperFactory(
|
|
46
51
|
name="project",
|
|
@@ -52,7 +57,7 @@ project_identifier = identifier_argument(sf_object="project", example="MY_PROJEC
|
|
|
52
57
|
version_flag = typer.Option(
|
|
53
58
|
None,
|
|
54
59
|
"--version",
|
|
55
|
-
help="Version of the project to use. If not specified default version is used",
|
|
60
|
+
help="Version of the project to use. If not specified default version is used. For names containing '$', use single quotes to prevent shell expansion (e.g., 'VERSION$1').",
|
|
56
61
|
show_default=False,
|
|
57
62
|
)
|
|
58
63
|
variables_flag = variables_option(
|
|
@@ -67,7 +72,6 @@ configuration_flag = typer.Option(
|
|
|
67
72
|
from_option = OverrideableOption(
|
|
68
73
|
None,
|
|
69
74
|
"--from",
|
|
70
|
-
help="Create a new version using given stage instead of uploading local files.",
|
|
71
75
|
show_default=False,
|
|
72
76
|
)
|
|
73
77
|
|
|
@@ -77,7 +81,7 @@ add_object_command_aliases(
|
|
|
77
81
|
object_type=ObjectType.PROJECT,
|
|
78
82
|
name_argument=project_identifier,
|
|
79
83
|
like_option=like_option(
|
|
80
|
-
help_example='`list --like "my%"` lists all projects that begin with
|
|
84
|
+
help_example='`list --like "my%"` lists all projects that begin with "my"'
|
|
81
85
|
),
|
|
82
86
|
scope_option=scope_option(help_example="`list --in database my_db`"),
|
|
83
87
|
ommit_commands=["create", "describe"],
|
|
@@ -88,6 +92,9 @@ add_object_command_aliases(
|
|
|
88
92
|
def execute(
|
|
89
93
|
identifier: FQN = project_identifier,
|
|
90
94
|
version: Optional[str] = version_flag,
|
|
95
|
+
from_stage: Optional[str] = from_option(
|
|
96
|
+
help="Execute project from given stage instead of using a specific version."
|
|
97
|
+
),
|
|
91
98
|
variables: Optional[List[str]] = variables_flag,
|
|
92
99
|
configuration: Optional[str] = configuration_flag,
|
|
93
100
|
**options,
|
|
@@ -95,19 +102,26 @@ def execute(
|
|
|
95
102
|
"""
|
|
96
103
|
Executes a project.
|
|
97
104
|
"""
|
|
105
|
+
if version and from_stage:
|
|
106
|
+
raise CliError("--version and --from are mutually exclusive.")
|
|
107
|
+
|
|
98
108
|
result = ProjectManager().execute(
|
|
99
109
|
project_name=identifier,
|
|
100
110
|
configuration=configuration,
|
|
101
111
|
version=version,
|
|
112
|
+
from_stage=from_stage,
|
|
102
113
|
variables=variables,
|
|
103
114
|
)
|
|
104
|
-
return
|
|
115
|
+
return QueryJsonValueResult(result)
|
|
105
116
|
|
|
106
117
|
|
|
107
118
|
@app.command(requires_connection=True)
|
|
108
119
|
def dry_run(
|
|
109
120
|
identifier: FQN = project_identifier,
|
|
110
121
|
version: Optional[str] = version_flag,
|
|
122
|
+
from_stage: Optional[str] = from_option(
|
|
123
|
+
help="Execute project from given stage instead of using a specific version."
|
|
124
|
+
),
|
|
111
125
|
variables: Optional[List[str]] = variables_flag,
|
|
112
126
|
configuration: Optional[str] = configuration_flag,
|
|
113
127
|
**options,
|
|
@@ -115,14 +129,18 @@ def dry_run(
|
|
|
115
129
|
"""
|
|
116
130
|
Validates a project.
|
|
117
131
|
"""
|
|
132
|
+
if version and from_stage:
|
|
133
|
+
raise CliError("--version and --from are mutually exclusive.")
|
|
134
|
+
|
|
118
135
|
result = ProjectManager().execute(
|
|
119
136
|
project_name=identifier,
|
|
120
137
|
configuration=configuration,
|
|
121
138
|
version=version,
|
|
139
|
+
from_stage=from_stage,
|
|
122
140
|
dry_run=True,
|
|
123
141
|
variables=variables,
|
|
124
142
|
)
|
|
125
|
-
return
|
|
143
|
+
return QueryJsonValueResult(result)
|
|
126
144
|
|
|
127
145
|
|
|
128
146
|
@app.command(requires_connection=True)
|
|
@@ -177,7 +195,9 @@ def create(
|
|
|
177
195
|
@with_project_definition()
|
|
178
196
|
def add_version(
|
|
179
197
|
entity_id: str = entity_argument("project"),
|
|
180
|
-
_from: Optional[str] = from_option(
|
|
198
|
+
_from: Optional[str] = from_option(
|
|
199
|
+
help="Create a new version using given stage instead of uploading local files."
|
|
200
|
+
),
|
|
181
201
|
_alias: Optional[str] = typer.Option(
|
|
182
202
|
None, "--alias", help="Alias for the version.", show_default=False
|
|
183
203
|
),
|
|
@@ -203,7 +223,7 @@ def add_version(
|
|
|
203
223
|
om = ObjectManager()
|
|
204
224
|
if not om.object_exists(object_type="project", fqn=project.fqn):
|
|
205
225
|
raise CliError(
|
|
206
|
-
f"Project '{project.fqn}' does not exist. Use `project create` command first"
|
|
226
|
+
f"Project '{project.fqn}' does not exist. Use `project create` command first."
|
|
207
227
|
)
|
|
208
228
|
ProjectManager().add_version(
|
|
209
229
|
project=project,
|
|
@@ -214,7 +234,7 @@ def add_version(
|
|
|
214
234
|
)
|
|
215
235
|
alias_str = "" if _alias is None else f"'{_alias}' "
|
|
216
236
|
return MessageResult(
|
|
217
|
-
f"New project version {alias_str}added to project '{project.fqn}'"
|
|
237
|
+
f"New project version {alias_str}added to project '{project.fqn}'."
|
|
218
238
|
)
|
|
219
239
|
|
|
220
240
|
|
|
@@ -229,3 +249,34 @@ def list_versions(
|
|
|
229
249
|
pm = ProjectManager()
|
|
230
250
|
results = pm.list_versions(project_name=identifier)
|
|
231
251
|
return QueryResult(results)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
@app.command(requires_connection=True)
|
|
255
|
+
def drop_version(
|
|
256
|
+
identifier: FQN = project_identifier,
|
|
257
|
+
version_name: str = typer.Argument(
|
|
258
|
+
help="Name or alias of the version to drop. For names containing '$', use single quotes to prevent shell expansion (e.g., 'VERSION$1').",
|
|
259
|
+
show_default=False,
|
|
260
|
+
),
|
|
261
|
+
if_exists: bool = IfExistsOption(help="Do nothing if the version does not exist."),
|
|
262
|
+
**options,
|
|
263
|
+
):
|
|
264
|
+
"""
|
|
265
|
+
Drops a version from the project.
|
|
266
|
+
"""
|
|
267
|
+
# Detect potential shell expansion issues
|
|
268
|
+
if version_name and version_name.upper() == "VERSION":
|
|
269
|
+
cli_console.warning(
|
|
270
|
+
f"Version name '{version_name}' might be truncated due to shell expansion. "
|
|
271
|
+
f"If you meant to use a version like 'VERSION$1', try using single quotes: 'VERSION$1'."
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
pm = ProjectManager()
|
|
275
|
+
pm.drop_version(
|
|
276
|
+
project_name=identifier,
|
|
277
|
+
version_name=version_name,
|
|
278
|
+
if_exists=if_exists,
|
|
279
|
+
)
|
|
280
|
+
return MessageResult(
|
|
281
|
+
f"Version '{version_name}' dropped from project '{identifier}'."
|
|
282
|
+
)
|
|
@@ -33,6 +33,7 @@ class ProjectManager(SqlExecutionMixin):
|
|
|
33
33
|
project_name: FQN,
|
|
34
34
|
configuration: str | None = None,
|
|
35
35
|
version: str | None = None,
|
|
36
|
+
from_stage: str | None = None,
|
|
36
37
|
variables: List[str] | None = None,
|
|
37
38
|
dry_run: bool = False,
|
|
38
39
|
):
|
|
@@ -47,6 +48,9 @@ class ProjectManager(SqlExecutionMixin):
|
|
|
47
48
|
).removeprefix(" using")
|
|
48
49
|
if version:
|
|
49
50
|
query += f" WITH VERSION {version}"
|
|
51
|
+
elif from_stage:
|
|
52
|
+
stage_path = StagePath.from_stage_str(from_stage)
|
|
53
|
+
query += f" FROM {stage_path.absolute_path()}"
|
|
50
54
|
if dry_run:
|
|
51
55
|
query += " DRY_RUN=TRUE"
|
|
52
56
|
return self.execute_query(query=query)
|
|
@@ -72,7 +76,7 @@ class ProjectManager(SqlExecutionMixin):
|
|
|
72
76
|
stage_path = StagePath.from_stage_str(from_stage)
|
|
73
77
|
query = f"ALTER PROJECT {project_name.identifier} ADD VERSION"
|
|
74
78
|
if alias:
|
|
75
|
-
query += f
|
|
79
|
+
query += f" IF NOT EXISTS {alias}"
|
|
76
80
|
query += f" FROM {stage_path.absolute_path(at_prefix=True)}"
|
|
77
81
|
if comment:
|
|
78
82
|
query += f" COMMENT = '{comment}'"
|
|
@@ -113,3 +117,18 @@ class ProjectManager(SqlExecutionMixin):
|
|
|
113
117
|
def list_versions(self, project_name: FQN):
|
|
114
118
|
query = f"SHOW VERSIONS IN PROJECT {project_name.identifier}"
|
|
115
119
|
return self.execute_query(query=query)
|
|
120
|
+
|
|
121
|
+
def drop_version(
|
|
122
|
+
self,
|
|
123
|
+
project_name: FQN,
|
|
124
|
+
version_name: str,
|
|
125
|
+
if_exists: bool = False,
|
|
126
|
+
):
|
|
127
|
+
"""
|
|
128
|
+
Drops a version from the project.
|
|
129
|
+
"""
|
|
130
|
+
query = f"ALTER PROJECT {project_name.identifier} DROP VERSION"
|
|
131
|
+
if if_exists:
|
|
132
|
+
query += " IF EXISTS"
|
|
133
|
+
query += f" {version_name}"
|
|
134
|
+
return self.execute_query(query=query)
|
|
@@ -79,7 +79,6 @@ class SnowparkObjectManager(SqlExecutionMixin):
|
|
|
79
79
|
) -> str:
|
|
80
80
|
entity.imports.extend(artifact_files)
|
|
81
81
|
imports = [f"'{x}'" for x in entity.imports]
|
|
82
|
-
packages_list = ",".join(f"'{p}'" for p in snowflake_dependencies)
|
|
83
82
|
|
|
84
83
|
object_type = entity.get_type()
|
|
85
84
|
|
|
@@ -91,7 +90,6 @@ class SnowparkObjectManager(SqlExecutionMixin):
|
|
|
91
90
|
f"runtime_version={entity.runtime or DEFAULT_RUNTIME}",
|
|
92
91
|
f"imports=({', '.join(imports)})",
|
|
93
92
|
f"handler='{entity.handler}'",
|
|
94
|
-
f"packages=({packages_list})",
|
|
95
93
|
]
|
|
96
94
|
|
|
97
95
|
if entity.external_access_integrations:
|
|
@@ -100,17 +98,24 @@ class SnowparkObjectManager(SqlExecutionMixin):
|
|
|
100
98
|
if entity.secrets:
|
|
101
99
|
query.append(entity.get_secrets_sql())
|
|
102
100
|
|
|
103
|
-
if
|
|
104
|
-
|
|
101
|
+
if entity.artifact_repository_packages and entity.packages:
|
|
102
|
+
raise UsageError(
|
|
103
|
+
"You cannot specify both artifact_repository_packages and packages.",
|
|
104
|
+
)
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
106
|
+
packages_list = snowflake_dependencies.copy()
|
|
107
|
+
if entity.artifact_repository and (
|
|
108
|
+
entity.artifact_repository_packages or entity.packages
|
|
109
|
+
):
|
|
110
|
+
if entity.artifact_repository_packages:
|
|
111
|
+
packages_list.extend(entity.artifact_repository_packages)
|
|
112
|
+
else:
|
|
113
|
+
packages_list.extend(entity.packages)
|
|
114
|
+
query.append(
|
|
115
|
+
f"ARTIFACT_REPOSITORY= {entity.artifact_repository}",
|
|
113
116
|
)
|
|
117
|
+
packages = [f"'{item}'" for item in packages_list]
|
|
118
|
+
query.append(f"packages=({','.join(packages)})")
|
|
114
119
|
|
|
115
120
|
if entity.resource_constraint:
|
|
116
121
|
constraints = ",".join(
|
|
@@ -118,6 +123,9 @@ class SnowparkObjectManager(SqlExecutionMixin):
|
|
|
118
123
|
)
|
|
119
124
|
query.append(f"RESOURCE_CONSTRAINT=({constraints})")
|
|
120
125
|
|
|
126
|
+
if isinstance(entity, ProcedureEntityModel) and entity.execute_as_caller:
|
|
127
|
+
query.append("execute as caller")
|
|
128
|
+
|
|
121
129
|
return self.execute_query("\n".join(query))
|
|
122
130
|
|
|
123
131
|
def deploy_entity(
|
|
@@ -215,6 +223,10 @@ def _check_if_replace_is_required(
|
|
|
215
223
|
)
|
|
216
224
|
return True
|
|
217
225
|
|
|
226
|
+
if entity.packages != resource_json.get("artifact_repository_packages", None):
|
|
227
|
+
log.info("Packages do not match. Replacing the %s", object_type)
|
|
228
|
+
return True
|
|
229
|
+
|
|
218
230
|
if isinstance(entity, ProcedureEntityModel):
|
|
219
231
|
if resource_json.get("execute as", "OWNER") != (
|
|
220
232
|
"CALLER" if entity.execute_as_caller else "OWNER"
|
|
@@ -173,20 +173,28 @@ class SnowparkEntity(EntityBase[Generic[T]]):
|
|
|
173
173
|
if self.model.secrets:
|
|
174
174
|
query.append(self.model.get_secrets_sql())
|
|
175
175
|
|
|
176
|
-
if self.model.
|
|
177
|
-
|
|
176
|
+
if self.model.artifact_repository and (
|
|
177
|
+
self.model.artifact_repository_packages or self.model.packages
|
|
178
|
+
):
|
|
179
|
+
if self.model.artifact_repository_packages:
|
|
180
|
+
packages = [
|
|
181
|
+
f"'{item}'" for item in self.model.artifact_repository_packages
|
|
182
|
+
]
|
|
183
|
+
else:
|
|
184
|
+
packages = [f"'{item}'" for item in self.model.packages]
|
|
178
185
|
|
|
179
|
-
if self.model.artifact_repository and self.model.artifact_repository_packages:
|
|
180
|
-
packages = [f"'{item}'" for item in self.model.artifact_repository_packages]
|
|
181
186
|
query.extend(
|
|
182
187
|
[
|
|
183
188
|
f"ARTIFACT_REPOSITORY= {self.model.artifact_repository} ",
|
|
184
|
-
f"
|
|
189
|
+
f"PACKAGES=({','.join(packages)})",
|
|
185
190
|
]
|
|
186
191
|
)
|
|
187
192
|
if self.model.resource_constraint:
|
|
188
193
|
query.append(self._get_resource_constraints_sql())
|
|
189
194
|
|
|
195
|
+
if self.model.type == "procedure" and self.model.execute_as_caller:
|
|
196
|
+
query.append("EXECUTE AS CALLER")
|
|
197
|
+
|
|
190
198
|
return "\n".join(query)
|
|
191
199
|
|
|
192
200
|
def get_execute_sql(self, execution_arguments: List[str] | None = None):
|
|
@@ -54,6 +54,9 @@ class SnowparkEntityModel(
|
|
|
54
54
|
default=None, title="Artifact repository to be used"
|
|
55
55
|
)
|
|
56
56
|
artifact_repository_packages: Optional[List[str]] = Field(
|
|
57
|
+
default=None, title="Alias for packages"
|
|
58
|
+
)
|
|
59
|
+
packages: Optional[List[str]] = Field(
|
|
57
60
|
default=None, title="Packages to be installed from artifact repository"
|
|
58
61
|
)
|
|
59
62
|
|
|
@@ -91,9 +94,14 @@ class SnowparkEntityModel(
|
|
|
91
94
|
def check_artifact_repository(cls, values: dict) -> dict:
|
|
92
95
|
artifact_repository = values.get("artifact_repository")
|
|
93
96
|
artifact_repository_packages = values.get("artifact_repository_packages")
|
|
94
|
-
|
|
97
|
+
packages = values.get("packages")
|
|
98
|
+
if artifact_repository_packages and packages:
|
|
99
|
+
raise ValueError(
|
|
100
|
+
"You cannot specify both artifact_repository_packages and packages.",
|
|
101
|
+
)
|
|
102
|
+
if (artifact_repository_packages or packages) and not artifact_repository:
|
|
95
103
|
raise ValueError(
|
|
96
|
-
"You specified
|
|
104
|
+
"You specified packages / artifact_repository_packages without setting artifact_repository.",
|
|
97
105
|
)
|
|
98
106
|
return values
|
|
99
107
|
|
|
@@ -64,10 +64,10 @@ def url(private_link: bool = PrivateLinkOption, **options) -> MessageResult:
|
|
|
64
64
|
|
|
65
65
|
|
|
66
66
|
@app.command(requires_connection=True)
|
|
67
|
-
def login(**options) -> MessageResult:
|
|
67
|
+
def login(private_link: bool = PrivateLinkOption, **options) -> MessageResult:
|
|
68
68
|
"""
|
|
69
69
|
Logs in to the account image registry with the current user's credentials through Docker.
|
|
70
70
|
|
|
71
71
|
Must be called from a role that can view at least one image repository in the image registry.
|
|
72
72
|
"""
|
|
73
|
-
return MessageResult(RegistryManager().docker_registry_login().strip())
|
|
73
|
+
return MessageResult(RegistryManager().docker_registry_login(private_link).strip())
|
|
@@ -101,8 +101,8 @@ class RegistryManager(SqlExecutionMixin):
|
|
|
101
101
|
sample_repository_url = f"//{sample_repository_url}"
|
|
102
102
|
return urlparse(sample_repository_url).netloc
|
|
103
103
|
|
|
104
|
-
def docker_registry_login(self) -> str:
|
|
105
|
-
registry_url = self.get_registry_url()
|
|
104
|
+
def docker_registry_login(self, private_link: bool = False) -> str:
|
|
105
|
+
registry_url = self.get_registry_url(private_link)
|
|
106
106
|
token = self.get_token()
|
|
107
107
|
command = [
|
|
108
108
|
"docker",
|
|
@@ -15,11 +15,13 @@
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
17
|
import sys
|
|
18
|
+
from enum import Enum
|
|
18
19
|
from logging import getLogger
|
|
19
20
|
from pathlib import Path
|
|
20
21
|
from typing import List, Optional
|
|
21
22
|
|
|
22
23
|
import typer
|
|
24
|
+
from click import UsageError
|
|
23
25
|
from snowflake.cli._plugins.sql.manager import SqlManager
|
|
24
26
|
from snowflake.cli.api.commands.decorators import with_project_definition
|
|
25
27
|
from snowflake.cli.api.commands.flags import (
|
|
@@ -34,6 +36,7 @@ from snowflake.cli.api.output.types import (
|
|
|
34
36
|
MultipleResults,
|
|
35
37
|
QueryResult,
|
|
36
38
|
)
|
|
39
|
+
from snowflake.cli.api.rendering.sql_templates import SQLTemplateSyntaxConfig
|
|
37
40
|
|
|
38
41
|
logger = getLogger(__name__)
|
|
39
42
|
# simple Typer with defaults because it won't become a command group as it contains only one command
|
|
@@ -46,6 +49,37 @@ SourceOption = OverrideableOption(
|
|
|
46
49
|
)
|
|
47
50
|
|
|
48
51
|
|
|
52
|
+
class _EnabledTemplating(str, Enum):
|
|
53
|
+
LEGACY = "LEGACY"
|
|
54
|
+
STANDARD = "STANDARD"
|
|
55
|
+
JINJA = "JINJA"
|
|
56
|
+
ALL = "ALL"
|
|
57
|
+
NONE = "NONE"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _parse_template_syntax_config(
|
|
61
|
+
enabled_syntaxes: List[_EnabledTemplating],
|
|
62
|
+
) -> SQLTemplateSyntaxConfig:
|
|
63
|
+
if (
|
|
64
|
+
_EnabledTemplating.ALL in enabled_syntaxes
|
|
65
|
+
or _EnabledTemplating.NONE in enabled_syntaxes
|
|
66
|
+
) and len(enabled_syntaxes) > 1:
|
|
67
|
+
raise UsageError(
|
|
68
|
+
"ALL and NONE template syntax options should not be used with other options."
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
if _EnabledTemplating.ALL in enabled_syntaxes:
|
|
72
|
+
return SQLTemplateSyntaxConfig(True, True, True)
|
|
73
|
+
if _EnabledTemplating.NONE in enabled_syntaxes:
|
|
74
|
+
return SQLTemplateSyntaxConfig(False, False, False)
|
|
75
|
+
|
|
76
|
+
result = SQLTemplateSyntaxConfig()
|
|
77
|
+
result.enable_legacy_syntax = _EnabledTemplating.LEGACY in enabled_syntaxes
|
|
78
|
+
result.enable_standard_syntax = _EnabledTemplating.STANDARD in enabled_syntaxes
|
|
79
|
+
result.enable_jinja_syntax = _EnabledTemplating.JINJA in enabled_syntaxes
|
|
80
|
+
return result
|
|
81
|
+
|
|
82
|
+
|
|
49
83
|
@app.command(name="sql", requires_connection=True, no_args_is_help=False)
|
|
50
84
|
@with_project_definition(is_optional=True)
|
|
51
85
|
def execute_sql(
|
|
@@ -83,6 +117,12 @@ def execute_sql(
|
|
|
83
117
|
flag_value=False,
|
|
84
118
|
is_flag=True,
|
|
85
119
|
),
|
|
120
|
+
enabled_templating: List[_EnabledTemplating] = typer.Option(
|
|
121
|
+
[_EnabledTemplating.LEGACY, _EnabledTemplating.STANDARD],
|
|
122
|
+
"--enable-templating",
|
|
123
|
+
help="Syntax used to resolve variables before passing queries to Snowflake.",
|
|
124
|
+
case_sensitive=False,
|
|
125
|
+
),
|
|
86
126
|
**options,
|
|
87
127
|
) -> CommandResult:
|
|
88
128
|
"""
|
|
@@ -100,6 +140,8 @@ def execute_sql(
|
|
|
100
140
|
if data_override:
|
|
101
141
|
data = {v.key: v.value for v in parse_key_value_variables(data_override)}
|
|
102
142
|
|
|
143
|
+
template_syntax_config = _parse_template_syntax_config(enabled_templating)
|
|
144
|
+
|
|
103
145
|
retain_comments = bool(retain_comments)
|
|
104
146
|
single_transaction = bool(single_transaction)
|
|
105
147
|
std_in = bool(std_in)
|
|
@@ -116,7 +158,12 @@ def execute_sql(
|
|
|
116
158
|
raise CliArgumentError("single transaction cannot be used with REPL")
|
|
117
159
|
from snowflake.cli._plugins.sql.repl import Repl
|
|
118
160
|
|
|
119
|
-
Repl(
|
|
161
|
+
Repl(
|
|
162
|
+
SqlManager(),
|
|
163
|
+
data=data,
|
|
164
|
+
retain_comments=retain_comments,
|
|
165
|
+
template_syntax_config=template_syntax_config,
|
|
166
|
+
).run()
|
|
120
167
|
sys.exit(0)
|
|
121
168
|
|
|
122
169
|
manager = SqlManager()
|
|
@@ -128,6 +175,7 @@ def execute_sql(
|
|
|
128
175
|
data=data,
|
|
129
176
|
retain_comments=retain_comments,
|
|
130
177
|
single_transaction=single_transaction,
|
|
178
|
+
template_syntax_config=template_syntax_config,
|
|
131
179
|
)
|
|
132
180
|
if expected_results_cnt == 0:
|
|
133
181
|
# case expected if input only scheduled async queries
|
|
@@ -32,7 +32,10 @@ from snowflake.cli.api.cli_global_context import get_cli_context
|
|
|
32
32
|
from snowflake.cli.api.console import cli_console
|
|
33
33
|
from snowflake.cli.api.exceptions import CliArgumentError, CliSqlError
|
|
34
34
|
from snowflake.cli.api.output.types import CollectionResult
|
|
35
|
-
from snowflake.cli.api.rendering.sql_templates import
|
|
35
|
+
from snowflake.cli.api.rendering.sql_templates import (
|
|
36
|
+
SQLTemplateSyntaxConfig,
|
|
37
|
+
snowflake_sql_jinja_render,
|
|
38
|
+
)
|
|
36
39
|
from snowflake.cli.api.secure_path import SecurePath
|
|
37
40
|
from snowflake.cli.api.sql_execution import SqlExecutionMixin, VerboseCursor
|
|
38
41
|
from snowflake.connector.cursor import SnowflakeCursor
|
|
@@ -51,6 +54,7 @@ class SqlManager(SqlExecutionMixin):
|
|
|
51
54
|
data: Dict | None = None,
|
|
52
55
|
retain_comments: bool = False,
|
|
53
56
|
single_transaction: bool = False,
|
|
57
|
+
template_syntax_config: SQLTemplateSyntaxConfig = SQLTemplateSyntaxConfig(),
|
|
54
58
|
) -> Tuple[ExpectedResultsCount, Iterable[SnowflakeCursor]]:
|
|
55
59
|
"""Reads, transforms and execute statements from input.
|
|
56
60
|
|
|
@@ -62,9 +66,15 @@ class SqlManager(SqlExecutionMixin):
|
|
|
62
66
|
"""
|
|
63
67
|
query = sys.stdin.read() if std_in else query
|
|
64
68
|
|
|
65
|
-
stmt_operators =
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
stmt_operators = []
|
|
70
|
+
if template_syntax_config.enable_legacy_syntax:
|
|
71
|
+
stmt_operators.append(transpile_snowsql_templates)
|
|
72
|
+
stmt_operators.append(
|
|
73
|
+
partial(
|
|
74
|
+
snowflake_sql_jinja_render,
|
|
75
|
+
template_syntax_config=template_syntax_config,
|
|
76
|
+
data=data,
|
|
77
|
+
)
|
|
68
78
|
)
|
|
69
79
|
remove_comments = not retain_comments
|
|
70
80
|
|
|
@@ -13,6 +13,7 @@ from snowflake.cli._plugins.sql.manager import SqlManager
|
|
|
13
13
|
from snowflake.cli.api.cli_global_context import get_cli_context_manager
|
|
14
14
|
from snowflake.cli.api.console import cli_console
|
|
15
15
|
from snowflake.cli.api.output.types import MultipleResults, QueryResult
|
|
16
|
+
from snowflake.cli.api.rendering.sql_templates import SQLTemplateSyntaxConfig
|
|
16
17
|
from snowflake.cli.api.secure_path import SecurePath
|
|
17
18
|
from snowflake.connector.config_manager import CONFIG_MANAGER
|
|
18
19
|
from snowflake.connector.cursor import SnowflakeCursor
|
|
@@ -35,6 +36,7 @@ class Repl:
|
|
|
35
36
|
sql_manager: SqlManager,
|
|
36
37
|
data: dict | None = None,
|
|
37
38
|
retain_comments: bool = False,
|
|
39
|
+
template_syntax_config: SQLTemplateSyntaxConfig = SQLTemplateSyntaxConfig(),
|
|
38
40
|
):
|
|
39
41
|
"""Requires a `SqlManager` instance to execute queries.
|
|
40
42
|
|
|
@@ -46,6 +48,7 @@ class Repl:
|
|
|
46
48
|
setattr(get_cli_context_manager(), "is_repl", True)
|
|
47
49
|
self._data = data or {}
|
|
48
50
|
self._retain_comments = retain_comments
|
|
51
|
+
self._template_syntax_config = template_syntax_config
|
|
49
52
|
self._history = FileHistory(HISTORY_FILE)
|
|
50
53
|
self._lexer = PygmentsLexer(CliLexer)
|
|
51
54
|
self._completer = cli_completer
|
|
@@ -155,6 +158,7 @@ class Repl:
|
|
|
155
158
|
std_in=False,
|
|
156
159
|
data=self._data,
|
|
157
160
|
retain_comments=self._retain_comments,
|
|
161
|
+
template_syntax_config=self._template_syntax_config,
|
|
158
162
|
)
|
|
159
163
|
return cursors
|
|
160
164
|
|
|
@@ -48,7 +48,6 @@ from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
|
|
|
48
48
|
from snowflake.cli.api.console import cli_console
|
|
49
49
|
from snowflake.cli.api.constants import ObjectType
|
|
50
50
|
from snowflake.cli.api.identifiers import FQN
|
|
51
|
-
from snowflake.cli.api.output.formats import OutputFormat
|
|
52
51
|
from snowflake.cli.api.output.types import (
|
|
53
52
|
CollectionResult,
|
|
54
53
|
CommandResult,
|
|
@@ -74,7 +73,7 @@ add_object_command_aliases(
|
|
|
74
73
|
object_type=ObjectType.STAGE,
|
|
75
74
|
name_argument=StageNameArgument,
|
|
76
75
|
like_option=like_option(
|
|
77
|
-
help_example='`list --like "my%"` lists all stages that begin with
|
|
76
|
+
help_example='`list --like "my%"` lists all stages that begin with "my"',
|
|
78
77
|
),
|
|
79
78
|
scope_option=scope_option(help_example="`list --in database my_db`"),
|
|
80
79
|
)
|
|
@@ -98,7 +97,7 @@ def copy(
|
|
|
98
97
|
show_default=False,
|
|
99
98
|
),
|
|
100
99
|
destination_path: str = typer.Argument(
|
|
101
|
-
help="Target directory path for copy operation.
|
|
100
|
+
help="Target directory path for copy operation.",
|
|
102
101
|
show_default=False,
|
|
103
102
|
),
|
|
104
103
|
overwrite: bool = typer.Option(
|
|
@@ -117,19 +116,24 @@ def copy(
|
|
|
117
116
|
default=False,
|
|
118
117
|
help="Specifies whether Snowflake uses gzip to compress files during upload. Ignored when downloading.",
|
|
119
118
|
),
|
|
119
|
+
refresh: bool = typer.Option(
|
|
120
|
+
default=False,
|
|
121
|
+
help="Specifies whether ALTER STAGE {name} REFRESH should be executed after uploading.",
|
|
122
|
+
),
|
|
120
123
|
**options,
|
|
121
124
|
) -> CommandResult:
|
|
122
125
|
"""
|
|
123
|
-
Copies all files from
|
|
124
|
-
to and downloading files from the stage.
|
|
126
|
+
Copies all files from source path to target directory. This works for uploading
|
|
127
|
+
to and downloading files from the stage, and copying between named stages.
|
|
125
128
|
"""
|
|
126
129
|
is_get = is_stage_path(source_path)
|
|
127
130
|
is_put = is_stage_path(destination_path)
|
|
128
131
|
|
|
129
132
|
if is_get and is_put:
|
|
130
|
-
|
|
131
|
-
|
|
133
|
+
cursor = StageManager().copy_files(
|
|
134
|
+
source_path=source_path, destination_path=destination_path
|
|
132
135
|
)
|
|
136
|
+
return QueryResult(cursor)
|
|
133
137
|
if not is_get and not is_put:
|
|
134
138
|
raise click.ClickException(
|
|
135
139
|
"Both source and target path are local. This operation is not supported."
|
|
@@ -149,6 +153,7 @@ def copy(
|
|
|
149
153
|
parallel=parallel,
|
|
150
154
|
overwrite=overwrite,
|
|
151
155
|
auto_compress=auto_compress,
|
|
156
|
+
refresh=refresh,
|
|
152
157
|
)
|
|
153
158
|
|
|
154
159
|
|
|
@@ -160,12 +165,19 @@ def stage_create(
|
|
|
160
165
|
"--encryption",
|
|
161
166
|
help="Type of encryption supported for all files stored on the stage.",
|
|
162
167
|
),
|
|
168
|
+
enable_directory: bool = typer.Option(
|
|
169
|
+
False,
|
|
170
|
+
"--enable-directory",
|
|
171
|
+
help="Specifies whether directory support is enabled for the stage.",
|
|
172
|
+
),
|
|
163
173
|
**options,
|
|
164
174
|
) -> CommandResult:
|
|
165
175
|
"""
|
|
166
176
|
Creates a named stage if it does not already exist.
|
|
167
177
|
"""
|
|
168
|
-
cursor = StageManager().create(
|
|
178
|
+
cursor = StageManager().create(
|
|
179
|
+
fqn=stage_name, encryption=encryption, enable_directory=enable_directory
|
|
180
|
+
)
|
|
169
181
|
return SingleQueryResult(cursor)
|
|
170
182
|
|
|
171
183
|
|
|
@@ -206,7 +218,7 @@ def stage_diff(
|
|
|
206
218
|
local_root=Path(folder_name),
|
|
207
219
|
stage_path=StageManager.stage_path_parts_from_str(stage_name), # noqa: SLF001
|
|
208
220
|
)
|
|
209
|
-
if get_cli_context().output_format
|
|
221
|
+
if get_cli_context().output_format.is_json:
|
|
210
222
|
return ObjectResult(diff.to_dict())
|
|
211
223
|
else:
|
|
212
224
|
print_diff_to_console(diff)
|
|
@@ -264,6 +276,7 @@ def _put(
|
|
|
264
276
|
parallel: int,
|
|
265
277
|
overwrite: bool,
|
|
266
278
|
auto_compress: bool,
|
|
279
|
+
refresh: bool,
|
|
267
280
|
):
|
|
268
281
|
if recursive and not source_path.is_file():
|
|
269
282
|
cursor_generator = StageManager().put_recursive(
|
|
@@ -273,7 +286,7 @@ def _put(
|
|
|
273
286
|
parallel=parallel,
|
|
274
287
|
auto_compress=auto_compress,
|
|
275
288
|
)
|
|
276
|
-
|
|
289
|
+
output = CollectionResult(cursor_generator)
|
|
277
290
|
else:
|
|
278
291
|
cursor = StageManager().put(
|
|
279
292
|
local_path=source_path.resolve(),
|
|
@@ -282,4 +295,10 @@ def _put(
|
|
|
282
295
|
parallel=parallel,
|
|
283
296
|
auto_compress=auto_compress,
|
|
284
297
|
)
|
|
285
|
-
|
|
298
|
+
output = QueryResult(cursor)
|
|
299
|
+
|
|
300
|
+
if refresh:
|
|
301
|
+
StageManager().refresh(
|
|
302
|
+
StageManager.stage_path_parts_from_str(destination_path).stage_name
|
|
303
|
+
)
|
|
304
|
+
return output
|