snowflake-cli 3.2.2__py3-none-any.whl → 3.4.1__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 (97) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/_app/__main__.py +2 -2
  3. snowflake/cli/_app/cli_app.py +224 -192
  4. snowflake/cli/_app/commands_registration/commands_registration_with_callbacks.py +1 -27
  5. snowflake/cli/_app/constants.py +4 -0
  6. snowflake/cli/_app/snow_connector.py +12 -0
  7. snowflake/cli/_app/telemetry.py +10 -3
  8. snowflake/cli/_plugins/connection/util.py +12 -19
  9. snowflake/cli/_plugins/cortex/commands.py +2 -4
  10. snowflake/cli/_plugins/git/manager.py +1 -1
  11. snowflake/cli/_plugins/helpers/commands.py +207 -1
  12. snowflake/cli/_plugins/nativeapp/artifacts.py +16 -628
  13. snowflake/cli/_plugins/nativeapp/bundle_context.py +1 -1
  14. snowflake/cli/_plugins/nativeapp/codegen/artifact_processor.py +1 -1
  15. snowflake/cli/_plugins/nativeapp/codegen/compiler.py +42 -20
  16. snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +9 -2
  17. snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +6 -3
  18. snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +44 -34
  19. snowflake/cli/_plugins/nativeapp/commands.py +113 -21
  20. snowflake/cli/_plugins/nativeapp/constants.py +5 -0
  21. snowflake/cli/_plugins/nativeapp/entities/application.py +226 -296
  22. snowflake/cli/_plugins/nativeapp/entities/application_package.py +911 -141
  23. snowflake/cli/_plugins/nativeapp/entities/application_package_child_interface.py +43 -0
  24. snowflake/cli/_plugins/nativeapp/feature_flags.py +5 -1
  25. snowflake/cli/_plugins/nativeapp/release_channel/__init__.py +13 -0
  26. snowflake/cli/_plugins/nativeapp/release_channel/commands.py +246 -0
  27. snowflake/cli/_plugins/nativeapp/release_directive/__init__.py +13 -0
  28. snowflake/cli/_plugins/nativeapp/release_directive/commands.py +243 -0
  29. snowflake/cli/_plugins/nativeapp/same_account_install_method.py +9 -17
  30. snowflake/cli/_plugins/nativeapp/sf_facade_exceptions.py +80 -0
  31. snowflake/cli/_plugins/nativeapp/sf_sql_facade.py +1184 -80
  32. snowflake/cli/_plugins/nativeapp/utils.py +11 -0
  33. snowflake/cli/_plugins/nativeapp/v2_conversions/compat.py +7 -3
  34. snowflake/cli/_plugins/nativeapp/version/commands.py +32 -5
  35. snowflake/cli/_plugins/notebook/commands.py +55 -2
  36. snowflake/cli/_plugins/notebook/exceptions.py +1 -1
  37. snowflake/cli/_plugins/notebook/manager.py +7 -5
  38. snowflake/cli/_plugins/notebook/notebook_entity.py +120 -0
  39. snowflake/cli/_plugins/notebook/notebook_entity_model.py +42 -0
  40. snowflake/cli/_plugins/notebook/notebook_project_paths.py +15 -0
  41. snowflake/cli/_plugins/notebook/types.py +3 -0
  42. snowflake/cli/_plugins/snowpark/commands.py +48 -30
  43. snowflake/cli/_plugins/snowpark/common.py +47 -2
  44. snowflake/cli/_plugins/snowpark/snowpark_entity.py +247 -4
  45. snowflake/cli/_plugins/snowpark/snowpark_entity_model.py +18 -30
  46. snowflake/cli/_plugins/snowpark/snowpark_project_paths.py +156 -23
  47. snowflake/cli/_plugins/snowpark/zipper.py +33 -1
  48. snowflake/cli/_plugins/spcs/common.py +129 -0
  49. snowflake/cli/_plugins/spcs/services/commands.py +131 -14
  50. snowflake/cli/_plugins/spcs/services/manager.py +169 -1
  51. snowflake/cli/_plugins/stage/commands.py +2 -1
  52. snowflake/cli/_plugins/stage/diff.py +60 -39
  53. snowflake/cli/_plugins/stage/manager.py +34 -13
  54. snowflake/cli/_plugins/stage/utils.py +1 -1
  55. snowflake/cli/_plugins/streamlit/commands.py +10 -1
  56. snowflake/cli/_plugins/streamlit/manager.py +70 -22
  57. snowflake/cli/_plugins/streamlit/streamlit_entity.py +131 -1
  58. snowflake/cli/_plugins/streamlit/streamlit_entity_model.py +14 -24
  59. snowflake/cli/_plugins/streamlit/streamlit_project_paths.py +30 -0
  60. snowflake/cli/_plugins/workspace/commands.py +6 -5
  61. snowflake/cli/_plugins/workspace/manager.py +9 -5
  62. snowflake/cli/api/artifacts/__init__.py +13 -0
  63. snowflake/cli/api/artifacts/bundle_map.py +500 -0
  64. snowflake/cli/api/artifacts/common.py +78 -0
  65. snowflake/cli/api/artifacts/utils.py +82 -0
  66. snowflake/cli/api/cli_global_context.py +36 -2
  67. snowflake/cli/api/commands/flags.py +10 -4
  68. snowflake/cli/api/commands/utils.py +28 -2
  69. snowflake/cli/api/config.py +6 -2
  70. snowflake/cli/api/connections.py +12 -1
  71. snowflake/cli/api/constants.py +10 -1
  72. snowflake/cli/api/entities/common.py +81 -14
  73. snowflake/cli/api/entities/resolver.py +160 -0
  74. snowflake/cli/api/entities/utils.py +65 -23
  75. snowflake/cli/api/errno.py +63 -3
  76. snowflake/cli/api/feature_flags.py +19 -4
  77. snowflake/cli/api/metrics.py +21 -27
  78. snowflake/cli/api/project/definition_conversion.py +4 -4
  79. snowflake/cli/api/project/project_paths.py +28 -0
  80. snowflake/cli/api/project/schemas/entities/common.py +130 -1
  81. snowflake/cli/api/project/schemas/entities/entities.py +4 -0
  82. snowflake/cli/api/project/schemas/project_definition.py +54 -6
  83. snowflake/cli/api/project/schemas/updatable_model.py +2 -2
  84. snowflake/cli/api/project/schemas/v1/native_app/native_app.py +5 -7
  85. snowflake/cli/api/project/schemas/v1/streamlit/streamlit.py +1 -1
  86. snowflake/cli/api/project/util.py +45 -0
  87. snowflake/cli/api/secure_path.py +6 -0
  88. snowflake/cli/api/sql_execution.py +5 -1
  89. snowflake/cli/api/stage_path.py +7 -2
  90. snowflake/cli/api/utils/graph.py +3 -0
  91. snowflake/cli/api/utils/path_utils.py +24 -0
  92. {snowflake_cli-3.2.2.dist-info → snowflake_cli-3.4.1.dist-info}/METADATA +14 -15
  93. {snowflake_cli-3.2.2.dist-info → snowflake_cli-3.4.1.dist-info}/RECORD +96 -82
  94. {snowflake_cli-3.2.2.dist-info → snowflake_cli-3.4.1.dist-info}/WHEEL +1 -1
  95. snowflake/cli/api/project/schemas/v1/native_app/path_mapping.py +0 -65
  96. {snowflake_cli-3.2.2.dist-info → snowflake_cli-3.4.1.dist-info}/entry_points.txt +0 -0
  97. {snowflake_cli-3.2.2.dist-info → snowflake_cli-3.4.1.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,27 +49,30 @@ 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
56
+ from snowflake.cli._plugins.stage.manager import DefaultStagePathParts
57
57
  from snowflake.cli._plugins.workspace.context import ActionContext
58
- from snowflake.cli.api.cli_global_context import get_cli_context
58
+ from snowflake.cli.api.cli_global_context import get_cli_context, span
59
59
  from snowflake.cli.api.console.abc import AbstractConsole
60
- from snowflake.cli.api.entities.common import EntityBase, get_sql_executor
60
+ from snowflake.cli.api.constants import ObjectType
61
+ from snowflake.cli.api.entities.common import (
62
+ EntityBase,
63
+ attach_spans_to_entity_actions,
64
+ )
61
65
  from snowflake.cli.api.entities.utils import (
62
66
  drop_generic_object,
63
67
  execute_post_deploy_hooks,
64
68
  generic_sql_error_handler,
69
+ get_sql_executor,
65
70
  print_messages,
66
71
  )
67
72
  from snowflake.cli.api.errno import (
68
73
  APPLICATION_NO_LONGER_AVAILABLE,
69
74
  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
75
  DOES_NOT_EXIST_OR_NOT_AUTHORIZED,
75
- NOT_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
76
- ONLY_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
77
76
  )
78
77
  from snowflake.cli.api.metrics import CLICounterField
79
78
  from snowflake.cli.api.project.schemas.entities.common import (
@@ -85,7 +84,6 @@ from snowflake.cli.api.project.schemas.entities.common import (
85
84
  from snowflake.cli.api.project.schemas.updatable_model import DiscriminatorField
86
85
  from snowflake.cli.api.project.util import (
87
86
  append_test_resource_suffix,
88
- extract_schema,
89
87
  identifier_for_url,
90
88
  to_identifier,
91
89
  unquote_identifier,
@@ -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,8 +329,8 @@ 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
- stage_fqn: Optional[str] = None,
367
334
  interactive: bool = InteractiveOption,
368
335
  version: Optional[str] = None,
369
336
  patch: Optional[int] = None,
@@ -378,7 +345,8 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
378
345
  package_entity: ApplicationPackageEntity = action_ctx.get_entity(
379
346
  self.package_entity_id
380
347
  )
381
- stage_fqn = stage_fqn or package_entity.stage_fqn
348
+
349
+ stage_path = package_entity.stage_path
382
350
 
383
351
  if force:
384
352
  policy = AllowAlwaysPolicy()
@@ -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
- stage_fqn=stage_fqn,
366
+ stage_path=stage_path,
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:
@@ -413,7 +391,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
413
391
 
414
392
  self.create_or_upgrade_app(
415
393
  package=package_entity,
416
- stage_fqn=stage_fqn,
394
+ stage_path=stage_path,
417
395
  install_method=SameAccountInstallMethod.versioned_dev(version, patch),
418
396
  policy=policy,
419
397
  interactive=interactive,
@@ -427,13 +405,12 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
427
405
  recursive=True,
428
406
  paths=[],
429
407
  validate=validate,
430
- stage_fqn=stage_fqn,
431
408
  interactive=interactive,
432
409
  force=force,
433
410
  )
434
411
  self.create_or_upgrade_app(
435
412
  package=package_entity,
436
- stage_fqn=stage_fqn,
413
+ stage_path=stage_path,
437
414
  install_method=SameAccountInstallMethod.unversioned_dev(),
438
415
  policy=policy,
439
416
  interactive=interactive,
@@ -451,14 +428,14 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
451
428
  """
452
429
  Attempts to drop the application object if all validations and user prompts allow so.
453
430
  """
454
- console = self._workspace_ctx.console
455
-
456
431
  needs_confirm = True
457
432
 
458
433
  # 1. If existing application is not found, exit gracefully
459
- show_obj_row = self.get_existing_app_info()
434
+ show_obj_row = get_snowflake_facade().get_existing_app_info(
435
+ self.name, self.role
436
+ )
460
437
  if show_obj_row is None:
461
- console.warning(
438
+ self.console.warning(
462
439
  f"Role {self.role} does not own any application object with the name {self.name}, or the application object does not exist."
463
440
  )
464
441
  return
@@ -485,7 +462,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
485
462
  )
486
463
  )
487
464
  if not should_drop_object:
488
- console.message(f"Did not drop application object {self.name}.")
465
+ self.console.message(f"Did not drop application object {self.name}.")
489
466
  # The user desires to keep the app, therefore we can't proceed since it would
490
467
  # leave behind an orphan app when we get to dropping the package
491
468
  raise typer.Abort()
@@ -524,22 +501,22 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
524
501
  if has_objects_to_drop:
525
502
  if cascade is True:
526
503
  # If the user explicitly passed the --cascade flag
527
- console.message(cascade_true_message)
528
- with console.indented():
504
+ self.console.message(cascade_true_message)
505
+ with self.console.indented():
529
506
  for obj in application_objects:
530
- console.message(_application_object_to_str(obj))
507
+ self.console.message(_application_object_to_str(obj))
531
508
  elif cascade is False:
532
509
  # If the user explicitly passed the --no-cascade flag
533
- console.message(cascade_false_message)
534
- with console.indented():
510
+ self.console.message(cascade_false_message)
511
+ with self.console.indented():
535
512
  for obj in application_objects:
536
- console.message(_application_object_to_str(obj))
513
+ self.console.message(_application_object_to_str(obj))
537
514
  elif interactive:
538
515
  # If the user didn't pass any cascade flag and the session is interactive
539
- console.message(message_prefix)
540
- with console.indented():
516
+ self.console.message(message_prefix)
517
+ with self.console.indented():
541
518
  for obj in application_objects:
542
- console.message(_application_object_to_str(obj))
519
+ self.console.message(_application_object_to_str(obj))
543
520
  user_response = typer.prompt(
544
521
  interactive_prompt,
545
522
  show_default=False,
@@ -553,11 +530,11 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
553
530
  raise typer.Abort()
554
531
  else:
555
532
  # 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():
533
+ self.console.message(message_prefix)
534
+ with self.console.indented():
558
535
  for obj in application_objects:
559
- console.message(_application_object_to_str(obj))
560
- console.message(non_interactive_abort)
536
+ self.console.message(_application_object_to_str(obj))
537
+ self.console.message(non_interactive_abort)
561
538
  raise typer.Abort()
562
539
  elif cascade is None:
563
540
  # If there's nothing to drop, set cascade to an explicit False value
@@ -565,7 +542,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
565
542
 
566
543
  # 4. All validations have passed, drop object
567
544
  drop_generic_object(
568
- console=console,
545
+ console=self.console,
569
546
  object_type="application",
570
547
  object_name=self.name,
571
548
  role=self.role,
@@ -629,181 +606,143 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
629
606
  ).fetchall()
630
607
  return [{"name": row[1], "type": row[2]} for row in results]
631
608
 
632
- def create_or_upgrade_app(
609
+ def _upgrade_app(
633
610
  self,
634
- package: ApplicationPackageEntity,
635
- stage_fqn: str,
611
+ stage_path: DefaultStagePathParts,
636
612
  install_method: SameAccountInstallMethod,
613
+ event_sharing: EventSharingHandler,
637
614
  policy: PolicyBase,
638
615
  interactive: bool,
639
- ):
640
- model = self._entity_model
641
- console = self._workspace_ctx.console
642
- debug_mode = model.debug
616
+ release_channel: Optional[str] = None,
617
+ ) -> list[tuple[str]] | None:
618
+ self.console.step(f"Upgrading existing application object {self.name}.")
643
619
 
644
- stage_fqn = stage_fqn or package.stage_fqn
645
- stage_schema = extract_schema(stage_fqn)
646
-
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,
620
+ try:
621
+ return get_snowflake_facade().upgrade_application(
622
+ name=self.name,
652
623
  install_method=install_method,
653
- console=console,
624
+ path_to_version_directory=stage_path.full_path,
625
+ debug_mode=self.debug,
626
+ should_authorize_event_sharing=event_sharing.should_authorize_event_sharing(),
627
+ release_channel=release_channel,
628
+ role=self.role,
629
+ warehouse=self.warehouse,
654
630
  )
631
+ except UpgradeApplicationRestrictionError as err:
632
+ self.console.warning(err.message)
633
+ self.drop_application_before_upgrade(policy=policy, interactive=interactive)
634
+ return None
655
635
 
656
- # 1. Need to use a warehouse to create an application object
657
- with sql_executor.use_warehouse(self.warehouse):
636
+ def _create_app(
637
+ self,
638
+ stage_path: DefaultStagePathParts,
639
+ install_method: SameAccountInstallMethod,
640
+ event_sharing: EventSharingHandler,
641
+ package: ApplicationPackageEntity,
642
+ release_channel: Optional[str] = None,
643
+ ) -> list[tuple[str]]:
644
+ self.console.step(f"Creating new application object {self.name} in account.")
645
+
646
+ if package.role != self.role:
647
+ get_snowflake_facade().grant_privileges_to_role(
648
+ privileges=["install", "develop"],
649
+ object_type=ObjectType.APPLICATION_PACKAGE,
650
+ object_identifier=package.name,
651
+ role_to_grant=self.role,
652
+ role_to_use=package.role,
653
+ )
658
654
 
659
- # 2. Check for an existing application by the same name
660
- show_app_row = self.get_existing_app_info()
655
+ stage_schema = stage_path.schema
656
+ get_snowflake_facade().grant_privileges_to_role(
657
+ privileges=["usage"],
658
+ object_type=ObjectType.SCHEMA,
659
+ object_identifier=f"{package.name}.{stage_schema}",
660
+ role_to_grant=self.role,
661
+ role_to_use=package.role,
662
+ )
661
663
 
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
- )
664
+ get_snowflake_facade().grant_privileges_to_role(
665
+ privileges=["read"],
666
+ object_type=ObjectType.STAGE,
667
+ object_identifier=stage_path.stage,
668
+ role_to_grant=self.role,
669
+ role_to_use=package.role,
670
+ )
669
671
 
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)
672
+ return get_snowflake_facade().create_application(
673
+ name=self.name,
674
+ package_name=package.name,
675
+ install_method=install_method,
676
+ path_to_version_directory=stage_path.full_path,
677
+ debug_mode=self.debug,
678
+ should_authorize_event_sharing=event_sharing.should_authorize_event_sharing(),
679
+ role=self.role,
680
+ warehouse=self.warehouse,
681
+ release_channel=release_channel,
682
+ )
680
683
 
681
- events_definitions = (
682
- get_snowflake_facade().get_event_definitions(
683
- self.name, self.role
684
- )
685
- )
684
+ @span("update_app_object")
685
+ def create_or_upgrade_app(
686
+ self,
687
+ package: ApplicationPackageEntity,
688
+ stage_path: DefaultStagePathParts,
689
+ install_method: SameAccountInstallMethod,
690
+ policy: PolicyBase,
691
+ interactive: bool,
692
+ release_channel: Optional[str] = None,
693
+ ):
694
+ event_sharing = EventSharingHandler(
695
+ telemetry_definition=self.telemetry,
696
+ deploy_root=package.deploy_root,
697
+ install_method=install_method,
698
+ console=self.console,
699
+ )
686
700
 
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
- )
701
+ # 1. Check for an existing application by the same name
702
+ show_app_row = get_snowflake_facade().get_existing_app_info(
703
+ self.name, self.role
704
+ )
750
705
 
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}"
706
+ # 2. If existing application is found, try to upgrade the application object.
707
+ create_or_upgrade_result = None
708
+ if show_app_row:
709
+ create_or_upgrade_result = self._upgrade_app(
710
+ stage_path=stage_path,
711
+ install_method=install_method,
712
+ event_sharing=event_sharing,
713
+ policy=policy,
714
+ interactive=interactive,
715
+ release_channel=release_channel,
716
+ )
760
717
 
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
- )
718
+ # 3. If no existing application found, or we performed a drop before the upgrade, we proceed to create
719
+ if create_or_upgrade_result is None:
720
+ create_or_upgrade_result = self._create_app(
721
+ stage_path=stage_path,
722
+ install_method=install_method,
723
+ event_sharing=event_sharing,
724
+ package=package,
725
+ release_channel=release_channel,
726
+ )
786
727
 
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
- )
728
+ print_messages(self.console, create_or_upgrade_result)
792
729
 
793
- # hooks always executed after a create or upgrade
794
- self.execute_post_deploy_hooks()
730
+ events_definitions = get_snowflake_facade().get_event_definitions(
731
+ self.name, self.role
732
+ )
795
733
 
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)
734
+ events_to_share = event_sharing.events_to_share(events_definitions)
735
+ if events_to_share is not None:
736
+ get_snowflake_facade().share_telemetry_events(
737
+ self.name, events_to_share, self.role
738
+ )
739
+
740
+ # hooks always executed after a create or upgrade
741
+ self.execute_post_deploy_hooks()
803
742
 
804
743
  def execute_post_deploy_hooks(self):
805
744
  execute_post_deploy_hooks(
806
- console=self._workspace_ctx.console,
745
+ console=self.console,
807
746
  project_root=self.project_root,
808
747
  post_deploy_hooks=self.post_deploy_hooks,
809
748
  deployed_object_type="application",
@@ -827,69 +766,59 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
827
766
  )
828
767
  )
829
768
 
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
769
  def drop_application_before_upgrade(
842
770
  self,
843
771
  policy: PolicyBase,
844
772
  interactive: bool,
845
773
  cascade: bool = False,
846
774
  ):
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
775
+ sql_executor = get_sql_executor()
776
+ with sql_executor.use_role(self.role):
777
+ if cascade:
778
+ try:
779
+ if application_objects := self.get_objects_owned_by_application():
780
+ application_objects_str = _application_objects_to_str(
781
+ application_objects
782
+ )
783
+ self.console.message(
784
+ f"The following objects are owned by application {self.name} and need to be dropped:\n{application_objects_str}"
785
+ )
786
+ except ProgrammingError as err:
787
+ if err.errno != APPLICATION_NO_LONGER_AVAILABLE:
788
+ generic_sql_error_handler(err)
789
+ self.console.warning(
790
+ "The application owns other objects but they could not be determined."
854
791
  )
855
- console.message(
856
- f"The following objects are owned by application {self.name} and need to be dropped:\n{application_objects_str}"
792
+ user_prompt = "Do you want the Snowflake CLI to drop these objects, then drop the existing application object and recreate it?"
793
+ else:
794
+ user_prompt = "Do you want the Snowflake CLI to drop the existing application object and recreate it?"
795
+
796
+ if not policy.should_proceed(user_prompt):
797
+ if interactive:
798
+ self.console.message("Not upgrading the application object.")
799
+ raise typer.Exit(0)
800
+ else:
801
+ self.console.message(
802
+ "Cannot upgrade the application object non-interactively without --force."
857
803
  )
804
+ raise typer.Exit(1)
805
+ try:
806
+ cascade_msg = " (cascade)" if cascade else ""
807
+ self.console.step(
808
+ f"Dropping application object {self.name}{cascade_msg}."
809
+ )
810
+ cascade_sql = " cascade" if cascade else ""
811
+ sql_executor.execute_query(f"drop application {self.name}{cascade_sql}")
858
812
  except ProgrammingError as err:
859
- if err.errno != APPLICATION_NO_LONGER_AVAILABLE:
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 self.drop_application_before_upgrade(
816
+ policy=policy,
817
+ interactive=interactive,
818
+ cascade=True,
819
+ )
820
+ else:
860
821
  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
822
 
894
823
  def get_events(
895
824
  self,
@@ -1043,6 +972,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
1043
972
  except KeyboardInterrupt:
1044
973
  return
1045
974
 
975
+ @span("get_snowsight_url_for_app")
1046
976
  def get_snowsight_url(self) -> str:
1047
977
  """Returns the URL that can be used to visit this app via Snowsight."""
1048
978
  name = identifier_for_url(self.name)