snowflake-cli 3.0.1__py3-none-any.whl → 3.1.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 (61) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/_app/cli_app.py +3 -0
  3. snowflake/cli/_app/dev/docs/templates/overview.rst.jinja2 +1 -1
  4. snowflake/cli/_app/dev/docs/templates/usage.rst.jinja2 +2 -2
  5. snowflake/cli/_app/telemetry.py +69 -4
  6. snowflake/cli/_plugins/connection/commands.py +40 -2
  7. snowflake/cli/_plugins/git/commands.py +6 -3
  8. snowflake/cli/_plugins/git/manager.py +5 -0
  9. snowflake/cli/_plugins/nativeapp/artifacts.py +13 -3
  10. snowflake/cli/_plugins/nativeapp/codegen/artifact_processor.py +1 -1
  11. snowflake/cli/_plugins/nativeapp/codegen/compiler.py +7 -0
  12. snowflake/cli/_plugins/nativeapp/codegen/sandbox.py +10 -10
  13. snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +2 -2
  14. snowflake/cli/_plugins/nativeapp/codegen/snowpark/extension_function_utils.py +1 -1
  15. snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +8 -8
  16. snowflake/cli/_plugins/nativeapp/commands.py +135 -186
  17. snowflake/cli/_plugins/nativeapp/entities/application.py +176 -24
  18. snowflake/cli/_plugins/nativeapp/entities/application_package.py +112 -136
  19. snowflake/cli/_plugins/nativeapp/exceptions.py +12 -0
  20. snowflake/cli/_plugins/nativeapp/manager.py +3 -26
  21. snowflake/cli/_plugins/nativeapp/v2_conversions/{v2_to_v1_decorator.py → compat.py} +131 -72
  22. snowflake/cli/_plugins/nativeapp/version/commands.py +30 -29
  23. snowflake/cli/_plugins/nativeapp/version/version_processor.py +1 -43
  24. snowflake/cli/_plugins/snowpark/commands.py +0 -2
  25. snowflake/cli/_plugins/snowpark/common.py +60 -18
  26. snowflake/cli/_plugins/snowpark/package/anaconda_packages.py +2 -2
  27. snowflake/cli/_plugins/snowpark/package/commands.py +0 -2
  28. snowflake/cli/_plugins/snowpark/package_utils.py +27 -38
  29. snowflake/cli/_plugins/snowpark/snowpark_entity_model.py +5 -2
  30. snowflake/cli/_plugins/spcs/image_repository/commands.py +4 -37
  31. snowflake/cli/_plugins/spcs/image_repository/manager.py +4 -1
  32. snowflake/cli/_plugins/spcs/services/commands.py +36 -4
  33. snowflake/cli/_plugins/spcs/services/manager.py +36 -4
  34. snowflake/cli/_plugins/stage/commands.py +8 -3
  35. snowflake/cli/_plugins/stage/diff.py +16 -16
  36. snowflake/cli/_plugins/stage/manager.py +164 -73
  37. snowflake/cli/_plugins/stage/md5.py +1 -1
  38. snowflake/cli/_plugins/workspace/commands.py +21 -1
  39. snowflake/cli/_plugins/workspace/context.py +38 -0
  40. snowflake/cli/_plugins/workspace/manager.py +23 -13
  41. snowflake/cli/api/cli_global_context.py +3 -3
  42. snowflake/cli/api/commands/flags.py +23 -7
  43. snowflake/cli/api/config.py +7 -4
  44. snowflake/cli/api/connections.py +12 -1
  45. snowflake/cli/api/entities/common.py +4 -2
  46. snowflake/cli/api/entities/utils.py +17 -37
  47. snowflake/cli/api/exceptions.py +32 -0
  48. snowflake/cli/api/identifiers.py +8 -0
  49. snowflake/cli/api/project/definition_conversion.py +139 -40
  50. snowflake/cli/api/project/schemas/entities/common.py +11 -0
  51. snowflake/cli/api/project/schemas/project_definition.py +30 -25
  52. snowflake/cli/api/sql_execution.py +5 -7
  53. snowflake/cli/api/stage_path.py +241 -0
  54. snowflake/cli/api/utils/definition_rendering.py +3 -5
  55. {snowflake_cli-3.0.1.dist-info → snowflake_cli-3.1.0.dist-info}/METADATA +11 -11
  56. {snowflake_cli-3.0.1.dist-info → snowflake_cli-3.1.0.dist-info}/RECORD +59 -59
  57. snowflake/cli/_plugins/nativeapp/teardown_processor.py +0 -70
  58. snowflake/cli/_plugins/workspace/action_context.py +0 -18
  59. {snowflake_cli-3.0.1.dist-info → snowflake_cli-3.1.0.dist-info}/WHEEL +0 -0
  60. {snowflake_cli-3.0.1.dist-info → snowflake_cli-3.1.0.dist-info}/entry_points.txt +0 -0
  61. {snowflake_cli-3.0.1.dist-info → snowflake_cli-3.1.0.dist-info}/licenses/LICENSE +0 -0
@@ -41,7 +41,7 @@ from snowflake.cli._plugins.nativeapp.same_account_install_method import (
41
41
  SameAccountInstallMethod,
42
42
  )
43
43
  from snowflake.cli._plugins.nativeapp.utils import needs_confirmation
44
- from snowflake.cli._plugins.workspace.action_context import ActionContext
44
+ from snowflake.cli._plugins.workspace.context import ActionContext
45
45
  from snowflake.cli.api.cli_global_context import get_cli_context
46
46
  from snowflake.cli.api.console.abc import AbstractConsole
47
47
  from snowflake.cli.api.entities.common import EntityBase, get_sql_executor
@@ -53,8 +53,10 @@ from snowflake.cli.api.entities.utils import (
53
53
  )
54
54
  from snowflake.cli.api.errno import (
55
55
  APPLICATION_NO_LONGER_AVAILABLE,
56
+ APPLICATION_OWNS_EXTERNAL_OBJECTS,
56
57
  CANNOT_UPGRADE_FROM_LOOSE_FILES_TO_VERSION,
57
58
  CANNOT_UPGRADE_FROM_VERSION_TO_LOOSE_FILES,
59
+ DOES_NOT_EXIST_OR_NOT_AUTHORIZED,
58
60
  NOT_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
59
61
  ONLY_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
60
62
  )
@@ -70,6 +72,7 @@ from snowflake.cli.api.project.util import (
70
72
  append_test_resource_suffix,
71
73
  extract_schema,
72
74
  identifier_for_url,
75
+ to_identifier,
73
76
  unquote_identifier,
74
77
  )
75
78
  from snowflake.connector import DictCursor, ProgrammingError
@@ -118,7 +121,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
118
121
 
119
122
  def action_deploy(
120
123
  self,
121
- ctx: ActionContext,
124
+ action_ctx: ActionContext,
122
125
  from_release_directive: bool,
123
126
  prune: bool,
124
127
  recursive: bool,
@@ -133,18 +136,21 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
133
136
  **kwargs,
134
137
  ):
135
138
  model = self._entity_model
139
+ workspace_ctx = self._workspace_ctx
136
140
  app_name = model.fqn.identifier
137
141
  debug_mode = model.debug
138
142
  if model.meta:
139
- app_role = model.meta.role or ctx.default_role
140
- app_warehouse = model.meta.warehouse or ctx.default_warehouse
143
+ app_role = model.meta.role or workspace_ctx.default_role
144
+ app_warehouse = model.meta.warehouse or workspace_ctx.default_warehouse
141
145
  post_deploy_hooks = model.meta.post_deploy
142
146
  else:
143
- app_role = ctx.default_role
144
- app_warehouse = ctx.default_warehouse
147
+ app_role = workspace_ctx.default_role
148
+ app_warehouse = workspace_ctx.default_warehouse
145
149
  post_deploy_hooks = None
146
150
 
147
- package_entity: ApplicationPackageEntity = ctx.get_entity(model.from_.target)
151
+ package_entity: ApplicationPackageEntity = action_ctx.get_entity(
152
+ model.from_.target
153
+ )
148
154
  package_model: ApplicationPackageEntityModel = (
149
155
  package_entity._entity_model # noqa: SLF001
150
156
  )
@@ -152,7 +158,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
152
158
  if package_model.meta and package_model.meta.role:
153
159
  package_role = package_model.meta.role
154
160
  else:
155
- package_role = ctx.default_role
161
+ package_role = workspace_ctx.default_role
156
162
 
157
163
  if not stage_fqn:
158
164
  stage_fqn = f"{package_name}.{package_model.stage}"
@@ -169,7 +175,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
169
175
 
170
176
  def deploy_package():
171
177
  package_entity.action_deploy(
172
- ctx=ctx,
178
+ action_ctx=action_ctx,
173
179
  prune=True,
174
180
  recursive=True,
175
181
  paths=[],
@@ -179,9 +185,19 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
179
185
  force=force,
180
186
  )
181
187
 
188
+ def drop_application_before_upgrade(cascade: bool = False):
189
+ self.drop_application_before_upgrade(
190
+ console=workspace_ctx.console,
191
+ app_name=app_name,
192
+ app_role=app_role,
193
+ policy=policy,
194
+ is_interactive=is_interactive,
195
+ cascade=cascade,
196
+ )
197
+
182
198
  self.deploy(
183
- console=ctx.console,
184
- project_root=ctx.project_root,
199
+ console=workspace_ctx.console,
200
+ project_root=workspace_ctx.project_root,
185
201
  app_name=app_name,
186
202
  app_role=app_role,
187
203
  app_warehouse=app_warehouse,
@@ -198,11 +214,12 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
198
214
  patch=patch,
199
215
  post_deploy_hooks=post_deploy_hooks,
200
216
  deploy_package=deploy_package,
217
+ drop_application_before_upgrade=drop_application_before_upgrade,
201
218
  )
202
219
 
203
220
  def action_drop(
204
221
  self,
205
- ctx: ActionContext,
222
+ action_ctx: ActionContext,
206
223
  interactive: bool,
207
224
  force_drop: bool = False,
208
225
  cascade: Optional[bool] = None,
@@ -210,13 +227,14 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
210
227
  **kwargs,
211
228
  ):
212
229
  model = self._entity_model
230
+ workspace_ctx = self._workspace_ctx
213
231
  app_name = model.fqn.identifier
214
232
  if model.meta and model.meta.role:
215
233
  app_role = model.meta.role
216
234
  else:
217
- app_role = ctx.default_role
235
+ app_role = workspace_ctx.default_role
218
236
  self.drop(
219
- console=ctx.console,
237
+ console=workspace_ctx.console,
220
238
  app_name=app_name,
221
239
  app_role=app_role,
222
240
  auto_yes=force_drop,
@@ -224,6 +242,58 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
224
242
  cascade=cascade,
225
243
  )
226
244
 
245
+ def action_events(
246
+ self,
247
+ action_ctx: ActionContext,
248
+ since: str | datetime | None = None,
249
+ until: str | datetime | None = None,
250
+ record_types: list[str] | None = None,
251
+ scopes: list[str] | None = None,
252
+ consumer_org: str = "",
253
+ consumer_account: str = "",
254
+ consumer_app_hash: str = "",
255
+ first: int = -1,
256
+ last: int = -1,
257
+ follow: bool = False,
258
+ interval_seconds: int = 10,
259
+ *args,
260
+ **kwargs,
261
+ ):
262
+ model = self._entity_model
263
+ package_entity: ApplicationPackageEntity = action_ctx.get_entity(
264
+ model.from_.target
265
+ )
266
+ package_model: ApplicationPackageEntityModel = (
267
+ package_entity._entity_model # noqa: SLF001
268
+ )
269
+ if follow:
270
+ return self.stream_events(
271
+ app_name=model.fqn.identifier,
272
+ package_name=package_model.fqn.identifier,
273
+ interval_seconds=interval_seconds,
274
+ since=since,
275
+ record_types=record_types,
276
+ scopes=scopes,
277
+ consumer_org=consumer_org,
278
+ consumer_account=consumer_account,
279
+ consumer_app_hash=consumer_app_hash,
280
+ last=last,
281
+ )
282
+ else:
283
+ return self.get_events(
284
+ app_name=model.fqn.identifier,
285
+ package_name=package_model.fqn.identifier,
286
+ since=since,
287
+ until=until,
288
+ record_types=record_types,
289
+ scopes=scopes,
290
+ consumer_org=consumer_org,
291
+ consumer_account=consumer_account,
292
+ consumer_app_hash=consumer_app_hash,
293
+ first=first,
294
+ last=last,
295
+ )
296
+
227
297
  @classmethod
228
298
  def drop(
229
299
  cls,
@@ -241,7 +311,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
241
311
  needs_confirm = True
242
312
 
243
313
  # 1. If existing application is not found, exit gracefully
244
- show_obj_row = cls.get_existing_app_info(
314
+ show_obj_row = cls.get_existing_app_info_static(
245
315
  app_name=app_name,
246
316
  app_role=app_role,
247
317
  )
@@ -531,7 +601,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
531
601
  with sql_executor.use_warehouse(app_warehouse):
532
602
 
533
603
  # 2. Check for an existing application by the same name
534
- show_app_row = cls.get_existing_app_info(
604
+ show_app_row = cls.get_existing_app_info_static(
535
605
  app_name=app_name,
536
606
  app_role=app_role,
537
607
  )
@@ -674,11 +744,15 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
674
744
  )
675
745
  )
676
746
 
747
+ def get_existing_app_info(self) -> Optional[dict]:
748
+ model = self._entity_model
749
+ ctx = self._workspace_ctx
750
+ role = (model.meta and model.meta.role) or ctx.default_role
751
+ return self.get_existing_app_info_static(model.fqn.name, role)
752
+
753
+ # Temporary static entrypoint until NativeAppManager.get_existing_app_info() is removed
677
754
  @staticmethod
678
- def get_existing_app_info(
679
- app_name: str,
680
- app_role: str,
681
- ) -> Optional[dict]:
755
+ def get_existing_app_info_static(app_name: str, app_role: str) -> Optional[dict]:
682
756
  """
683
757
  Check for an existing application object by the same name as in project definition, in account.
684
758
  It executes a 'show applications like' query and returns the result as single row, if one exists.
@@ -689,6 +763,66 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
689
763
  "applications", app_name, name_col=NAME_COL
690
764
  )
691
765
 
766
+ @classmethod
767
+ def drop_application_before_upgrade(
768
+ cls,
769
+ console: AbstractConsole,
770
+ app_name: str,
771
+ app_role: str,
772
+ policy: PolicyBase,
773
+ is_interactive: bool,
774
+ cascade: bool = False,
775
+ ):
776
+ if cascade:
777
+ try:
778
+ if application_objects := cls.get_objects_owned_by_application(
779
+ app_name, app_role
780
+ ):
781
+ application_objects_str = cls.application_objects_to_str(
782
+ application_objects
783
+ )
784
+ console.message(
785
+ f"The following objects are owned by application {app_name} and need to be dropped:\n{application_objects_str}"
786
+ )
787
+ except ProgrammingError as err:
788
+ if err.errno != APPLICATION_NO_LONGER_AVAILABLE:
789
+ generic_sql_error_handler(err)
790
+ console.warning(
791
+ "The application owns other objects but they could not be determined."
792
+ )
793
+ user_prompt = "Do you want the Snowflake CLI to drop these objects, then drop the existing application object and recreate it?"
794
+ else:
795
+ user_prompt = "Do you want the Snowflake CLI to drop the existing application object and recreate it?"
796
+
797
+ if not policy.should_proceed(user_prompt):
798
+ if is_interactive:
799
+ console.message("Not upgrading the application object.")
800
+ raise typer.Exit(0)
801
+ else:
802
+ console.message(
803
+ "Cannot upgrade the application object non-interactively without --force."
804
+ )
805
+ raise typer.Exit(1)
806
+ try:
807
+ cascade_msg = " (cascade)" if cascade else ""
808
+ console.step(f"Dropping application object {app_name}{cascade_msg}.")
809
+ cascade_sql = " cascade" if cascade else ""
810
+ sql_executor = get_sql_executor()
811
+ sql_executor.execute_query(f"drop application {app_name}{cascade_sql}")
812
+ except ProgrammingError as err:
813
+ if err.errno == APPLICATION_OWNS_EXTERNAL_OBJECTS and not cascade:
814
+ # We need to cascade the deletion, let's try again (only if we didn't try with cascade already)
815
+ return cls.drop_application_before_upgrade(
816
+ console=console,
817
+ app_name=app_name,
818
+ app_role=app_role,
819
+ policy=policy,
820
+ is_interactive=is_interactive,
821
+ cascade=True,
822
+ )
823
+ else:
824
+ generic_sql_error_handler(err)
825
+
692
826
  @classmethod
693
827
  def get_events(
694
828
  cls,
@@ -704,7 +838,6 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
704
838
  first: int = -1,
705
839
  last: int = -1,
706
840
  ):
707
-
708
841
  record_types = record_types or []
709
842
  scopes = scopes or []
710
843
 
@@ -712,7 +845,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
712
845
  raise ValueError("first and last cannot be used together")
713
846
 
714
847
  account_event_table = cls.get_account_event_table()
715
- if not account_event_table:
848
+ if not account_event_table or account_event_table == "NONE":
716
849
  raise NoEventTableForAccount()
717
850
 
718
851
  # resource_attributes uses the unquoted/uppercase app and package name
@@ -787,7 +920,16 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
787
920
  try:
788
921
  return sql_executor.execute_query(query, cursor_class=DictCursor).fetchall()
789
922
  except ProgrammingError as err:
790
- generic_sql_error_handler(err)
923
+ if err.errno == DOES_NOT_EXIST_OR_NOT_AUTHORIZED:
924
+ raise ClickException(
925
+ dedent(
926
+ f"""\
927
+ Event table '{account_event_table}' does not exist or you are not authorized to perform this operation.
928
+ Please check your EVENT_TABLE parameter to ensure that it is set to a valid event table."""
929
+ )
930
+ ) from err
931
+ else:
932
+ generic_sql_error_handler(err)
791
933
 
792
934
  @classmethod
793
935
  def stream_events(
@@ -846,8 +988,18 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
846
988
  results = sql_executor.execute_query(query, cursor_class=DictCursor)
847
989
  return next((r["value"] for r in results if r["key"] == "EVENT_TABLE"), "")
848
990
 
991
+ def get_snowsight_url(self) -> str:
992
+ """Returns the URL that can be used to visit this app via Snowsight."""
993
+ model = self._entity_model
994
+ ctx = self._workspace_ctx
995
+ warehouse = (
996
+ model.meta and model.meta.warehouse and to_identifier(model.meta.warehouse)
997
+ ) or to_identifier(ctx.default_warehouse)
998
+ return self.get_snowsight_url_static(model.fqn.name, warehouse)
999
+
1000
+ # Temporary static entrypoint until NativeAppManager.get_snowsight_url() is removed
849
1001
  @classmethod
850
- def get_snowsight_url(cls, app_name: str, app_warehouse: str | None) -> str:
1002
+ def get_snowsight_url_static(cls, app_name: str, app_warehouse: str) -> str:
851
1003
  """Returns the URL that can be used to visit this app via Snowsight."""
852
1004
  name = identifier_for_url(app_name)
853
1005
  with cls.use_application_warehouse(app_warehouse):