snowflake-cli-labs 2.6.0rc0__py3-none-any.whl → 2.7.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 (90) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/api/cli_global_context.py +9 -0
  3. snowflake/cli/api/commands/decorators.py +9 -4
  4. snowflake/cli/api/commands/execution_metadata.py +40 -0
  5. snowflake/cli/api/commands/flags.py +45 -36
  6. snowflake/cli/api/commands/project_initialisation.py +5 -2
  7. snowflake/cli/api/commands/snow_typer.py +20 -9
  8. snowflake/cli/api/config.py +4 -0
  9. snowflake/cli/api/errno.py +27 -0
  10. snowflake/cli/api/feature_flags.py +5 -0
  11. snowflake/cli/api/identifiers.py +20 -3
  12. snowflake/cli/api/output/types.py +9 -0
  13. snowflake/cli/api/project/definition_manager.py +2 -2
  14. snowflake/cli/api/project/project_verification.py +23 -0
  15. snowflake/cli/api/project/schemas/entities/application_entity.py +50 -0
  16. snowflake/cli/api/project/schemas/entities/application_package_entity.py +63 -0
  17. snowflake/cli/api/project/schemas/entities/common.py +85 -0
  18. snowflake/cli/api/project/schemas/entities/entities.py +30 -0
  19. snowflake/cli/api/project/schemas/project_definition.py +114 -22
  20. snowflake/cli/api/project/schemas/streamlit/streamlit.py +5 -4
  21. snowflake/cli/api/project/schemas/template.py +77 -0
  22. snowflake/cli/{plugins/nativeapp/errno.py → api/rendering/__init__.py} +0 -2
  23. snowflake/cli/api/{utils/rendering.py → rendering/jinja.py} +3 -48
  24. snowflake/cli/api/rendering/project_definition_templates.py +39 -0
  25. snowflake/cli/api/rendering/project_templates.py +97 -0
  26. snowflake/cli/api/rendering/sql_templates.py +56 -0
  27. snowflake/cli/api/rest_api.py +84 -25
  28. snowflake/cli/api/sql_execution.py +40 -1
  29. snowflake/cli/api/utils/definition_rendering.py +8 -5
  30. snowflake/cli/app/cli_app.py +0 -2
  31. snowflake/cli/app/commands_registration/builtin_plugins.py +4 -0
  32. snowflake/cli/app/dev/docs/project_definition_docs_generator.py +2 -2
  33. snowflake/cli/app/loggers.py +10 -6
  34. snowflake/cli/app/printing.py +17 -7
  35. snowflake/cli/app/snow_connector.py +9 -1
  36. snowflake/cli/app/telemetry.py +41 -2
  37. snowflake/cli/plugins/connection/commands.py +13 -3
  38. snowflake/cli/plugins/connection/util.py +73 -18
  39. snowflake/cli/plugins/cortex/commands.py +2 -1
  40. snowflake/cli/plugins/git/commands.py +20 -4
  41. snowflake/cli/plugins/git/manager.py +44 -20
  42. snowflake/cli/plugins/init/__init__.py +13 -0
  43. snowflake/cli/plugins/init/commands.py +242 -0
  44. snowflake/cli/plugins/init/plugin_spec.py +30 -0
  45. snowflake/cli/plugins/nativeapp/codegen/artifact_processor.py +40 -0
  46. snowflake/cli/plugins/nativeapp/codegen/compiler.py +57 -27
  47. snowflake/cli/plugins/nativeapp/codegen/sandbox.py +99 -10
  48. snowflake/cli/plugins/nativeapp/codegen/setup/native_app_setup_processor.py +172 -0
  49. snowflake/cli/plugins/nativeapp/codegen/setup/setup_driver.py.source +56 -0
  50. snowflake/cli/plugins/nativeapp/codegen/snowpark/python_processor.py +21 -21
  51. snowflake/cli/plugins/nativeapp/commands.py +100 -6
  52. snowflake/cli/plugins/nativeapp/constants.py +0 -6
  53. snowflake/cli/plugins/nativeapp/exceptions.py +37 -12
  54. snowflake/cli/plugins/nativeapp/init.py +1 -1
  55. snowflake/cli/plugins/nativeapp/manager.py +114 -39
  56. snowflake/cli/plugins/nativeapp/project_model.py +8 -4
  57. snowflake/cli/plugins/nativeapp/run_processor.py +117 -102
  58. snowflake/cli/plugins/nativeapp/teardown_processor.py +7 -2
  59. snowflake/cli/plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +146 -0
  60. snowflake/cli/plugins/nativeapp/version/commands.py +19 -3
  61. snowflake/cli/plugins/nativeapp/version/version_processor.py +11 -3
  62. snowflake/cli/plugins/object/commands.py +1 -1
  63. snowflake/cli/plugins/object/manager.py +2 -15
  64. snowflake/cli/plugins/snowpark/commands.py +34 -26
  65. snowflake/cli/plugins/snowpark/common.py +88 -27
  66. snowflake/cli/plugins/snowpark/manager.py +16 -5
  67. snowflake/cli/plugins/snowpark/models.py +6 -0
  68. snowflake/cli/plugins/sql/commands.py +3 -5
  69. snowflake/cli/plugins/sql/manager.py +1 -1
  70. snowflake/cli/plugins/stage/commands.py +2 -2
  71. snowflake/cli/plugins/stage/diff.py +27 -64
  72. snowflake/cli/plugins/stage/manager.py +290 -86
  73. snowflake/cli/plugins/stage/md5.py +160 -0
  74. snowflake/cli/plugins/streamlit/commands.py +20 -6
  75. snowflake/cli/plugins/streamlit/manager.py +46 -32
  76. snowflake/cli/plugins/workspace/__init__.py +13 -0
  77. snowflake/cli/plugins/workspace/commands.py +35 -0
  78. snowflake/cli/plugins/workspace/plugin_spec.py +30 -0
  79. snowflake/cli/templates/default_snowpark/app/__init__.py +0 -13
  80. snowflake/cli/templates/default_snowpark/app/common.py +0 -15
  81. snowflake/cli/templates/default_snowpark/app/functions.py +0 -14
  82. snowflake/cli/templates/default_snowpark/app/procedures.py +0 -14
  83. snowflake/cli/templates/default_streamlit/common/hello.py +0 -15
  84. snowflake/cli/templates/default_streamlit/pages/my_page.py +0 -14
  85. snowflake/cli/templates/default_streamlit/streamlit_app.py +0 -14
  86. {snowflake_cli_labs-2.6.0rc0.dist-info → snowflake_cli_labs-2.7.0.dist-info}/METADATA +7 -6
  87. {snowflake_cli_labs-2.6.0rc0.dist-info → snowflake_cli_labs-2.7.0.dist-info}/RECORD +90 -69
  88. {snowflake_cli_labs-2.6.0rc0.dist-info → snowflake_cli_labs-2.7.0.dist-info}/WHEEL +0 -0
  89. {snowflake_cli_labs-2.6.0rc0.dist-info → snowflake_cli_labs-2.7.0.dist-info}/entry_points.txt +0 -0
  90. {snowflake_cli_labs-2.6.0rc0.dist-info → snowflake_cli_labs-2.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -18,30 +18,39 @@ from pathlib import Path
18
18
  from textwrap import dedent
19
19
  from typing import Optional
20
20
 
21
+ import jinja2
21
22
  import typer
22
23
  from click import UsageError
24
+ from snowflake.cli.api.cli_global_context import cli_context
23
25
  from snowflake.cli.api.console import cli_console as cc
26
+ from snowflake.cli.api.errno import (
27
+ APPLICATION_NO_LONGER_AVAILABLE,
28
+ APPLICATION_OWNS_EXTERNAL_OBJECTS,
29
+ CANNOT_UPGRADE_FROM_LOOSE_FILES_TO_VERSION,
30
+ CANNOT_UPGRADE_FROM_VERSION_TO_LOOSE_FILES,
31
+ NOT_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
32
+ ONLY_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
33
+ )
24
34
  from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
25
35
  from snowflake.cli.api.project.schemas.native_app.native_app import NativeApp
26
36
  from snowflake.cli.api.project.util import (
27
37
  identifier_to_show_like_pattern,
28
38
  unquote_identifier,
29
39
  )
40
+ from snowflake.cli.api.rendering.sql_templates import (
41
+ get_sql_cli_jinja_env,
42
+ )
30
43
  from snowflake.cli.api.utils.cursor import find_all_rows
31
- from snowflake.cli.api.utils.rendering import snowflake_sql_jinja_render
32
44
  from snowflake.cli.plugins.nativeapp.artifacts import BundleMap
33
45
  from snowflake.cli.plugins.nativeapp.constants import (
34
46
  ALLOWED_SPECIAL_COMMENTS,
35
47
  COMMENT_COL,
36
- ERROR_MESSAGE_093079,
37
- ERROR_MESSAGE_093128,
38
- LOOSE_FILES_MAGIC_VERSION,
39
48
  PATCH_COL,
40
49
  SPECIAL_COMMENT,
41
50
  VERSION_COL,
42
51
  )
43
52
  from snowflake.cli.plugins.nativeapp.exceptions import (
44
- ApplicationAlreadyExistsError,
53
+ ApplicationCreatedExternallyError,
45
54
  ApplicationPackageDoesNotExistError,
46
55
  )
47
56
  from snowflake.cli.plugins.nativeapp.manager import (
@@ -60,11 +69,11 @@ from snowflake.connector.cursor import DictCursor, SnowflakeCursor
60
69
 
61
70
  # Reasons why an `alter application ... upgrade` might fail
62
71
  UPGRADE_RESTRICTION_CODES = {
63
- 93044, # Cannot upgrade dev mode application from loose stage files to version
64
- 93045, # Cannot upgrade dev mode application from version to loose stage files
65
- 93046, # Operation only permitted on dev mode application
66
- 93055, # Operation not supported on dev mode application
67
- 93079, # App package access lost
72
+ CANNOT_UPGRADE_FROM_LOOSE_FILES_TO_VERSION,
73
+ CANNOT_UPGRADE_FROM_VERSION_TO_LOOSE_FILES,
74
+ ONLY_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
75
+ NOT_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
76
+ APPLICATION_NO_LONGER_AVAILABLE,
68
77
  }
69
78
 
70
79
 
@@ -118,11 +127,9 @@ class SameAccountInstallMethod:
118
127
  """Raise an exception if we cannot proceed with install given the pre-existing application object"""
119
128
 
120
129
  if self._requires_created_by_cli:
121
- if show_app_row[COMMENT_COL] not in ALLOWED_SPECIAL_COMMENTS or (
122
- show_app_row[VERSION_COL] != LOOSE_FILES_MAGIC_VERSION
123
- ):
130
+ if show_app_row[COMMENT_COL] not in ALLOWED_SPECIAL_COMMENTS:
124
131
  # this application object was not created by this tooling
125
- raise ApplicationAlreadyExistsError(app.app_name)
132
+ raise ApplicationCreatedExternallyError(app.app_name)
126
133
 
127
134
  # expected owner
128
135
  ensure_correct_owner(row=show_app_row, role=app.app_role, obj_name=app.app_name)
@@ -132,36 +139,46 @@ class NativeAppRunProcessor(NativeAppManager, NativeAppCommandProcessor):
132
139
  def __init__(self, project_definition: NativeApp, project_root: Path):
133
140
  super().__init__(project_definition, project_root)
134
141
 
135
- def _execute_sql_script(self, sql_script_path):
142
+ def _execute_sql_script(
143
+ self, script_content: str, database_name: Optional[str] = None
144
+ ):
136
145
  """
137
- Executing the SQL script in the provided file path after expanding template variables.
138
- "use warehouse" and "use database" will be executed first if they are set in definition file or in the current connection.
146
+ Executing the provided SQL script content.
147
+ This assumes that a relevant warehouse is already active.
148
+ If database_name is passed in, it will be used first.
139
149
  """
140
- with open(sql_script_path) as f:
141
- sql_script = f.read()
142
- try:
143
- if self.application_warehouse:
144
- self._execute_query(f"use warehouse {self.application_warehouse}")
145
- if self._conn.database:
146
- self._execute_query(f"use database {self._conn.database}")
147
- sql_script = snowflake_sql_jinja_render(content=sql_script)
148
- self._execute_queries(sql_script)
149
- except ProgrammingError as err:
150
- generic_sql_error_handler(err)
150
+ try:
151
+ if database_name is not None:
152
+ self._execute_query(f"use database {database_name}")
153
+
154
+ self._execute_queries(script_content)
155
+ except ProgrammingError as err:
156
+ generic_sql_error_handler(err)
151
157
 
152
158
  def _execute_post_deploy_hooks(self):
153
159
  post_deploy_script_hooks = self.app_post_deploy_hooks
154
160
  if post_deploy_script_hooks:
155
161
  with cc.phase("Executing application post-deploy actions"):
162
+ sql_scripts_paths = []
156
163
  for hook in post_deploy_script_hooks:
157
164
  if hook.sql_script:
158
- cc.step(f"Executing SQL script: {hook.sql_script}")
159
- self._execute_sql_script(hook.sql_script)
165
+ sql_scripts_paths.append(hook.sql_script)
160
166
  else:
161
167
  raise ValueError(
162
168
  f"Unsupported application post-deploy hook type: {hook}"
163
169
  )
164
170
 
171
+ env = get_sql_cli_jinja_env(
172
+ loader=jinja2.loaders.FileSystemLoader(self.project_root)
173
+ )
174
+ scripts_content_list = self._expand_script_templates(
175
+ env, cli_context.template_context, sql_scripts_paths
176
+ )
177
+
178
+ for index, sql_script_path in enumerate(sql_scripts_paths):
179
+ cc.step(f"Executing SQL script: {sql_script_path}")
180
+ self._execute_sql_script(scripts_content_list[index], self.app_name)
181
+
165
182
  def get_all_existing_versions(self) -> SnowflakeCursor:
166
183
  """
167
184
  Get all existing versions, if defined, for an application package.
@@ -223,7 +240,7 @@ class NativeAppRunProcessor(NativeAppManager, NativeAppCommandProcessor):
223
240
  f"The following objects are owned by application {self.app_name} and need to be dropped:\n{application_objects_str}"
224
241
  )
225
242
  except ProgrammingError as err:
226
- if err.errno != 93079 and ERROR_MESSAGE_093079 not in err.msg:
243
+ if err.errno != APPLICATION_NO_LONGER_AVAILABLE:
227
244
  generic_sql_error_handler(err)
228
245
  cc.warning(
229
246
  "The application owns other objects but they could not be determined."
@@ -242,10 +259,12 @@ class NativeAppRunProcessor(NativeAppManager, NativeAppCommandProcessor):
242
259
  )
243
260
  raise typer.Exit(1)
244
261
  try:
262
+ cascade_msg = " (cascade)" if cascade else ""
263
+ cc.step(f"Dropping application object {self.app_name}{cascade_msg}.")
245
264
  cascade_sql = " cascade" if cascade else ""
246
265
  self._execute_query(f"drop application {self.app_name}{cascade_sql}")
247
266
  except ProgrammingError as err:
248
- if (err.errno == 93128 or ERROR_MESSAGE_093128 in err.msg) and not cascade:
267
+ if err.errno == APPLICATION_OWNS_EXTERNAL_OBJECTS and not cascade:
249
268
  # We need to cascade the deletion, let's try again (only if we didn't try with cascade already)
250
269
  return self.drop_application_before_upgrade(
251
270
  policy, is_interactive, cascade=True
@@ -262,89 +281,85 @@ class NativeAppRunProcessor(NativeAppManager, NativeAppCommandProcessor):
262
281
  with self.use_role(self.app_role):
263
282
 
264
283
  # 1. Need to use a warehouse to create an application object
265
- try:
266
- if self.application_warehouse:
267
- self._execute_query(f"use warehouse {self.application_warehouse}")
268
- except ProgrammingError as err:
269
- generic_sql_error_handler(
270
- err=err, role=self.app_role, warehouse=self.application_warehouse
271
- )
284
+ with self.use_warehouse(self.application_warehouse):
285
+
286
+ # 2. Check for an existing application by the same name
287
+ show_app_row = self.get_existing_app_info()
288
+
289
+ # 3. If existing application is found, perform a few validations and upgrade the application object.
290
+ if show_app_row:
272
291
 
273
- # 2. Check for an existing application by the same name
274
- show_app_row = self.get_existing_app_info()
292
+ install_method.ensure_app_usable(self._na_project, show_app_row)
275
293
 
276
- # 3. If existing application is found, perform a few validations and upgrade the application object.
277
- if show_app_row:
294
+ # If all the above checks are in order, proceed to upgrade
295
+ try:
296
+ cc.step(
297
+ f"Upgrading existing application object {self.app_name}."
298
+ )
299
+ using_clause = install_method.using_clause(self._na_project)
300
+ self._execute_query(
301
+ f"alter application {self.app_name} upgrade {using_clause}"
302
+ )
278
303
 
279
- install_method.ensure_app_usable(self._na_project, show_app_row)
304
+ if install_method.is_dev_mode:
305
+ # if debug_mode is present (controlled), ensure it is up-to-date
306
+ if self.debug_mode is not None:
307
+ self._execute_query(
308
+ f"alter application {self.app_name} set debug_mode = {self.debug_mode}"
309
+ )
310
+
311
+ # hooks always executed after a create or upgrade
312
+ self._execute_post_deploy_hooks()
313
+ return
314
+
315
+ except ProgrammingError as err:
316
+ if err.errno not in UPGRADE_RESTRICTION_CODES:
317
+ generic_sql_error_handler(err=err)
318
+ else: # The existing application object was created from a different process.
319
+ cc.warning(err.msg)
320
+ self.drop_application_before_upgrade(policy, is_interactive)
321
+
322
+ # 4. With no (more) existing application objects, create an application object using the release directives
323
+ cc.step(f"Creating new application object {self.app_name} in account.")
324
+
325
+ if self.app_role != self.package_role:
326
+ with self.use_role(self.package_role):
327
+ self._execute_query(
328
+ f"grant install, develop on application package {self.package_name} to role {self.app_role}"
329
+ )
330
+ self._execute_query(
331
+ f"grant usage on schema {self.package_name}.{self.stage_schema} to role {self.app_role}"
332
+ )
333
+ self._execute_query(
334
+ f"grant read on stage {self.stage_fqn} to role {self.app_role}"
335
+ )
280
336
 
281
- # If all the above checks are in order, proceed to upgrade
282
337
  try:
283
- cc.step(f"Upgrading existing application object {self.app_name}.")
338
+ # by default, applications are created in debug mode when possible;
339
+ # this can be overridden in the project definition
340
+ debug_mode_clause = ""
341
+ if install_method.is_dev_mode:
342
+ initial_debug_mode = (
343
+ self.debug_mode if self.debug_mode is not None else True
344
+ )
345
+ debug_mode_clause = f"debug_mode = {initial_debug_mode}"
346
+
284
347
  using_clause = install_method.using_clause(self._na_project)
285
348
  self._execute_query(
286
- f"alter application {self.app_name} upgrade {using_clause}"
349
+ dedent(
350
+ f"""\
351
+ create application {self.app_name}
352
+ from application package {self.package_name} {using_clause} {debug_mode_clause}
353
+ comment = {SPECIAL_COMMENT}
354
+ """
355
+ )
287
356
  )
288
357
 
289
- if install_method.is_dev_mode:
290
- # if debug_mode is present (controlled), ensure it is up-to-date
291
- if self.debug_mode is not None:
292
- self._execute_query(
293
- f"alter application {self.app_name} set debug_mode = {self.debug_mode}"
294
- )
295
-
296
358
  # hooks always executed after a create or upgrade
297
359
  self._execute_post_deploy_hooks()
298
- return
299
360
 
300
361
  except ProgrammingError as err:
301
- if err.errno not in UPGRADE_RESTRICTION_CODES:
302
- generic_sql_error_handler(err=err)
303
- else: # The existing application object was created from a different process.
304
- cc.warning(err.msg)
305
- self.drop_application_before_upgrade(policy, is_interactive)
306
-
307
- # 4. With no (more) existing application objects, create an application object using the release directives
308
- cc.step(f"Creating new application object {self.app_name} in account.")
309
-
310
- if self.app_role != self.package_role:
311
- with self.use_role(self.package_role):
312
- self._execute_query(
313
- f"grant install, develop on application package {self.package_name} to role {self.app_role}"
314
- )
315
- self._execute_query(
316
- f"grant usage on schema {self.package_name}.{self.stage_schema} to role {self.app_role}"
317
- )
318
- self._execute_query(
319
- f"grant read on stage {self.stage_fqn} to role {self.app_role}"
320
- )
321
-
322
- try:
323
- # by default, applications are created in debug mode when possible;
324
- # this can be overridden in the project definition
325
- debug_mode_clause = ""
326
- if install_method.is_dev_mode:
327
- initial_debug_mode = (
328
- self.debug_mode if self.debug_mode is not None else True
329
- )
330
- debug_mode_clause = f"debug_mode = {initial_debug_mode}"
331
-
332
- using_clause = install_method.using_clause(self._na_project)
333
- self._execute_query(
334
- dedent(
335
- f"""\
336
- create application {self.app_name}
337
- from application package {self.package_name} {using_clause} {debug_mode_clause}
338
- comment = {SPECIAL_COMMENT}
339
- """
340
- )
341
- )
342
-
343
- # hooks always executed after a create or upgrade
344
- self._execute_post_deploy_hooks()
345
-
346
- except ProgrammingError as err:
347
- generic_sql_error_handler(err)
362
+ generic_sql_error_handler(err)
348
363
 
349
364
  def process(
350
365
  self,
@@ -20,6 +20,7 @@ from typing import Dict, Optional
20
20
 
21
21
  import typer
22
22
  from snowflake.cli.api.console import cli_console as cc
23
+ from snowflake.cli.api.errno import APPLICATION_NO_LONGER_AVAILABLE
23
24
  from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
24
25
  from snowflake.cli.plugins.nativeapp.constants import (
25
26
  ALLOWED_SPECIAL_COMMENTS,
@@ -28,7 +29,6 @@ from snowflake.cli.plugins.nativeapp.constants import (
28
29
  INTERNAL_DISTRIBUTION,
29
30
  OWNER_COL,
30
31
  )
31
- from snowflake.cli.plugins.nativeapp.errno import APPLICATION_NO_LONGER_AVAILABLE
32
32
  from snowflake.cli.plugins.nativeapp.exceptions import (
33
33
  CouldNotDropApplicationPackageWithVersions,
34
34
  )
@@ -226,8 +226,13 @@ class NativeAppTeardownProcessor(NativeAppManager, NativeAppCommandProcessor):
226
226
  )
227
227
  if show_versions_cursor.rowcount is None:
228
228
  raise SnowflakeSQLExecutionError(show_versions_query)
229
+
229
230
  if show_versions_cursor.rowcount > 0:
230
- raise CouldNotDropApplicationPackageWithVersions()
231
+ # allow dropping a package with versions when --force is set
232
+ if not auto_yes:
233
+ raise CouldNotDropApplicationPackageWithVersions(
234
+ "Drop versions first, or use --force to override."
235
+ )
231
236
 
232
237
  # 4. Check distribution of the existing application package
233
238
  actual_distribution = self.get_app_pkg_distribution_in_snowflake
@@ -0,0 +1,146 @@
1
+ # Copyright (c) 2024 Snowflake Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ from functools import wraps
18
+ from pathlib import Path
19
+ from typing import Any, Dict, List, Optional, Union
20
+
21
+ from click import ClickException
22
+ from snowflake.cli.api.cli_global_context import cli_context, cli_context_manager
23
+ from snowflake.cli.api.project.schemas.entities.application_entity import (
24
+ ApplicationEntity,
25
+ )
26
+ from snowflake.cli.api.project.schemas.entities.application_package_entity import (
27
+ ApplicationPackageEntity,
28
+ )
29
+ from snowflake.cli.api.project.schemas.native_app.application import (
30
+ ApplicationPostDeployHook,
31
+ SqlScriptHookType,
32
+ )
33
+ from snowflake.cli.api.project.schemas.native_app.path_mapping import PathMapping
34
+ from snowflake.cli.api.project.schemas.project_definition import (
35
+ DefinitionV11,
36
+ DefinitionV20,
37
+ )
38
+
39
+
40
+ def _convert_v2_artifact_to_v1_dict(
41
+ v2_artifact: Union[PathMapping, Path]
42
+ ) -> Union[Dict, str]:
43
+ if isinstance(v2_artifact, PathMapping):
44
+ return {
45
+ "src": v2_artifact.src,
46
+ "dest": v2_artifact.dest,
47
+ "processors": v2_artifact.processors,
48
+ }
49
+ return str(v2_artifact)
50
+
51
+
52
+ def _convert_v2_post_deploy_hook_to_v1_scripts(
53
+ v2_post_deploy_hook: ApplicationPostDeployHook,
54
+ ) -> List[str]:
55
+ if isinstance(v2_post_deploy_hook, SqlScriptHookType):
56
+ return v2_post_deploy_hook.sql_script
57
+ raise ValueError(f"Unsupported post deploy hook type: {v2_post_deploy_hook}")
58
+
59
+
60
+ def _pdf_v2_to_v1(v2_definition: DefinitionV20) -> DefinitionV11:
61
+ pdfv1: Dict[str, Any] = {"definition_version": "1.1", "native_app": {}}
62
+
63
+ app_package_definition: ApplicationPackageEntity = None
64
+ app_definition: Optional[ApplicationEntity] = None
65
+
66
+ for key, entity in v2_definition.entities.items():
67
+ if entity.get_type() == ApplicationPackageEntity.get_type():
68
+ if app_package_definition:
69
+ raise ClickException(
70
+ "More than one application package entity exists in the project definition file."
71
+ )
72
+ app_package_definition = entity
73
+ elif entity.get_type() == ApplicationEntity.get_type():
74
+ if app_definition:
75
+ raise ClickException(
76
+ "More than one application entity exists in the project definition file."
77
+ )
78
+ app_definition = entity
79
+ if not app_package_definition:
80
+ raise ClickException(
81
+ "Could not find an application package entity in the project definition file."
82
+ )
83
+
84
+ # NativeApp
85
+ if app_definition and app_definition.name:
86
+ pdfv1["native_app"]["name"] = app_definition.name
87
+ else:
88
+ pdfv1["native_app"]["name"] = app_package_definition.name.split("_pkg_")[0]
89
+ pdfv1["native_app"]["artifacts"] = [
90
+ _convert_v2_artifact_to_v1_dict(a) for a in app_package_definition.artifacts
91
+ ]
92
+ pdfv1["native_app"]["source_stage"] = app_package_definition.stage
93
+ pdfv1["native_app"]["bundle_root"] = str(app_package_definition.bundle_root)
94
+ pdfv1["native_app"]["generated_root"] = str(app_package_definition.generated_root)
95
+ pdfv1["native_app"]["deploy_root"] = str(app_package_definition.deploy_root)
96
+
97
+ # Package
98
+ pdfv1["native_app"]["package"] = {}
99
+ pdfv1["native_app"]["package"]["name"] = app_package_definition.name
100
+ if app_package_definition.distribution:
101
+ pdfv1["native_app"]["package"][
102
+ "distribution"
103
+ ] = app_package_definition.distribution
104
+ if app_package_definition.meta and app_package_definition.meta.post_deploy:
105
+ pdfv1["native_app"]["package"]["scripts"] = [
106
+ _convert_v2_post_deploy_hook_to_v1_scripts(s)
107
+ for s in app_package_definition.meta.post_deploy
108
+ ]
109
+
110
+ # Application
111
+ if app_definition:
112
+ pdfv1["native_app"]["application"] = {}
113
+ pdfv1["native_app"]["application"]["name"] = app_definition.name
114
+ if app_definition.meta and app_definition.meta.role:
115
+ pdfv1["native_app"]["application"]["role"] = app_definition.meta.role
116
+ if app_definition.meta and app_definition.meta.post_deploy:
117
+ pdfv1["native_app"]["application"][
118
+ "post_deploy"
119
+ ] = app_definition.meta.post_deploy
120
+
121
+ # Override the definition object in global context
122
+ return DefinitionV11(**pdfv1)
123
+
124
+
125
+ def nativeapp_definition_v2_to_v1(func):
126
+ """
127
+ A command decorator that attempts to automatically convert a native app project from
128
+ definition v2 to v1.1. Assumes with_project_definition() has already been called.
129
+ The definition object in CliGlobalContext will be replaced with the converted object.
130
+ Exactly one application package entity type is expected, and up to one application
131
+ entity type is expected.
132
+ """
133
+
134
+ @wraps(func)
135
+ def wrapper(*args, **kwargs):
136
+ original_pdf: DefinitionV20 = cli_context.project_definition
137
+ if not original_pdf:
138
+ raise ValueError(
139
+ "Project definition could not be found. The nativeapp_definition_v2_to_v1 command decorator assumes with_project_definition() was called before it."
140
+ )
141
+ if original_pdf.definition_version == "2":
142
+ pdfv1 = _pdf_v2_to_v1(original_pdf)
143
+ cli_context_manager.set_project_definition(pdfv1)
144
+ return func(*args, **kwargs)
145
+
146
+ return wrapper
@@ -25,6 +25,7 @@ from snowflake.cli.api.commands.decorators import (
25
25
  )
26
26
  from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
27
27
  from snowflake.cli.api.output.types import CommandResult, MessageResult, QueryResult
28
+ from snowflake.cli.api.project.project_verification import assert_project_type
28
29
  from snowflake.cli.plugins.nativeapp.common_flags import ForceOption, InteractiveOption
29
30
  from snowflake.cli.plugins.nativeapp.policy import (
30
31
  AllowAlwaysPolicy,
@@ -32,6 +33,9 @@ from snowflake.cli.plugins.nativeapp.policy import (
32
33
  DenyAlwaysPolicy,
33
34
  )
34
35
  from snowflake.cli.plugins.nativeapp.run_processor import NativeAppRunProcessor
36
+ from snowflake.cli.plugins.nativeapp.v2_conversions.v2_to_v1_decorator import (
37
+ nativeapp_definition_v2_to_v1,
38
+ )
35
39
  from snowflake.cli.plugins.nativeapp.version.version_processor import (
36
40
  NativeAppVersionCreateProcessor,
37
41
  NativeAppVersionDropProcessor,
@@ -46,7 +50,8 @@ log = logging.getLogger(__name__)
46
50
 
47
51
 
48
52
  @app.command(requires_connection=True)
49
- @with_project_definition("native_app")
53
+ @with_project_definition()
54
+ @nativeapp_definition_v2_to_v1
50
55
  def create(
51
56
  version: Optional[str] = typer.Argument(
52
57
  None,
@@ -71,6 +76,9 @@ def create(
71
76
  """
72
77
  Adds a new patch to the provided version defined in your application package. If the version does not exist, creates a version with patch 0.
73
78
  """
79
+
80
+ assert_project_type("native_app")
81
+
74
82
  if version is None and patch is not None:
75
83
  raise MissingParameter("Cannot provide a patch without version!")
76
84
 
@@ -107,13 +115,17 @@ def create(
107
115
 
108
116
 
109
117
  @app.command("list", requires_connection=True)
110
- @with_project_definition("native_app")
118
+ @with_project_definition()
119
+ @nativeapp_definition_v2_to_v1
111
120
  def version_list(
112
121
  **options,
113
122
  ) -> CommandResult:
114
123
  """
115
124
  Lists all versions defined in an application package.
116
125
  """
126
+
127
+ assert_project_type("native_app")
128
+
117
129
  processor = NativeAppRunProcessor(
118
130
  project_definition=cli_context.project_definition.native_app,
119
131
  project_root=cli_context.project_root,
@@ -123,7 +135,8 @@ def version_list(
123
135
 
124
136
 
125
137
  @app.command(requires_connection=True)
126
- @with_project_definition("native_app")
138
+ @with_project_definition()
139
+ @nativeapp_definition_v2_to_v1
127
140
  def drop(
128
141
  version: Optional[str] = typer.Argument(
129
142
  None,
@@ -137,6 +150,9 @@ def drop(
137
150
  Drops a version defined in your application package. Versions can either be passed in as an argument to the command or read from the `manifest.yml` file.
138
151
  Dropping patches is not allowed.
139
152
  """
153
+
154
+ assert_project_type("native_app")
155
+
140
156
  is_interactive = False
141
157
  if force:
142
158
  policy = AllowAlwaysPolicy()
@@ -34,6 +34,7 @@ from snowflake.cli.plugins.nativeapp.artifacts import (
34
34
  )
35
35
  from snowflake.cli.plugins.nativeapp.constants import VERSION_COL
36
36
  from snowflake.cli.plugins.nativeapp.exceptions import (
37
+ ApplicationPackageAlreadyExistsError,
37
38
  ApplicationPackageDoesNotExistError,
38
39
  )
39
40
  from snowflake.cli.plugins.nativeapp.manager import (
@@ -64,12 +65,14 @@ def check_index_changes_in_git_repo(
64
65
  # Check if the repo has any changes, including untracked files
65
66
  if repo.is_dirty(untracked_files=True):
66
67
  cc.warning(
67
- "Changes detected in the git repository. (Rerun your command with --skip-git-check flag to ignore this check)"
68
+ "Changes detected in the git repository. "
69
+ "(Rerun your command with --skip-git-check flag to ignore this check)"
68
70
  )
69
71
  repo.git.execute(["git", "status"])
70
72
 
71
73
  user_prompt = (
72
- "You have local changes in this repository that are not part of a previous commit. Do you still want to continue?",
74
+ "You have local changes in this repository that are not part of a previous commit. "
75
+ "Do you still want to continue?"
73
76
  )
74
77
  if not policy.should_proceed(user_prompt):
75
78
  if is_interactive:
@@ -212,7 +215,12 @@ class NativeAppVersionCreateProcessor(NativeAppRunProcessor):
212
215
  is_interactive=is_interactive,
213
216
  )
214
217
 
215
- self.create_app_package()
218
+ try:
219
+ self.create_app_package()
220
+ except ApplicationPackageAlreadyExistsError as e:
221
+ cc.warning(e.message)
222
+ if not policy.should_proceed("Proceed with using this package?"):
223
+ raise typer.Abort() from e
216
224
 
217
225
  with self.use_role(self.package_role):
218
226
  # Now that the application package exists, create shared data
@@ -33,7 +33,7 @@ app = SnowTyperFactory(
33
33
 
34
34
  NameArgument = typer.Argument(help="Name of the object")
35
35
  ObjectArgument = typer.Argument(
36
- help="Type of object. For example table, procedure, streamlit.",
36
+ help="Type of object. For example table, database, compute-pool.",
37
37
  case_sensitive=False,
38
38
  show_default=False,
39
39
  )
@@ -36,16 +36,6 @@ def _get_object_names(object_type: str) -> ObjectNames:
36
36
  return OBJECT_TO_NAMES[object_type]
37
37
 
38
38
 
39
- def _pluralize_object_type(object_type: str) -> str:
40
- """
41
- Pluralize object type without depending on OBJECT_TO_NAMES.
42
- """
43
- if object_type.endswith("y"):
44
- return object_type[:-1].lower() + "ies"
45
- else:
46
- return object_type.lower() + "s"
47
-
48
-
49
39
  class ObjectManager(SqlExecutionMixin):
50
40
  def show(
51
41
  self,
@@ -85,11 +75,8 @@ class ObjectManager(SqlExecutionMixin):
85
75
 
86
76
  def create(self, object_type: str, object_data: Dict[str, Any]) -> str:
87
77
  rest = RestApi(self._conn)
88
- url = rest.determine_url_for_create_query(
89
- plural_object_type=_pluralize_object_type(object_type)
90
- )
91
- if not url:
92
- return f"Create operation for type {object_type} is not supported. Try using `sql -q 'CREATE ...'` command"
78
+ url = rest.determine_url_for_create_query(object_type=object_type)
79
+
93
80
  try:
94
81
  response = rest.send_rest_request(url=url, method="post", data=object_data)
95
82
  # workaround as SnowflakeRestful class ignores some errors, dropping their info and returns {} instead.