snowflake-cli 3.1.0__py3-none-any.whl → 3.2.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 (60) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/_app/dev/docs/templates/usage.rst.jinja2 +1 -1
  3. snowflake/cli/_plugins/connection/commands.py +124 -109
  4. snowflake/cli/_plugins/connection/util.py +54 -9
  5. snowflake/cli/_plugins/cortex/manager.py +1 -1
  6. snowflake/cli/_plugins/git/manager.py +4 -4
  7. snowflake/cli/_plugins/nativeapp/artifacts.py +64 -10
  8. snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +5 -3
  9. snowflake/cli/_plugins/nativeapp/commands.py +10 -3
  10. snowflake/cli/_plugins/nativeapp/constants.py +1 -0
  11. snowflake/cli/_plugins/nativeapp/entities/application.py +501 -440
  12. snowflake/cli/_plugins/nativeapp/entities/application_package.py +563 -885
  13. snowflake/cli/_plugins/nativeapp/entities/models/event_sharing_telemetry.py +58 -0
  14. snowflake/cli/_plugins/nativeapp/same_account_install_method.py +0 -2
  15. snowflake/cli/_plugins/nativeapp/sf_facade.py +30 -0
  16. snowflake/cli/_plugins/nativeapp/sf_facade_constants.py +25 -0
  17. snowflake/cli/_plugins/nativeapp/sf_facade_exceptions.py +117 -0
  18. snowflake/cli/_plugins/nativeapp/sf_sql_facade.py +525 -0
  19. snowflake/cli/_plugins/nativeapp/v2_conversions/compat.py +1 -89
  20. snowflake/cli/_plugins/nativeapp/version/commands.py +6 -3
  21. snowflake/cli/_plugins/notebook/manager.py +2 -2
  22. snowflake/cli/_plugins/object/commands.py +10 -1
  23. snowflake/cli/_plugins/object/manager.py +13 -5
  24. snowflake/cli/_plugins/snowpark/common.py +3 -3
  25. snowflake/cli/_plugins/snowpark/package/anaconda_packages.py +1 -1
  26. snowflake/cli/_plugins/spcs/common.py +29 -0
  27. snowflake/cli/_plugins/spcs/compute_pool/manager.py +7 -9
  28. snowflake/cli/_plugins/spcs/image_registry/manager.py +2 -2
  29. snowflake/cli/_plugins/spcs/image_repository/manager.py +1 -1
  30. snowflake/cli/_plugins/spcs/services/commands.py +64 -13
  31. snowflake/cli/_plugins/spcs/services/manager.py +75 -15
  32. snowflake/cli/_plugins/sql/commands.py +9 -1
  33. snowflake/cli/_plugins/sql/manager.py +9 -4
  34. snowflake/cli/_plugins/stage/commands.py +20 -16
  35. snowflake/cli/_plugins/stage/diff.py +1 -1
  36. snowflake/cli/_plugins/stage/manager.py +140 -11
  37. snowflake/cli/_plugins/streamlit/manager.py +5 -5
  38. snowflake/cli/_plugins/workspace/commands.py +6 -3
  39. snowflake/cli/api/cli_global_context.py +1 -0
  40. snowflake/cli/api/config.py +23 -5
  41. snowflake/cli/api/console/console.py +4 -19
  42. snowflake/cli/api/entities/utils.py +19 -32
  43. snowflake/cli/api/errno.py +2 -0
  44. snowflake/cli/api/exceptions.py +9 -0
  45. snowflake/cli/api/metrics.py +223 -7
  46. snowflake/cli/api/output/types.py +1 -1
  47. snowflake/cli/api/project/definition_conversion.py +179 -62
  48. snowflake/cli/api/rest_api.py +26 -4
  49. snowflake/cli/api/secure_utils.py +1 -1
  50. snowflake/cli/api/sql_execution.py +35 -22
  51. snowflake/cli/api/stage_path.py +5 -2
  52. {snowflake_cli-3.1.0.dist-info → snowflake_cli-3.2.1.dist-info}/METADATA +7 -8
  53. {snowflake_cli-3.1.0.dist-info → snowflake_cli-3.2.1.dist-info}/RECORD +56 -55
  54. {snowflake_cli-3.1.0.dist-info → snowflake_cli-3.2.1.dist-info}/WHEEL +1 -1
  55. snowflake/cli/_plugins/nativeapp/manager.py +0 -392
  56. snowflake/cli/_plugins/nativeapp/project_model.py +0 -211
  57. snowflake/cli/_plugins/nativeapp/run_processor.py +0 -184
  58. snowflake/cli/_plugins/nativeapp/version/version_processor.py +0 -56
  59. {snowflake_cli-3.1.0.dist-info → snowflake_cli-3.2.1.dist-info}/entry_points.txt +0 -0
  60. {snowflake_cli-3.1.0.dist-info → snowflake_cli-3.2.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,16 +1,24 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import logging
3
4
  import time
4
5
  from contextlib import contextmanager
5
6
  from datetime import datetime
6
7
  from pathlib import Path
7
8
  from textwrap import dedent
8
- from typing import Callable, Generator, List, Literal, Optional, TypedDict
9
+ from typing import Dict, Generator, List, Literal, Optional, TypedDict
9
10
 
10
11
  import typer
11
12
  from click import ClickException, UsageError
12
13
  from pydantic import Field, field_validator
13
- from snowflake.cli._plugins.connection.util import make_snowsight_url
14
+ from snowflake.cli._plugins.connection.util import (
15
+ UIParameter,
16
+ get_ui_parameter,
17
+ make_snowsight_url,
18
+ )
19
+ from snowflake.cli._plugins.nativeapp.artifacts import (
20
+ find_events_definitions_in_manifest_file,
21
+ )
14
22
  from snowflake.cli._plugins.nativeapp.common_flags import (
15
23
  ForceOption,
16
24
  InteractiveOption,
@@ -18,6 +26,7 @@ from snowflake.cli._plugins.nativeapp.common_flags import (
18
26
  )
19
27
  from snowflake.cli._plugins.nativeapp.constants import (
20
28
  ALLOWED_SPECIAL_COMMENTS,
29
+ AUTHORIZE_TELEMETRY_COL,
21
30
  COMMENT_COL,
22
31
  NAME_COL,
23
32
  OWNER_COL,
@@ -27,6 +36,9 @@ from snowflake.cli._plugins.nativeapp.entities.application_package import (
27
36
  ApplicationPackageEntity,
28
37
  ApplicationPackageEntityModel,
29
38
  )
39
+ from snowflake.cli._plugins.nativeapp.entities.models.event_sharing_telemetry import (
40
+ EventSharingTelemetry,
41
+ )
30
42
  from snowflake.cli._plugins.nativeapp.exceptions import (
31
43
  ApplicationPackageDoesNotExistError,
32
44
  NoEventTableForAccount,
@@ -40,6 +52,7 @@ from snowflake.cli._plugins.nativeapp.policy import (
40
52
  from snowflake.cli._plugins.nativeapp.same_account_install_method import (
41
53
  SameAccountInstallMethod,
42
54
  )
55
+ from snowflake.cli._plugins.nativeapp.sf_facade import get_snowflake_facade
43
56
  from snowflake.cli._plugins.nativeapp.utils import needs_confirmation
44
57
  from snowflake.cli._plugins.workspace.context import ActionContext
45
58
  from snowflake.cli.api.cli_global_context import get_cli_context
@@ -54,6 +67,8 @@ from snowflake.cli.api.entities.utils import (
54
67
  from snowflake.cli.api.errno import (
55
68
  APPLICATION_NO_LONGER_AVAILABLE,
56
69
  APPLICATION_OWNS_EXTERNAL_OBJECTS,
70
+ APPLICATION_REQUIRES_TELEMETRY_SHARING,
71
+ CANNOT_DISABLE_MANDATORY_TELEMETRY,
57
72
  CANNOT_UPGRADE_FROM_LOOSE_FILES_TO_VERSION,
58
73
  CANNOT_UPGRADE_FROM_VERSION_TO_LOOSE_FILES,
59
74
  DOES_NOT_EXIST_OR_NOT_AUTHORIZED,
@@ -77,6 +92,8 @@ from snowflake.cli.api.project.util import (
77
92
  )
78
93
  from snowflake.connector import DictCursor, ProgrammingError
79
94
 
95
+ log = logging.getLogger(__name__)
96
+
80
97
  # Reasons why an `alter application ... upgrade` might fail
81
98
  UPGRADE_RESTRICTION_CODES = {
82
99
  CANNOT_UPGRADE_FROM_LOOSE_FILES_TO_VERSION,
@@ -89,6 +106,192 @@ UPGRADE_RESTRICTION_CODES = {
89
106
  ApplicationOwnedObject = TypedDict("ApplicationOwnedObject", {"name": str, "type": str})
90
107
 
91
108
 
109
+ class EventSharingHandler:
110
+ """
111
+ Handles the logic around event sharing for applications.
112
+ This class is used to determine whether event sharing should be authorized or not, and which events should be shared.
113
+ """
114
+
115
+ def __init__(
116
+ self,
117
+ *,
118
+ telemetry_definition: Optional[EventSharingTelemetry],
119
+ deploy_root: Path,
120
+ install_method: SameAccountInstallMethod,
121
+ console: AbstractConsole,
122
+ ):
123
+ """
124
+ Initializes the event sharing handler.
125
+ If telemetry_definition is not present or share_mandatory_events is not set,
126
+ we will default to sharing events if mandatory events are present in the manifest file and we are in dev mode.
127
+
128
+ :param telemetry_definition: The telemetry configuration for this application from the PDF if present.
129
+ :param deploy_root: The root directory of the application package.
130
+ :param install_method: The install method of the application.
131
+ :param console: The console to use for logging.
132
+ """
133
+
134
+ self._is_dev_mode = install_method.is_dev_mode
135
+ self._metrics = get_cli_context().metrics
136
+ 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"
143
+ )
144
+ self._event_sharing_enforced = (
145
+ get_ui_parameter(
146
+ connection, UIParameter.NA_ENFORCE_MANDATORY_FILTERS, "true"
147
+ ).lower()
148
+ == "true"
149
+ )
150
+
151
+ self._share_mandatory_events = (
152
+ telemetry_definition and telemetry_definition.share_mandatory_events
153
+ )
154
+ self._optional_shared_events = (
155
+ telemetry_definition and telemetry_definition.optional_shared_events
156
+ ) or []
157
+
158
+ self._metrics.set_counter(
159
+ CLICounterField.EVENT_SHARING, int(self._share_mandatory_events or False)
160
+ )
161
+ self._metrics.set_counter(CLICounterField.EVENT_SHARING_WARNING, 0)
162
+ self._metrics.set_counter(CLICounterField.EVENT_SHARING_ERROR, 0)
163
+
164
+ if not self._event_sharing_enabled:
165
+ # We cannot set AUTHORIZE_TELEMETRY_EVENT_SHARING to True or False if event sharing is not enabled,
166
+ # so we will ignore the field in both cases, but warn only if it is set to True
167
+ if self._share_mandatory_events or self._optional_shared_events:
168
+ self.event_sharing_warning(
169
+ "Same-account event sharing is not enabled in your account, therefore, application telemetry section will be ignored."
170
+ )
171
+ self._share_mandatory_events = None
172
+ self._optional_shared_events = []
173
+ return
174
+
175
+ if install_method.is_dev_mode:
176
+ mandatory_events_in_manifest = self._contains_mandatory_events(
177
+ find_events_definitions_in_manifest_file(deploy_root)
178
+ )
179
+
180
+ if mandatory_events_in_manifest and self._event_sharing_enforced:
181
+ if self._share_mandatory_events is None:
182
+ self.event_sharing_warning(
183
+ "Mandatory events are present in the manifest file. Automatically authorizing event sharing in dev mode. To suppress this warning, please add 'share_mandatory_events: true' in the application telemetry section."
184
+ )
185
+ self._share_mandatory_events = True
186
+
187
+ def _contains_mandatory_events(self, events_definitions: List[Dict[str, str]]):
188
+ return any(event["sharing"] == "MANDATORY" for event in events_definitions)
189
+
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(
208
+ self,
209
+ upgraded_app_properties: Dict[str, str],
210
+ ) -> Optional[bool]:
211
+ """
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.
215
+
216
+ Outputs:
217
+ - None: Event sharing should not be updated or explicitly set.
218
+ - True: Event sharing should be authorized.
219
+ - False: Event sharing should be disabled.
220
+ """
221
+
222
+ if not self._event_sharing_enabled:
223
+ return None
224
+
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
+ return self._share_mandatory_events
235
+
236
+ def event_sharing_warning(self, message: str):
237
+ """
238
+ Logs a warning message about event sharing, and emits an event sharing warning metric.
239
+ """
240
+
241
+ self._metrics.set_counter(CLICounterField.EVENT_SHARING_WARNING, 1)
242
+ self._console.warning(f"WARNING: {message}")
243
+
244
+ def event_sharing_error(self, message: str, err: Exception):
245
+ """
246
+ Raises an error about event sharing, and emits an event sharing error metric.
247
+ """
248
+
249
+ self._metrics.set_counter(CLICounterField.EVENT_SHARING_ERROR, 1)
250
+ raise ClickException(message) from err
251
+
252
+ def events_to_share(
253
+ self, events_definitions: List[Dict[str, str]]
254
+ ) -> Optional[List[str]]:
255
+ """
256
+ Determines which events should be shared based on the application events definitions.
257
+ It also combines the mandatory events with the optional shared events.
258
+
259
+ :param events_definitions: The events definitions of the application.
260
+
261
+ Outputs:
262
+ - None: No events should be updated.
263
+ - List[str]: The names of the events that should be shared.
264
+ """
265
+
266
+ # events definition has this format: [{'name': 'SNOWFLAKE$ERRORS_AND_WARNINGS', 'type': 'ERRORS_AND_WARNINGS', 'sharing': 'MANDATORY'...}]
267
+ events_map = {event["type"]: event for event in events_definitions}
268
+ events_names = []
269
+ for event_type in self._optional_shared_events:
270
+ if event_type not in events_map:
271
+ raise ClickException(
272
+ f"Event '{event_type}' from application 'telemetry' section in the project definition file could not be found in the application."
273
+ )
274
+ else:
275
+ events_names.append(events_map[event_type]["name"])
276
+
277
+ # add mandatory events to event_names list:
278
+ for event in events_definitions:
279
+ if event["sharing"] == "MANDATORY":
280
+ events_names.append(event["name"])
281
+
282
+ if not self._share_mandatory_events:
283
+ if (
284
+ self._contains_mandatory_events(events_definitions)
285
+ and not self._event_sharing_enforced
286
+ ):
287
+ self.event_sharing_warning(
288
+ "Mandatory events are present in the application, but event sharing is not authorized in the application telemetry field. This will soon be required to set in order to deploy this application."
289
+ )
290
+ return None
291
+
292
+ return sorted(list(set(events_names)))
293
+
294
+
92
295
  class ApplicationEntityModel(EntityModelBase):
93
296
  type: Literal["application"] = DiscriminatorField() # noqa A003
94
297
  from_: TargetField[ApplicationPackageEntityModel] = Field(
@@ -99,6 +302,10 @@ class ApplicationEntityModel(EntityModelBase):
99
302
  title="Whether to enable debug mode when using a named stage to create an application object",
100
303
  default=None,
101
304
  )
305
+ telemetry: Optional[EventSharingTelemetry] = Field(
306
+ title="Telemetry configuration for the application",
307
+ default=None,
308
+ )
102
309
 
103
310
  @field_validator("identifier")
104
311
  @classmethod
@@ -119,6 +326,35 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
119
326
  A Native App application object, created from an application package.
120
327
  """
121
328
 
329
+ @property
330
+ def project_root(self) -> Path:
331
+ return self._workspace_ctx.project_root
332
+
333
+ @property
334
+ def package_entity_id(self) -> str:
335
+ return self._entity_model.from_.target
336
+
337
+ @property
338
+ def name(self) -> str:
339
+ return to_identifier(self._entity_model.fqn.name)
340
+
341
+ @property
342
+ def role(self) -> str:
343
+ model = self._entity_model
344
+ return (model.meta and model.meta.role) or self._workspace_ctx.default_role
345
+
346
+ @property
347
+ def warehouse(self) -> str:
348
+ model = self._entity_model
349
+ return (
350
+ model.meta and model.meta.warehouse and to_identifier(model.meta.warehouse)
351
+ ) or to_identifier(self._workspace_ctx.default_warehouse)
352
+
353
+ @property
354
+ def post_deploy_hooks(self) -> list[PostDeployHook] | None:
355
+ model = self._entity_model
356
+ return model.meta and model.meta.post_deploy
357
+
122
358
  def action_deploy(
123
359
  self,
124
360
  action_ctx: ActionContext,
@@ -135,86 +371,72 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
135
371
  *args,
136
372
  **kwargs,
137
373
  ):
138
- model = self._entity_model
139
- workspace_ctx = self._workspace_ctx
140
- app_name = model.fqn.identifier
141
- debug_mode = model.debug
142
- if model.meta:
143
- app_role = model.meta.role or workspace_ctx.default_role
144
- app_warehouse = model.meta.warehouse or workspace_ctx.default_warehouse
145
- post_deploy_hooks = model.meta.post_deploy
146
- else:
147
- app_role = workspace_ctx.default_role
148
- app_warehouse = workspace_ctx.default_warehouse
149
- post_deploy_hooks = None
150
-
374
+ """
375
+ Create or upgrade the application object using the given strategy
376
+ (unversioned dev, versioned dev, or same-account release directive).
377
+ """
151
378
  package_entity: ApplicationPackageEntity = action_ctx.get_entity(
152
- model.from_.target
153
- )
154
- package_model: ApplicationPackageEntityModel = (
155
- package_entity._entity_model # noqa: SLF001
379
+ self.package_entity_id
156
380
  )
157
- package_name = package_model.fqn.identifier
158
- if package_model.meta and package_model.meta.role:
159
- package_role = package_model.meta.role
160
- else:
161
- package_role = workspace_ctx.default_role
381
+ stage_fqn = stage_fqn or package_entity.stage_fqn
162
382
 
163
- if not stage_fqn:
164
- stage_fqn = f"{package_name}.{package_model.stage}"
165
- stage_schema = extract_schema(stage_fqn)
166
-
167
- is_interactive = False
168
383
  if force:
169
384
  policy = AllowAlwaysPolicy()
170
385
  elif interactive:
171
- is_interactive = True
172
386
  policy = AskAlwaysPolicy()
173
387
  else:
174
388
  policy = DenyAlwaysPolicy()
175
389
 
176
- def deploy_package():
177
- package_entity.action_deploy(
178
- action_ctx=action_ctx,
179
- prune=True,
180
- recursive=True,
181
- paths=[],
182
- validate=validate,
390
+ # same-account release directive
391
+ if from_release_directive:
392
+ self.create_or_upgrade_app(
393
+ package=package_entity,
183
394
  stage_fqn=stage_fqn,
395
+ install_method=SameAccountInstallMethod.release_directive(),
396
+ policy=policy,
184
397
  interactive=interactive,
185
- force=force,
186
398
  )
399
+ return
400
+
401
+ # versioned dev
402
+ if version:
403
+ try:
404
+ version_exists = package_entity.get_existing_version_info(version)
405
+ if not version_exists:
406
+ raise UsageError(
407
+ f"Application package {package_entity.name} does not have any version {version} defined. Use 'snow app version create' to define a version in the application package first."
408
+ )
409
+ except ApplicationPackageDoesNotExistError as app_err:
410
+ raise UsageError(
411
+ f"Application package {package_entity.name} does not exist. Use 'snow app version create' to first create an application package and then define a version in it."
412
+ )
187
413
 
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,
414
+ self.create_or_upgrade_app(
415
+ package=package_entity,
416
+ stage_fqn=stage_fqn,
417
+ install_method=SameAccountInstallMethod.versioned_dev(version, patch),
193
418
  policy=policy,
194
- is_interactive=is_interactive,
195
- cascade=cascade,
419
+ interactive=interactive,
196
420
  )
421
+ return
197
422
 
198
- self.deploy(
199
- console=workspace_ctx.console,
200
- project_root=workspace_ctx.project_root,
201
- app_name=app_name,
202
- app_role=app_role,
203
- app_warehouse=app_warehouse,
204
- package_name=package_name,
205
- package_role=package_role,
206
- stage_schema=stage_schema,
207
- stage_fqn=stage_fqn,
208
- debug_mode=debug_mode,
423
+ # unversioned dev
424
+ package_entity.action_deploy(
425
+ action_ctx=action_ctx,
426
+ prune=True,
427
+ recursive=True,
428
+ paths=[],
209
429
  validate=validate,
210
- from_release_directive=from_release_directive,
211
- is_interactive=is_interactive,
430
+ stage_fqn=stage_fqn,
431
+ interactive=interactive,
432
+ force=force,
433
+ )
434
+ self.create_or_upgrade_app(
435
+ package=package_entity,
436
+ stage_fqn=stage_fqn,
437
+ install_method=SameAccountInstallMethod.unversioned_dev(),
212
438
  policy=policy,
213
- version=version,
214
- patch=patch,
215
- post_deploy_hooks=post_deploy_hooks,
216
- deploy_package=deploy_package,
217
- drop_application_before_upgrade=drop_application_before_upgrade,
439
+ interactive=interactive,
218
440
  )
219
441
 
220
442
  def action_drop(
@@ -225,113 +447,33 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
225
447
  cascade: Optional[bool] = None,
226
448
  *args,
227
449
  **kwargs,
228
- ):
229
- model = self._entity_model
230
- workspace_ctx = self._workspace_ctx
231
- app_name = model.fqn.identifier
232
- if model.meta and model.meta.role:
233
- app_role = model.meta.role
234
- else:
235
- app_role = workspace_ctx.default_role
236
- self.drop(
237
- console=workspace_ctx.console,
238
- app_name=app_name,
239
- app_role=app_role,
240
- auto_yes=force_drop,
241
- interactive=interactive,
242
- cascade=cascade,
243
- )
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
-
297
- @classmethod
298
- def drop(
299
- cls,
300
- console: AbstractConsole,
301
- app_name: str,
302
- app_role: str,
303
- auto_yes: bool,
304
- interactive: bool = False,
305
- cascade: Optional[bool] = None,
306
450
  ):
307
451
  """
308
452
  Attempts to drop the application object if all validations and user prompts allow so.
309
453
  """
454
+ console = self._workspace_ctx.console
310
455
 
311
456
  needs_confirm = True
312
457
 
313
458
  # 1. If existing application is not found, exit gracefully
314
- show_obj_row = cls.get_existing_app_info_static(
315
- app_name=app_name,
316
- app_role=app_role,
317
- )
459
+ show_obj_row = self.get_existing_app_info()
318
460
  if show_obj_row is None:
319
461
  console.warning(
320
- f"Role {app_role} does not own any application object with the name {app_name}, or the application object does not exist."
462
+ f"Role {self.role} does not own any application object with the name {self.name}, or the application object does not exist."
321
463
  )
322
464
  return
323
465
 
324
466
  # 2. Check if created by the Snowflake CLI
325
467
  row_comment = show_obj_row[COMMENT_COL]
326
468
  if row_comment not in ALLOWED_SPECIAL_COMMENTS and needs_confirmation(
327
- needs_confirm, auto_yes
469
+ needs_confirm, force_drop
328
470
  ):
329
471
  should_drop_object = typer.confirm(
330
472
  dedent(
331
473
  f"""\
332
- Application object {app_name} was not created by Snowflake CLI.
474
+ Application object {self.name} was not created by Snowflake CLI.
333
475
  Application object details:
334
- Name: {app_name}
476
+ Name: {self.name}
335
477
  Created on: {show_obj_row["created_on"]}
336
478
  Source: {show_obj_row["source"]}
337
479
  Owner: {show_obj_row[OWNER_COL]}
@@ -343,7 +485,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
343
485
  )
344
486
  )
345
487
  if not should_drop_object:
346
- console.message(f"Did not drop application object {app_name}.")
488
+ console.message(f"Did not drop application object {self.name}.")
347
489
  # The user desires to keep the app, therefore we can't proceed since it would
348
490
  # leave behind an orphan app when we get to dropping the package
349
491
  raise typer.Abort()
@@ -357,13 +499,10 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
357
499
  interactive_prompt = ""
358
500
  non_interactive_abort = ""
359
501
  try:
360
- if application_objects := cls.get_objects_owned_by_application(
361
- app_name=app_name,
362
- app_role=app_role,
363
- ):
502
+ if application_objects := self.get_objects_owned_by_application():
364
503
  has_objects_to_drop = True
365
504
  message_prefix = (
366
- f"The following objects are owned by application {app_name}"
505
+ f"The following objects are owned by application {self.name}"
367
506
  )
368
507
  cascade_true_message = f"{message_prefix} and will be dropped:"
369
508
  cascade_false_message = f"{message_prefix} and will NOT be dropped:"
@@ -373,9 +512,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
373
512
  if e.errno != APPLICATION_NO_LONGER_AVAILABLE:
374
513
  raise
375
514
  application_objects = []
376
- message_prefix = (
377
- f"Could not determine which objects are owned by application {app_name}"
378
- )
515
+ message_prefix = f"Could not determine which objects are owned by application {self.name}"
379
516
  has_objects_to_drop = True # potentially, but we don't know what they are
380
517
  cascade_true_message = (
381
518
  f"{message_prefix}, an unknown number of objects will be dropped."
@@ -390,19 +527,19 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
390
527
  console.message(cascade_true_message)
391
528
  with console.indented():
392
529
  for obj in application_objects:
393
- console.message(cls.application_object_to_str(obj))
530
+ console.message(_application_object_to_str(obj))
394
531
  elif cascade is False:
395
532
  # If the user explicitly passed the --no-cascade flag
396
533
  console.message(cascade_false_message)
397
534
  with console.indented():
398
535
  for obj in application_objects:
399
- console.message(cls.application_object_to_str(obj))
536
+ console.message(_application_object_to_str(obj))
400
537
  elif interactive:
401
538
  # If the user didn't pass any cascade flag and the session is interactive
402
539
  console.message(message_prefix)
403
540
  with console.indented():
404
541
  for obj in application_objects:
405
- console.message(cls.application_object_to_str(obj))
542
+ console.message(_application_object_to_str(obj))
406
543
  user_response = typer.prompt(
407
544
  interactive_prompt,
408
545
  show_default=False,
@@ -419,7 +556,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
419
556
  console.message(message_prefix)
420
557
  with console.indented():
421
558
  for obj in application_objects:
422
- console.message(cls.application_object_to_str(obj))
559
+ console.message(_application_object_to_str(obj))
423
560
  console.message(non_interactive_abort)
424
561
  raise typer.Abort()
425
562
  elif cascade is None:
@@ -430,243 +567,185 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
430
567
  drop_generic_object(
431
568
  console=console,
432
569
  object_type="application",
433
- object_name=app_name,
434
- role=app_role,
570
+ object_name=self.name,
571
+ role=self.role,
435
572
  cascade=cascade,
436
573
  )
437
- return # The application object was successfully dropped, therefore exit gracefully
438
574
 
439
- @staticmethod
440
- def get_objects_owned_by_application(
441
- app_name: str,
442
- app_role: str,
443
- ) -> List[ApplicationOwnedObject]:
575
+ def action_events(
576
+ self,
577
+ action_ctx: ActionContext,
578
+ since: str | datetime | None = None,
579
+ until: str | datetime | None = None,
580
+ record_types: list[str] | None = None,
581
+ scopes: list[str] | None = None,
582
+ consumer_org: str = "",
583
+ consumer_account: str = "",
584
+ consumer_app_hash: str = "",
585
+ first: int = -1,
586
+ last: int = -1,
587
+ follow: bool = False,
588
+ interval_seconds: int = 10,
589
+ *args,
590
+ **kwargs,
591
+ ):
592
+ package_entity: ApplicationPackageEntity = action_ctx.get_entity(
593
+ self.package_entity_id
594
+ )
595
+ if follow:
596
+ return self.stream_events(
597
+ package_name=package_entity.name,
598
+ interval_seconds=interval_seconds,
599
+ since=since,
600
+ record_types=record_types,
601
+ scopes=scopes,
602
+ consumer_org=consumer_org,
603
+ consumer_account=consumer_account,
604
+ consumer_app_hash=consumer_app_hash,
605
+ last=last,
606
+ )
607
+ else:
608
+ return self.get_events(
609
+ package_name=package_entity.name,
610
+ since=since,
611
+ until=until,
612
+ record_types=record_types,
613
+ scopes=scopes,
614
+ consumer_org=consumer_org,
615
+ consumer_account=consumer_account,
616
+ consumer_app_hash=consumer_app_hash,
617
+ first=first,
618
+ last=last,
619
+ )
620
+
621
+ def get_objects_owned_by_application(self) -> List[ApplicationOwnedObject]:
444
622
  """
445
623
  Returns all application objects owned by this application.
446
624
  """
447
625
  sql_executor = get_sql_executor()
448
- with sql_executor.use_role(app_role):
626
+ with sql_executor.use_role(self.role):
449
627
  results = sql_executor.execute_query(
450
- f"show objects owned by application {app_name}"
628
+ f"show objects owned by application {self.name}"
451
629
  ).fetchall()
452
630
  return [{"name": row[1], "type": row[2]} for row in results]
453
631
 
454
- @classmethod
455
- def application_objects_to_str(
456
- cls, application_objects: list[ApplicationOwnedObject]
457
- ) -> str:
458
- """
459
- Returns a list in an "(Object Type) Object Name" format. Database-level and schema-level object names are fully qualified:
460
- (COMPUTE_POOL) POOL_NAME
461
- (DATABASE) DB_NAME
462
- (SCHEMA) DB_NAME.PUBLIC
463
- ...
464
- """
465
- return "\n".join(
466
- [cls.application_object_to_str(obj) for obj in application_objects]
467
- )
468
-
469
- @staticmethod
470
- def application_object_to_str(obj: ApplicationOwnedObject) -> str:
471
- return f"({obj['type']}) {obj['name']}"
472
-
473
- @classmethod
474
- def deploy(
475
- cls,
476
- console: AbstractConsole,
477
- project_root: Path,
478
- app_name: str,
479
- app_role: str,
480
- app_warehouse: str,
481
- package_name: str,
482
- package_role: str,
483
- stage_schema: str,
632
+ def create_or_upgrade_app(
633
+ self,
634
+ package: ApplicationPackageEntity,
484
635
  stage_fqn: str,
485
- debug_mode: bool,
486
- validate: bool,
487
- from_release_directive: bool,
488
- is_interactive: bool,
636
+ install_method: SameAccountInstallMethod,
489
637
  policy: PolicyBase,
490
- deploy_package: Callable,
491
- version: Optional[str] = None,
492
- patch: Optional[int] = None,
493
- post_deploy_hooks: Optional[List[PostDeployHook]] = None,
494
- drop_application_before_upgrade: Optional[Callable] = None,
638
+ interactive: bool,
495
639
  ):
496
- """
497
- Create or upgrade the application object using the given strategy
498
- (unversioned dev, versioned dev, or same-account release directive).
499
- """
500
-
501
- # same-account release directive
502
- if from_release_directive:
503
- cls.create_or_upgrade_app(
504
- console=console,
505
- project_root=project_root,
506
- package_name=package_name,
507
- package_role=package_role,
508
- app_name=app_name,
509
- app_role=app_role,
510
- app_warehouse=app_warehouse,
511
- stage_schema=stage_schema,
512
- stage_fqn=stage_fqn,
513
- debug_mode=debug_mode,
514
- policy=policy,
515
- install_method=SameAccountInstallMethod.release_directive(),
516
- is_interactive=is_interactive,
517
- post_deploy_hooks=post_deploy_hooks,
518
- drop_application_before_upgrade=drop_application_before_upgrade,
519
- )
520
- return
640
+ model = self._entity_model
641
+ console = self._workspace_ctx.console
642
+ debug_mode = model.debug
521
643
 
522
- # versioned dev
523
- if version:
524
- try:
525
- version_exists = ApplicationPackageEntity.get_existing_version_info(
526
- version=version,
527
- package_name=package_name,
528
- package_role=package_role,
529
- )
530
- if not version_exists:
531
- raise UsageError(
532
- f"Application package {package_name} does not have any version {version} defined. Use 'snow app version create' to define a version in the application package first."
533
- )
534
- except ApplicationPackageDoesNotExistError as app_err:
535
- raise UsageError(
536
- f"Application package {package_name} does not exist. Use 'snow app version create' to first create an application package and then define a version in it."
537
- )
644
+ stage_fqn = stage_fqn or package.stage_fqn
645
+ stage_schema = extract_schema(stage_fqn)
538
646
 
539
- cls.create_or_upgrade_app(
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,
652
+ install_method=install_method,
540
653
  console=console,
541
- project_root=project_root,
542
- package_name=package_name,
543
- package_role=package_role,
544
- app_name=app_name,
545
- app_role=app_role,
546
- app_warehouse=app_warehouse,
547
- stage_schema=stage_schema,
548
- stage_fqn=stage_fqn,
549
- debug_mode=debug_mode,
550
- policy=policy,
551
- install_method=SameAccountInstallMethod.versioned_dev(version, patch),
552
- is_interactive=is_interactive,
553
- post_deploy_hooks=post_deploy_hooks,
554
- drop_application_before_upgrade=drop_application_before_upgrade,
555
654
  )
556
- return
557
-
558
- # unversioned dev
559
- deploy_package()
560
- cls.create_or_upgrade_app(
561
- console=console,
562
- project_root=project_root,
563
- package_name=package_name,
564
- package_role=package_role,
565
- app_name=app_name,
566
- app_role=app_role,
567
- app_warehouse=app_warehouse,
568
- stage_schema=stage_schema,
569
- stage_fqn=stage_fqn,
570
- debug_mode=debug_mode,
571
- policy=policy,
572
- install_method=SameAccountInstallMethod.unversioned_dev(),
573
- is_interactive=is_interactive,
574
- post_deploy_hooks=post_deploy_hooks,
575
- drop_application_before_upgrade=drop_application_before_upgrade,
576
- )
577
-
578
- @classmethod
579
- def create_or_upgrade_app(
580
- cls,
581
- console: AbstractConsole,
582
- project_root: Path,
583
- package_name: str,
584
- package_role: str,
585
- app_name: str,
586
- app_role: str,
587
- app_warehouse: Optional[str],
588
- stage_schema: Optional[str],
589
- stage_fqn: str,
590
- debug_mode: bool,
591
- policy: PolicyBase,
592
- install_method: SameAccountInstallMethod,
593
- is_interactive: bool = False,
594
- post_deploy_hooks: Optional[List[PostDeployHook]] = None,
595
- drop_application_before_upgrade: Optional[Callable] = None,
596
- ):
597
- sql_executor = get_sql_executor()
598
- with sql_executor.use_role(app_role):
599
655
 
600
656
  # 1. Need to use a warehouse to create an application object
601
- with sql_executor.use_warehouse(app_warehouse):
657
+ with sql_executor.use_warehouse(self.warehouse):
602
658
 
603
659
  # 2. Check for an existing application by the same name
604
- show_app_row = cls.get_existing_app_info_static(
605
- app_name=app_name,
606
- app_role=app_role,
607
- )
660
+ show_app_row = self.get_existing_app_info()
608
661
 
609
662
  # 3. If existing application is found, perform a few validations and upgrade the application object.
610
663
  if show_app_row:
611
-
612
664
  install_method.ensure_app_usable(
613
- app_name=app_name,
614
- app_role=app_role,
665
+ app_name=self.name,
666
+ app_role=self.role,
615
667
  show_app_row=show_app_row,
616
668
  )
617
669
 
618
670
  # If all the above checks are in order, proceed to upgrade
619
671
  try:
620
672
  console.step(
621
- f"Upgrading existing application object {app_name}."
673
+ f"Upgrading existing application object {self.name}."
622
674
  )
623
675
  using_clause = install_method.using_clause(stage_fqn)
624
676
  upgrade_cursor = sql_executor.execute_query(
625
- f"alter application {app_name} upgrade {using_clause}",
677
+ f"alter application {self.name} upgrade {using_clause}",
626
678
  )
627
679
  print_messages(console, upgrade_cursor)
628
680
 
681
+ events_definitions = (
682
+ get_snowflake_facade().get_event_definitions(
683
+ self.name, self.role
684
+ )
685
+ )
686
+
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
+
629
711
  if install_method.is_dev_mode:
630
712
  # if debug_mode is present (controlled), ensure it is up-to-date
631
713
  if debug_mode is not None:
632
714
  sql_executor.execute_query(
633
- f"alter application {app_name} set debug_mode = {debug_mode}"
715
+ f"alter application {self.name} set debug_mode = {debug_mode}"
634
716
  )
635
717
 
636
718
  # hooks always executed after a create or upgrade
637
- cls.execute_post_deploy_hooks(
638
- console=console,
639
- project_root=project_root,
640
- post_deploy_hooks=post_deploy_hooks,
641
- app_name=app_name,
642
- app_warehouse=app_warehouse,
643
- )
719
+ self.execute_post_deploy_hooks()
644
720
  return
645
721
 
646
722
  except ProgrammingError as err:
647
- if err.errno not in UPGRADE_RESTRICTION_CODES:
648
- generic_sql_error_handler(err=err)
649
- else: # The existing application object was created from a different process.
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:
650
729
  console.warning(err.msg)
651
- # TODO Drop the entity here instead of taking a callback once action_drop() is implemented
652
- if drop_application_before_upgrade:
653
- drop_application_before_upgrade()
654
- else:
655
- raise NotImplementedError
730
+ self.drop_application_before_upgrade(
731
+ policy=policy, interactive=interactive
732
+ )
733
+ else:
734
+ generic_sql_error_handler(err=err)
656
735
 
657
736
  # 4. With no (more) existing application objects, create an application object using the release directives
658
- console.step(f"Creating new application object {app_name} in account.")
737
+ console.step(f"Creating new application object {self.name} in account.")
659
738
 
660
- if app_role != package_role:
661
- with sql_executor.use_role(package_role):
739
+ if self.role != package.role:
740
+ with sql_executor.use_role(package.role):
662
741
  sql_executor.execute_query(
663
- f"grant install, develop on application package {package_name} to role {app_role}"
742
+ f"grant install, develop on application package {package.name} to role {self.role}"
664
743
  )
665
744
  sql_executor.execute_query(
666
- f"grant usage on schema {package_name}.{stage_schema} to role {app_role}"
745
+ f"grant usage on schema {package.name}.{stage_schema} to role {self.role}"
667
746
  )
668
747
  sql_executor.execute_query(
669
- f"grant read on stage {stage_fqn} to role {app_role}"
748
+ f"grant read on stage {stage_fqn} to role {self.role}"
670
749
  )
671
750
 
672
751
  try:
@@ -679,60 +758,64 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
679
758
  )
680
759
  debug_mode_clause = f"debug_mode = {initial_debug_mode}"
681
760
 
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
+
682
772
  using_clause = install_method.using_clause(stage_fqn)
683
773
  create_cursor = sql_executor.execute_query(
684
774
  dedent(
685
775
  f"""\
686
- create application {app_name}
687
- from application package {package_name} {using_clause} {debug_mode_clause}
776
+ create application {self.name}
777
+ from application package {package.name} {using_clause} {debug_mode_clause}{authorize_telemetry_clause}
688
778
  comment = {SPECIAL_COMMENT}
689
779
  """
690
780
  ),
691
781
  )
692
782
  print_messages(console, create_cursor)
783
+ events_definitions = get_snowflake_facade().get_event_definitions(
784
+ self.name, self.role
785
+ )
786
+
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
+ )
693
792
 
694
793
  # hooks always executed after a create or upgrade
695
- cls.execute_post_deploy_hooks(
696
- console=console,
697
- project_root=project_root,
698
- post_deploy_hooks=post_deploy_hooks,
699
- app_name=app_name,
700
- app_warehouse=app_warehouse,
701
- )
794
+ self.execute_post_deploy_hooks()
702
795
 
703
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
+ )
704
802
  generic_sql_error_handler(err)
705
803
 
706
- @classmethod
707
- def execute_post_deploy_hooks(
708
- cls,
709
- console: AbstractConsole,
710
- project_root: Path,
711
- post_deploy_hooks: Optional[List[PostDeployHook]],
712
- app_name: str,
713
- app_warehouse: Optional[str],
714
- ):
715
- get_cli_context().metrics.set_counter_default(
716
- CLICounterField.POST_DEPLOY_SCRIPTS, 0
804
+ def execute_post_deploy_hooks(self):
805
+ execute_post_deploy_hooks(
806
+ console=self._workspace_ctx.console,
807
+ project_root=self.project_root,
808
+ post_deploy_hooks=self.post_deploy_hooks,
809
+ deployed_object_type="application",
810
+ role_name=self.role,
811
+ warehouse_name=self.warehouse,
812
+ database_name=self.name,
717
813
  )
718
814
 
719
- if post_deploy_hooks:
720
- with cls.use_application_warehouse(app_warehouse):
721
- execute_post_deploy_hooks(
722
- console=console,
723
- project_root=project_root,
724
- post_deploy_hooks=post_deploy_hooks,
725
- deployed_object_type="application",
726
- database_name=app_name,
727
- )
728
-
729
- @staticmethod
730
815
  @contextmanager
731
- def use_application_warehouse(
732
- app_warehouse: Optional[str],
733
- ):
734
- if app_warehouse:
735
- with get_sql_executor().use_warehouse(app_warehouse):
816
+ def use_application_warehouse(self):
817
+ if self.warehouse:
818
+ with get_sql_executor().use_warehouse(self.warehouse):
736
819
  yield
737
820
  else:
738
821
  raise ClickException(
@@ -745,44 +828,32 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
745
828
  )
746
829
 
747
830
  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
754
- @staticmethod
755
- def get_existing_app_info_static(app_name: str, app_role: str) -> Optional[dict]:
756
831
  """
757
832
  Check for an existing application object by the same name as in project definition, in account.
758
833
  It executes a 'show applications like' query and returns the result as single row, if one exists.
759
834
  """
760
835
  sql_executor = get_sql_executor()
761
- with sql_executor.use_role(app_role):
836
+ with sql_executor.use_role(self.role):
762
837
  return sql_executor.show_specific_object(
763
- "applications", app_name, name_col=NAME_COL
838
+ "applications", self.name, name_col=NAME_COL
764
839
  )
765
840
 
766
- @classmethod
767
841
  def drop_application_before_upgrade(
768
- cls,
769
- console: AbstractConsole,
770
- app_name: str,
771
- app_role: str,
842
+ self,
772
843
  policy: PolicyBase,
773
- is_interactive: bool,
844
+ interactive: bool,
774
845
  cascade: bool = False,
775
846
  ):
847
+ console = self._workspace_ctx.console
848
+
776
849
  if cascade:
777
850
  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(
851
+ if application_objects := self.get_objects_owned_by_application():
852
+ application_objects_str = _application_objects_to_str(
782
853
  application_objects
783
854
  )
784
855
  console.message(
785
- f"The following objects are owned by application {app_name} and need to be dropped:\n{application_objects_str}"
856
+ f"The following objects are owned by application {self.name} and need to be dropped:\n{application_objects_str}"
786
857
  )
787
858
  except ProgrammingError as err:
788
859
  if err.errno != APPLICATION_NO_LONGER_AVAILABLE:
@@ -795,7 +866,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
795
866
  user_prompt = "Do you want the Snowflake CLI to drop the existing application object and recreate it?"
796
867
 
797
868
  if not policy.should_proceed(user_prompt):
798
- if is_interactive:
869
+ if interactive:
799
870
  console.message("Not upgrading the application object.")
800
871
  raise typer.Exit(0)
801
872
  else:
@@ -805,28 +876,23 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
805
876
  raise typer.Exit(1)
806
877
  try:
807
878
  cascade_msg = " (cascade)" if cascade else ""
808
- console.step(f"Dropping application object {app_name}{cascade_msg}.")
879
+ console.step(f"Dropping application object {self.name}{cascade_msg}.")
809
880
  cascade_sql = " cascade" if cascade else ""
810
881
  sql_executor = get_sql_executor()
811
- sql_executor.execute_query(f"drop application {app_name}{cascade_sql}")
882
+ sql_executor.execute_query(f"drop application {self.name}{cascade_sql}")
812
883
  except ProgrammingError as err:
813
884
  if err.errno == APPLICATION_OWNS_EXTERNAL_OBJECTS and not cascade:
814
885
  # 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,
886
+ return self.drop_application_before_upgrade(
819
887
  policy=policy,
820
- is_interactive=is_interactive,
888
+ interactive=interactive,
821
889
  cascade=True,
822
890
  )
823
891
  else:
824
892
  generic_sql_error_handler(err)
825
893
 
826
- @classmethod
827
894
  def get_events(
828
- cls,
829
- app_name: str,
895
+ self,
830
896
  package_name: str,
831
897
  since: str | datetime | None = None,
832
898
  until: str | datetime | None = None,
@@ -844,12 +910,12 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
844
910
  if first >= 0 and last >= 0:
845
911
  raise ValueError("first and last cannot be used together")
846
912
 
847
- account_event_table = cls.get_account_event_table()
848
- if not account_event_table or account_event_table == "NONE":
913
+ account_event_table = get_snowflake_facade().get_account_event_table()
914
+ if account_event_table is None:
849
915
  raise NoEventTableForAccount()
850
916
 
851
917
  # resource_attributes uses the unquoted/uppercase app and package name
852
- app_name = unquote_identifier(app_name)
918
+ app_name = unquote_identifier(self.name)
853
919
  package_name = unquote_identifier(package_name)
854
920
  org_name = unquote_identifier(consumer_org)
855
921
  account_name = unquote_identifier(consumer_account)
@@ -931,10 +997,8 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
931
997
  else:
932
998
  generic_sql_error_handler(err)
933
999
 
934
- @classmethod
935
1000
  def stream_events(
936
- cls,
937
- app_name: str,
1001
+ self,
938
1002
  package_name: str,
939
1003
  interval_seconds: int,
940
1004
  since: str | datetime | None = None,
@@ -946,8 +1010,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
946
1010
  last: int = -1,
947
1011
  ) -> Generator[dict, None, None]:
948
1012
  try:
949
- events = cls.get_events(
950
- app_name=app_name,
1013
+ events = self.get_events(
951
1014
  package_name=package_name,
952
1015
  since=since,
953
1016
  record_types=record_types,
@@ -963,8 +1026,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
963
1026
  while True: # Then infinite poll for new events
964
1027
  time.sleep(interval_seconds)
965
1028
  previous_events = events
966
- events = cls.get_events(
967
- app_name=app_name,
1029
+ events = self.get_events(
968
1030
  package_name=package_name,
969
1031
  since=last_event_time,
970
1032
  record_types=record_types,
@@ -981,28 +1043,10 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
981
1043
  except KeyboardInterrupt:
982
1044
  return
983
1045
 
984
- @staticmethod
985
- def get_account_event_table():
986
- query = "show parameters like 'event_table' in account"
987
- sql_executor = get_sql_executor()
988
- results = sql_executor.execute_query(query, cursor_class=DictCursor)
989
- return next((r["value"] for r in results if r["key"] == "EVENT_TABLE"), "")
990
-
991
1046
  def get_snowsight_url(self) -> str:
992
1047
  """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
1001
- @classmethod
1002
- def get_snowsight_url_static(cls, app_name: str, app_warehouse: str) -> str:
1003
- """Returns the URL that can be used to visit this app via Snowsight."""
1004
- name = identifier_for_url(app_name)
1005
- with cls.use_application_warehouse(app_warehouse):
1048
+ name = identifier_for_url(self.name)
1049
+ with self.use_application_warehouse():
1006
1050
  sql_executor = get_sql_executor()
1007
1051
  return make_snowsight_url(
1008
1052
  sql_executor._conn, f"/#/apps/application/{name}" # noqa: SLF001
@@ -1028,3 +1072,20 @@ def _new_events_only(previous_events: list[dict], new_events: list[dict]) -> lis
1028
1072
  # either be in both lists or in new_events only
1029
1073
  new_events.remove(event)
1030
1074
  return new_events
1075
+
1076
+
1077
+ def _application_objects_to_str(
1078
+ application_objects: list[ApplicationOwnedObject],
1079
+ ) -> str:
1080
+ """
1081
+ Returns a list in an "(Object Type) Object Name" format. Database-level and schema-level object names are fully qualified:
1082
+ (COMPUTE_POOL) POOL_NAME
1083
+ (DATABASE) DB_NAME
1084
+ (SCHEMA) DB_NAME.PUBLIC
1085
+ ...
1086
+ """
1087
+ return "\n".join([_application_object_to_str(obj) for obj in application_objects])
1088
+
1089
+
1090
+ def _application_object_to_str(obj: ApplicationOwnedObject) -> str:
1091
+ return f"({obj['type']}) {obj['name']}"