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