snowflake-cli 3.2.1__py3-none-any.whl → 3.3.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 (56) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/_app/constants.py +4 -0
  3. snowflake/cli/_app/snow_connector.py +12 -0
  4. snowflake/cli/_app/telemetry.py +10 -3
  5. snowflake/cli/_plugins/connection/util.py +12 -19
  6. snowflake/cli/_plugins/helpers/commands.py +207 -1
  7. snowflake/cli/_plugins/nativeapp/artifacts.py +10 -4
  8. snowflake/cli/_plugins/nativeapp/codegen/compiler.py +41 -17
  9. snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +7 -0
  10. snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +4 -1
  11. snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +42 -32
  12. snowflake/cli/_plugins/nativeapp/commands.py +92 -2
  13. snowflake/cli/_plugins/nativeapp/constants.py +5 -0
  14. snowflake/cli/_plugins/nativeapp/entities/application.py +221 -288
  15. snowflake/cli/_plugins/nativeapp/entities/application_package.py +772 -89
  16. snowflake/cli/_plugins/nativeapp/entities/application_package_child_interface.py +43 -0
  17. snowflake/cli/_plugins/nativeapp/feature_flags.py +5 -1
  18. snowflake/cli/_plugins/nativeapp/release_channel/__init__.py +13 -0
  19. snowflake/cli/_plugins/nativeapp/release_channel/commands.py +212 -0
  20. snowflake/cli/_plugins/nativeapp/release_directive/__init__.py +13 -0
  21. snowflake/cli/_plugins/nativeapp/release_directive/commands.py +165 -0
  22. snowflake/cli/_plugins/nativeapp/same_account_install_method.py +9 -17
  23. snowflake/cli/_plugins/nativeapp/sf_facade_exceptions.py +80 -0
  24. snowflake/cli/_plugins/nativeapp/sf_sql_facade.py +999 -75
  25. snowflake/cli/_plugins/nativeapp/utils.py +11 -0
  26. snowflake/cli/_plugins/nativeapp/v2_conversions/compat.py +5 -1
  27. snowflake/cli/_plugins/nativeapp/version/commands.py +31 -4
  28. snowflake/cli/_plugins/notebook/manager.py +4 -2
  29. snowflake/cli/_plugins/snowpark/snowpark_entity.py +234 -4
  30. snowflake/cli/_plugins/spcs/common.py +129 -0
  31. snowflake/cli/_plugins/spcs/services/commands.py +134 -14
  32. snowflake/cli/_plugins/spcs/services/manager.py +169 -1
  33. snowflake/cli/_plugins/stage/manager.py +12 -4
  34. snowflake/cli/_plugins/streamlit/manager.py +8 -1
  35. snowflake/cli/_plugins/streamlit/streamlit_entity.py +153 -2
  36. snowflake/cli/_plugins/workspace/commands.py +3 -2
  37. snowflake/cli/_plugins/workspace/manager.py +8 -4
  38. snowflake/cli/api/cli_global_context.py +22 -1
  39. snowflake/cli/api/config.py +6 -2
  40. snowflake/cli/api/connections.py +12 -1
  41. snowflake/cli/api/constants.py +9 -1
  42. snowflake/cli/api/entities/common.py +85 -0
  43. snowflake/cli/api/entities/utils.py +9 -8
  44. snowflake/cli/api/errno.py +60 -3
  45. snowflake/cli/api/feature_flags.py +20 -4
  46. snowflake/cli/api/metrics.py +21 -27
  47. snowflake/cli/api/project/definition_conversion.py +1 -2
  48. snowflake/cli/api/project/schemas/project_definition.py +27 -6
  49. snowflake/cli/api/project/schemas/v1/streamlit/streamlit.py +1 -1
  50. snowflake/cli/api/project/util.py +45 -0
  51. snowflake/cli/api/rest_api.py +3 -2
  52. {snowflake_cli-3.2.1.dist-info → snowflake_cli-3.3.0.dist-info}/METADATA +13 -13
  53. {snowflake_cli-3.2.1.dist-info → snowflake_cli-3.3.0.dist-info}/RECORD +56 -51
  54. {snowflake_cli-3.2.1.dist-info → snowflake_cli-3.3.0.dist-info}/WHEEL +1 -1
  55. {snowflake_cli-3.2.1.dist-info → snowflake_cli-3.3.0.dist-info}/entry_points.txt +0 -0
  56. {snowflake_cli-3.2.1.dist-info → snowflake_cli-3.3.0.dist-info}/licenses/LICENSE +0 -0
@@ -13,7 +13,6 @@ from click import ClickException, UsageError
13
13
  from pydantic import Field, field_validator
14
14
  from snowflake.cli._plugins.connection.util import (
15
15
  UIParameter,
16
- get_ui_parameter,
17
16
  make_snowsight_url,
18
17
  )
19
18
  from snowflake.cli._plugins.nativeapp.artifacts import (
@@ -26,11 +25,8 @@ from snowflake.cli._plugins.nativeapp.common_flags import (
26
25
  )
27
26
  from snowflake.cli._plugins.nativeapp.constants import (
28
27
  ALLOWED_SPECIAL_COMMENTS,
29
- AUTHORIZE_TELEMETRY_COL,
30
28
  COMMENT_COL,
31
- NAME_COL,
32
29
  OWNER_COL,
33
- SPECIAL_COMMENT,
34
30
  )
35
31
  from snowflake.cli._plugins.nativeapp.entities.application_package import (
36
32
  ApplicationPackageEntity,
@@ -53,11 +49,19 @@ from snowflake.cli._plugins.nativeapp.same_account_install_method import (
53
49
  SameAccountInstallMethod,
54
50
  )
55
51
  from snowflake.cli._plugins.nativeapp.sf_facade import get_snowflake_facade
52
+ from snowflake.cli._plugins.nativeapp.sf_facade_exceptions import (
53
+ UpgradeApplicationRestrictionError,
54
+ )
56
55
  from snowflake.cli._plugins.nativeapp.utils import needs_confirmation
57
56
  from snowflake.cli._plugins.workspace.context import ActionContext
58
- from snowflake.cli.api.cli_global_context import get_cli_context
57
+ from snowflake.cli.api.cli_global_context import get_cli_context, span
59
58
  from snowflake.cli.api.console.abc import AbstractConsole
60
- from snowflake.cli.api.entities.common import EntityBase, get_sql_executor
59
+ from snowflake.cli.api.constants import ObjectType
60
+ from snowflake.cli.api.entities.common import (
61
+ EntityBase,
62
+ attach_spans_to_entity_actions,
63
+ get_sql_executor,
64
+ )
61
65
  from snowflake.cli.api.entities.utils import (
62
66
  drop_generic_object,
63
67
  execute_post_deploy_hooks,
@@ -67,13 +71,7 @@ from snowflake.cli.api.entities.utils import (
67
71
  from snowflake.cli.api.errno import (
68
72
  APPLICATION_NO_LONGER_AVAILABLE,
69
73
  APPLICATION_OWNS_EXTERNAL_OBJECTS,
70
- APPLICATION_REQUIRES_TELEMETRY_SHARING,
71
- CANNOT_DISABLE_MANDATORY_TELEMETRY,
72
- CANNOT_UPGRADE_FROM_LOOSE_FILES_TO_VERSION,
73
- CANNOT_UPGRADE_FROM_VERSION_TO_LOOSE_FILES,
74
74
  DOES_NOT_EXIST_OR_NOT_AUTHORIZED,
75
- NOT_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
76
- ONLY_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
77
75
  )
78
76
  from snowflake.cli.api.metrics import CLICounterField
79
77
  from snowflake.cli.api.project.schemas.entities.common import (
@@ -94,15 +92,6 @@ from snowflake.connector import DictCursor, ProgrammingError
94
92
 
95
93
  log = logging.getLogger(__name__)
96
94
 
97
- # Reasons why an `alter application ... upgrade` might fail
98
- UPGRADE_RESTRICTION_CODES = {
99
- CANNOT_UPGRADE_FROM_LOOSE_FILES_TO_VERSION,
100
- CANNOT_UPGRADE_FROM_VERSION_TO_LOOSE_FILES,
101
- ONLY_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
102
- NOT_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
103
- APPLICATION_NO_LONGER_AVAILABLE,
104
- }
105
-
106
95
  ApplicationOwnedObject = TypedDict("ApplicationOwnedObject", {"name": str, "type": str})
107
96
 
108
97
 
@@ -134,18 +123,12 @@ class EventSharingHandler:
134
123
  self._is_dev_mode = install_method.is_dev_mode
135
124
  self._metrics = get_cli_context().metrics
136
125
  self._console = console
137
- connection = get_sql_executor()._conn # noqa: SLF001
138
- self._event_sharing_enabled = (
139
- get_ui_parameter(
140
- connection, UIParameter.NA_EVENT_SHARING_V2, "true"
141
- ).lower()
142
- == "true"
126
+
127
+ self._event_sharing_enabled = get_snowflake_facade().get_ui_parameter(
128
+ UIParameter.NA_EVENT_SHARING_V2, True
143
129
  )
144
- self._event_sharing_enforced = (
145
- get_ui_parameter(
146
- connection, UIParameter.NA_ENFORCE_MANDATORY_FILTERS, "true"
147
- ).lower()
148
- == "true"
130
+ self._event_sharing_enforced = get_snowflake_facade().get_ui_parameter(
131
+ UIParameter.NA_ENFORCE_MANDATORY_FILTERS, True
149
132
  )
150
133
 
151
134
  self._share_mandatory_events = (
@@ -187,31 +170,11 @@ class EventSharingHandler:
187
170
  def _contains_mandatory_events(self, events_definitions: List[Dict[str, str]]):
188
171
  return any(event["sharing"] == "MANDATORY" for event in events_definitions)
189
172
 
190
- def should_authorize_event_sharing_during_create(
191
- self,
192
- ) -> Optional[bool]:
193
- """
194
- Determines whether event sharing should be authorized during the creation of the application object.
195
-
196
- Outputs:
197
- - None: Event sharing should not be updated or explicitly set.
198
- - True: Event sharing should be authorized.
199
- - False: Event sharing should be disabled.
200
- """
201
-
202
- if not self._event_sharing_enabled:
203
- return None
204
-
205
- return self._share_mandatory_events
206
-
207
- def should_authorize_event_sharing_after_upgrade(
173
+ def should_authorize_event_sharing(
208
174
  self,
209
- upgraded_app_properties: Dict[str, str],
210
175
  ) -> Optional[bool]:
211
176
  """
212
- Determines whether event sharing should be authorized after upgrading the application object.
213
-
214
- :param upgraded_app_properties: The properties of the application after upgrading.
177
+ Determines whether event sharing should be authorized.
215
178
 
216
179
  Outputs:
217
180
  - None: Event sharing should not be updated or explicitly set.
@@ -222,15 +185,6 @@ class EventSharingHandler:
222
185
  if not self._event_sharing_enabled:
223
186
  return None
224
187
 
225
- current_app_authorization = (
226
- upgraded_app_properties.get(AUTHORIZE_TELEMETRY_COL, "false").lower()
227
- == "true"
228
- )
229
-
230
- # Skip the update if the current value is the same as the one we want to set
231
- if current_app_authorization == self._share_mandatory_events:
232
- return None
233
-
234
188
  return self._share_mandatory_events
235
189
 
236
190
  def event_sharing_warning(self, message: str):
@@ -321,6 +275,7 @@ class ApplicationEntityModel(EntityModelBase):
321
275
  return with_suffix
322
276
 
323
277
 
278
+ @attach_spans_to_entity_actions(entity_name="app")
324
279
  class ApplicationEntity(EntityBase[ApplicationEntityModel]):
325
280
  """
326
281
  A Native App application object, created from an application package.
@@ -355,6 +310,18 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
355
310
  model = self._entity_model
356
311
  return model.meta and model.meta.post_deploy
357
312
 
313
+ @property
314
+ def console(self) -> AbstractConsole:
315
+ return self._workspace_ctx.console
316
+
317
+ @property
318
+ def debug(self) -> bool | None:
319
+ return self._entity_model.debug
320
+
321
+ @property
322
+ def telemetry(self) -> EventSharingTelemetry | None:
323
+ return self._entity_model.telemetry
324
+
358
325
  def action_deploy(
359
326
  self,
360
327
  action_ctx: ActionContext,
@@ -362,6 +329,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
362
329
  prune: bool,
363
330
  recursive: bool,
364
331
  paths: List[Path],
332
+ release_channel: Optional[str] = None,
365
333
  validate: bool = ValidateOption,
366
334
  stage_fqn: Optional[str] = None,
367
335
  interactive: bool = InteractiveOption,
@@ -389,15 +357,25 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
389
357
 
390
358
  # same-account release directive
391
359
  if from_release_directive:
360
+ release_channel = package_entity.get_sanitized_release_channel(
361
+ release_channel
362
+ )
363
+
392
364
  self.create_or_upgrade_app(
393
365
  package=package_entity,
394
366
  stage_fqn=stage_fqn,
395
367
  install_method=SameAccountInstallMethod.release_directive(),
368
+ release_channel=release_channel,
396
369
  policy=policy,
397
370
  interactive=interactive,
398
371
  )
399
372
  return
400
373
 
374
+ if release_channel:
375
+ raise UsageError(
376
+ f"Release channel is only supported when --from-release-directive is used."
377
+ )
378
+
401
379
  # versioned dev
402
380
  if version:
403
381
  try:
@@ -451,14 +429,14 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
451
429
  """
452
430
  Attempts to drop the application object if all validations and user prompts allow so.
453
431
  """
454
- console = self._workspace_ctx.console
455
-
456
432
  needs_confirm = True
457
433
 
458
434
  # 1. If existing application is not found, exit gracefully
459
- show_obj_row = self.get_existing_app_info()
435
+ show_obj_row = get_snowflake_facade().get_existing_app_info(
436
+ self.name, self.role
437
+ )
460
438
  if show_obj_row is None:
461
- console.warning(
439
+ self.console.warning(
462
440
  f"Role {self.role} does not own any application object with the name {self.name}, or the application object does not exist."
463
441
  )
464
442
  return
@@ -485,7 +463,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
485
463
  )
486
464
  )
487
465
  if not should_drop_object:
488
- console.message(f"Did not drop application object {self.name}.")
466
+ self.console.message(f"Did not drop application object {self.name}.")
489
467
  # The user desires to keep the app, therefore we can't proceed since it would
490
468
  # leave behind an orphan app when we get to dropping the package
491
469
  raise typer.Abort()
@@ -524,22 +502,22 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
524
502
  if has_objects_to_drop:
525
503
  if cascade is True:
526
504
  # If the user explicitly passed the --cascade flag
527
- console.message(cascade_true_message)
528
- with console.indented():
505
+ self.console.message(cascade_true_message)
506
+ with self.console.indented():
529
507
  for obj in application_objects:
530
- console.message(_application_object_to_str(obj))
508
+ self.console.message(_application_object_to_str(obj))
531
509
  elif cascade is False:
532
510
  # If the user explicitly passed the --no-cascade flag
533
- console.message(cascade_false_message)
534
- with console.indented():
511
+ self.console.message(cascade_false_message)
512
+ with self.console.indented():
535
513
  for obj in application_objects:
536
- console.message(_application_object_to_str(obj))
514
+ self.console.message(_application_object_to_str(obj))
537
515
  elif interactive:
538
516
  # If the user didn't pass any cascade flag and the session is interactive
539
- console.message(message_prefix)
540
- with console.indented():
517
+ self.console.message(message_prefix)
518
+ with self.console.indented():
541
519
  for obj in application_objects:
542
- console.message(_application_object_to_str(obj))
520
+ self.console.message(_application_object_to_str(obj))
543
521
  user_response = typer.prompt(
544
522
  interactive_prompt,
545
523
  show_default=False,
@@ -553,11 +531,11 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
553
531
  raise typer.Abort()
554
532
  else:
555
533
  # Else abort since we don't know what to do and can't ask the user
556
- console.message(message_prefix)
557
- with console.indented():
534
+ self.console.message(message_prefix)
535
+ with self.console.indented():
558
536
  for obj in application_objects:
559
- console.message(_application_object_to_str(obj))
560
- console.message(non_interactive_abort)
537
+ self.console.message(_application_object_to_str(obj))
538
+ self.console.message(non_interactive_abort)
561
539
  raise typer.Abort()
562
540
  elif cascade is None:
563
541
  # If there's nothing to drop, set cascade to an explicit False value
@@ -565,7 +543,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
565
543
 
566
544
  # 4. All validations have passed, drop object
567
545
  drop_generic_object(
568
- console=console,
546
+ console=self.console,
569
547
  object_type="application",
570
548
  object_name=self.name,
571
549
  role=self.role,
@@ -629,181 +607,145 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
629
607
  ).fetchall()
630
608
  return [{"name": row[1], "type": row[2]} for row in results]
631
609
 
632
- def create_or_upgrade_app(
610
+ def _upgrade_app(
633
611
  self,
634
- package: ApplicationPackageEntity,
635
612
  stage_fqn: str,
636
613
  install_method: SameAccountInstallMethod,
614
+ event_sharing: EventSharingHandler,
637
615
  policy: PolicyBase,
638
616
  interactive: bool,
639
- ):
640
- model = self._entity_model
641
- console = self._workspace_ctx.console
642
- debug_mode = model.debug
643
-
644
- stage_fqn = stage_fqn or package.stage_fqn
645
- stage_schema = extract_schema(stage_fqn)
617
+ release_channel: Optional[str] = None,
618
+ ) -> list[tuple[str]] | None:
619
+ self.console.step(f"Upgrading existing application object {self.name}.")
646
620
 
647
- sql_executor = get_sql_executor()
648
- with sql_executor.use_role(self.role):
649
- event_sharing = EventSharingHandler(
650
- telemetry_definition=model.telemetry,
651
- deploy_root=package.deploy_root,
621
+ try:
622
+ return get_snowflake_facade().upgrade_application(
623
+ name=self.name,
652
624
  install_method=install_method,
653
- console=console,
625
+ stage_fqn=stage_fqn,
626
+ debug_mode=self.debug,
627
+ should_authorize_event_sharing=event_sharing.should_authorize_event_sharing(),
628
+ release_channel=release_channel,
629
+ role=self.role,
630
+ warehouse=self.warehouse,
654
631
  )
632
+ except UpgradeApplicationRestrictionError as err:
633
+ self.console.warning(err.message)
634
+ self.drop_application_before_upgrade(policy=policy, interactive=interactive)
635
+ return None
655
636
 
656
- # 1. Need to use a warehouse to create an application object
657
- with sql_executor.use_warehouse(self.warehouse):
637
+ def _create_app(
638
+ self,
639
+ stage_fqn: str,
640
+ install_method: SameAccountInstallMethod,
641
+ event_sharing: EventSharingHandler,
642
+ package: ApplicationPackageEntity,
643
+ release_channel: Optional[str] = None,
644
+ ) -> list[tuple[str]]:
645
+ self.console.step(f"Creating new application object {self.name} in account.")
646
+
647
+ if package.role != self.role:
648
+ get_snowflake_facade().grant_privileges_to_role(
649
+ privileges=["install", "develop"],
650
+ object_type=ObjectType.APPLICATION_PACKAGE,
651
+ object_identifier=package.name,
652
+ role_to_grant=self.role,
653
+ role_to_use=package.role,
654
+ )
658
655
 
659
- # 2. Check for an existing application by the same name
660
- show_app_row = self.get_existing_app_info()
656
+ stage_schema = extract_schema(stage_fqn)
657
+ get_snowflake_facade().grant_privileges_to_role(
658
+ privileges=["usage"],
659
+ object_type=ObjectType.SCHEMA,
660
+ object_identifier=f"{package.name}.{stage_schema}",
661
+ role_to_grant=self.role,
662
+ role_to_use=package.role,
663
+ )
661
664
 
662
- # 3. If existing application is found, perform a few validations and upgrade the application object.
663
- if show_app_row:
664
- install_method.ensure_app_usable(
665
- app_name=self.name,
666
- app_role=self.role,
667
- show_app_row=show_app_row,
668
- )
665
+ get_snowflake_facade().grant_privileges_to_role(
666
+ privileges=["read"],
667
+ object_type=ObjectType.STAGE,
668
+ object_identifier=stage_fqn,
669
+ role_to_grant=self.role,
670
+ role_to_use=package.role,
671
+ )
669
672
 
670
- # If all the above checks are in order, proceed to upgrade
671
- try:
672
- console.step(
673
- f"Upgrading existing application object {self.name}."
674
- )
675
- using_clause = install_method.using_clause(stage_fqn)
676
- upgrade_cursor = sql_executor.execute_query(
677
- f"alter application {self.name} upgrade {using_clause}",
678
- )
679
- print_messages(console, upgrade_cursor)
673
+ return get_snowflake_facade().create_application(
674
+ name=self.name,
675
+ package_name=package.name,
676
+ install_method=install_method,
677
+ stage_fqn=stage_fqn,
678
+ debug_mode=self.debug,
679
+ should_authorize_event_sharing=event_sharing.should_authorize_event_sharing(),
680
+ role=self.role,
681
+ warehouse=self.warehouse,
682
+ release_channel=release_channel,
683
+ )
680
684
 
681
- events_definitions = (
682
- get_snowflake_facade().get_event_definitions(
683
- self.name, self.role
684
- )
685
- )
685
+ @span("update_app_object")
686
+ def create_or_upgrade_app(
687
+ self,
688
+ package: ApplicationPackageEntity,
689
+ stage_fqn: str,
690
+ install_method: SameAccountInstallMethod,
691
+ policy: PolicyBase,
692
+ interactive: bool,
693
+ release_channel: Optional[str] = None,
694
+ ):
695
+ event_sharing = EventSharingHandler(
696
+ telemetry_definition=self.telemetry,
697
+ deploy_root=package.deploy_root,
698
+ install_method=install_method,
699
+ console=self.console,
700
+ )
686
701
 
687
- app_properties = get_snowflake_facade().get_app_properties(
688
- self.name, self.role
689
- )
690
- new_authorize_event_sharing_value = (
691
- event_sharing.should_authorize_event_sharing_after_upgrade(
692
- app_properties,
693
- )
694
- )
695
- if new_authorize_event_sharing_value is not None:
696
- log.info(
697
- "Setting telemetry sharing authorization to %s",
698
- new_authorize_event_sharing_value,
699
- )
700
- sql_executor.execute_query(
701
- f"alter application {self.name} set AUTHORIZE_TELEMETRY_EVENT_SHARING = {str(new_authorize_event_sharing_value).upper()}"
702
- )
703
- events_to_share = event_sharing.events_to_share(
704
- events_definitions
705
- )
706
- if events_to_share is not None:
707
- get_snowflake_facade().share_telemetry_events(
708
- self.name, events_to_share
709
- )
710
-
711
- if install_method.is_dev_mode:
712
- # if debug_mode is present (controlled), ensure it is up-to-date
713
- if debug_mode is not None:
714
- sql_executor.execute_query(
715
- f"alter application {self.name} set debug_mode = {debug_mode}"
716
- )
717
-
718
- # hooks always executed after a create or upgrade
719
- self.execute_post_deploy_hooks()
720
- return
721
-
722
- except ProgrammingError as err:
723
- if err.errno == CANNOT_DISABLE_MANDATORY_TELEMETRY:
724
- event_sharing.event_sharing_error(
725
- "Could not disable telemetry event sharing for the application because it contains mandatory events. Please set 'share_mandatory_events' to true in the application telemetry section of the project definition file.",
726
- err,
727
- )
728
- elif err.errno in UPGRADE_RESTRICTION_CODES:
729
- console.warning(err.msg)
730
- self.drop_application_before_upgrade(
731
- policy=policy, interactive=interactive
732
- )
733
- else:
734
- generic_sql_error_handler(err=err)
735
-
736
- # 4. With no (more) existing application objects, create an application object using the release directives
737
- console.step(f"Creating new application object {self.name} in account.")
738
-
739
- if self.role != package.role:
740
- with sql_executor.use_role(package.role):
741
- sql_executor.execute_query(
742
- f"grant install, develop on application package {package.name} to role {self.role}"
743
- )
744
- sql_executor.execute_query(
745
- f"grant usage on schema {package.name}.{stage_schema} to role {self.role}"
746
- )
747
- sql_executor.execute_query(
748
- f"grant read on stage {stage_fqn} to role {self.role}"
749
- )
702
+ # 1. Check for an existing application by the same name
703
+ show_app_row = get_snowflake_facade().get_existing_app_info(
704
+ self.name, self.role
705
+ )
750
706
 
751
- try:
752
- # by default, applications are created in debug mode when possible;
753
- # this can be overridden in the project definition
754
- debug_mode_clause = ""
755
- if install_method.is_dev_mode:
756
- initial_debug_mode = (
757
- debug_mode if debug_mode is not None else True
758
- )
759
- debug_mode_clause = f"debug_mode = {initial_debug_mode}"
707
+ stage_fqn = stage_fqn or package.stage_fqn
760
708
 
761
- authorize_telemetry_clause = ""
762
- new_authorize_event_sharing_value = (
763
- event_sharing.should_authorize_event_sharing_during_create()
764
- )
765
- if new_authorize_event_sharing_value is not None:
766
- log.info(
767
- "Setting AUTHORIZE_TELEMETRY_EVENT_SHARING to %s",
768
- new_authorize_event_sharing_value,
769
- )
770
- authorize_telemetry_clause = f" AUTHORIZE_TELEMETRY_EVENT_SHARING = {str(new_authorize_event_sharing_value).upper()}"
771
-
772
- using_clause = install_method.using_clause(stage_fqn)
773
- create_cursor = sql_executor.execute_query(
774
- dedent(
775
- f"""\
776
- create application {self.name}
777
- from application package {package.name} {using_clause} {debug_mode_clause}{authorize_telemetry_clause}
778
- comment = {SPECIAL_COMMENT}
779
- """
780
- ),
781
- )
782
- print_messages(console, create_cursor)
783
- events_definitions = get_snowflake_facade().get_event_definitions(
784
- self.name, self.role
785
- )
709
+ # 2. If existing application is found, try to upgrade the application object.
710
+ create_or_upgrade_result = None
711
+ if show_app_row:
712
+ create_or_upgrade_result = self._upgrade_app(
713
+ stage_fqn=stage_fqn,
714
+ install_method=install_method,
715
+ event_sharing=event_sharing,
716
+ policy=policy,
717
+ interactive=interactive,
718
+ release_channel=release_channel,
719
+ )
786
720
 
787
- events_to_share = event_sharing.events_to_share(events_definitions)
788
- if events_to_share is not None:
789
- get_snowflake_facade().share_telemetry_events(
790
- self.name, events_to_share
791
- )
721
+ # 3. If no existing application found, or we performed a drop before the upgrade, we proceed to create
722
+ if create_or_upgrade_result is None:
723
+ create_or_upgrade_result = self._create_app(
724
+ stage_fqn=stage_fqn,
725
+ install_method=install_method,
726
+ event_sharing=event_sharing,
727
+ package=package,
728
+ release_channel=release_channel,
729
+ )
792
730
 
793
- # hooks always executed after a create or upgrade
794
- self.execute_post_deploy_hooks()
731
+ print_messages(self.console, create_or_upgrade_result)
795
732
 
796
- except ProgrammingError as err:
797
- if err.errno == APPLICATION_REQUIRES_TELEMETRY_SHARING:
798
- event_sharing.event_sharing_error(
799
- "The application package requires event sharing to be authorized. Please set 'share_mandatory_events' to true in the application telemetry section of the project definition file.",
800
- err,
801
- )
802
- generic_sql_error_handler(err)
733
+ events_definitions = get_snowflake_facade().get_event_definitions(
734
+ self.name, self.role
735
+ )
736
+
737
+ events_to_share = event_sharing.events_to_share(events_definitions)
738
+ if events_to_share is not None:
739
+ get_snowflake_facade().share_telemetry_events(
740
+ self.name, events_to_share, self.role
741
+ )
742
+
743
+ # hooks always executed after a create or upgrade
744
+ self.execute_post_deploy_hooks()
803
745
 
804
746
  def execute_post_deploy_hooks(self):
805
747
  execute_post_deploy_hooks(
806
- console=self._workspace_ctx.console,
748
+ console=self.console,
807
749
  project_root=self.project_root,
808
750
  post_deploy_hooks=self.post_deploy_hooks,
809
751
  deployed_object_type="application",
@@ -827,69 +769,59 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
827
769
  )
828
770
  )
829
771
 
830
- def get_existing_app_info(self) -> Optional[dict]:
831
- """
832
- Check for an existing application object by the same name as in project definition, in account.
833
- It executes a 'show applications like' query and returns the result as single row, if one exists.
834
- """
835
- sql_executor = get_sql_executor()
836
- with sql_executor.use_role(self.role):
837
- return sql_executor.show_specific_object(
838
- "applications", self.name, name_col=NAME_COL
839
- )
840
-
841
772
  def drop_application_before_upgrade(
842
773
  self,
843
774
  policy: PolicyBase,
844
775
  interactive: bool,
845
776
  cascade: bool = False,
846
777
  ):
847
- console = self._workspace_ctx.console
848
-
849
- if cascade:
850
- try:
851
- if application_objects := self.get_objects_owned_by_application():
852
- application_objects_str = _application_objects_to_str(
853
- application_objects
778
+ sql_executor = get_sql_executor()
779
+ with sql_executor.use_role(self.role):
780
+ if cascade:
781
+ try:
782
+ if application_objects := self.get_objects_owned_by_application():
783
+ application_objects_str = _application_objects_to_str(
784
+ application_objects
785
+ )
786
+ self.console.message(
787
+ f"The following objects are owned by application {self.name} and need to be dropped:\n{application_objects_str}"
788
+ )
789
+ except ProgrammingError as err:
790
+ if err.errno != APPLICATION_NO_LONGER_AVAILABLE:
791
+ generic_sql_error_handler(err)
792
+ self.console.warning(
793
+ "The application owns other objects but they could not be determined."
854
794
  )
855
- console.message(
856
- f"The following objects are owned by application {self.name} and need to be dropped:\n{application_objects_str}"
795
+ user_prompt = "Do you want the Snowflake CLI to drop these objects, then drop the existing application object and recreate it?"
796
+ else:
797
+ user_prompt = "Do you want the Snowflake CLI to drop the existing application object and recreate it?"
798
+
799
+ if not policy.should_proceed(user_prompt):
800
+ if interactive:
801
+ self.console.message("Not upgrading the application object.")
802
+ raise typer.Exit(0)
803
+ else:
804
+ self.console.message(
805
+ "Cannot upgrade the application object non-interactively without --force."
857
806
  )
807
+ raise typer.Exit(1)
808
+ try:
809
+ cascade_msg = " (cascade)" if cascade else ""
810
+ self.console.step(
811
+ f"Dropping application object {self.name}{cascade_msg}."
812
+ )
813
+ cascade_sql = " cascade" if cascade else ""
814
+ sql_executor.execute_query(f"drop application {self.name}{cascade_sql}")
858
815
  except ProgrammingError as err:
859
- if err.errno != APPLICATION_NO_LONGER_AVAILABLE:
816
+ if err.errno == APPLICATION_OWNS_EXTERNAL_OBJECTS and not cascade:
817
+ # We need to cascade the deletion, let's try again (only if we didn't try with cascade already)
818
+ return self.drop_application_before_upgrade(
819
+ policy=policy,
820
+ interactive=interactive,
821
+ cascade=True,
822
+ )
823
+ else:
860
824
  generic_sql_error_handler(err)
861
- console.warning(
862
- "The application owns other objects but they could not be determined."
863
- )
864
- user_prompt = "Do you want the Snowflake CLI to drop these objects, then drop the existing application object and recreate it?"
865
- else:
866
- user_prompt = "Do you want the Snowflake CLI to drop the existing application object and recreate it?"
867
-
868
- if not policy.should_proceed(user_prompt):
869
- if interactive:
870
- console.message("Not upgrading the application object.")
871
- raise typer.Exit(0)
872
- else:
873
- console.message(
874
- "Cannot upgrade the application object non-interactively without --force."
875
- )
876
- raise typer.Exit(1)
877
- try:
878
- cascade_msg = " (cascade)" if cascade else ""
879
- console.step(f"Dropping application object {self.name}{cascade_msg}.")
880
- cascade_sql = " cascade" if cascade else ""
881
- sql_executor = get_sql_executor()
882
- sql_executor.execute_query(f"drop application {self.name}{cascade_sql}")
883
- except ProgrammingError as err:
884
- if err.errno == APPLICATION_OWNS_EXTERNAL_OBJECTS and not cascade:
885
- # We need to cascade the deletion, let's try again (only if we didn't try with cascade already)
886
- return self.drop_application_before_upgrade(
887
- policy=policy,
888
- interactive=interactive,
889
- cascade=True,
890
- )
891
- else:
892
- generic_sql_error_handler(err)
893
825
 
894
826
  def get_events(
895
827
  self,
@@ -1043,6 +975,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
1043
975
  except KeyboardInterrupt:
1044
976
  return
1045
977
 
978
+ @span("get_snowsight_url_for_app")
1046
979
  def get_snowsight_url(self) -> str:
1047
980
  """Returns the URL that can be used to visit this app via Snowsight."""
1048
981
  name = identifier_for_url(self.name)