snowflake-cli 3.9.1__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.
Files changed (46) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/_app/printing.py +53 -13
  3. snowflake/cli/_app/snow_connector.py +1 -0
  4. snowflake/cli/_app/telemetry.py +2 -0
  5. snowflake/cli/_app/version_check.py +73 -6
  6. snowflake/cli/_plugins/cortex/commands.py +8 -3
  7. snowflake/cli/_plugins/cortex/manager.py +24 -20
  8. snowflake/cli/_plugins/dbt/commands.py +5 -2
  9. snowflake/cli/_plugins/git/manager.py +1 -11
  10. snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +4 -0
  11. snowflake/cli/_plugins/nativeapp/commands.py +3 -4
  12. snowflake/cli/_plugins/nativeapp/entities/application_package.py +1 -1
  13. snowflake/cli/_plugins/nativeapp/release_channel/commands.py +1 -2
  14. snowflake/cli/_plugins/nativeapp/version/commands.py +1 -2
  15. snowflake/cli/_plugins/project/commands.py +54 -7
  16. snowflake/cli/_plugins/project/manager.py +20 -1
  17. snowflake/cli/_plugins/snowpark/common.py +23 -11
  18. snowflake/cli/_plugins/snowpark/snowpark_entity.py +13 -5
  19. snowflake/cli/_plugins/snowpark/snowpark_entity_model.py +10 -2
  20. snowflake/cli/_plugins/sql/commands.py +49 -1
  21. snowflake/cli/_plugins/sql/manager.py +14 -4
  22. snowflake/cli/_plugins/sql/repl.py +4 -0
  23. snowflake/cli/_plugins/stage/commands.py +30 -11
  24. snowflake/cli/_plugins/stage/diff.py +2 -0
  25. snowflake/cli/_plugins/stage/manager.py +79 -55
  26. snowflake/cli/_plugins/streamlit/streamlit_entity.py +17 -30
  27. snowflake/cli/api/artifacts/upload.py +1 -1
  28. snowflake/cli/api/cli_global_context.py +5 -14
  29. snowflake/cli/api/commands/decorators.py +7 -0
  30. snowflake/cli/api/commands/flags.py +12 -0
  31. snowflake/cli/api/commands/snow_typer.py +23 -2
  32. snowflake/cli/api/config.py +9 -5
  33. snowflake/cli/api/connections.py +1 -0
  34. snowflake/cli/api/entities/common.py +16 -13
  35. snowflake/cli/api/entities/utils.py +15 -9
  36. snowflake/cli/api/feature_flags.py +2 -5
  37. snowflake/cli/api/output/formats.py +6 -0
  38. snowflake/cli/api/output/types.py +48 -2
  39. snowflake/cli/api/rendering/sql_templates.py +67 -11
  40. snowflake/cli/api/stage_path.py +37 -5
  41. {snowflake_cli-3.9.1.dist-info → snowflake_cli-3.10.0.dist-info}/METADATA +45 -12
  42. {snowflake_cli-3.9.1.dist-info → snowflake_cli-3.10.0.dist-info}/RECORD +45 -46
  43. snowflake/cli/_plugins/project/feature_flags.py +0 -22
  44. {snowflake_cli-3.9.1.dist-info → snowflake_cli-3.10.0.dist-info}/WHEEL +0 -0
  45. {snowflake_cli-3.9.1.dist-info → snowflake_cli-3.10.0.dist-info}/entry_points.txt +0 -0
  46. {snowflake_cli-3.9.1.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,6 +39,7 @@ 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
44
  from snowflake.cli.api.output.types import (
44
45
  MessageResult,
@@ -56,7 +57,7 @@ project_identifier = identifier_argument(sf_object="project", example="MY_PROJEC
56
57
  version_flag = typer.Option(
57
58
  None,
58
59
  "--version",
59
- 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').",
60
61
  show_default=False,
61
62
  )
62
63
  variables_flag = variables_option(
@@ -71,7 +72,6 @@ configuration_flag = typer.Option(
71
72
  from_option = OverrideableOption(
72
73
  None,
73
74
  "--from",
74
- help="Create a new version using given stage instead of uploading local files.",
75
75
  show_default=False,
76
76
  )
77
77
 
@@ -81,7 +81,7 @@ add_object_command_aliases(
81
81
  object_type=ObjectType.PROJECT,
82
82
  name_argument=project_identifier,
83
83
  like_option=like_option(
84
- help_example='`list --like "my%"` lists all projects that begin with my'
84
+ help_example='`list --like "my%"` lists all projects that begin with "my"'
85
85
  ),
86
86
  scope_option=scope_option(help_example="`list --in database my_db`"),
87
87
  ommit_commands=["create", "describe"],
@@ -92,6 +92,9 @@ add_object_command_aliases(
92
92
  def execute(
93
93
  identifier: FQN = project_identifier,
94
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
+ ),
95
98
  variables: Optional[List[str]] = variables_flag,
96
99
  configuration: Optional[str] = configuration_flag,
97
100
  **options,
@@ -99,10 +102,14 @@ def execute(
99
102
  """
100
103
  Executes a project.
101
104
  """
105
+ if version and from_stage:
106
+ raise CliError("--version and --from are mutually exclusive.")
107
+
102
108
  result = ProjectManager().execute(
103
109
  project_name=identifier,
104
110
  configuration=configuration,
105
111
  version=version,
112
+ from_stage=from_stage,
106
113
  variables=variables,
107
114
  )
108
115
  return QueryJsonValueResult(result)
@@ -112,6 +119,9 @@ def execute(
112
119
  def dry_run(
113
120
  identifier: FQN = project_identifier,
114
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
+ ),
115
125
  variables: Optional[List[str]] = variables_flag,
116
126
  configuration: Optional[str] = configuration_flag,
117
127
  **options,
@@ -119,10 +129,14 @@ def dry_run(
119
129
  """
120
130
  Validates a project.
121
131
  """
132
+ if version and from_stage:
133
+ raise CliError("--version and --from are mutually exclusive.")
134
+
122
135
  result = ProjectManager().execute(
123
136
  project_name=identifier,
124
137
  configuration=configuration,
125
138
  version=version,
139
+ from_stage=from_stage,
126
140
  dry_run=True,
127
141
  variables=variables,
128
142
  )
@@ -181,7 +195,9 @@ def create(
181
195
  @with_project_definition()
182
196
  def add_version(
183
197
  entity_id: str = entity_argument("project"),
184
- _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
+ ),
185
201
  _alias: Optional[str] = typer.Option(
186
202
  None, "--alias", help="Alias for the version.", show_default=False
187
203
  ),
@@ -207,7 +223,7 @@ def add_version(
207
223
  om = ObjectManager()
208
224
  if not om.object_exists(object_type="project", fqn=project.fqn):
209
225
  raise CliError(
210
- 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."
211
227
  )
212
228
  ProjectManager().add_version(
213
229
  project=project,
@@ -218,7 +234,7 @@ def add_version(
218
234
  )
219
235
  alias_str = "" if _alias is None else f"'{_alias}' "
220
236
  return MessageResult(
221
- f"New project version {alias_str}added to project '{project.fqn}'"
237
+ f"New project version {alias_str}added to project '{project.fqn}'."
222
238
  )
223
239
 
224
240
 
@@ -233,3 +249,34 @@ def list_versions(
233
249
  pm = ProjectManager()
234
250
  results = pm.list_versions(project_name=identifier)
235
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' IF NOT EXISTS "{alias}"'
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 isinstance(entity, ProcedureEntityModel) and entity.execute_as_caller:
104
- query.append("execute as caller")
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
- if entity.artifact_repository and entity.artifact_repository_packages:
107
- packages = [f"'{item}'" for item in entity.artifact_repository_packages]
108
- query.extend(
109
- [
110
- f"ARTIFACT_REPOSITORY= {entity.artifact_repository}",
111
- f"ARTIFACT_REPOSITORY_PACKAGES=({','.join(packages)})",
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.type == "procedure" and self.model.execute_as_caller:
177
- query.append("EXECUTE AS CALLER")
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"ARTIFACT_REPOSITORY_PACKAGES=({','.join(packages)})",
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
- if artifact_repository_packages and not artifact_repository:
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 Artifact_repository_packages without setting Artifact_repository.",
104
+ "You specified packages / artifact_repository_packages without setting artifact_repository.",
97
105
  )
98
106
  return values
99
107
 
@@ -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(SqlManager(), data=data, retain_comments=retain_comments).run()
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 snowflake_sql_jinja_render
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
- transpile_snowsql_templates,
67
- partial(snowflake_sql_jinja_render, data=data),
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 my',
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. Should be stage if source is local or local if source is stage.",
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 target path to target directory. This works for both uploading
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
- raise click.ClickException(
131
- "Both source and target path are remote. This operation is not supported."
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(fqn=stage_name, encryption=encryption)
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 == OutputFormat.JSON:
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
- return CollectionResult(cursor_generator)
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
- return QueryResult(cursor)
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
@@ -239,6 +239,7 @@ def sync_local_diff_with_stage(
239
239
  deploy_root_path: Path,
240
240
  diff_result: DiffResult,
241
241
  stage_full_path: str,
242
+ force_overwrite: bool = False,
242
243
  ):
243
244
  """
244
245
  Syncs a given local directory's contents with a Snowflake stage, including removing old files, and re-uploading modified and new files.
@@ -267,6 +268,7 @@ def sync_local_diff_with_stage(
267
268
  deploy_root_path=deploy_root_path,
268
269
  stage_paths=diff_result.only_local,
269
270
  role=role,
271
+ overwrite=force_overwrite,
270
272
  )
271
273
  except Exception as err:
272
274
  # Could be ProgrammingError or IntegrityError from SnowflakeCursor