snowflake-cli-labs 3.0.0rc2__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 (73) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/_app/commands_registration/builtin_plugins.py +2 -0
  3. snowflake/cli/_app/secret.py +9 -0
  4. snowflake/cli/_app/snow_connector.py +39 -27
  5. snowflake/cli/_plugins/git/manager.py +53 -7
  6. snowflake/cli/_plugins/helpers/commands.py +57 -0
  7. snowflake/cli/{api/project/schemas/snowpark/__init__.py → _plugins/helpers/plugin_spec.py} +17 -0
  8. snowflake/cli/{api/entities → _plugins/nativeapp}/application_entity.py +18 -64
  9. snowflake/cli/{api/project/schemas/entities → _plugins/nativeapp}/application_entity_model.py +2 -2
  10. snowflake/cli/{api/entities → _plugins/nativeapp}/application_package_entity.py +482 -33
  11. snowflake/cli/{api/project/schemas/entities → _plugins/nativeapp}/application_package_entity_model.py +3 -3
  12. snowflake/cli/_plugins/nativeapp/artifacts.py +10 -9
  13. snowflake/cli/_plugins/nativeapp/bundle_context.py +1 -1
  14. snowflake/cli/_plugins/nativeapp/codegen/artifact_processor.py +1 -1
  15. snowflake/cli/_plugins/nativeapp/codegen/compiler.py +1 -1
  16. snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +1 -1
  17. snowflake/cli/_plugins/nativeapp/codegen/snowpark/extension_function_utils.py +1 -1
  18. snowflake/cli/_plugins/nativeapp/codegen/snowpark/models.py +1 -1
  19. snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +1 -1
  20. snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +1 -1
  21. snowflake/cli/_plugins/nativeapp/commands.py +84 -16
  22. snowflake/cli/_plugins/nativeapp/exceptions.py +0 -9
  23. snowflake/cli/_plugins/nativeapp/manager.py +14 -9
  24. snowflake/cli/_plugins/nativeapp/policy.py +3 -0
  25. snowflake/cli/_plugins/nativeapp/project_model.py +2 -2
  26. snowflake/cli/_plugins/nativeapp/run_processor.py +16 -19
  27. snowflake/cli/_plugins/nativeapp/same_account_install_method.py +0 -4
  28. snowflake/cli/_plugins/nativeapp/teardown_processor.py +6 -6
  29. snowflake/cli/_plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +124 -88
  30. snowflake/cli/_plugins/nativeapp/version/commands.py +6 -24
  31. snowflake/cli/_plugins/nativeapp/version/version_processor.py +35 -235
  32. snowflake/cli/_plugins/snowpark/commands.py +4 -4
  33. snowflake/cli/_plugins/snowpark/common.py +4 -4
  34. snowflake/cli/{api/entities → _plugins/snowpark}/snowpark_entity.py +2 -2
  35. snowflake/cli/{api/project/schemas/entities/snowpark_entity.py → _plugins/snowpark/snowpark_entity_model.py} +3 -6
  36. snowflake/cli/_plugins/snowpark/snowpark_project_paths.py +1 -1
  37. snowflake/cli/_plugins/stage/manager.py +9 -4
  38. snowflake/cli/_plugins/streamlit/commands.py +3 -3
  39. snowflake/cli/_plugins/streamlit/manager.py +8 -4
  40. snowflake/cli/{api/entities → _plugins/streamlit}/streamlit_entity.py +2 -2
  41. snowflake/cli/{api/project/schemas/entities → _plugins/streamlit}/streamlit_entity_model.py +5 -12
  42. snowflake/cli/_plugins/workspace/commands.py +83 -36
  43. snowflake/cli/_plugins/workspace/plugin_spec.py +1 -1
  44. snowflake/cli/api/commands/snow_typer.py +1 -1
  45. snowflake/cli/api/entities/common.py +3 -0
  46. snowflake/cli/api/entities/utils.py +0 -14
  47. snowflake/cli/api/errno.py +1 -0
  48. snowflake/cli/api/identifiers.py +4 -3
  49. snowflake/cli/api/project/definition_conversion.py +10 -9
  50. snowflake/cli/api/project/schemas/entities/common.py +17 -4
  51. snowflake/cli/api/project/schemas/entities/entities.py +13 -10
  52. snowflake/cli/api/project/schemas/project_definition.py +6 -6
  53. snowflake/cli/api/project/schemas/v1/__init__.py +0 -0
  54. snowflake/cli/api/project/schemas/{identifier_model.py → v1/identifier_model.py} +0 -7
  55. snowflake/cli/api/project/schemas/v1/native_app/__init__.py +0 -0
  56. snowflake/cli/api/project/schemas/{native_app → v1/native_app}/native_app.py +4 -4
  57. snowflake/cli/api/project/schemas/v1/snowpark/__init__.py +0 -0
  58. snowflake/cli/api/project/schemas/{snowpark → v1/snowpark}/callable.py +2 -2
  59. snowflake/cli/api/project/schemas/{snowpark → v1/snowpark}/snowpark.py +2 -2
  60. snowflake/cli/api/project/schemas/v1/streamlit/__init__.py +0 -0
  61. snowflake/cli/api/project/schemas/{streamlit → v1/streamlit}/streamlit.py +2 -1
  62. snowflake/cli/api/sql_execution.py +6 -15
  63. {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc3.dist-info}/METADATA +6 -6
  64. {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc3.dist-info}/RECORD +72 -67
  65. snowflake/cli/api/project/schemas/streamlit/__init__.py +0 -13
  66. /snowflake/cli/{api/project/schemas/native_app → _plugins/helpers}/__init__.py +0 -0
  67. /snowflake/cli/api/project/schemas/{native_app → v1/native_app}/application.py +0 -0
  68. /snowflake/cli/api/project/schemas/{native_app → v1/native_app}/package.py +0 -0
  69. /snowflake/cli/api/project/schemas/{native_app → v1/native_app}/path_mapping.py +0 -0
  70. /snowflake/cli/api/project/schemas/{snowpark → v1/snowpark}/argument.py +0 -0
  71. {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc3.dist-info}/WHEEL +0 -0
  72. {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc3.dist-info}/entry_points.txt +0 -0
  73. {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc3.dist-info}/licenses/LICENSE +0 -0
@@ -5,8 +5,15 @@ from textwrap import dedent
5
5
  from typing import Callable, List, Optional
6
6
 
7
7
  import typer
8
- from click import ClickException
9
- from snowflake.cli._plugins.nativeapp.artifacts import build_bundle
8
+ from click import BadOptionUsage, ClickException
9
+ from snowflake.cli._plugins.nativeapp.application_package_entity_model import (
10
+ ApplicationPackageEntityModel,
11
+ )
12
+ from snowflake.cli._plugins.nativeapp.artifacts import (
13
+ BundleMap,
14
+ build_bundle,
15
+ find_version_info_in_manifest_file,
16
+ )
10
17
  from snowflake.cli._plugins.nativeapp.bundle_context import BundleContext
11
18
  from snowflake.cli._plugins.nativeapp.codegen.compiler import NativeAppCompiler
12
19
  from snowflake.cli._plugins.nativeapp.constants import (
@@ -16,7 +23,9 @@ from snowflake.cli._plugins.nativeapp.constants import (
16
23
  INTERNAL_DISTRIBUTION,
17
24
  NAME_COL,
18
25
  OWNER_COL,
26
+ PATCH_COL,
19
27
  SPECIAL_COMMENT,
28
+ VERSION_COL,
20
29
  )
21
30
  from snowflake.cli._plugins.nativeapp.exceptions import (
22
31
  ApplicationPackageAlreadyExistsError,
@@ -24,17 +33,23 @@ from snowflake.cli._plugins.nativeapp.exceptions import (
24
33
  CouldNotDropApplicationPackageWithVersions,
25
34
  SetupScriptFailedValidation,
26
35
  )
36
+ from snowflake.cli._plugins.nativeapp.policy import (
37
+ AllowAlwaysPolicy,
38
+ AskAlwaysPolicy,
39
+ DenyAlwaysPolicy,
40
+ PolicyBase,
41
+ )
27
42
  from snowflake.cli._plugins.nativeapp.utils import (
28
43
  needs_confirmation,
29
44
  )
30
45
  from snowflake.cli._plugins.stage.diff import DiffResult
31
46
  from snowflake.cli._plugins.stage.manager import StageManager
32
47
  from snowflake.cli._plugins.workspace.action_context import ActionContext
48
+ from snowflake.cli.api.console import cli_console as cc
33
49
  from snowflake.cli.api.console.abc import AbstractConsole
34
50
  from snowflake.cli.api.entities.common import EntityBase, get_sql_executor
35
51
  from snowflake.cli.api.entities.utils import (
36
52
  drop_generic_object,
37
- ensure_correct_owner,
38
53
  execute_post_deploy_hooks,
39
54
  generic_sql_error_handler,
40
55
  render_script_templates,
@@ -45,17 +60,20 @@ from snowflake.cli.api.errno import (
45
60
  DOES_NOT_EXIST_OR_NOT_AUTHORIZED,
46
61
  )
47
62
  from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
48
- from snowflake.cli.api.project.schemas.entities.application_package_entity_model import (
49
- ApplicationPackageEntityModel,
50
- )
51
63
  from snowflake.cli.api.project.schemas.entities.common import PostDeployHook
52
- from snowflake.cli.api.project.schemas.native_app.path_mapping import PathMapping
53
- from snowflake.cli.api.project.util import extract_schema
64
+ from snowflake.cli.api.project.schemas.v1.native_app.path_mapping import PathMapping
65
+ from snowflake.cli.api.project.util import (
66
+ extract_schema,
67
+ identifier_to_show_like_pattern,
68
+ to_identifier,
69
+ unquote_identifier,
70
+ )
54
71
  from snowflake.cli.api.rendering.jinja import (
55
72
  get_basic_jinja_env,
56
73
  )
74
+ from snowflake.cli.api.utils.cursor import find_all_rows
57
75
  from snowflake.connector import ProgrammingError
58
- from snowflake.connector.cursor import DictCursor
76
+ from snowflake.connector.cursor import DictCursor, SnowflakeCursor
59
77
 
60
78
 
61
79
  class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
@@ -81,12 +99,22 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
81
99
  recursive: bool,
82
100
  paths: List[Path],
83
101
  validate: bool,
102
+ interactive: bool,
103
+ force: bool,
84
104
  stage_fqn: Optional[str] = None,
85
105
  *args,
86
106
  **kwargs,
87
107
  ):
88
108
  model = self._entity_model
89
109
  package_name = model.fqn.identifier
110
+
111
+ if force:
112
+ policy = AllowAlwaysPolicy()
113
+ elif interactive:
114
+ policy = AskAlwaysPolicy()
115
+ else:
116
+ policy = DenyAlwaysPolicy()
117
+
90
118
  return self.deploy(
91
119
  console=ctx.console,
92
120
  project_root=ctx.project_root,
@@ -94,6 +122,7 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
94
122
  bundle_root=Path(model.bundle_root),
95
123
  generated_root=Path(model.generated_root),
96
124
  artifacts=model.artifacts,
125
+ bundle_map=None,
97
126
  package_name=package_name,
98
127
  package_role=(model.meta and model.meta.role) or ctx.default_role,
99
128
  package_distribution=model.distribution,
@@ -108,6 +137,7 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
108
137
  ),
109
138
  post_deploy_hooks=model.meta and model.meta.post_deploy,
110
139
  package_scripts=[], # Package scripts are not supported in PDFv2
140
+ policy=policy,
111
141
  )
112
142
 
113
143
  def action_drop(self, ctx: ActionContext, force_drop: bool, *args, **kwargs):
@@ -125,7 +155,9 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
125
155
  force_drop=force_drop,
126
156
  )
127
157
 
128
- def action_validate(self, ctx: ActionContext, *args, **kwargs):
158
+ def action_validate(
159
+ self, ctx: ActionContext, interactive: bool, force: bool, *args, **kwargs
160
+ ):
129
161
  model = self._entity_model
130
162
  package_name = model.fqn.identifier
131
163
  stage_fqn = f"{package_name}.{model.stage}"
@@ -141,7 +173,9 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
141
173
  recursive=True,
142
174
  paths=[],
143
175
  validate=False,
144
- stage_fqn=model.scratch_stage,
176
+ stage_fqn=f"{package_name}.{model.scratch_stage}",
177
+ interactive=interactive,
178
+ force=force,
145
179
  )
146
180
 
147
181
  self.validate_setup_script(
@@ -150,11 +184,61 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
150
184
  package_role=package_role,
151
185
  stage_fqn=stage_fqn,
152
186
  use_scratch_stage=True,
153
- scratch_stage_fqn=model.scratch_stage,
187
+ scratch_stage_fqn=f"{package_name}.{model.scratch_stage}",
154
188
  deploy_to_scratch_stage_fn=deploy_to_scratch_stage_fn,
155
189
  )
156
190
  ctx.console.message("Setup script is valid")
157
191
 
192
+ def action_version_list(
193
+ self, ctx: ActionContext, *args, **kwargs
194
+ ) -> SnowflakeCursor:
195
+ model = self._entity_model
196
+ return self.version_list(
197
+ package_name=model.fqn.identifier,
198
+ package_role=(model.meta and model.meta.role) or ctx.default_role,
199
+ )
200
+
201
+ def action_version_create(
202
+ self,
203
+ ctx: ActionContext,
204
+ version: Optional[str],
205
+ patch: Optional[int],
206
+ skip_git_check: bool,
207
+ interactive: bool,
208
+ force: bool,
209
+ *args,
210
+ **kwargs,
211
+ ):
212
+ model = self._entity_model
213
+ package_name = model.fqn.identifier
214
+ return self.version_create(
215
+ console=ctx.console,
216
+ project_root=ctx.project_root,
217
+ deploy_root=Path(model.deploy_root),
218
+ bundle_root=Path(model.bundle_root),
219
+ generated_root=Path(model.generated_root),
220
+ artifacts=model.artifacts,
221
+ package_name=package_name,
222
+ package_role=(model.meta and model.meta.role) or ctx.default_role,
223
+ package_distribution=model.distribution,
224
+ prune=True,
225
+ recursive=True,
226
+ paths=None,
227
+ print_diff=True,
228
+ validate=True,
229
+ stage_fqn=f"{package_name}.{model.stage}",
230
+ package_warehouse=(
231
+ (model.meta and model.meta.warehouse) or ctx.default_warehouse
232
+ ),
233
+ post_deploy_hooks=model.meta and model.meta.post_deploy,
234
+ package_scripts=[], # Package scripts are not supported in PDFv2
235
+ version=version,
236
+ patch=patch,
237
+ skip_git_check=skip_git_check,
238
+ force=force,
239
+ interactive=interactive,
240
+ )
241
+
158
242
  @staticmethod
159
243
  def bundle(
160
244
  project_root: Path,
@@ -186,21 +270,23 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
186
270
  bundle_root: Path,
187
271
  generated_root: Path,
188
272
  artifacts: list[PathMapping],
273
+ bundle_map: BundleMap | None,
189
274
  package_name: str,
190
275
  package_role: str,
191
276
  package_distribution: str,
192
277
  package_warehouse: str | None,
193
278
  prune: bool,
194
279
  recursive: bool,
195
- paths: List[Path],
280
+ paths: List[Path] | None,
196
281
  print_diff: bool,
197
282
  validate: bool,
198
283
  stage_fqn: str,
199
284
  post_deploy_hooks: list[PostDeployHook] | None,
200
285
  package_scripts: List[str],
286
+ policy: PolicyBase,
201
287
  ) -> DiffResult:
202
- # 1. Create a bundle
203
- bundle_map = cls.bundle(
288
+ # 1. Create a bundle if one wasn't passed in
289
+ bundle_map = bundle_map or cls.bundle(
204
290
  project_root=project_root,
205
291
  deploy_root=deploy_root,
206
292
  bundle_root=bundle_root,
@@ -210,13 +296,17 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
210
296
  )
211
297
 
212
298
  # 2. Create an empty application package, if none exists
213
- cls.create_app_package(
214
- console=console,
215
- package_name=package_name,
216
- package_role=package_role,
217
- package_distribution=package_distribution,
218
- )
219
-
299
+ try:
300
+ cls.create_app_package(
301
+ console=console,
302
+ package_name=package_name,
303
+ package_role=package_role,
304
+ package_distribution=package_distribution,
305
+ )
306
+ except ApplicationPackageAlreadyExistsError as e:
307
+ cc.warning(e.message)
308
+ if not policy.should_proceed("Proceed with using this package?"):
309
+ raise typer.Abort() from e
220
310
  with get_sql_executor().use_role(package_role):
221
311
  if package_scripts:
222
312
  cls.apply_package_scripts(
@@ -266,6 +356,373 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
266
356
 
267
357
  return diff
268
358
 
359
+ @staticmethod
360
+ def version_list(package_name: str, package_role: str) -> SnowflakeCursor:
361
+ """
362
+ Get all existing versions, if defined, for an application package.
363
+ It executes a 'show versions in application package' query and returns all the results.
364
+ """
365
+ sql_executor = get_sql_executor()
366
+ with sql_executor.use_role(package_role):
367
+ show_obj_query = f"show versions in application package {package_name}"
368
+ show_obj_cursor = sql_executor.execute_query(show_obj_query)
369
+
370
+ if show_obj_cursor.rowcount is None:
371
+ raise SnowflakeSQLExecutionError(show_obj_query)
372
+
373
+ return show_obj_cursor
374
+
375
+ @classmethod
376
+ def version_create(
377
+ cls,
378
+ console: AbstractConsole,
379
+ project_root: Path,
380
+ deploy_root: Path,
381
+ bundle_root: Path,
382
+ generated_root: Path,
383
+ artifacts: list[PathMapping],
384
+ package_name: str,
385
+ package_role: str,
386
+ package_distribution: str,
387
+ package_warehouse: str | None,
388
+ prune: bool,
389
+ recursive: bool,
390
+ paths: List[Path] | None,
391
+ print_diff: bool,
392
+ validate: bool,
393
+ stage_fqn: str,
394
+ post_deploy_hooks: list[PostDeployHook] | None,
395
+ package_scripts: List[str],
396
+ version: Optional[str],
397
+ patch: Optional[int],
398
+ force: bool,
399
+ interactive: bool,
400
+ skip_git_check: bool,
401
+ ):
402
+ """
403
+ Perform bundle, application package creation, stage upload, version and/or patch to an application package.
404
+ """
405
+ is_interactive = False
406
+ if force:
407
+ policy = AllowAlwaysPolicy()
408
+ elif interactive:
409
+ is_interactive = True
410
+ policy = AskAlwaysPolicy()
411
+ else:
412
+ policy = DenyAlwaysPolicy()
413
+
414
+ if skip_git_check:
415
+ git_policy = DenyAlwaysPolicy()
416
+ else:
417
+ git_policy = AllowAlwaysPolicy()
418
+
419
+ # Make sure version is not None before proceeding any further.
420
+ # This will raise an exception if version information is not found. Patch can be None.
421
+ bundle_map = None
422
+ if not version:
423
+ console.message(
424
+ dedent(
425
+ f"""\
426
+ Version was not provided through the Snowflake CLI. Checking version in the manifest.yml instead.
427
+ This step will bundle your app artifacts to determine the location of the manifest.yml file.
428
+ """
429
+ )
430
+ )
431
+ bundle_map = cls.bundle(
432
+ project_root=project_root,
433
+ deploy_root=deploy_root,
434
+ bundle_root=bundle_root,
435
+ generated_root=generated_root,
436
+ artifacts=artifacts,
437
+ package_name=package_name,
438
+ )
439
+ version, patch = find_version_info_in_manifest_file(deploy_root)
440
+ if not version:
441
+ raise ClickException(
442
+ "Manifest.yml file does not contain a value for the version field."
443
+ )
444
+
445
+ # Check if --patch needs to throw a bad option error, either if application package does not exist or if version does not exist
446
+ if patch is not None:
447
+ try:
448
+ if not cls.get_existing_version_info(
449
+ version, package_name, package_role
450
+ ):
451
+ raise BadOptionUsage(
452
+ option_name="patch",
453
+ message=f"Cannot create a custom patch when version {version} is not defined in the application package {package_name}. Try again without using --patch.",
454
+ )
455
+ except ApplicationPackageDoesNotExistError as app_err:
456
+ raise BadOptionUsage(
457
+ option_name="patch",
458
+ message=f"Cannot create a custom patch when application package {package_name} does not exist. Try again without using --patch.",
459
+ )
460
+
461
+ if git_policy.should_proceed():
462
+ cls.check_index_changes_in_git_repo(
463
+ console=console,
464
+ project_root=project_root,
465
+ policy=policy,
466
+ is_interactive=is_interactive,
467
+ )
468
+
469
+ cls.deploy(
470
+ console=console,
471
+ project_root=project_root,
472
+ deploy_root=deploy_root,
473
+ bundle_root=bundle_root,
474
+ generated_root=generated_root,
475
+ artifacts=artifacts,
476
+ bundle_map=bundle_map,
477
+ package_name=package_name,
478
+ package_role=package_role,
479
+ package_distribution=package_distribution,
480
+ prune=prune,
481
+ recursive=recursive,
482
+ paths=paths,
483
+ print_diff=print_diff,
484
+ validate=validate,
485
+ stage_fqn=stage_fqn,
486
+ package_warehouse=package_warehouse,
487
+ post_deploy_hooks=post_deploy_hooks,
488
+ package_scripts=package_scripts,
489
+ policy=policy,
490
+ )
491
+
492
+ # Warn if the version exists in a release directive(s)
493
+ existing_release_directives = (
494
+ cls.get_existing_release_directive_info_for_version(
495
+ package_name, package_role, version
496
+ )
497
+ )
498
+
499
+ if existing_release_directives:
500
+ release_directive_names = ", ".join(
501
+ row["name"] for row in existing_release_directives
502
+ )
503
+ console.warning(
504
+ dedent(
505
+ f"""\
506
+ Version {version} already defined in application package {package_name} and in release directive(s): {release_directive_names}.
507
+ """
508
+ )
509
+ )
510
+
511
+ user_prompt = (
512
+ f"Are you sure you want to create a new patch for version {version} in application "
513
+ f"package {package_name}? Once added, this operation cannot be undone."
514
+ )
515
+ if not policy.should_proceed(user_prompt):
516
+ if is_interactive:
517
+ console.message("Not creating a new patch.")
518
+ raise typer.Exit(0)
519
+ else:
520
+ console.message(
521
+ "Cannot create a new patch non-interactively without --force."
522
+ )
523
+ raise typer.Exit(1)
524
+
525
+ # Define a new version in the application package
526
+ if not cls.get_existing_version_info(version, package_name, package_role):
527
+ cls.add_new_version(
528
+ console=console,
529
+ package_name=package_name,
530
+ package_role=package_role,
531
+ stage_fqn=stage_fqn,
532
+ version=version,
533
+ )
534
+ return # A new version created automatically has patch 0, we do not need to further increment the patch.
535
+
536
+ # Add a new patch to an existing (old) version
537
+ cls.add_new_patch_to_version(
538
+ console=console,
539
+ package_name=package_name,
540
+ package_role=package_role,
541
+ stage_fqn=stage_fqn,
542
+ version=version,
543
+ patch=patch,
544
+ )
545
+
546
+ @staticmethod
547
+ def get_existing_version_info(
548
+ version: str,
549
+ package_name: str,
550
+ package_role: str,
551
+ ) -> Optional[dict]:
552
+ """
553
+ Get the latest patch on an existing version by name in the application package.
554
+ Executes 'show versions like ... in application package' query and returns
555
+ the latest patch in the version as a single row, if one exists. Otherwise,
556
+ returns None.
557
+ """
558
+ sql_executor = get_sql_executor()
559
+ with sql_executor.use_role(package_role):
560
+ try:
561
+ query = f"show versions like {identifier_to_show_like_pattern(version)} in application package {package_name}"
562
+ cursor = sql_executor.execute_query(query, cursor_class=DictCursor)
563
+
564
+ if cursor.rowcount is None:
565
+ raise SnowflakeSQLExecutionError(query)
566
+
567
+ matching_rows = find_all_rows(
568
+ cursor, lambda row: row[VERSION_COL] == unquote_identifier(version)
569
+ )
570
+
571
+ if not matching_rows:
572
+ return None
573
+
574
+ return max(matching_rows, key=lambda row: row[PATCH_COL])
575
+
576
+ except ProgrammingError as err:
577
+ if err.msg.__contains__("does not exist or not authorized"):
578
+ raise ApplicationPackageDoesNotExistError(package_name)
579
+ else:
580
+ generic_sql_error_handler(err=err, role=package_role)
581
+ return None
582
+
583
+ @classmethod
584
+ def get_existing_release_directive_info_for_version(
585
+ cls,
586
+ package_name: str,
587
+ package_role: str,
588
+ version: str,
589
+ ) -> List[dict]:
590
+ """
591
+ Get all existing release directives, if present, set on the version defined in an application package.
592
+ It executes a 'show release directives in application package' query and returns the filtered results, if they exist.
593
+ """
594
+ sql_executor = get_sql_executor()
595
+ with sql_executor.use_role(package_role):
596
+ show_obj_query = (
597
+ f"show release directives in application package {package_name}"
598
+ )
599
+ show_obj_cursor = sql_executor.execute_query(
600
+ show_obj_query, cursor_class=DictCursor
601
+ )
602
+
603
+ if show_obj_cursor.rowcount is None:
604
+ raise SnowflakeSQLExecutionError(show_obj_query)
605
+
606
+ show_obj_rows = find_all_rows(
607
+ show_obj_cursor,
608
+ lambda row: row[VERSION_COL] == unquote_identifier(version),
609
+ )
610
+
611
+ return show_obj_rows
612
+
613
+ @classmethod
614
+ def add_new_version(
615
+ cls,
616
+ console: AbstractConsole,
617
+ package_name: str,
618
+ package_role: str,
619
+ stage_fqn: str,
620
+ version: str,
621
+ ) -> None:
622
+ """
623
+ Defines a new version in an existing application package.
624
+ """
625
+ # Make the version a valid identifier, adding quotes if necessary
626
+ version = to_identifier(version)
627
+ sql_executor = get_sql_executor()
628
+ with sql_executor.use_role(package_role):
629
+ console.step(
630
+ f"Defining a new version {version} in application package {package_name}"
631
+ )
632
+ add_version_query = dedent(
633
+ f"""\
634
+ alter application package {package_name}
635
+ add version {version}
636
+ using @{stage_fqn}
637
+ """
638
+ )
639
+ sql_executor.execute_query(add_version_query, cursor_class=DictCursor)
640
+ console.message(
641
+ f"Version {version} created for application package {package_name}."
642
+ )
643
+
644
+ @classmethod
645
+ def add_new_patch_to_version(
646
+ cls,
647
+ console: AbstractConsole,
648
+ package_name: str,
649
+ package_role: str,
650
+ stage_fqn: str,
651
+ version: str,
652
+ patch: Optional[int] = None,
653
+ ):
654
+ """
655
+ Add a new patch, optionally a custom one, to an existing version in an application package.
656
+ """
657
+ # Make the version a valid identifier, adding quotes if necessary
658
+ version = to_identifier(version)
659
+ sql_executor = get_sql_executor()
660
+ with sql_executor.use_role(package_role):
661
+ console.step(
662
+ f"Adding new patch to version {version} defined in application package {package_name}"
663
+ )
664
+ add_version_query = dedent(
665
+ f"""\
666
+ alter application package {package_name}
667
+ add patch {patch if patch else ""} for version {version}
668
+ using @{stage_fqn}
669
+ """
670
+ )
671
+ result_cursor = sql_executor.execute_query(
672
+ add_version_query, cursor_class=DictCursor
673
+ )
674
+
675
+ show_row = result_cursor.fetchall()[0]
676
+ new_patch = show_row["patch"]
677
+ console.message(
678
+ f"Patch {new_patch} created for version {version} defined in application package {package_name}."
679
+ )
680
+
681
+ @classmethod
682
+ def check_index_changes_in_git_repo(
683
+ cls,
684
+ console: AbstractConsole,
685
+ project_root: Path,
686
+ policy: PolicyBase,
687
+ is_interactive: bool,
688
+ ) -> None:
689
+ """
690
+ Checks if the project root, i.e. the native apps project is a git repository. If it is a git repository,
691
+ it also checks if there any local changes to the directory that may not be on the application package stage.
692
+ """
693
+
694
+ from git import Repo
695
+ from git.exc import InvalidGitRepositoryError
696
+
697
+ try:
698
+ repo = Repo(project_root, search_parent_directories=True)
699
+ assert repo.git_dir is not None
700
+
701
+ # Check if the repo has any changes, including untracked files
702
+ if repo.is_dirty(untracked_files=True):
703
+ console.warning(
704
+ "Changes detected in the git repository. "
705
+ "(Rerun your command with --skip-git-check flag to ignore this check)"
706
+ )
707
+ repo.git.execute(["git", "status"])
708
+
709
+ user_prompt = (
710
+ "You have local changes in this repository that are not part of a previous commit. "
711
+ "Do you still want to continue?"
712
+ )
713
+ if not policy.should_proceed(user_prompt):
714
+ if is_interactive:
715
+ console.message("Not creating a new version.")
716
+ raise typer.Exit(0)
717
+ else:
718
+ console.message(
719
+ "Cannot create a new version non-interactively without --force."
720
+ )
721
+ raise typer.Exit(1)
722
+
723
+ except InvalidGitRepositoryError:
724
+ pass # not a git repository, which is acceptable
725
+
269
726
  @staticmethod
270
727
  def get_existing_app_pkg_info(
271
728
  package_name: str,
@@ -424,11 +881,6 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
424
881
  )
425
882
 
426
883
  if show_obj_row:
427
- # 1. Check for the right owner role
428
- ensure_correct_owner(
429
- row=show_obj_row, role=package_role, obj_name=package_name
430
- )
431
-
432
884
  # 2. Check distribution of the existing application package
433
885
  actual_distribution = cls.get_app_pkg_distribution_in_snowflake(
434
886
  package_name=package_name,
@@ -581,11 +1033,8 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
581
1033
  )
582
1034
  return
583
1035
 
584
- # 2. Check for the right owner
585
- ensure_correct_owner(row=show_obj_row, role=package_role, obj_name=package_name)
586
-
587
1036
  with sql_executor.use_role(package_role):
588
- # 3. Check for versions in the application package
1037
+ # 2. Check for versions in the application package
589
1038
  show_versions_query = f"show versions in application package {package_name}"
590
1039
  show_versions_cursor = sql_executor.execute_query(
591
1040
  show_versions_query, cursor_class=DictCursor
@@ -600,7 +1049,7 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
600
1049
  "Drop versions first, or use --force to override."
601
1050
  )
602
1051
 
603
- # 4. Check distribution of the existing application package
1052
+ # 3. Check distribution of the existing application package
604
1053
  actual_distribution = cls.get_app_pkg_distribution_in_snowflake(
605
1054
  package_name=package_name,
606
1055
  package_role=package_role,
@@ -615,7 +1064,7 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
615
1064
  f"Dropping application package {package_name} with distribution '{actual_distribution}'."
616
1065
  )
617
1066
 
618
- # 5. If distribution is internal, check if created by the Snowflake CLI
1067
+ # 4. If distribution is internal, check if created by the Snowflake CLI
619
1068
  row_comment = show_obj_row[COMMENT_COL]
620
1069
  if actual_distribution == INTERNAL_DISTRIBUTION:
621
1070
  if row_comment in ALLOWED_SPECIAL_COMMENTS:
@@ -19,14 +19,14 @@ from typing import List, Literal, Optional, Union
19
19
  from pydantic import Field, field_validator
20
20
  from snowflake.cli.api.project.schemas.entities.common import (
21
21
  EntityModelBase,
22
+ Identifier,
22
23
  )
23
- from snowflake.cli.api.project.schemas.identifier_model import Identifier
24
- from snowflake.cli.api.project.schemas.native_app.package import DistributionOptions
25
- from snowflake.cli.api.project.schemas.native_app.path_mapping import PathMapping
26
24
  from snowflake.cli.api.project.schemas.updatable_model import (
27
25
  DiscriminatorField,
28
26
  IdentifierField,
29
27
  )
28
+ from snowflake.cli.api.project.schemas.v1.native_app.package import DistributionOptions
29
+ from snowflake.cli.api.project.schemas.v1.native_app.path_mapping import PathMapping
30
30
  from snowflake.cli.api.project.util import append_test_resource_suffix
31
31
 
32
32