snowflake-cli-labs 3.0.0rc1__py3-none-any.whl → 3.0.0rc2__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 (45) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/_app/cli_app.py +10 -1
  3. snowflake/cli/_app/snow_connector.py +76 -29
  4. snowflake/cli/_app/telemetry.py +8 -4
  5. snowflake/cli/_app/version_check.py +74 -0
  6. snowflake/cli/_plugins/git/commands.py +55 -14
  7. snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +2 -5
  8. snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +49 -31
  9. snowflake/cli/_plugins/nativeapp/manager.py +46 -87
  10. snowflake/cli/_plugins/nativeapp/run_processor.py +56 -260
  11. snowflake/cli/_plugins/nativeapp/same_account_install_method.py +74 -0
  12. snowflake/cli/_plugins/nativeapp/teardown_processor.py +9 -152
  13. snowflake/cli/_plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +91 -17
  14. snowflake/cli/_plugins/snowpark/commands.py +1 -1
  15. snowflake/cli/_plugins/snowpark/models.py +2 -1
  16. snowflake/cli/_plugins/streamlit/commands.py +1 -1
  17. snowflake/cli/_plugins/streamlit/manager.py +9 -0
  18. snowflake/cli/_plugins/workspace/action_context.py +2 -1
  19. snowflake/cli/_plugins/workspace/commands.py +48 -16
  20. snowflake/cli/_plugins/workspace/manager.py +1 -0
  21. snowflake/cli/api/cli_global_context.py +136 -313
  22. snowflake/cli/api/commands/flags.py +76 -91
  23. snowflake/cli/api/commands/snow_typer.py +6 -4
  24. snowflake/cli/api/config.py +1 -1
  25. snowflake/cli/api/connections.py +214 -0
  26. snowflake/cli/api/console/abc.py +4 -2
  27. snowflake/cli/api/entities/application_entity.py +687 -2
  28. snowflake/cli/api/entities/application_package_entity.py +151 -46
  29. snowflake/cli/api/entities/common.py +1 -0
  30. snowflake/cli/api/entities/utils.py +41 -17
  31. snowflake/cli/api/identifiers.py +3 -0
  32. snowflake/cli/api/project/definition.py +11 -0
  33. snowflake/cli/api/project/definition_conversion.py +171 -13
  34. snowflake/cli/api/project/schemas/entities/common.py +0 -12
  35. snowflake/cli/api/project/schemas/identifier_model.py +2 -2
  36. snowflake/cli/api/project/schemas/project_definition.py +101 -39
  37. snowflake/cli/api/rendering/project_definition_templates.py +4 -0
  38. snowflake/cli/api/rendering/sql_templates.py +7 -0
  39. snowflake/cli/api/utils/definition_rendering.py +3 -1
  40. {snowflake_cli_labs-3.0.0rc1.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/METADATA +6 -6
  41. {snowflake_cli_labs-3.0.0rc1.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/RECORD +44 -42
  42. snowflake/cli/api/commands/typer_pre_execute.py +0 -26
  43. {snowflake_cli_labs-3.0.0rc1.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/WHEEL +0 -0
  44. {snowflake_cli_labs-3.0.0rc1.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/entry_points.txt +0 -0
  45. {snowflake_cli_labs-3.0.0rc1.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/licenses/LICENSE +0 -0
@@ -1,7 +1,86 @@
1
- from snowflake.cli.api.entities.common import EntityBase
1
+ from contextlib import contextmanager
2
+ from pathlib import Path
3
+ from textwrap import dedent
4
+ from typing import Callable, List, Optional, TypedDict
5
+
6
+ import typer
7
+ from click import ClickException, UsageError
8
+ from snowflake.cli._plugins.nativeapp.common_flags import (
9
+ ForceOption,
10
+ InteractiveOption,
11
+ ValidateOption,
12
+ )
13
+ from snowflake.cli._plugins.nativeapp.constants import (
14
+ ALLOWED_SPECIAL_COMMENTS,
15
+ COMMENT_COL,
16
+ NAME_COL,
17
+ OWNER_COL,
18
+ PATCH_COL,
19
+ SPECIAL_COMMENT,
20
+ VERSION_COL,
21
+ )
22
+ from snowflake.cli._plugins.nativeapp.exceptions import (
23
+ ApplicationPackageDoesNotExistError,
24
+ )
25
+ from snowflake.cli._plugins.nativeapp.policy import (
26
+ AllowAlwaysPolicy,
27
+ AskAlwaysPolicy,
28
+ DenyAlwaysPolicy,
29
+ PolicyBase,
30
+ )
31
+ from snowflake.cli._plugins.nativeapp.same_account_install_method import (
32
+ SameAccountInstallMethod,
33
+ )
34
+ from snowflake.cli._plugins.nativeapp.utils import (
35
+ needs_confirmation,
36
+ )
37
+ from snowflake.cli._plugins.workspace.action_context import ActionContext
38
+ from snowflake.cli.api.console.abc import AbstractConsole
39
+ from snowflake.cli.api.entities.application_package_entity import (
40
+ ApplicationPackageEntity,
41
+ )
42
+ from snowflake.cli.api.entities.common import EntityBase, get_sql_executor
43
+ from snowflake.cli.api.entities.utils import (
44
+ drop_generic_object,
45
+ ensure_correct_owner,
46
+ execute_post_deploy_hooks,
47
+ generic_sql_error_handler,
48
+ print_messages,
49
+ )
50
+ from snowflake.cli.api.errno import (
51
+ APPLICATION_NO_LONGER_AVAILABLE,
52
+ CANNOT_UPGRADE_FROM_LOOSE_FILES_TO_VERSION,
53
+ CANNOT_UPGRADE_FROM_VERSION_TO_LOOSE_FILES,
54
+ NOT_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
55
+ ONLY_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
56
+ )
57
+ from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
2
58
  from snowflake.cli.api.project.schemas.entities.application_entity_model import (
3
59
  ApplicationEntityModel,
4
60
  )
61
+ from snowflake.cli.api.project.schemas.entities.application_package_entity_model import (
62
+ ApplicationPackageEntityModel,
63
+ )
64
+ from snowflake.cli.api.project.schemas.entities.common import PostDeployHook
65
+ from snowflake.cli.api.project.util import (
66
+ extract_schema,
67
+ identifier_to_show_like_pattern,
68
+ unquote_identifier,
69
+ )
70
+ from snowflake.cli.api.utils.cursor import find_all_rows
71
+ from snowflake.connector import ProgrammingError
72
+ from snowflake.connector.cursor import DictCursor
73
+
74
+ # Reasons why an `alter application ... upgrade` might fail
75
+ UPGRADE_RESTRICTION_CODES = {
76
+ CANNOT_UPGRADE_FROM_LOOSE_FILES_TO_VERSION,
77
+ CANNOT_UPGRADE_FROM_VERSION_TO_LOOSE_FILES,
78
+ ONLY_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
79
+ NOT_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
80
+ APPLICATION_NO_LONGER_AVAILABLE,
81
+ }
82
+
83
+ ApplicationOwnedObject = TypedDict("ApplicationOwnedObject", {"name": str, "type": str})
5
84
 
6
85
 
7
86
  class ApplicationEntity(EntityBase[ApplicationEntityModel]):
@@ -9,4 +88,610 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
9
88
  A Native App application object, created from an application package.
10
89
  """
11
90
 
12
- pass
91
+ def action_deploy(
92
+ self,
93
+ ctx: ActionContext,
94
+ from_release_directive: bool,
95
+ prune: bool,
96
+ recursive: bool,
97
+ paths: List[Path],
98
+ validate: bool = ValidateOption,
99
+ stage_fqn: Optional[str] = None,
100
+ interactive: bool = InteractiveOption,
101
+ version: Optional[str] = None,
102
+ patch: Optional[int] = None,
103
+ force: Optional[bool] = ForceOption,
104
+ *args,
105
+ **kwargs,
106
+ ):
107
+ model = self._entity_model
108
+ app_name = model.fqn.identifier
109
+ debug_mode = model.debug
110
+ if model.meta:
111
+ app_role = getattr(model.meta, "role", ctx.default_role)
112
+ app_warehouse = getattr(model.meta, "warehouse", ctx.default_warehouse)
113
+ post_deploy_hooks = getattr(model.meta, "post_deploy", None)
114
+ else:
115
+ app_role = ctx.default_role
116
+ app_warehouse = ctx.default_warehouse
117
+ post_deploy_hooks = None
118
+
119
+ package_entity: ApplicationPackageEntity = ctx.get_entity(model.from_.target)
120
+ package_model: ApplicationPackageEntityModel = (
121
+ package_entity._entity_model # noqa: SLF001
122
+ )
123
+ package_name = package_model.fqn.identifier
124
+ if package_model.meta and package_model.meta.role:
125
+ package_role = package_model.meta.role
126
+ else:
127
+ package_role = ctx.default_role
128
+
129
+ if not stage_fqn:
130
+ stage_fqn = f"{package_name}.{package_model.stage}"
131
+ stage_schema = extract_schema(stage_fqn)
132
+
133
+ is_interactive = False
134
+ if force:
135
+ policy = AllowAlwaysPolicy()
136
+ elif interactive:
137
+ is_interactive = True
138
+ policy = AskAlwaysPolicy()
139
+ else:
140
+ policy = DenyAlwaysPolicy()
141
+
142
+ def deploy_package():
143
+ package_entity.action_deploy(
144
+ ctx=ctx,
145
+ prune=True,
146
+ recursive=True,
147
+ paths=[],
148
+ validate=validate,
149
+ stage_fqn=stage_fqn,
150
+ )
151
+
152
+ self.deploy(
153
+ console=ctx.console,
154
+ project_root=ctx.project_root,
155
+ app_name=app_name,
156
+ app_role=app_role,
157
+ app_warehouse=app_warehouse,
158
+ package_name=package_name,
159
+ package_role=package_role,
160
+ stage_schema=stage_schema,
161
+ stage_fqn=stage_fqn,
162
+ debug_mode=debug_mode,
163
+ validate=validate,
164
+ from_release_directive=from_release_directive,
165
+ is_interactive=is_interactive,
166
+ policy=policy,
167
+ version=version,
168
+ patch=patch,
169
+ post_deploy_hooks=post_deploy_hooks,
170
+ deploy_package=deploy_package,
171
+ )
172
+
173
+ def action_drop(
174
+ self,
175
+ ctx: ActionContext,
176
+ interactive: bool,
177
+ force_drop: bool = False,
178
+ cascade: Optional[bool] = None,
179
+ *args,
180
+ **kwargs,
181
+ ):
182
+ model = self._entity_model
183
+ app_name = model.fqn.identifier
184
+ if model.meta and model.meta.role:
185
+ app_role = model.meta.role
186
+ else:
187
+ app_role = ctx.default_role
188
+ self.drop(
189
+ console=ctx.console,
190
+ app_name=app_name,
191
+ app_role=app_role,
192
+ auto_yes=force_drop,
193
+ interactive=interactive,
194
+ cascade=cascade,
195
+ )
196
+
197
+ @classmethod
198
+ def drop(
199
+ cls,
200
+ console: AbstractConsole,
201
+ app_name: str,
202
+ app_role: str,
203
+ auto_yes: bool,
204
+ interactive: bool = False,
205
+ cascade: Optional[bool] = None,
206
+ ):
207
+ """
208
+ Attempts to drop the application object if all validations and user prompts allow so.
209
+ """
210
+
211
+ needs_confirm = True
212
+
213
+ # 1. If existing application is not found, exit gracefully
214
+ show_obj_row = cls.get_existing_app_info(
215
+ app_name=app_name,
216
+ app_role=app_role,
217
+ )
218
+ if show_obj_row is None:
219
+ console.warning(
220
+ f"Role {app_role} does not own any application object with the name {app_name}, or the application object does not exist."
221
+ )
222
+ return
223
+
224
+ # 2. Check for the right owner
225
+ ensure_correct_owner(row=show_obj_row, role=app_role, obj_name=app_name)
226
+
227
+ # 3. Check if created by the Snowflake CLI
228
+ row_comment = show_obj_row[COMMENT_COL]
229
+ if row_comment not in ALLOWED_SPECIAL_COMMENTS and needs_confirmation(
230
+ needs_confirm, auto_yes
231
+ ):
232
+ should_drop_object = typer.confirm(
233
+ dedent(
234
+ f"""\
235
+ Application object {app_name} was not created by Snowflake CLI.
236
+ Application object details:
237
+ Name: {app_name}
238
+ Created on: {show_obj_row["created_on"]}
239
+ Source: {show_obj_row["source"]}
240
+ Owner: {show_obj_row[OWNER_COL]}
241
+ Comment: {show_obj_row[COMMENT_COL]}
242
+ Version: {show_obj_row["version"]}
243
+ Patch: {show_obj_row["patch"]}
244
+ Are you sure you want to drop it?
245
+ """
246
+ )
247
+ )
248
+ if not should_drop_object:
249
+ console.message(f"Did not drop application object {app_name}.")
250
+ # The user desires to keep the app, therefore we can't proceed since it would
251
+ # leave behind an orphan app when we get to dropping the package
252
+ raise typer.Abort()
253
+
254
+ # 4. Check for application objects owned by the application
255
+ # This query will fail if the application package has already been dropped, so handle this case gracefully
256
+ has_objects_to_drop = False
257
+ message_prefix = ""
258
+ cascade_true_message = ""
259
+ cascade_false_message = ""
260
+ interactive_prompt = ""
261
+ non_interactive_abort = ""
262
+ try:
263
+ if application_objects := cls.get_objects_owned_by_application(
264
+ app_name=app_name,
265
+ app_role=app_role,
266
+ ):
267
+ has_objects_to_drop = True
268
+ message_prefix = (
269
+ f"The following objects are owned by application {app_name}"
270
+ )
271
+ cascade_true_message = f"{message_prefix} and will be dropped:"
272
+ cascade_false_message = f"{message_prefix} and will NOT be dropped:"
273
+ interactive_prompt = "Would you like to drop these objects in addition to the application? [y/n/ABORT]"
274
+ non_interactive_abort = "Re-run teardown again with --cascade or --no-cascade to specify whether these objects should be dropped along with the application"
275
+ except ProgrammingError as e:
276
+ if e.errno != APPLICATION_NO_LONGER_AVAILABLE:
277
+ raise
278
+ application_objects = []
279
+ message_prefix = (
280
+ f"Could not determine which objects are owned by application {app_name}"
281
+ )
282
+ has_objects_to_drop = True # potentially, but we don't know what they are
283
+ cascade_true_message = (
284
+ f"{message_prefix}, an unknown number of objects will be dropped."
285
+ )
286
+ cascade_false_message = f"{message_prefix}, they will NOT be dropped."
287
+ interactive_prompt = f"Would you like to drop an unknown set of objects in addition to the application? [y/n/ABORT]"
288
+ non_interactive_abort = f"Re-run teardown again with --cascade or --no-cascade to specify whether any objects should be dropped along with the application."
289
+
290
+ if has_objects_to_drop:
291
+ if cascade is True:
292
+ # If the user explicitly passed the --cascade flag
293
+ console.message(cascade_true_message)
294
+ with console.indented():
295
+ for obj in application_objects:
296
+ console.message(cls.application_object_to_str(obj))
297
+ elif cascade is False:
298
+ # If the user explicitly passed the --no-cascade flag
299
+ console.message(cascade_false_message)
300
+ with console.indented():
301
+ for obj in application_objects:
302
+ console.message(cls.application_object_to_str(obj))
303
+ elif interactive:
304
+ # If the user didn't pass any cascade flag and the session is interactive
305
+ console.message(message_prefix)
306
+ with console.indented():
307
+ for obj in application_objects:
308
+ console.message(cls.application_object_to_str(obj))
309
+ user_response = typer.prompt(
310
+ interactive_prompt,
311
+ show_default=False,
312
+ default="ABORT",
313
+ ).lower()
314
+ if user_response in ["y", "yes"]:
315
+ cascade = True
316
+ elif user_response in ["n", "no"]:
317
+ cascade = False
318
+ else:
319
+ raise typer.Abort()
320
+ else:
321
+ # Else abort since we don't know what to do and can't ask the user
322
+ console.message(message_prefix)
323
+ with console.indented():
324
+ for obj in application_objects:
325
+ console.message(cls.application_object_to_str(obj))
326
+ console.message(non_interactive_abort)
327
+ raise typer.Abort()
328
+ elif cascade is None:
329
+ # If there's nothing to drop, set cascade to an explicit False value
330
+ cascade = False
331
+
332
+ # 5. All validations have passed, drop object
333
+ drop_generic_object(
334
+ console=console,
335
+ object_type="application",
336
+ object_name=app_name,
337
+ role=app_role,
338
+ cascade=cascade,
339
+ )
340
+ return # The application object was successfully dropped, therefore exit gracefully
341
+
342
+ @staticmethod
343
+ def get_objects_owned_by_application(
344
+ app_name: str,
345
+ app_role: str,
346
+ ) -> List[ApplicationOwnedObject]:
347
+ """
348
+ Returns all application objects owned by this application.
349
+ """
350
+ sql_executor = get_sql_executor()
351
+ with sql_executor.use_role(app_role):
352
+ results = sql_executor.execute_query(
353
+ f"show objects owned by application {app_name}"
354
+ ).fetchall()
355
+ return [{"name": row[1], "type": row[2]} for row in results]
356
+
357
+ @classmethod
358
+ def application_objects_to_str(
359
+ cls, application_objects: list[ApplicationOwnedObject]
360
+ ) -> str:
361
+ """
362
+ Returns a list in an "(Object Type) Object Name" format. Database-level and schema-level object names are fully qualified:
363
+ (COMPUTE_POOL) POOL_NAME
364
+ (DATABASE) DB_NAME
365
+ (SCHEMA) DB_NAME.PUBLIC
366
+ ...
367
+ """
368
+ return "\n".join(
369
+ [cls.application_object_to_str(obj) for obj in application_objects]
370
+ )
371
+
372
+ @staticmethod
373
+ def application_object_to_str(obj: ApplicationOwnedObject) -> str:
374
+ return f"({obj['type']}) {obj['name']}"
375
+
376
+ @classmethod
377
+ def deploy(
378
+ cls,
379
+ console: AbstractConsole,
380
+ project_root: Path,
381
+ app_name: str,
382
+ app_role: str,
383
+ app_warehouse: str,
384
+ package_name: str,
385
+ package_role: str,
386
+ stage_schema: str,
387
+ stage_fqn: str,
388
+ debug_mode: bool,
389
+ validate: bool,
390
+ from_release_directive: bool,
391
+ is_interactive: bool,
392
+ policy: PolicyBase,
393
+ deploy_package: Callable,
394
+ version: Optional[str] = None,
395
+ patch: Optional[int] = None,
396
+ post_deploy_hooks: Optional[List[PostDeployHook]] = None,
397
+ drop_application_before_upgrade: Optional[Callable] = None,
398
+ ):
399
+ """
400
+ Create or upgrade the application object using the given strategy
401
+ (unversioned dev, versioned dev, or same-account release directive).
402
+ """
403
+
404
+ # same-account release directive
405
+ if from_release_directive:
406
+ cls.create_or_upgrade_app(
407
+ console=console,
408
+ project_root=project_root,
409
+ package_name=package_name,
410
+ package_role=package_role,
411
+ app_name=app_name,
412
+ app_role=app_role,
413
+ app_warehouse=app_warehouse,
414
+ stage_schema=stage_schema,
415
+ stage_fqn=stage_fqn,
416
+ debug_mode=debug_mode,
417
+ policy=policy,
418
+ install_method=SameAccountInstallMethod.release_directive(),
419
+ is_interactive=is_interactive,
420
+ post_deploy_hooks=post_deploy_hooks,
421
+ drop_application_before_upgrade=drop_application_before_upgrade,
422
+ )
423
+ return
424
+
425
+ # versioned dev
426
+ if version:
427
+ try:
428
+ version_exists = cls.get_existing_version_info(
429
+ version=version,
430
+ package_name=package_name,
431
+ package_role=package_role,
432
+ )
433
+ if not version_exists:
434
+ raise UsageError(
435
+ 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."
436
+ )
437
+ except ApplicationPackageDoesNotExistError as app_err:
438
+ raise UsageError(
439
+ 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."
440
+ )
441
+
442
+ cls.create_or_upgrade_app(
443
+ console=console,
444
+ project_root=project_root,
445
+ package_name=package_name,
446
+ package_role=package_role,
447
+ app_name=app_name,
448
+ app_role=app_role,
449
+ app_warehouse=app_warehouse,
450
+ stage_schema=stage_schema,
451
+ stage_fqn=stage_fqn,
452
+ debug_mode=debug_mode,
453
+ policy=policy,
454
+ install_method=SameAccountInstallMethod.versioned_dev(version, patch),
455
+ is_interactive=is_interactive,
456
+ post_deploy_hooks=post_deploy_hooks,
457
+ drop_application_before_upgrade=drop_application_before_upgrade,
458
+ )
459
+ return
460
+
461
+ # unversioned dev
462
+ deploy_package()
463
+ cls.create_or_upgrade_app(
464
+ console=console,
465
+ project_root=project_root,
466
+ package_name=package_name,
467
+ package_role=package_role,
468
+ app_name=app_name,
469
+ app_role=app_role,
470
+ app_warehouse=app_warehouse,
471
+ stage_schema=stage_schema,
472
+ stage_fqn=stage_fqn,
473
+ debug_mode=debug_mode,
474
+ policy=policy,
475
+ install_method=SameAccountInstallMethod.unversioned_dev(),
476
+ is_interactive=is_interactive,
477
+ post_deploy_hooks=post_deploy_hooks,
478
+ drop_application_before_upgrade=drop_application_before_upgrade,
479
+ )
480
+
481
+ @classmethod
482
+ def create_or_upgrade_app(
483
+ cls,
484
+ console: AbstractConsole,
485
+ project_root: Path,
486
+ package_name: str,
487
+ package_role: str,
488
+ app_name: str,
489
+ app_role: str,
490
+ app_warehouse: Optional[str],
491
+ stage_schema: Optional[str],
492
+ stage_fqn: str,
493
+ debug_mode: bool,
494
+ policy: PolicyBase,
495
+ install_method: SameAccountInstallMethod,
496
+ is_interactive: bool = False,
497
+ post_deploy_hooks: Optional[List[PostDeployHook]] = None,
498
+ drop_application_before_upgrade: Optional[Callable] = None,
499
+ ):
500
+ sql_executor = get_sql_executor()
501
+ with sql_executor.use_role(app_role):
502
+
503
+ # 1. Need to use a warehouse to create an application object
504
+ with sql_executor.use_warehouse(app_warehouse):
505
+
506
+ # 2. Check for an existing application by the same name
507
+ show_app_row = cls.get_existing_app_info(
508
+ app_name=app_name,
509
+ app_role=app_role,
510
+ )
511
+
512
+ # 3. If existing application is found, perform a few validations and upgrade the application object.
513
+ if show_app_row:
514
+
515
+ install_method.ensure_app_usable(
516
+ app_name=app_name,
517
+ app_role=app_role,
518
+ show_app_row=show_app_row,
519
+ )
520
+
521
+ # If all the above checks are in order, proceed to upgrade
522
+ try:
523
+ console.step(
524
+ f"Upgrading existing application object {app_name}."
525
+ )
526
+ using_clause = install_method.using_clause(stage_fqn)
527
+ upgrade_cursor = sql_executor.execute_query(
528
+ f"alter application {app_name} upgrade {using_clause}",
529
+ )
530
+ print_messages(console, upgrade_cursor)
531
+
532
+ if install_method.is_dev_mode:
533
+ # if debug_mode is present (controlled), ensure it is up-to-date
534
+ if debug_mode is not None:
535
+ sql_executor.execute_query(
536
+ f"alter application {app_name} set debug_mode = {debug_mode}"
537
+ )
538
+
539
+ # hooks always executed after a create or upgrade
540
+ if post_deploy_hooks:
541
+ cls.execute_post_deploy_hooks(
542
+ console=console,
543
+ project_root=project_root,
544
+ post_deploy_hooks=post_deploy_hooks,
545
+ app_name=app_name,
546
+ app_warehouse=app_warehouse,
547
+ )
548
+ return
549
+
550
+ except ProgrammingError as err:
551
+ if err.errno not in UPGRADE_RESTRICTION_CODES:
552
+ generic_sql_error_handler(err=err)
553
+ else: # The existing application object was created from a different process.
554
+ console.warning(err.msg)
555
+ # TODO Drop the entity here instead of taking a callback once action_drop() is implemented
556
+ if drop_application_before_upgrade:
557
+ drop_application_before_upgrade()
558
+ else:
559
+ raise NotImplementedError
560
+
561
+ # 4. With no (more) existing application objects, create an application object using the release directives
562
+ console.step(f"Creating new application object {app_name} in account.")
563
+
564
+ if app_role != package_role:
565
+ with sql_executor.use_role(package_role):
566
+ sql_executor.execute_query(
567
+ f"grant install, develop on application package {package_name} to role {app_role}"
568
+ )
569
+ sql_executor.execute_query(
570
+ f"grant usage on schema {package_name}.{stage_schema} to role {app_role}"
571
+ )
572
+ sql_executor.execute_query(
573
+ f"grant read on stage {stage_fqn} to role {app_role}"
574
+ )
575
+
576
+ try:
577
+ # by default, applications are created in debug mode when possible;
578
+ # this can be overridden in the project definition
579
+ debug_mode_clause = ""
580
+ if install_method.is_dev_mode:
581
+ initial_debug_mode = (
582
+ debug_mode if debug_mode is not None else True
583
+ )
584
+ debug_mode_clause = f"debug_mode = {initial_debug_mode}"
585
+
586
+ using_clause = install_method.using_clause(stage_fqn)
587
+ create_cursor = sql_executor.execute_query(
588
+ dedent(
589
+ f"""\
590
+ create application {app_name}
591
+ from application package {package_name} {using_clause} {debug_mode_clause}
592
+ comment = {SPECIAL_COMMENT}
593
+ """
594
+ ),
595
+ )
596
+ print_messages(console, create_cursor)
597
+
598
+ # hooks always executed after a create or upgrade
599
+ if post_deploy_hooks:
600
+ cls.execute_post_deploy_hooks(
601
+ console=console,
602
+ project_root=project_root,
603
+ post_deploy_hooks=post_deploy_hooks,
604
+ app_name=app_name,
605
+ app_warehouse=app_warehouse,
606
+ )
607
+
608
+ except ProgrammingError as err:
609
+ generic_sql_error_handler(err)
610
+
611
+ @classmethod
612
+ def execute_post_deploy_hooks(
613
+ cls,
614
+ console: AbstractConsole,
615
+ project_root: Path,
616
+ post_deploy_hooks: Optional[List[PostDeployHook]],
617
+ app_name: str,
618
+ app_warehouse: Optional[str],
619
+ ):
620
+ with cls.use_application_warehouse(app_warehouse):
621
+ execute_post_deploy_hooks(
622
+ console=console,
623
+ project_root=project_root,
624
+ post_deploy_hooks=post_deploy_hooks,
625
+ deployed_object_type="application",
626
+ database_name=app_name,
627
+ )
628
+
629
+ @staticmethod
630
+ @contextmanager
631
+ def use_application_warehouse(
632
+ app_warehouse: Optional[str],
633
+ ):
634
+ if app_warehouse:
635
+ with get_sql_executor().use_warehouse(app_warehouse):
636
+ yield
637
+ else:
638
+ raise ClickException(
639
+ dedent(
640
+ f"""\
641
+ Application warehouse cannot be empty.
642
+ Please provide a value for it in your connection information or your project definition file.
643
+ """
644
+ )
645
+ )
646
+
647
+ @staticmethod
648
+ def get_existing_app_info(
649
+ app_name: str,
650
+ app_role: str,
651
+ ) -> Optional[dict]:
652
+ """
653
+ Check for an existing application object by the same name as in project definition, in account.
654
+ It executes a 'show applications like' query and returns the result as single row, if one exists.
655
+ """
656
+ sql_executor = get_sql_executor()
657
+ with sql_executor.use_role(app_role):
658
+ return sql_executor.show_specific_object(
659
+ "applications", app_name, name_col=NAME_COL
660
+ )
661
+
662
+ @staticmethod
663
+ def get_existing_version_info(
664
+ version: str,
665
+ package_name: str,
666
+ package_role: str,
667
+ ) -> Optional[dict]:
668
+ """
669
+ Get the latest patch on an existing version by name in the application package.
670
+ Executes 'show versions like ... in application package' query and returns
671
+ the latest patch in the version as a single row, if one exists. Otherwise,
672
+ returns None.
673
+ """
674
+ sql_executor = get_sql_executor()
675
+ with sql_executor.use_role(package_role):
676
+ try:
677
+ query = f"show versions like {identifier_to_show_like_pattern(version)} in application package {package_name}"
678
+ cursor = sql_executor.execute_query(query, cursor_class=DictCursor)
679
+
680
+ if cursor.rowcount is None:
681
+ raise SnowflakeSQLExecutionError(query)
682
+
683
+ matching_rows = find_all_rows(
684
+ cursor, lambda row: row[VERSION_COL] == unquote_identifier(version)
685
+ )
686
+
687
+ if not matching_rows:
688
+ return None
689
+
690
+ return max(matching_rows, key=lambda row: row[PATCH_COL])
691
+
692
+ except ProgrammingError as err:
693
+ if err.msg.__contains__("does not exist or not authorized"):
694
+ raise ApplicationPackageDoesNotExistError(package_name)
695
+ else:
696
+ generic_sql_error_handler(err=err, role=package_role)
697
+ return None