snowflake-cli 3.0.2__py3-none-any.whl → 3.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/_app/cli_app.py +3 -0
  3. snowflake/cli/_app/dev/docs/templates/overview.rst.jinja2 +1 -1
  4. snowflake/cli/_app/dev/docs/templates/usage.rst.jinja2 +2 -2
  5. snowflake/cli/_app/telemetry.py +69 -4
  6. snowflake/cli/_plugins/connection/commands.py +152 -99
  7. snowflake/cli/_plugins/connection/util.py +54 -9
  8. snowflake/cli/_plugins/cortex/manager.py +1 -1
  9. snowflake/cli/_plugins/git/commands.py +6 -3
  10. snowflake/cli/_plugins/git/manager.py +9 -4
  11. snowflake/cli/_plugins/nativeapp/artifacts.py +77 -13
  12. snowflake/cli/_plugins/nativeapp/codegen/artifact_processor.py +1 -1
  13. snowflake/cli/_plugins/nativeapp/codegen/compiler.py +7 -0
  14. snowflake/cli/_plugins/nativeapp/codegen/sandbox.py +10 -10
  15. snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +2 -2
  16. snowflake/cli/_plugins/nativeapp/codegen/snowpark/extension_function_utils.py +1 -1
  17. snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +8 -8
  18. snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +5 -3
  19. snowflake/cli/_plugins/nativeapp/commands.py +144 -188
  20. snowflake/cli/_plugins/nativeapp/constants.py +1 -0
  21. snowflake/cli/_plugins/nativeapp/entities/application.py +564 -351
  22. snowflake/cli/_plugins/nativeapp/entities/application_package.py +583 -929
  23. snowflake/cli/_plugins/nativeapp/entities/models/event_sharing_telemetry.py +58 -0
  24. snowflake/cli/_plugins/nativeapp/exceptions.py +12 -0
  25. snowflake/cli/_plugins/nativeapp/same_account_install_method.py +0 -2
  26. snowflake/cli/_plugins/nativeapp/sf_facade.py +30 -0
  27. snowflake/cli/_plugins/nativeapp/sf_facade_constants.py +25 -0
  28. snowflake/cli/_plugins/nativeapp/sf_facade_exceptions.py +117 -0
  29. snowflake/cli/_plugins/nativeapp/sf_sql_facade.py +525 -0
  30. snowflake/cli/_plugins/nativeapp/v2_conversions/{v2_to_v1_decorator.py → compat.py} +88 -117
  31. snowflake/cli/_plugins/nativeapp/version/commands.py +36 -32
  32. snowflake/cli/_plugins/notebook/manager.py +2 -2
  33. snowflake/cli/_plugins/object/commands.py +10 -1
  34. snowflake/cli/_plugins/object/manager.py +13 -5
  35. snowflake/cli/_plugins/snowpark/common.py +63 -21
  36. snowflake/cli/_plugins/snowpark/package/anaconda_packages.py +3 -3
  37. snowflake/cli/_plugins/spcs/common.py +29 -0
  38. snowflake/cli/_plugins/spcs/compute_pool/manager.py +7 -9
  39. snowflake/cli/_plugins/spcs/image_registry/manager.py +2 -2
  40. snowflake/cli/_plugins/spcs/image_repository/commands.py +4 -37
  41. snowflake/cli/_plugins/spcs/image_repository/manager.py +4 -1
  42. snowflake/cli/_plugins/spcs/services/commands.py +100 -17
  43. snowflake/cli/_plugins/spcs/services/manager.py +108 -16
  44. snowflake/cli/_plugins/sql/commands.py +9 -1
  45. snowflake/cli/_plugins/sql/manager.py +9 -4
  46. snowflake/cli/_plugins/stage/commands.py +28 -19
  47. snowflake/cli/_plugins/stage/diff.py +17 -17
  48. snowflake/cli/_plugins/stage/manager.py +304 -84
  49. snowflake/cli/_plugins/stage/md5.py +1 -1
  50. snowflake/cli/_plugins/streamlit/manager.py +5 -5
  51. snowflake/cli/_plugins/workspace/commands.py +27 -4
  52. snowflake/cli/_plugins/workspace/context.py +38 -0
  53. snowflake/cli/_plugins/workspace/manager.py +23 -13
  54. snowflake/cli/api/cli_global_context.py +4 -3
  55. snowflake/cli/api/commands/flags.py +23 -7
  56. snowflake/cli/api/config.py +30 -9
  57. snowflake/cli/api/connections.py +12 -1
  58. snowflake/cli/api/console/console.py +4 -19
  59. snowflake/cli/api/entities/common.py +4 -2
  60. snowflake/cli/api/entities/utils.py +36 -69
  61. snowflake/cli/api/errno.py +2 -0
  62. snowflake/cli/api/exceptions.py +41 -0
  63. snowflake/cli/api/identifiers.py +8 -0
  64. snowflake/cli/api/metrics.py +223 -7
  65. snowflake/cli/api/output/types.py +1 -1
  66. snowflake/cli/api/project/definition_conversion.py +293 -77
  67. snowflake/cli/api/project/schemas/entities/common.py +11 -0
  68. snowflake/cli/api/project/schemas/project_definition.py +30 -25
  69. snowflake/cli/api/rest_api.py +26 -4
  70. snowflake/cli/api/secure_utils.py +1 -1
  71. snowflake/cli/api/sql_execution.py +40 -29
  72. snowflake/cli/api/stage_path.py +244 -0
  73. snowflake/cli/api/utils/definition_rendering.py +3 -5
  74. {snowflake_cli-3.0.2.dist-info → snowflake_cli-3.2.0.dist-info}/METADATA +14 -15
  75. {snowflake_cli-3.0.2.dist-info → snowflake_cli-3.2.0.dist-info}/RECORD +78 -77
  76. {snowflake_cli-3.0.2.dist-info → snowflake_cli-3.2.0.dist-info}/WHEEL +1 -1
  77. snowflake/cli/_plugins/nativeapp/manager.py +0 -415
  78. snowflake/cli/_plugins/nativeapp/project_model.py +0 -211
  79. snowflake/cli/_plugins/nativeapp/run_processor.py +0 -184
  80. snowflake/cli/_plugins/nativeapp/teardown_processor.py +0 -70
  81. snowflake/cli/_plugins/nativeapp/version/version_processor.py +0 -98
  82. snowflake/cli/_plugins/workspace/action_context.py +0 -18
  83. {snowflake_cli-3.0.2.dist-info → snowflake_cli-3.2.0.dist-info}/entry_points.txt +0 -0
  84. {snowflake_cli-3.0.2.dist-info → snowflake_cli-3.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import json
4
- from contextlib import contextmanager
4
+ import re
5
5
  from pathlib import Path
6
6
  from textwrap import dedent
7
7
  from typing import List, Literal, Optional, Union
@@ -11,6 +11,7 @@ from click import BadOptionUsage, ClickException
11
11
  from pydantic import Field, field_validator
12
12
  from snowflake.cli._plugins.nativeapp.artifacts import (
13
13
  BundleMap,
14
+ VersionInfo,
14
15
  build_bundle,
15
16
  find_version_info_in_manifest_file,
16
17
  )
@@ -31,6 +32,7 @@ from snowflake.cli._plugins.nativeapp.exceptions import (
31
32
  ApplicationPackageAlreadyExistsError,
32
33
  ApplicationPackageDoesNotExistError,
33
34
  CouldNotDropApplicationPackageWithVersions,
35
+ ObjectPropertyNotFoundError,
34
36
  SetupScriptFailedValidation,
35
37
  )
36
38
  from snowflake.cli._plugins.nativeapp.policy import (
@@ -39,24 +41,24 @@ from snowflake.cli._plugins.nativeapp.policy import (
39
41
  DenyAlwaysPolicy,
40
42
  PolicyBase,
41
43
  )
44
+ from snowflake.cli._plugins.nativeapp.sf_facade import get_snowflake_facade
45
+ from snowflake.cli._plugins.nativeapp.sf_facade_exceptions import (
46
+ InsufficientPrivilegesError,
47
+ )
42
48
  from snowflake.cli._plugins.nativeapp.utils import needs_confirmation
43
49
  from snowflake.cli._plugins.stage.diff import DiffResult
44
50
  from snowflake.cli._plugins.stage.manager import StageManager
45
- from snowflake.cli._plugins.workspace.action_context import ActionContext
46
- from snowflake.cli.api.cli_global_context import get_cli_context
47
- from snowflake.cli.api.console.abc import AbstractConsole
51
+ from snowflake.cli._plugins.workspace.context import ActionContext
48
52
  from snowflake.cli.api.entities.common import EntityBase, get_sql_executor
49
53
  from snowflake.cli.api.entities.utils import (
50
54
  drop_generic_object,
51
55
  execute_post_deploy_hooks,
52
56
  generic_sql_error_handler,
53
- render_script_templates,
54
57
  sync_deploy_root_with_stage,
55
58
  validation_item_to_str,
56
59
  )
57
60
  from snowflake.cli.api.errno import DOES_NOT_EXIST_OR_NOT_AUTHORIZED
58
61
  from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
59
- from snowflake.cli.api.metrics import CLICounterField
60
62
  from snowflake.cli.api.project.schemas.entities.common import (
61
63
  EntityModelBase,
62
64
  Identifier,
@@ -69,13 +71,13 @@ from snowflake.cli.api.project.schemas.updatable_model import (
69
71
  from snowflake.cli.api.project.schemas.v1.native_app.package import DistributionOptions
70
72
  from snowflake.cli.api.project.schemas.v1.native_app.path_mapping import PathMapping
71
73
  from snowflake.cli.api.project.util import (
74
+ SCHEMA_AND_NAME,
72
75
  append_test_resource_suffix,
73
76
  extract_schema,
74
77
  identifier_to_show_like_pattern,
75
78
  to_identifier,
76
79
  unquote_identifier,
77
80
  )
78
- from snowflake.cli.api.rendering.jinja import get_basic_jinja_env
79
81
  from snowflake.cli.api.utils.cursor import find_all_rows
80
82
  from snowflake.connector import DictCursor, ProgrammingError
81
83
  from snowflake.connector.cursor import SnowflakeCursor
@@ -110,8 +112,9 @@ class ApplicationPackageEntityModel(EntityModelBase):
110
112
  title="Distribution of the application package created by the Snowflake CLI",
111
113
  default="internal",
112
114
  )
113
- manifest: str = Field(
114
- title="Path to manifest.yml",
115
+ manifest: Optional[str] = Field(
116
+ title="Path to manifest.yml. Unused and deprecated starting with Snowflake CLI 3.2",
117
+ default="",
115
118
  )
116
119
 
117
120
  @field_validator("identifier")
@@ -144,26 +147,72 @@ class ApplicationPackageEntityModel(EntityModelBase):
144
147
 
145
148
  return transformed_artifacts
146
149
 
150
+ @field_validator("stage")
151
+ @classmethod
152
+ def validate_source_stage(cls, input_value: str):
153
+ if not re.match(SCHEMA_AND_NAME, input_value):
154
+ raise ValueError(
155
+ "Incorrect value for stage of native_app. Expected format for this field is {schema_name}.{stage_name} "
156
+ )
157
+ return input_value
158
+
147
159
 
148
160
  class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
149
161
  """
150
162
  A Native App application package.
151
163
  """
152
164
 
153
- def action_bundle(self, ctx: ActionContext, *args, **kwargs):
165
+ @property
166
+ def project_root(self) -> Path:
167
+ return self._workspace_ctx.project_root
168
+
169
+ @property
170
+ def deploy_root(self) -> Path:
171
+ return self.project_root / self._entity_model.deploy_root
172
+
173
+ @property
174
+ def bundle_root(self) -> Path:
175
+ return self.project_root / self._entity_model.bundle_root
176
+
177
+ @property
178
+ def generated_root(self) -> Path:
179
+ return self.deploy_root / self._entity_model.generated_root
180
+
181
+ @property
182
+ def name(self) -> str:
183
+ return self._entity_model.fqn.name
184
+
185
+ @property
186
+ def role(self) -> str:
154
187
  model = self._entity_model
155
- return self.bundle(
156
- project_root=ctx.project_root,
157
- deploy_root=Path(model.deploy_root),
158
- bundle_root=Path(model.bundle_root),
159
- generated_root=Path(model.generated_root),
160
- package_name=model.identifier,
161
- artifacts=model.artifacts,
162
- )
188
+ return (model.meta and model.meta.role) or self._workspace_ctx.default_role
189
+
190
+ @property
191
+ def warehouse(self) -> str:
192
+ model = self._entity_model
193
+ return (
194
+ model.meta and model.meta.warehouse and to_identifier(model.meta.warehouse)
195
+ ) or to_identifier(self._workspace_ctx.default_warehouse)
196
+
197
+ @property
198
+ def stage_fqn(self) -> str:
199
+ return f"{self.name}.{self._entity_model.stage}"
200
+
201
+ @property
202
+ def scratch_stage_fqn(self) -> str:
203
+ return f"{self.name}.{self._entity_model.scratch_stage}"
204
+
205
+ @property
206
+ def post_deploy_hooks(self) -> list[PostDeployHook] | None:
207
+ model = self._entity_model
208
+ return model.meta and model.meta.post_deploy
209
+
210
+ def action_bundle(self, action_ctx: ActionContext, *args, **kwargs):
211
+ return self._bundle()
163
212
 
164
213
  def action_deploy(
165
214
  self,
166
- ctx: ActionContext,
215
+ action_ctx: ActionContext,
167
216
  prune: bool,
168
217
  recursive: bool,
169
218
  paths: List[Path],
@@ -174,256 +223,381 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
174
223
  *args,
175
224
  **kwargs,
176
225
  ):
177
- model = self._entity_model
178
- package_name = model.fqn.identifier
179
-
180
- if force:
181
- policy = AllowAlwaysPolicy()
182
- elif interactive:
183
- policy = AskAlwaysPolicy()
184
- else:
185
- policy = DenyAlwaysPolicy()
186
-
187
- return self.deploy(
188
- console=ctx.console,
189
- project_root=ctx.project_root,
190
- deploy_root=Path(model.deploy_root),
191
- bundle_root=Path(model.bundle_root),
192
- generated_root=Path(model.generated_root),
193
- artifacts=model.artifacts,
226
+ return self._deploy(
194
227
  bundle_map=None,
195
- package_name=package_name,
196
- package_role=(model.meta and model.meta.role) or ctx.default_role,
197
- package_distribution=model.distribution,
198
228
  prune=prune,
199
229
  recursive=recursive,
200
230
  paths=paths,
201
231
  print_diff=True,
202
232
  validate=validate,
203
- stage_fqn=stage_fqn or f"{package_name}.{model.stage}",
204
- package_warehouse=(
205
- (model.meta and model.meta.warehouse) or ctx.default_warehouse
206
- ),
207
- post_deploy_hooks=model.meta and model.meta.post_deploy,
208
- package_scripts=[], # Package scripts are not supported in PDFv2
209
- policy=policy,
233
+ stage_fqn=stage_fqn or self.stage_fqn,
234
+ interactive=interactive,
235
+ force=force,
210
236
  )
211
237
 
212
- def action_drop(self, ctx: ActionContext, force_drop: bool, *args, **kwargs):
213
- model = self._entity_model
214
- package_name = model.fqn.identifier
215
- if model.meta and model.meta.role:
216
- package_role = model.meta.role
238
+ def action_drop(self, action_ctx: ActionContext, force_drop: bool, *args, **kwargs):
239
+ console = self._workspace_ctx.console
240
+ sql_executor = get_sql_executor()
241
+ needs_confirm = True
242
+
243
+ # 1. If existing application package is not found, exit gracefully
244
+ show_obj_row = self.get_existing_app_pkg_info()
245
+ if show_obj_row is None:
246
+ console.warning(
247
+ f"Role {self.role} does not own any application package with the name {self.name}, or the application package does not exist."
248
+ )
249
+ return
250
+
251
+ with sql_executor.use_role(self.role):
252
+ # 2. Check for versions in the application package
253
+ show_versions_query = f"show versions in application package {self.name}"
254
+ show_versions_cursor = sql_executor.execute_query(
255
+ show_versions_query, cursor_class=DictCursor
256
+ )
257
+ if show_versions_cursor.rowcount is None:
258
+ raise SnowflakeSQLExecutionError(show_versions_query)
259
+
260
+ if show_versions_cursor.rowcount > 0:
261
+ # allow dropping a package with versions when --force is set
262
+ if not force_drop:
263
+ raise CouldNotDropApplicationPackageWithVersions(
264
+ "Drop versions first, or use --force to override."
265
+ )
266
+
267
+ # 3. Check distribution of the existing application package
268
+ actual_distribution = self.get_app_pkg_distribution_in_snowflake()
269
+ if not self.verify_project_distribution():
270
+ console.warning(
271
+ f"Dropping application package {self.name} with distribution '{actual_distribution}'."
272
+ )
273
+
274
+ # 4. If distribution is internal, check if created by the Snowflake CLI
275
+ row_comment = show_obj_row[COMMENT_COL]
276
+ if actual_distribution == INTERNAL_DISTRIBUTION:
277
+ if row_comment in ALLOWED_SPECIAL_COMMENTS:
278
+ needs_confirm = False
279
+ else:
280
+ if needs_confirmation(needs_confirm, force_drop):
281
+ console.warning(
282
+ f"Application package {self.name} was not created by Snowflake CLI."
283
+ )
217
284
  else:
218
- package_role = ctx.default_role
285
+ if needs_confirmation(needs_confirm, force_drop):
286
+ console.warning(
287
+ f"Application package {self.name} in your Snowflake account has distribution property '{EXTERNAL_DISTRIBUTION}' and could be associated with one or more of your listings on Snowflake Marketplace."
288
+ )
219
289
 
220
- self.drop(
221
- console=ctx.console,
222
- package_name=package_name,
223
- package_role=package_role,
224
- force_drop=force_drop,
290
+ if needs_confirmation(needs_confirm, force_drop):
291
+ should_drop_object = typer.confirm(
292
+ dedent(
293
+ f"""\
294
+ Application package details:
295
+ Name: {self.name}
296
+ Created on: {show_obj_row["created_on"]}
297
+ Distribution: {actual_distribution}
298
+ Owner: {show_obj_row[OWNER_COL]}
299
+ Comment: {show_obj_row[COMMENT_COL]}
300
+ Are you sure you want to drop it?
301
+ """
302
+ )
303
+ )
304
+ if not should_drop_object:
305
+ console.message(f"Did not drop application package {self.name}.")
306
+ return # The user desires to keep the application package, therefore exit gracefully
307
+
308
+ # All validations have passed, drop object
309
+ drop_generic_object(
310
+ console=console,
311
+ object_type="application package",
312
+ object_name=(self.name),
313
+ role=(self.role),
225
314
  )
226
315
 
227
316
  def action_validate(
228
- self, ctx: ActionContext, interactive: bool, force: bool, *args, **kwargs
317
+ self,
318
+ action_ctx: ActionContext,
319
+ interactive: bool,
320
+ force: bool,
321
+ use_scratch_stage: bool = True,
322
+ *args,
323
+ **kwargs,
229
324
  ):
230
- model = self._entity_model
231
- package_name = model.fqn.identifier
232
- if force:
233
- policy = AllowAlwaysPolicy()
234
- elif interactive:
235
- policy = AskAlwaysPolicy()
236
- else:
237
- policy = DenyAlwaysPolicy()
238
-
239
325
  self.validate_setup_script(
240
- console=ctx.console,
241
- project_root=ctx.project_root,
242
- deploy_root=Path(model.deploy_root),
243
- bundle_root=Path(model.bundle_root),
244
- generated_root=Path(model.generated_root),
245
- artifacts=model.artifacts,
246
- package_name=package_name,
247
- package_role=(model.meta and model.meta.role) or ctx.default_role,
248
- package_distribution=model.distribution,
249
- prune=True,
250
- recursive=True,
251
- paths=[],
252
- stage_fqn=f"{package_name}.{model.stage}",
253
- package_warehouse=(
254
- (model.meta and model.meta.warehouse) or ctx.default_warehouse
255
- ),
256
- post_deploy_hooks=model.meta and model.meta.post_deploy,
257
- package_scripts=[], # Package scripts are not supported in PDFv2
258
- policy=policy,
259
- use_scratch_stage=True,
260
- scratch_stage_fqn=f"{package_name}.{model.scratch_stage}",
326
+ use_scratch_stage=use_scratch_stage,
327
+ interactive=interactive,
328
+ force=force,
261
329
  )
262
- ctx.console.message("Setup script is valid")
330
+ self._workspace_ctx.console.message("Setup script is valid")
263
331
 
264
332
  def action_version_list(
265
- self, ctx: ActionContext, *args, **kwargs
333
+ self, action_ctx: ActionContext, *args, **kwargs
266
334
  ) -> SnowflakeCursor:
267
- model = self._entity_model
268
- return self.version_list(
269
- package_name=model.fqn.identifier,
270
- package_role=(model.meta and model.meta.role) or ctx.default_role,
271
- )
335
+ """
336
+ Get all existing versions, if defined, for an application package.
337
+ It executes a 'show versions in application package' query and returns all the results.
338
+ """
339
+ sql_executor = get_sql_executor()
340
+ with sql_executor.use_role(self.role):
341
+ show_obj_query = f"show versions in application package {self.name}"
342
+ show_obj_cursor = sql_executor.execute_query(show_obj_query)
343
+
344
+ if show_obj_cursor.rowcount is None:
345
+ raise SnowflakeSQLExecutionError(show_obj_query)
346
+
347
+ return show_obj_cursor
272
348
 
273
349
  def action_version_create(
274
350
  self,
275
- ctx: ActionContext,
351
+ action_ctx: ActionContext,
276
352
  version: Optional[str],
277
353
  patch: Optional[int],
354
+ label: Optional[str],
278
355
  skip_git_check: bool,
279
356
  interactive: bool,
280
357
  force: bool,
281
358
  *args,
282
359
  **kwargs,
283
360
  ):
284
- model = self._entity_model
285
- package_name = model.fqn.identifier
286
- return self.version_create(
287
- console=ctx.console,
288
- project_root=ctx.project_root,
289
- deploy_root=Path(model.deploy_root),
290
- bundle_root=Path(model.bundle_root),
291
- generated_root=Path(model.generated_root),
292
- artifacts=model.artifacts,
293
- package_name=package_name,
294
- package_role=(model.meta and model.meta.role) or ctx.default_role,
295
- package_distribution=model.distribution,
361
+ """
362
+ Create a version and/or patch for a new or existing application package.
363
+ Always performs a deploy action before creating version or patch.
364
+ If version is not provided in CLI, bundle is performed to read version from manifest.yml. Raises a ClickException if version is not found.
365
+ """
366
+ console = self._workspace_ctx.console
367
+
368
+ if force:
369
+ policy = AllowAlwaysPolicy()
370
+ elif interactive:
371
+ policy = AskAlwaysPolicy()
372
+ else:
373
+ policy = DenyAlwaysPolicy()
374
+
375
+ if skip_git_check:
376
+ git_policy = DenyAlwaysPolicy()
377
+ else:
378
+ git_policy = AllowAlwaysPolicy()
379
+
380
+ bundle_map = self._bundle()
381
+ resolved_version, resolved_patch, resolved_label = self.resolve_version_info(
382
+ version=version,
383
+ patch=patch,
384
+ label=label,
385
+ bundle_map=bundle_map,
386
+ policy=policy,
387
+ interactive=interactive,
388
+ )
389
+
390
+ if git_policy.should_proceed():
391
+ self.check_index_changes_in_git_repo(policy=policy, interactive=interactive)
392
+
393
+ self._deploy(
394
+ bundle_map=bundle_map,
296
395
  prune=True,
297
396
  recursive=True,
298
- paths=None,
397
+ paths=[],
299
398
  print_diff=True,
300
399
  validate=True,
301
- stage_fqn=f"{package_name}.{model.stage}",
302
- package_warehouse=(
303
- (model.meta and model.meta.warehouse) or ctx.default_warehouse
304
- ),
305
- post_deploy_hooks=model.meta and model.meta.post_deploy,
306
- package_scripts=[], # Package scripts are not supported in PDFv2
307
- version=version,
308
- patch=patch,
309
- skip_git_check=skip_git_check,
310
- force=force,
400
+ stage_fqn=self.stage_fqn,
311
401
  interactive=interactive,
402
+ force=force,
403
+ )
404
+
405
+ # Warn if the version exists in a release directive(s)
406
+ try:
407
+ existing_release_directives = (
408
+ self.get_existing_release_directive_info_for_version(resolved_version)
409
+ )
410
+ except InsufficientPrivilegesError:
411
+ warning = (
412
+ "Could not check for existing release directives due to insufficient privileges. "
413
+ "The MANAGE RELEASES privilege is required to check for existing release directives."
414
+ )
415
+ else:
416
+ if existing_release_directives:
417
+ release_directive_names = ", ".join(
418
+ row["name"] for row in existing_release_directives
419
+ )
420
+ warning = f"Version {resolved_version} already defined in application package {self.name} and in release directive(s): {release_directive_names}."
421
+ else:
422
+ warning = ""
423
+
424
+ if warning:
425
+ console.warning(warning)
426
+ user_prompt = (
427
+ f"Are you sure you want to create a new patch for version {resolved_version} in application "
428
+ f"package {self.name}? Once added, this operation cannot be undone."
429
+ )
430
+ if not policy.should_proceed(user_prompt):
431
+ if interactive:
432
+ console.message("Not creating a new patch.")
433
+ raise typer.Exit(0)
434
+ else:
435
+ console.message(
436
+ "Cannot create a new patch non-interactively without --force."
437
+ )
438
+ raise typer.Exit(1)
439
+
440
+ # Define a new version in the application package
441
+ if not self.get_existing_version_info(resolved_version):
442
+ self.add_new_version(version=resolved_version, label=resolved_label)
443
+ return # A new version created automatically has patch 0, we do not need to further increment the patch.
444
+
445
+ # Add a new patch to an existing (old) version
446
+ self.add_new_patch_to_version(
447
+ version=resolved_version, patch=resolved_patch, label=resolved_label
312
448
  )
313
449
 
314
450
  def action_version_drop(
315
451
  self,
316
- ctx: ActionContext,
452
+ action_ctx: ActionContext,
317
453
  version: Optional[str],
318
454
  interactive: bool,
319
455
  force: bool,
320
456
  *args,
321
457
  **kwargs,
322
458
  ):
323
- model = self._entity_model
324
- package_name = model.fqn.identifier
325
- return self.version_drop(
326
- console=ctx.console,
327
- project_root=ctx.project_root,
328
- deploy_root=Path(model.deploy_root),
329
- bundle_root=Path(model.bundle_root),
330
- generated_root=Path(model.generated_root),
331
- artifacts=model.artifacts,
332
- package_name=package_name,
333
- package_role=(model.meta and model.meta.role) or ctx.default_role,
334
- package_distribution=model.distribution,
335
- version=version,
336
- force=force,
337
- interactive=interactive,
459
+ """
460
+ Drops a version defined in an application package. If --force is provided, then no user prompts will be executed.
461
+ """
462
+ console = self._workspace_ctx.console
463
+
464
+ if force:
465
+ interactive = False
466
+ policy = AllowAlwaysPolicy()
467
+ else:
468
+ policy = AskAlwaysPolicy() if interactive else DenyAlwaysPolicy()
469
+
470
+ # 1. Check for existing an existing application package
471
+ show_obj_row = self.get_existing_app_pkg_info()
472
+ if not show_obj_row:
473
+ raise ApplicationPackageDoesNotExistError(self.name)
474
+
475
+ # 2. Check distribution of the existing application package
476
+ actual_distribution = self.get_app_pkg_distribution_in_snowflake()
477
+ if not self.verify_project_distribution(
478
+ expected_distribution=actual_distribution
479
+ ):
480
+ console.warning(
481
+ f"Continuing to execute version drop on application package "
482
+ f"{self.name} with distribution '{actual_distribution}'."
483
+ )
484
+
485
+ # 3. If the user did not pass in a version string, determine from manifest.yml
486
+ if not version:
487
+ console.message(
488
+ dedent(
489
+ f"""\
490
+ Version was not provided through the Snowflake CLI. Checking version in the manifest.yml instead.
491
+ This step will bundle your app artifacts to determine the location of the manifest.yml file.
492
+ """
493
+ )
494
+ )
495
+ self._bundle()
496
+ version_info = find_version_info_in_manifest_file(self.deploy_root)
497
+ version = version_info.version_name
498
+ if not version:
499
+ raise ClickException(
500
+ "Manifest.yml file does not contain a value for the version field."
501
+ )
502
+
503
+ # Make the version a valid identifier, adding quotes if necessary
504
+ version = to_identifier(version)
505
+
506
+ console.step(
507
+ f"About to drop version {version} in application package {self.name}."
338
508
  )
339
509
 
340
- @staticmethod
341
- def bundle(
342
- project_root: Path,
343
- deploy_root: Path,
344
- bundle_root: Path,
345
- generated_root: Path,
346
- artifacts: list[PathMapping],
347
- package_name: str,
348
- ):
349
- bundle_map = build_bundle(project_root, deploy_root, artifacts)
510
+ # If user did not provide --force, ask for confirmation
511
+ user_prompt = (
512
+ f"Are you sure you want to drop version {version} "
513
+ f"in application package {self.name}? "
514
+ f"Once dropped, this operation cannot be undone."
515
+ )
516
+ if not policy.should_proceed(user_prompt):
517
+ if interactive:
518
+ console.message("Not dropping version.")
519
+ raise typer.Exit(0)
520
+ else:
521
+ console.message(
522
+ "Cannot drop version non-interactively without --force."
523
+ )
524
+ raise typer.Exit(1)
525
+
526
+ # Drop the version
527
+ sql_executor = get_sql_executor()
528
+ with sql_executor.use_role(self.role):
529
+ try:
530
+ sql_executor.execute_query(
531
+ f"alter application package {self.name} drop version {version}"
532
+ )
533
+ except ProgrammingError as err:
534
+ raise err # e.g. version is referenced in a release directive(s)
535
+
536
+ console.message(
537
+ f"Version {version} in application package {self.name} dropped successfully."
538
+ )
539
+
540
+ def _bundle(self):
541
+ model = self._entity_model
542
+ bundle_map = build_bundle(self.project_root, self.deploy_root, model.artifacts)
350
543
  bundle_context = BundleContext(
351
- package_name=package_name,
352
- artifacts=artifacts,
353
- project_root=project_root,
354
- bundle_root=bundle_root,
355
- deploy_root=deploy_root,
356
- generated_root=generated_root,
544
+ package_name=self.name,
545
+ artifacts=model.artifacts,
546
+ project_root=self.project_root,
547
+ bundle_root=self.bundle_root,
548
+ deploy_root=self.deploy_root,
549
+ generated_root=self.generated_root,
357
550
  )
358
551
  compiler = NativeAppCompiler(bundle_context)
359
552
  compiler.compile_artifacts()
360
553
  return bundle_map
361
554
 
362
- @classmethod
363
- def deploy(
364
- cls,
365
- console: AbstractConsole,
366
- project_root: Path,
367
- deploy_root: Path,
368
- bundle_root: Path,
369
- generated_root: Path,
370
- artifacts: list[PathMapping],
555
+ def _deploy(
556
+ self,
371
557
  bundle_map: BundleMap | None,
372
- package_name: str,
373
- package_role: str,
374
- package_distribution: str,
375
- package_warehouse: str | None,
376
558
  prune: bool,
377
559
  recursive: bool,
378
- paths: List[Path] | None,
560
+ paths: list[Path],
379
561
  print_diff: bool,
380
562
  validate: bool,
381
563
  stage_fqn: str,
382
- post_deploy_hooks: list[PostDeployHook] | None,
383
- package_scripts: List[str],
384
- policy: PolicyBase,
564
+ interactive: bool,
565
+ force: bool,
566
+ run_post_deploy_hooks: bool = True,
385
567
  ) -> DiffResult:
568
+ model = self._entity_model
569
+ workspace_ctx = self._workspace_ctx
570
+ if force:
571
+ policy = AllowAlwaysPolicy()
572
+ elif interactive:
573
+ policy = AskAlwaysPolicy()
574
+ else:
575
+ policy = DenyAlwaysPolicy()
576
+
577
+ console = workspace_ctx.console
578
+ stage_fqn = stage_fqn or self.stage_fqn
579
+
386
580
  # 1. Create a bundle if one wasn't passed in
387
- bundle_map = bundle_map or cls.bundle(
388
- project_root=project_root,
389
- deploy_root=deploy_root,
390
- bundle_root=bundle_root,
391
- generated_root=generated_root,
392
- artifacts=artifacts,
393
- package_name=package_name,
394
- )
581
+ bundle_map = bundle_map or self._bundle()
395
582
 
396
583
  # 2. Create an empty application package, if none exists
397
584
  try:
398
- cls.create_app_package(
399
- console=console,
400
- package_name=package_name,
401
- package_role=package_role,
402
- package_distribution=package_distribution,
403
- )
585
+ self.create_app_package()
404
586
  except ApplicationPackageAlreadyExistsError as e:
405
587
  console.warning(e.message)
406
588
  if not policy.should_proceed("Proceed with using this package?"):
407
589
  raise typer.Abort() from e
408
- with get_sql_executor().use_role(package_role):
409
- cls.apply_package_scripts(
410
- console=console,
411
- package_scripts=package_scripts,
412
- package_warehouse=package_warehouse,
413
- project_root=project_root,
414
- package_role=package_role,
415
- package_name=package_name,
416
- )
417
590
 
591
+ with get_sql_executor().use_role(self.role):
418
592
  # 3. Upload files from deploy root local folder to the above stage
419
593
  stage_schema = extract_schema(stage_fqn)
420
594
  diff = sync_deploy_root_with_stage(
421
595
  console=console,
422
- deploy_root=deploy_root,
423
- package_name=package_name,
596
+ deploy_root=self.deploy_root,
597
+ package_name=self.name,
424
598
  stage_schema=stage_schema,
425
599
  bundle_map=bundle_map,
426
- role=package_role,
600
+ role=self.role,
427
601
  prune=prune,
428
602
  recursive=recursive,
429
603
  stage_fqn=stage_fqn,
@@ -431,232 +605,19 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
431
605
  print_diff=print_diff,
432
606
  )
433
607
 
434
- cls.execute_post_deploy_hooks(
435
- console=console,
436
- project_root=project_root,
437
- post_deploy_hooks=post_deploy_hooks,
438
- package_name=package_name,
439
- package_warehouse=package_warehouse,
440
- )
608
+ if run_post_deploy_hooks:
609
+ self.execute_post_deploy_hooks()
441
610
 
442
611
  if validate:
443
- cls.validate_setup_script(
444
- console=console,
445
- project_root=project_root,
446
- deploy_root=deploy_root,
447
- bundle_root=bundle_root,
448
- generated_root=generated_root,
449
- artifacts=artifacts,
450
- package_name=package_name,
451
- package_role=package_role,
452
- package_distribution=package_distribution,
453
- prune=prune,
454
- recursive=recursive,
455
- paths=paths,
456
- stage_fqn=stage_fqn,
457
- package_warehouse=package_warehouse,
458
- post_deploy_hooks=post_deploy_hooks,
459
- package_scripts=package_scripts,
460
- policy=policy,
612
+ self.validate_setup_script(
461
613
  use_scratch_stage=False,
462
- scratch_stage_fqn="",
614
+ interactive=interactive,
615
+ force=force,
463
616
  )
464
617
 
465
618
  return diff
466
619
 
467
- @staticmethod
468
- def version_list(package_name: str, package_role: str) -> SnowflakeCursor:
469
- """
470
- Get all existing versions, if defined, for an application package.
471
- It executes a 'show versions in application package' query and returns all the results.
472
- """
473
- sql_executor = get_sql_executor()
474
- with sql_executor.use_role(package_role):
475
- show_obj_query = f"show versions in application package {package_name}"
476
- show_obj_cursor = sql_executor.execute_query(show_obj_query)
477
-
478
- if show_obj_cursor.rowcount is None:
479
- raise SnowflakeSQLExecutionError(show_obj_query)
480
-
481
- return show_obj_cursor
482
-
483
- @classmethod
484
- def version_create(
485
- cls,
486
- console: AbstractConsole,
487
- project_root: Path,
488
- deploy_root: Path,
489
- bundle_root: Path,
490
- generated_root: Path,
491
- artifacts: list[PathMapping],
492
- package_name: str,
493
- package_role: str,
494
- package_distribution: str,
495
- package_warehouse: str | None,
496
- prune: bool,
497
- recursive: bool,
498
- paths: List[Path] | None,
499
- print_diff: bool,
500
- validate: bool,
501
- stage_fqn: str,
502
- post_deploy_hooks: list[PostDeployHook] | None,
503
- package_scripts: List[str],
504
- version: Optional[str],
505
- patch: Optional[int],
506
- force: bool,
507
- interactive: bool,
508
- skip_git_check: bool,
509
- ):
510
- """
511
- Perform bundle, application package creation, stage upload, version and/or patch to an application package.
512
- """
513
- is_interactive = False
514
- if force:
515
- policy = AllowAlwaysPolicy()
516
- elif interactive:
517
- is_interactive = True
518
- policy = AskAlwaysPolicy()
519
- else:
520
- policy = DenyAlwaysPolicy()
521
-
522
- if skip_git_check:
523
- git_policy = DenyAlwaysPolicy()
524
- else:
525
- git_policy = AllowAlwaysPolicy()
526
-
527
- # Make sure version is not None before proceeding any further.
528
- # This will raise an exception if version information is not found. Patch can be None.
529
- bundle_map = None
530
- if not version:
531
- console.message(
532
- dedent(
533
- f"""\
534
- Version was not provided through the Snowflake CLI. Checking version in the manifest.yml instead.
535
- This step will bundle your app artifacts to determine the location of the manifest.yml file.
536
- """
537
- )
538
- )
539
- bundle_map = cls.bundle(
540
- project_root=project_root,
541
- deploy_root=deploy_root,
542
- bundle_root=bundle_root,
543
- generated_root=generated_root,
544
- artifacts=artifacts,
545
- package_name=package_name,
546
- )
547
- version, patch = find_version_info_in_manifest_file(deploy_root)
548
- if not version:
549
- raise ClickException(
550
- "Manifest.yml file does not contain a value for the version field."
551
- )
552
-
553
- # Check if --patch needs to throw a bad option error, either if application package does not exist or if version does not exist
554
- if patch is not None:
555
- try:
556
- if not cls.get_existing_version_info(
557
- version, package_name, package_role
558
- ):
559
- raise BadOptionUsage(
560
- option_name="patch",
561
- message=f"Cannot create a custom patch when version {version} is not defined in the application package {package_name}. Try again without using --patch.",
562
- )
563
- except ApplicationPackageDoesNotExistError as app_err:
564
- raise BadOptionUsage(
565
- option_name="patch",
566
- message=f"Cannot create a custom patch when application package {package_name} does not exist. Try again without using --patch.",
567
- )
568
-
569
- if git_policy.should_proceed():
570
- cls.check_index_changes_in_git_repo(
571
- console=console,
572
- project_root=project_root,
573
- policy=policy,
574
- is_interactive=is_interactive,
575
- )
576
-
577
- cls.deploy(
578
- console=console,
579
- project_root=project_root,
580
- deploy_root=deploy_root,
581
- bundle_root=bundle_root,
582
- generated_root=generated_root,
583
- artifacts=artifacts,
584
- bundle_map=bundle_map,
585
- package_name=package_name,
586
- package_role=package_role,
587
- package_distribution=package_distribution,
588
- prune=prune,
589
- recursive=recursive,
590
- paths=paths,
591
- print_diff=print_diff,
592
- validate=validate,
593
- stage_fqn=stage_fqn,
594
- package_warehouse=package_warehouse,
595
- post_deploy_hooks=post_deploy_hooks,
596
- package_scripts=package_scripts,
597
- policy=policy,
598
- )
599
-
600
- # Warn if the version exists in a release directive(s)
601
- existing_release_directives = (
602
- cls.get_existing_release_directive_info_for_version(
603
- package_name, package_role, version
604
- )
605
- )
606
-
607
- if existing_release_directives:
608
- release_directive_names = ", ".join(
609
- row["name"] for row in existing_release_directives
610
- )
611
- console.warning(
612
- dedent(
613
- f"""\
614
- Version {version} already defined in application package {package_name} and in release directive(s): {release_directive_names}.
615
- """
616
- )
617
- )
618
-
619
- user_prompt = (
620
- f"Are you sure you want to create a new patch for version {version} in application "
621
- f"package {package_name}? Once added, this operation cannot be undone."
622
- )
623
- if not policy.should_proceed(user_prompt):
624
- if is_interactive:
625
- console.message("Not creating a new patch.")
626
- raise typer.Exit(0)
627
- else:
628
- console.message(
629
- "Cannot create a new patch non-interactively without --force."
630
- )
631
- raise typer.Exit(1)
632
-
633
- # Define a new version in the application package
634
- if not cls.get_existing_version_info(version, package_name, package_role):
635
- cls.add_new_version(
636
- console=console,
637
- package_name=package_name,
638
- package_role=package_role,
639
- stage_fqn=stage_fqn,
640
- version=version,
641
- )
642
- return # A new version created automatically has patch 0, we do not need to further increment the patch.
643
-
644
- # Add a new patch to an existing (old) version
645
- cls.add_new_patch_to_version(
646
- console=console,
647
- package_name=package_name,
648
- package_role=package_role,
649
- stage_fqn=stage_fqn,
650
- version=version,
651
- patch=patch,
652
- )
653
-
654
- @staticmethod
655
- def get_existing_version_info(
656
- version: str,
657
- package_name: str,
658
- package_role: str,
659
- ) -> Optional[dict]:
620
+ def get_existing_version_info(self, version: str) -> Optional[dict]:
660
621
  """
661
622
  Get the latest patch on an existing version by name in the application package.
662
623
  Executes 'show versions like ... in application package' query and returns
@@ -664,9 +625,9 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
664
625
  returns None.
665
626
  """
666
627
  sql_executor = get_sql_executor()
667
- with sql_executor.use_role(package_role):
628
+ with sql_executor.use_role(self.role):
668
629
  try:
669
- query = f"show versions like {identifier_to_show_like_pattern(version)} in application package {package_name}"
630
+ query = f"show versions like {identifier_to_show_like_pattern(version)} in application package {self.name}"
670
631
  cursor = sql_executor.execute_query(query, cursor_class=DictCursor)
671
632
 
672
633
  if cursor.rowcount is None:
@@ -683,116 +644,75 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
683
644
 
684
645
  except ProgrammingError as err:
685
646
  if err.msg.__contains__("does not exist or not authorized"):
686
- raise ApplicationPackageDoesNotExistError(package_name)
647
+ raise ApplicationPackageDoesNotExistError(self.name)
687
648
  else:
688
- generic_sql_error_handler(err=err, role=package_role)
649
+ generic_sql_error_handler(err=err)
689
650
  return None
690
651
 
691
- @classmethod
692
652
  def get_existing_release_directive_info_for_version(
693
- cls,
694
- package_name: str,
695
- package_role: str,
696
- version: str,
653
+ self, version: str
697
654
  ) -> List[dict]:
698
655
  """
699
656
  Get all existing release directives, if present, set on the version defined in an application package.
700
657
  It executes a 'show release directives in application package' query and returns the filtered results, if they exist.
701
658
  """
702
- sql_executor = get_sql_executor()
703
- with sql_executor.use_role(package_role):
704
- show_obj_query = (
705
- f"show release directives in application package {package_name}"
706
- )
707
- show_obj_cursor = sql_executor.execute_query(
708
- show_obj_query, cursor_class=DictCursor
709
- )
710
-
711
- if show_obj_cursor.rowcount is None:
712
- raise SnowflakeSQLExecutionError(show_obj_query)
713
-
714
- show_obj_rows = find_all_rows(
715
- show_obj_cursor,
716
- lambda row: row[VERSION_COL] == unquote_identifier(version),
717
- )
718
-
719
- return show_obj_rows
659
+ release_directives = get_snowflake_facade().show_release_directives(
660
+ self.name, self.role
661
+ )
662
+ return [
663
+ directive
664
+ for directive in release_directives
665
+ if directive[VERSION_COL] == unquote_identifier(version)
666
+ ]
720
667
 
721
- @classmethod
722
- def add_new_version(
723
- cls,
724
- console: AbstractConsole,
725
- package_name: str,
726
- package_role: str,
727
- stage_fqn: str,
728
- version: str,
729
- ) -> None:
730
- """
731
- Defines a new version in an existing application package.
732
- """
733
- # Make the version a valid identifier, adding quotes if necessary
734
- version = to_identifier(version)
735
- sql_executor = get_sql_executor()
736
- with sql_executor.use_role(package_role):
737
- console.step(
738
- f"Defining a new version {version} in application package {package_name}"
739
- )
740
- add_version_query = dedent(
741
- f"""\
742
- alter application package {package_name}
743
- add version {version}
744
- using @{stage_fqn}
745
- """
746
- )
747
- sql_executor.execute_query(add_version_query, cursor_class=DictCursor)
748
- console.message(
749
- f"Version {version} created for application package {package_name}."
750
- )
668
+ def add_new_version(self, version: str, label: str | None = None) -> None:
669
+ """
670
+ Add a new version with an optional label in application package.
671
+ """
672
+ console = self._workspace_ctx.console
673
+ with_label_prompt = f" labeled {label}" if label else ""
674
+
675
+ console.step(
676
+ f"Defining a new version {version}{with_label_prompt} in application package {self.name}"
677
+ )
678
+ get_snowflake_facade().create_version_in_package(
679
+ role=self.role,
680
+ package_name=self.name,
681
+ stage_fqn=self.stage_fqn,
682
+ version=version,
683
+ label=label,
684
+ )
685
+ console.message(
686
+ f"Version {version}{with_label_prompt} created for application package {self.name}."
687
+ )
751
688
 
752
- @classmethod
753
689
  def add_new_patch_to_version(
754
- cls,
755
- console: AbstractConsole,
756
- package_name: str,
757
- package_role: str,
758
- stage_fqn: str,
759
- version: str,
760
- patch: Optional[int] = None,
690
+ self, version: str, patch: int | None = None, label: str | None = None
761
691
  ):
762
692
  """
763
693
  Add a new patch, optionally a custom one, to an existing version in an application package.
764
694
  """
765
- # Make the version a valid identifier, adding quotes if necessary
766
- version = to_identifier(version)
767
- sql_executor = get_sql_executor()
768
- with sql_executor.use_role(package_role):
769
- console.step(
770
- f"Adding new patch to version {version} defined in application package {package_name}"
771
- )
772
- add_version_query = dedent(
773
- f"""\
774
- alter application package {package_name}
775
- add patch {patch if patch else ""} for version {version}
776
- using @{stage_fqn}
777
- """
778
- )
779
- result_cursor = sql_executor.execute_query(
780
- add_version_query, cursor_class=DictCursor
781
- )
695
+ console = self._workspace_ctx.console
782
696
 
783
- show_row = result_cursor.fetchall()[0]
784
- new_patch = show_row["patch"]
785
- console.message(
786
- f"Patch {new_patch} created for version {version} defined in application package {package_name}."
787
- )
697
+ with_label_prompt = f" labeled {label}" if label else ""
698
+
699
+ console.step(
700
+ f"Adding new patch to version {version}{with_label_prompt} defined in application package {self.name}"
701
+ )
702
+ new_patch = get_snowflake_facade().add_patch_to_package_version(
703
+ role=self.role,
704
+ package_name=self.name,
705
+ stage_fqn=self.stage_fqn,
706
+ version=version,
707
+ patch=patch,
708
+ label=label,
709
+ )
710
+ console.message(
711
+ f"Patch {new_patch}{with_label_prompt} created for version {version} defined in application package {self.name}."
712
+ )
788
713
 
789
- @classmethod
790
714
  def check_index_changes_in_git_repo(
791
- cls,
792
- console: AbstractConsole,
793
- project_root: Path,
794
- policy: PolicyBase,
795
- is_interactive: bool,
715
+ self, policy: PolicyBase, interactive: bool
796
716
  ) -> None:
797
717
  """
798
718
  Checks if the project root, i.e. the native apps project is a git repository. If it is a git repository,
@@ -802,8 +722,10 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
802
722
  from git import Repo
803
723
  from git.exc import InvalidGitRepositoryError
804
724
 
725
+ console = self._workspace_ctx.console
726
+
805
727
  try:
806
- repo = Repo(project_root, search_parent_directories=True)
728
+ repo = Repo(self.project_root, search_parent_directories=True)
807
729
  assert repo.git_dir is not None
808
730
 
809
731
  # Check if the repo has any changes, including untracked files
@@ -819,7 +741,7 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
819
741
  "Do you still want to continue?"
820
742
  )
821
743
  if not policy.should_proceed(user_prompt):
822
- if is_interactive:
744
+ if interactive:
823
745
  console.message("Not creating a new version.")
824
746
  raise typer.Exit(0)
825
747
  else:
@@ -831,141 +753,26 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
831
753
  except InvalidGitRepositoryError:
832
754
  pass # not a git repository, which is acceptable
833
755
 
834
- @classmethod
835
- def version_drop(
836
- cls,
837
- console: AbstractConsole,
838
- project_root: Path,
839
- deploy_root: Path,
840
- bundle_root: Path,
841
- generated_root: Path,
842
- artifacts: list[PathMapping],
843
- package_name: str,
844
- package_role: str,
845
- package_distribution: str,
846
- version: Optional[str],
847
- force: bool,
848
- interactive: bool,
849
- ):
850
- """
851
- Drops a version defined in an application package. If --force is provided, then no user prompts will be executed.
852
- """
853
- if force:
854
- interactive = False
855
- policy = AllowAlwaysPolicy()
856
- else:
857
- policy = AskAlwaysPolicy() if interactive else DenyAlwaysPolicy()
858
-
859
- # 1. Check for existing an existing application package
860
- show_obj_row = cls.get_existing_app_pkg_info(package_name, package_role)
861
- if not show_obj_row:
862
- raise ApplicationPackageDoesNotExistError(package_name)
863
-
864
- # 2. Check distribution of the existing application package
865
- actual_distribution = cls.get_app_pkg_distribution_in_snowflake(
866
- package_name, package_role
867
- )
868
- if not cls.verify_project_distribution(
869
- console=console,
870
- package_name=package_name,
871
- package_role=package_role,
872
- package_distribution=package_distribution,
873
- expected_distribution=actual_distribution,
874
- ):
875
- console.warning(
876
- f"Continuing to execute version drop on application package "
877
- f"{package_name} with distribution '{actual_distribution}'."
878
- )
879
-
880
- # 3. If the user did not pass in a version string, determine from manifest.yml
881
- if not version:
882
- console.message(
883
- dedent(
884
- f"""\
885
- Version was not provided through the Snowflake CLI. Checking version in the manifest.yml instead.
886
- This step will bundle your app artifacts to determine the location of the manifest.yml file.
887
- """
888
- )
889
- )
890
- cls.bundle(
891
- project_root=project_root,
892
- deploy_root=deploy_root,
893
- bundle_root=bundle_root,
894
- generated_root=generated_root,
895
- artifacts=artifacts,
896
- package_name=package_name,
897
- )
898
- version, _ = find_version_info_in_manifest_file(deploy_root)
899
- if not version:
900
- raise ClickException(
901
- "Manifest.yml file does not contain a value for the version field."
902
- )
903
-
904
- # Make the version a valid identifier, adding quotes if necessary
905
- version = to_identifier(version)
906
-
907
- console.step(
908
- f"About to drop version {version} in application package {package_name}."
909
- )
910
-
911
- # If user did not provide --force, ask for confirmation
912
- user_prompt = (
913
- f"Are you sure you want to drop version {version} "
914
- f"in application package {package_name}? "
915
- f"Once dropped, this operation cannot be undone."
916
- )
917
- if not policy.should_proceed(user_prompt):
918
- if interactive:
919
- console.message("Not dropping version.")
920
- raise typer.Exit(0)
921
- else:
922
- console.message(
923
- "Cannot drop version non-interactively without --force."
924
- )
925
- raise typer.Exit(1)
926
-
927
- # Drop the version
928
- sql_executor = get_sql_executor()
929
- with sql_executor.use_role(package_role):
930
- try:
931
- sql_executor.execute_query(
932
- f"alter application package {package_name} drop version {version}"
933
- )
934
- except ProgrammingError as err:
935
- raise err # e.g. version is referenced in a release directive(s)
936
-
937
- console.message(
938
- f"Version {version} in application package {package_name} dropped successfully."
939
- )
940
-
941
- @staticmethod
942
- def get_existing_app_pkg_info(
943
- package_name: str,
944
- package_role: str,
945
- ) -> Optional[dict]:
756
+ def get_existing_app_pkg_info(self) -> Optional[dict]:
946
757
  """
947
758
  Check for an existing application package by the same name as in project definition, in account.
948
759
  It executes a 'show application packages like' query and returns the result as single row, if one exists.
949
760
  """
950
761
  sql_executor = get_sql_executor()
951
- with sql_executor.use_role(package_role):
762
+ with sql_executor.use_role(self.role):
952
763
  return sql_executor.show_specific_object(
953
- "application packages", package_name, name_col=NAME_COL
764
+ "application packages", self.name, name_col=NAME_COL
954
765
  )
955
766
 
956
- @staticmethod
957
- def get_app_pkg_distribution_in_snowflake(
958
- package_name: str,
959
- package_role: str,
960
- ) -> str:
767
+ def get_app_pkg_distribution_in_snowflake(self) -> str:
961
768
  """
962
769
  Returns the 'distribution' attribute of a 'describe application package' SQL query, in lowercase.
963
770
  """
964
771
  sql_executor = get_sql_executor()
965
- with sql_executor.use_role(package_role):
772
+ with sql_executor.use_role(self.role):
966
773
  try:
967
774
  desc_cursor = sql_executor.execute_query(
968
- f"describe application package {package_name}"
775
+ f"describe application package {self.name}"
969
776
  )
970
777
  except ProgrammingError as err:
971
778
  generic_sql_error_handler(err)
@@ -976,42 +783,34 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
976
783
  for row in desc_cursor:
977
784
  if row[0].lower() == "distribution":
978
785
  return row[1].lower()
979
- raise ProgrammingError(
980
- msg=dedent(
981
- f"""\
982
- Could not find the 'distribution' attribute for application package {package_name} in the output of SQL query:
983
- 'describe application package {package_name}'
984
- """
985
- )
786
+ raise ObjectPropertyNotFoundError(
787
+ property_name="distribution",
788
+ object_type="application package",
789
+ object_name=self.name,
986
790
  )
987
791
 
988
- @classmethod
989
792
  def verify_project_distribution(
990
- cls,
991
- console: AbstractConsole,
992
- package_name: str,
993
- package_role: str,
994
- package_distribution: str,
793
+ self,
995
794
  expected_distribution: Optional[str] = None,
996
795
  ) -> bool:
997
796
  """
998
797
  Returns true if the 'distribution' attribute of an existing application package in snowflake
999
798
  is the same as the the attribute specified in project definition file.
1000
799
  """
800
+ model = self._entity_model
801
+ workspace_ctx = self._workspace_ctx
802
+
1001
803
  actual_distribution = (
1002
804
  expected_distribution
1003
805
  if expected_distribution
1004
- else cls.get_app_pkg_distribution_in_snowflake(
1005
- package_name=package_name,
1006
- package_role=package_role,
1007
- )
806
+ else self.get_app_pkg_distribution_in_snowflake()
1008
807
  )
1009
- project_def_distribution = package_distribution.lower()
808
+ project_def_distribution = model.distribution.lower()
1010
809
  if actual_distribution != project_def_distribution:
1011
- console.warning(
810
+ workspace_ctx.console.warning(
1012
811
  dedent(
1013
812
  f"""\
1014
- Application package {package_name} in your Snowflake account has distribution property {actual_distribution},
813
+ Application package {self.name} in your Snowflake account has distribution property {actual_distribution},
1015
814
  which does not match the value specified in project definition file: {project_def_distribution}.
1016
815
  """
1017
816
  )
@@ -1019,104 +818,24 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
1019
818
  return False
1020
819
  return True
1021
820
 
1022
- @staticmethod
1023
- @contextmanager
1024
- def use_package_warehouse(
1025
- package_warehouse: Optional[str],
1026
- ):
1027
- if package_warehouse:
1028
- with get_sql_executor().use_warehouse(package_warehouse):
1029
- yield
1030
- else:
1031
- raise ClickException(
1032
- dedent(
1033
- f"""\
1034
- Application package warehouse cannot be empty.
1035
- Please provide a value for it in your connection information or your project definition file.
1036
- """
1037
- )
1038
- )
1039
-
1040
- @classmethod
1041
- def apply_package_scripts(
1042
- cls,
1043
- console: AbstractConsole,
1044
- package_scripts: List[str],
1045
- package_warehouse: Optional[str],
1046
- project_root: Path,
1047
- package_role: str,
1048
- package_name: str,
1049
- ) -> None:
1050
- """
1051
- Assuming the application package exists and we are using the correct role,
1052
- applies all package scripts in-order to the application package.
1053
- """
1054
-
1055
- metrics = get_cli_context().metrics
1056
- metrics.set_counter_default(CLICounterField.PACKAGE_SCRIPTS, 0)
1057
-
1058
- if not package_scripts:
1059
- return
1060
-
1061
- metrics.set_counter(CLICounterField.PACKAGE_SCRIPTS, 1)
1062
-
1063
- console.warning(
1064
- "WARNING: native_app.package.scripts is deprecated. Please migrate to using native_app.package.post_deploy."
1065
- )
1066
-
1067
- queued_queries = render_script_templates(
1068
- project_root,
1069
- dict(package_name=package_name),
1070
- package_scripts,
1071
- get_basic_jinja_env(),
1072
- )
1073
-
1074
- # once we're sure all the templates expanded correctly, execute all of them
1075
- with cls.use_package_warehouse(
1076
- package_warehouse=package_warehouse,
1077
- ):
1078
- try:
1079
- for i, queries in enumerate(queued_queries):
1080
- console.step(f"Applying package script: {package_scripts[i]}")
1081
- get_sql_executor().execute_queries(queries)
1082
- except ProgrammingError as err:
1083
- generic_sql_error_handler(
1084
- err, role=package_role, warehouse=package_warehouse
1085
- )
1086
-
1087
- @classmethod
1088
- def create_app_package(
1089
- cls,
1090
- console: AbstractConsole,
1091
- package_name: str,
1092
- package_role: str,
1093
- package_distribution: str,
1094
- ) -> None:
821
+ def create_app_package(self) -> None:
1095
822
  """
1096
823
  Creates the application package with our up-to-date stage if none exists.
1097
824
  """
825
+ model = self._entity_model
826
+ console = self._workspace_ctx.console
1098
827
 
1099
- # 1. Check for existing existing application package
1100
- show_obj_row = cls.get_existing_app_pkg_info(
1101
- package_name=package_name,
1102
- package_role=package_role,
1103
- )
828
+ # 1. Check for existing application package
829
+ show_obj_row = self.get_existing_app_pkg_info()
1104
830
 
1105
831
  if show_obj_row:
1106
832
  # 2. Check distribution of the existing application package
1107
- actual_distribution = cls.get_app_pkg_distribution_in_snowflake(
1108
- package_name=package_name,
1109
- package_role=package_role,
1110
- )
1111
- if not cls.verify_project_distribution(
1112
- console=console,
1113
- package_name=package_name,
1114
- package_role=package_role,
1115
- package_distribution=package_distribution,
1116
- expected_distribution=actual_distribution,
833
+ actual_distribution = self.get_app_pkg_distribution_in_snowflake()
834
+ if not self.verify_project_distribution(
835
+ expected_distribution=actual_distribution
1117
836
  ):
1118
837
  console.warning(
1119
- f"Continuing to execute `snow app run` on application package {package_name} with distribution '{actual_distribution}'."
838
+ f"Continuing to execute `snow app run` on application package {self.name} with distribution '{actual_distribution}'."
1120
839
  )
1121
840
 
1122
841
  # 3. If actual_distribution is external, skip comment check
@@ -1124,92 +843,47 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
1124
843
  row_comment = show_obj_row[COMMENT_COL]
1125
844
 
1126
845
  if row_comment not in ALLOWED_SPECIAL_COMMENTS:
1127
- raise ApplicationPackageAlreadyExistsError(package_name)
846
+ raise ApplicationPackageAlreadyExistsError(self.name)
1128
847
 
1129
848
  return
1130
849
 
1131
850
  # If no application package pre-exists, create an application package, with the specified distribution in the project definition file.
1132
851
  sql_executor = get_sql_executor()
1133
- with sql_executor.use_role(package_role):
1134
- console.step(f"Creating new application package {package_name} in account.")
852
+ with sql_executor.use_role(self.role):
853
+ console.step(f"Creating new application package {self.name} in account.")
1135
854
  sql_executor.execute_query(
1136
855
  dedent(
1137
856
  f"""\
1138
- create application package {package_name}
857
+ create application package {self.name}
1139
858
  comment = {SPECIAL_COMMENT}
1140
- distribution = {package_distribution}
859
+ distribution = {model.distribution}
1141
860
  """
1142
861
  )
1143
862
  )
1144
863
 
1145
- @classmethod
1146
- def execute_post_deploy_hooks(
1147
- cls,
1148
- console: AbstractConsole,
1149
- project_root: Path,
1150
- post_deploy_hooks: Optional[List[PostDeployHook]],
1151
- package_name: str,
1152
- package_warehouse: Optional[str],
1153
- ):
1154
- get_cli_context().metrics.set_counter_default(
1155
- CLICounterField.POST_DEPLOY_SCRIPTS, 0
864
+ def execute_post_deploy_hooks(self):
865
+ execute_post_deploy_hooks(
866
+ console=self._workspace_ctx.console,
867
+ project_root=self.project_root,
868
+ post_deploy_hooks=self.post_deploy_hooks,
869
+ deployed_object_type="application package",
870
+ role_name=self.role,
871
+ warehouse_name=self.warehouse,
872
+ database_name=self.name,
1156
873
  )
1157
874
 
1158
- if post_deploy_hooks:
1159
- with cls.use_package_warehouse(package_warehouse):
1160
- execute_post_deploy_hooks(
1161
- console=console,
1162
- project_root=project_root,
1163
- post_deploy_hooks=post_deploy_hooks,
1164
- deployed_object_type="application package",
1165
- database_name=package_name,
1166
- )
1167
-
1168
- @classmethod
1169
875
  def validate_setup_script(
1170
- cls,
1171
- console: AbstractConsole,
1172
- project_root: Path,
1173
- deploy_root: Path,
1174
- bundle_root: Path,
1175
- generated_root: Path,
1176
- artifacts: list[PathMapping],
1177
- package_name: str,
1178
- package_role: str,
1179
- package_distribution: str,
1180
- package_warehouse: str | None,
1181
- prune: bool,
1182
- recursive: bool,
1183
- paths: List[Path] | None,
1184
- stage_fqn: str,
1185
- post_deploy_hooks: list[PostDeployHook] | None,
1186
- package_scripts: List[str],
1187
- policy: PolicyBase,
1188
- use_scratch_stage: bool,
1189
- scratch_stage_fqn: str,
876
+ self, use_scratch_stage: bool, interactive: bool, force: bool
1190
877
  ):
878
+ workspace_ctx = self._workspace_ctx
879
+ console = workspace_ctx.console
880
+
1191
881
  """Validates Native App setup script SQL."""
1192
882
  with console.phase(f"Validating Snowflake Native App setup script."):
1193
- validation_result = cls.get_validation_result(
1194
- console=console,
1195
- project_root=project_root,
1196
- deploy_root=deploy_root,
1197
- bundle_root=bundle_root,
1198
- generated_root=generated_root,
1199
- artifacts=artifacts,
1200
- package_name=package_name,
1201
- package_role=package_role,
1202
- package_distribution=package_distribution,
1203
- prune=prune,
1204
- recursive=recursive,
1205
- paths=paths,
1206
- stage_fqn=stage_fqn,
1207
- package_warehouse=package_warehouse,
1208
- post_deploy_hooks=post_deploy_hooks,
1209
- package_scripts=package_scripts,
1210
- policy=policy,
883
+ validation_result = self.get_validation_result(
1211
884
  use_scratch_stage=use_scratch_stage,
1212
- scratch_stage_fqn=scratch_stage_fqn,
885
+ force=force,
886
+ interactive=interactive,
1213
887
  )
1214
888
 
1215
889
  # First print warnings, regardless of the outcome of validation
@@ -1226,53 +900,24 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
1226
900
  if validation_result["status"] == "FAIL":
1227
901
  raise SetupScriptFailedValidation()
1228
902
 
1229
- @classmethod
1230
903
  def get_validation_result(
1231
- cls,
1232
- console: AbstractConsole,
1233
- project_root: Path,
1234
- deploy_root: Path,
1235
- bundle_root: Path,
1236
- generated_root: Path,
1237
- artifacts: list[PathMapping],
1238
- package_name: str,
1239
- package_role: str,
1240
- package_distribution: str,
1241
- package_warehouse: str | None,
1242
- prune: bool,
1243
- recursive: bool,
1244
- paths: List[Path] | None,
1245
- stage_fqn: str,
1246
- post_deploy_hooks: list[PostDeployHook] | None,
1247
- package_scripts: List[str],
1248
- policy: PolicyBase,
1249
- use_scratch_stage: bool,
1250
- scratch_stage_fqn: str,
904
+ self, use_scratch_stage: bool, interactive: bool, force: bool
1251
905
  ):
1252
906
  """Call system$validate_native_app_setup() to validate deployed Native App setup script."""
907
+ stage_fqn = self.stage_fqn
1253
908
  if use_scratch_stage:
1254
- stage_fqn = scratch_stage_fqn
1255
- cls.deploy(
1256
- console=console,
1257
- project_root=project_root,
1258
- deploy_root=deploy_root,
1259
- bundle_root=bundle_root,
1260
- generated_root=generated_root,
1261
- artifacts=artifacts,
909
+ stage_fqn = self.scratch_stage_fqn
910
+ self._deploy(
1262
911
  bundle_map=None,
1263
- package_name=package_name,
1264
- package_role=package_role,
1265
- package_distribution=package_distribution,
1266
- prune=prune,
1267
- recursive=recursive,
1268
- paths=paths,
912
+ prune=True,
913
+ recursive=True,
914
+ paths=[],
1269
915
  print_diff=False,
1270
916
  validate=False,
1271
- stage_fqn=stage_fqn,
1272
- package_warehouse=package_warehouse,
1273
- post_deploy_hooks=post_deploy_hooks,
1274
- package_scripts=package_scripts,
1275
- policy=policy,
917
+ stage_fqn=self.scratch_stage_fqn,
918
+ interactive=interactive,
919
+ force=force,
920
+ run_post_deploy_hooks=False,
1276
921
  )
1277
922
  prefixed_stage_fqn = StageManager.get_standard_stage_prefix(stage_fqn)
1278
923
  sql_executor = get_sql_executor()
@@ -1282,7 +927,7 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
1282
927
  )
1283
928
  except ProgrammingError as err:
1284
929
  if err.errno == DOES_NOT_EXIST_OR_NOT_AUTHORIZED:
1285
- raise ApplicationPackageDoesNotExistError(package_name)
930
+ raise ApplicationPackageDoesNotExistError(self.name)
1286
931
  generic_sql_error_handler(err)
1287
932
  else:
1288
933
  if not cursor.rowcount:
@@ -1290,103 +935,112 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
1290
935
  return json.loads(cursor.fetchone()[0])
1291
936
  finally:
1292
937
  if use_scratch_stage:
1293
- console.step(f"Dropping stage {scratch_stage_fqn}.")
1294
- with sql_executor.use_role(package_role):
938
+ self._workspace_ctx.console.step(
939
+ f"Dropping stage {self.scratch_stage_fqn}."
940
+ )
941
+ with sql_executor.use_role(self.role):
1295
942
  sql_executor.execute_query(
1296
- f"drop stage if exists {scratch_stage_fqn}"
943
+ f"drop stage if exists {self.scratch_stage_fqn}"
1297
944
  )
1298
945
 
1299
- @classmethod
1300
- def drop(
1301
- cls,
1302
- console: AbstractConsole,
1303
- package_name: str,
1304
- package_role: str,
1305
- force_drop: bool,
946
+ def resolve_version_info(
947
+ self,
948
+ version: str | None,
949
+ patch: int | None,
950
+ label: str | None,
951
+ bundle_map: BundleMap | None,
952
+ policy: PolicyBase,
953
+ interactive: bool,
1306
954
  ):
1307
- sql_executor = get_sql_executor()
1308
- needs_confirm = True
1309
-
1310
- # 1. If existing application package is not found, exit gracefully
1311
- show_obj_row = cls.get_existing_app_pkg_info(
1312
- package_name=package_name,
1313
- package_role=package_role,
1314
- )
1315
- if show_obj_row is None:
1316
- console.warning(
1317
- f"Role {package_role} does not own any application package with the name {package_name}, or the application package does not exist."
1318
- )
1319
- return
1320
-
1321
- with sql_executor.use_role(package_role):
1322
- # 2. Check for versions in the application package
1323
- show_versions_query = f"show versions in application package {package_name}"
1324
- show_versions_cursor = sql_executor.execute_query(
1325
- show_versions_query, cursor_class=DictCursor
1326
- )
1327
- if show_versions_cursor.rowcount is None:
1328
- raise SnowflakeSQLExecutionError(show_versions_query)
955
+ """Determine version name, patch number, and label from CLI provided values and manifest.yml version entry.
956
+ @param [Optional] version: version name as specified in the command
957
+ @param [Optional] patch: patch number as specified in the command
958
+ @param [Optional] label: version/patch label as specified in the command
959
+ @param [Optional] bundle_map: bundle_map if a deploy_root is prepared. _bundle() is performed otherwise.
960
+ @param policy: CLI policy
961
+ @param interactive: True if command is run in interactive mode, otherwise False
962
+ """
963
+ console = self._workspace_ctx.console
1329
964
 
1330
- if show_versions_cursor.rowcount > 0:
1331
- # allow dropping a package with versions when --force is set
1332
- if not force_drop:
1333
- raise CouldNotDropApplicationPackageWithVersions(
1334
- "Drop versions first, or use --force to override."
1335
- )
965
+ resolved_version = None
966
+ resolved_patch = None
967
+ resolved_label = ""
1336
968
 
1337
- # 3. Check distribution of the existing application package
1338
- actual_distribution = cls.get_app_pkg_distribution_in_snowflake(
1339
- package_name=package_name,
1340
- package_role=package_role,
1341
- )
1342
- if not cls.verify_project_distribution(
1343
- console=console,
1344
- package_name=package_name,
1345
- package_role=package_role,
1346
- package_distribution=actual_distribution,
1347
- ):
1348
- console.warning(
1349
- f"Dropping application package {package_name} with distribution '{actual_distribution}'."
969
+ # If version is specified in CLI, no version information from manifest.yml is used (except for comment, we can't control comment as of now).
970
+ if version is not None:
971
+ console.message(
972
+ "Ignoring version information from the application manifest since a version was explicitly specified with the command."
1350
973
  )
974
+ resolved_patch = patch
975
+ resolved_label = label if label is not None else ""
976
+ resolved_version = version
1351
977
 
1352
- # 4. If distribution is internal, check if created by the Snowflake CLI
1353
- row_comment = show_obj_row[COMMENT_COL]
1354
- if actual_distribution == INTERNAL_DISTRIBUTION:
1355
- if row_comment in ALLOWED_SPECIAL_COMMENTS:
1356
- needs_confirm = False
1357
- else:
1358
- if needs_confirmation(needs_confirm, force_drop):
1359
- console.warning(
1360
- f"Application package {package_name} was not created by Snowflake CLI."
1361
- )
978
+ # When version is not set by CLI, version name is read from manifest.yml. patch and label from CLI will be used, if provided.
1362
979
  else:
1363
- if needs_confirmation(needs_confirm, force_drop):
1364
- console.warning(
1365
- f"Application package {package_name} in your Snowflake account has distribution property '{EXTERNAL_DISTRIBUTION}' and could be associated with one or more of your listings on Snowflake Marketplace."
1366
- )
1367
-
1368
- if needs_confirmation(needs_confirm, force_drop):
1369
- should_drop_object = typer.confirm(
980
+ console.message(
1370
981
  dedent(
1371
982
  f"""\
1372
- Application package details:
1373
- Name: {package_name}
1374
- Created on: {show_obj_row["created_on"]}
1375
- Distribution: {actual_distribution}
1376
- Owner: {show_obj_row[OWNER_COL]}
1377
- Comment: {show_obj_row[COMMENT_COL]}
1378
- Are you sure you want to drop it?
983
+ Version was not provided through the Snowflake CLI. Checking version in the manifest.yml instead.
1379
984
  """
1380
985
  )
1381
986
  )
1382
- if not should_drop_object:
1383
- console.message(f"Did not drop application package {package_name}.")
1384
- return # The user desires to keep the application package, therefore exit gracefully
987
+ if bundle_map is None:
988
+ self._bundle()
989
+ (
990
+ resolved_version,
991
+ patch_manifest,
992
+ label_manifest,
993
+ ) = find_version_info_in_manifest_file(self.deploy_root)
994
+ if resolved_version is None:
995
+ raise ClickException(
996
+ "Manifest.yml file does not contain a value for the version field."
997
+ )
1385
998
 
1386
- # All validations have passed, drop object
1387
- drop_generic_object(
1388
- console=console,
1389
- object_type="application package",
1390
- object_name=package_name,
1391
- role=package_role,
999
+ # If patch is set in CLI and is also present in manifest.yml with different value, confirmation from
1000
+ # user is required to ignore patch from manifest.yml and proceed with CLI value.
1001
+ if (
1002
+ patch is not None
1003
+ and patch_manifest is not None
1004
+ and patch_manifest != patch
1005
+ ):
1006
+ console.warning(
1007
+ f"Cannot resolve version. Found patch: {patch_manifest} in manifest.yml which is different from provided patch {patch}."
1008
+ )
1009
+ user_prompt = f"Do you want to ignore patch in manifest.yml and proceed with provided --patch {patch}?"
1010
+ if not policy.should_proceed(user_prompt):
1011
+ if interactive:
1012
+ console.message("Not creating a new patch.")
1013
+ raise typer.Exit(0)
1014
+ else:
1015
+ console.message(
1016
+ "Could not create a new patch non-interactively without --force."
1017
+ )
1018
+ raise typer.Exit(1)
1019
+ resolved_patch = patch
1020
+ elif patch is not None:
1021
+ resolved_patch = patch
1022
+ else:
1023
+ resolved_patch = patch_manifest
1024
+
1025
+ # If label is not specified in CLI, label from manifest.yml is used. Even if patch is from CLI.
1026
+ resolved_label = label if label is not None else label_manifest
1027
+
1028
+ # Check if patch needs to throw a bad option error, either if application package does not exist or if version does not exist
1029
+ if resolved_patch is not None:
1030
+ try:
1031
+ if not self.get_existing_version_info(resolved_version):
1032
+ raise BadOptionUsage(
1033
+ option_name="patch",
1034
+ message=f"Cannot create patch {resolved_patch} when version {resolved_version} is not defined in the application package {self.name}. Try again without specifying a patch.",
1035
+ )
1036
+ except ApplicationPackageDoesNotExistError as app_err:
1037
+ raise BadOptionUsage(
1038
+ option_name="patch",
1039
+ message=f"Cannot create patch {resolved_patch} when application package {self.name} does not exist. Try again without specifying a patch.",
1040
+ )
1041
+
1042
+ return VersionInfo(
1043
+ version_name=resolved_version,
1044
+ patch_number=resolved_patch,
1045
+ label=resolved_label,
1392
1046
  )