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
@@ -14,4 +14,4 @@
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
- VERSION = "3.0.0rc2"
17
+ VERSION = "3.0.0rc3"
@@ -15,6 +15,7 @@
15
15
  from snowflake.cli._plugins.connection import plugin_spec as connection_plugin_spec
16
16
  from snowflake.cli._plugins.cortex import plugin_spec as cortex_plugin_spec
17
17
  from snowflake.cli._plugins.git import plugin_spec as git_plugin_spec
18
+ from snowflake.cli._plugins.helpers import plugin_spec as migrate_plugin_spec
18
19
  from snowflake.cli._plugins.init import plugin_spec as init_plugin_spec
19
20
  from snowflake.cli._plugins.nativeapp import plugin_spec as nativeapp_plugin_spec
20
21
  from snowflake.cli._plugins.notebook import plugin_spec as notebook_plugin_spec
@@ -31,6 +32,7 @@ from snowflake.cli._plugins.workspace import plugin_spec as workspace_plugin_spe
31
32
  def get_builtin_plugin_name_to_plugin_spec():
32
33
  plugin_specs = {
33
34
  "connection": connection_plugin_spec,
35
+ "helpers": migrate_plugin_spec,
34
36
  "spcs": spcs_plugin_spec,
35
37
  "nativeapp": nativeapp_plugin_spec,
36
38
  "object": object_plugin_spec,
@@ -0,0 +1,9 @@
1
+ class SecretType:
2
+ def __init__(self, value):
3
+ self.value = value
4
+
5
+ def __repr__(self):
6
+ return "SecretType(***)"
7
+
8
+ def __str___(self):
9
+ return "***"
@@ -24,6 +24,7 @@ from click.exceptions import ClickException
24
24
  from snowflake.cli._app.constants import (
25
25
  PARAM_APPLICATION_NAME,
26
26
  )
27
+ from snowflake.cli._app.secret import SecretType
27
28
  from snowflake.cli._app.telemetry import command_info
28
29
  from snowflake.cli.api.config import (
29
30
  get_connection_dict,
@@ -205,7 +206,7 @@ def _load_private_key(connection_parameters: Dict, private_key_var_name: str) ->
205
206
  connection_parameters[private_key_var_name]
206
207
  )
207
208
  private_key = _load_pem_to_der(private_key_pem)
208
- connection_parameters["private_key"] = private_key
209
+ connection_parameters["private_key"] = private_key.value
209
210
  del connection_parameters[private_key_var_name]
210
211
  else:
211
212
  raise ClickException(
@@ -217,10 +218,11 @@ def _load_private_key_from_parameters(
217
218
  connection_parameters: Dict, private_key_var_name: str
218
219
  ) -> None:
219
220
  if connection_parameters.get("authenticator") == "SNOWFLAKE_JWT":
220
- private_key_pem = connection_parameters[private_key_var_name]
221
- private_key_pem = private_key_pem.encode("utf-8")
221
+ private_key_pem = _load_pem_from_parameters(
222
+ connection_parameters[private_key_var_name]
223
+ )
222
224
  private_key = _load_pem_to_der(private_key_pem)
223
- connection_parameters["private_key"] = private_key
225
+ connection_parameters["private_key"] = private_key.value
224
226
  del connection_parameters[private_key_var_name]
225
227
  else:
226
228
  raise ClickException(
@@ -236,43 +238,49 @@ def _update_connection_application_name(connection_parameters: Dict):
236
238
  connection_parameters.update(connection_application_params)
237
239
 
238
240
 
239
- def _load_pem_from_file(private_key_file: str) -> bytes:
241
+ def _load_pem_from_file(private_key_file: str) -> SecretType:
240
242
  with SecurePath(private_key_file).open(
241
243
  "rb", read_file_limit_mb=DEFAULT_SIZE_LIMIT_MB
242
244
  ) as f:
243
- private_key_pem = f.read()
245
+ private_key_pem = SecretType(f.read())
244
246
  return private_key_pem
245
247
 
246
248
 
247
- def _load_pem_to_der(private_key_pem: bytes) -> bytes:
249
+ def _load_pem_from_parameters(private_key_raw: str) -> SecretType:
250
+ return SecretType(private_key_raw.encode("utf-8"))
251
+
252
+
253
+ def _load_pem_to_der(private_key_pem: SecretType) -> SecretType:
248
254
  """
249
255
  Given a private key file path (in PEM format), decode key data into DER
250
256
  format
251
257
  """
252
- private_key_passphrase = os.getenv("PRIVATE_KEY_PASSPHRASE", None)
258
+ private_key_passphrase = SecretType(os.getenv("PRIVATE_KEY_PASSPHRASE", None))
253
259
  if (
254
- private_key_pem.startswith(ENCRYPTED_PKCS8_PK_HEADER)
255
- and private_key_passphrase is None
260
+ private_key_pem.value.startswith(ENCRYPTED_PKCS8_PK_HEADER)
261
+ and private_key_passphrase.value is None
256
262
  ):
257
263
  raise ClickException(
258
264
  "Encrypted private key, you must provide the"
259
265
  "passphrase in the environment variable PRIVATE_KEY_PASSPHRASE"
260
266
  )
261
267
 
262
- if not private_key_pem.startswith(
268
+ if not private_key_pem.value.startswith(
263
269
  ENCRYPTED_PKCS8_PK_HEADER
264
- ) and not private_key_pem.startswith(UNENCRYPTED_PKCS8_PK_HEADER):
270
+ ) and not private_key_pem.value.startswith(UNENCRYPTED_PKCS8_PK_HEADER):
265
271
  raise ClickException(
266
272
  "Private key provided is not in PKCS#8 format. Please use correct format."
267
273
  )
268
274
 
269
- if private_key_pem.startswith(UNENCRYPTED_PKCS8_PK_HEADER):
270
- private_key_passphrase = None
275
+ if private_key_pem.value.startswith(UNENCRYPTED_PKCS8_PK_HEADER):
276
+ private_key_passphrase = SecretType(None)
271
277
 
272
278
  return prepare_private_key(private_key_pem, private_key_passphrase)
273
279
 
274
280
 
275
- def prepare_private_key(private_key_pem, private_key_passphrase=None):
281
+ def prepare_private_key(
282
+ private_key_pem: SecretType, private_key_passphrase: SecretType = SecretType(None)
283
+ ):
276
284
  from cryptography.hazmat.backends import default_backend
277
285
  from cryptography.hazmat.primitives.serialization import (
278
286
  Encoding,
@@ -281,17 +289,21 @@ def prepare_private_key(private_key_pem, private_key_passphrase=None):
281
289
  load_pem_private_key,
282
290
  )
283
291
 
284
- private_key = load_pem_private_key(
285
- private_key_pem,
286
- (
287
- str.encode(private_key_passphrase)
288
- if private_key_passphrase is not None
289
- else private_key_passphrase
290
- ),
291
- default_backend(),
292
+ private_key = SecretType(
293
+ load_pem_private_key(
294
+ private_key_pem.value,
295
+ (
296
+ str.encode(private_key_passphrase.value)
297
+ if private_key_passphrase.value is not None
298
+ else private_key_passphrase.value
299
+ ),
300
+ default_backend(),
301
+ )
292
302
  )
293
- return private_key.private_bytes(
294
- encoding=Encoding.DER,
295
- format=PrivateFormat.PKCS8,
296
- encryption_algorithm=NoEncryption(),
303
+ return SecretType(
304
+ private_key.value.private_bytes(
305
+ encoding=Encoding.DER,
306
+ format=PrivateFormat.PKCS8,
307
+ encryption_algorithm=NoEncryption(),
308
+ )
297
309
  )
@@ -14,10 +14,11 @@
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
- from pathlib import Path
17
+ from pathlib import Path, PurePosixPath
18
18
  from textwrap import dedent
19
19
  from typing import List
20
20
 
21
+ from click import UsageError
21
22
  from snowflake.cli._plugins.stage.manager import (
22
23
  USER_STAGE_PREFIX,
23
24
  StageManager,
@@ -27,16 +28,22 @@ from snowflake.cli._plugins.stage.manager import (
27
28
  from snowflake.cli.api.identifiers import FQN
28
29
  from snowflake.connector.cursor import SnowflakeCursor
29
30
 
31
+ # Replace magic numbers with constants
32
+ OMIT_FIRST = slice(1, None)
33
+ OMIT_STAGE = slice(3, None)
34
+ OMIT_STAGE_IN_NEW_LIST_FILES = slice(2, None)
35
+ ONLY_STAGE = slice(3)
36
+
30
37
 
31
38
  class GitStagePathParts(StagePathParts):
32
39
  def __init__(self, stage_path: str):
33
40
  self.stage = GitManager.get_stage_from_path(stage_path)
34
- stage_path_parts = Path(stage_path).parts
41
+ stage_path_parts = GitManager.split_git_path(stage_path)
35
42
  git_repo_name = stage_path_parts[0].split(".")[-1]
36
43
  if git_repo_name.startswith("@"):
37
- git_repo_name = git_repo_name[1:]
44
+ git_repo_name = git_repo_name[OMIT_FIRST]
38
45
  self.stage_name = "/".join([git_repo_name, *stage_path_parts[1:3], ""])
39
- self.directory = "/".join(stage_path_parts[3:])
46
+ self.directory = "/".join(stage_path_parts[OMIT_STAGE])
40
47
  self.is_directory = True if stage_path.endswith("/") else False
41
48
 
42
49
  @property
@@ -45,7 +52,12 @@ class GitStagePathParts(StagePathParts):
45
52
 
46
53
  @classmethod
47
54
  def get_directory(cls, stage_path: str) -> str:
48
- return "/".join(Path(stage_path).parts[3:])
55
+ git_path_parts = GitManager.split_git_path(stage_path)
56
+ # New file list does not have a stage name at the beginning
57
+ if stage_path.startswith("/"):
58
+ return "/".join(git_path_parts[OMIT_STAGE_IN_NEW_LIST_FILES])
59
+ else:
60
+ return "/".join(git_path_parts[OMIT_STAGE])
49
61
 
50
62
  @property
51
63
  def full_path(self) -> str:
@@ -53,7 +65,7 @@ class GitStagePathParts(StagePathParts):
53
65
 
54
66
  def replace_stage_prefix(self, file_path: str) -> str:
55
67
  stage = Path(self.stage).parts[0]
56
- file_path_without_prefix = Path(file_path).parts[1:]
68
+ file_path_without_prefix = Path(file_path).parts[OMIT_FIRST]
57
69
  return f"{stage}/{'/'.join(file_path_without_prefix)}"
58
70
 
59
71
  def add_stage_prefix(self, file_path: str) -> str:
@@ -95,7 +107,8 @@ class GitManager(StageManager):
95
107
  Returns stage name from potential path on stage. For example
96
108
  repo/branches/main/foo/bar -> repo/branches/main/
97
109
  """
98
- return f"{'/'.join(Path(path).parts[0:3])}/"
110
+ path_parts = GitManager.split_git_path(path)
111
+ return f"{'/'.join(path_parts[ONLY_STAGE])}/"
99
112
 
100
113
  @staticmethod
101
114
  def _stage_path_part_factory(stage_path: str) -> StagePathParts:
@@ -103,3 +116,36 @@ class GitManager(StageManager):
103
116
  if stage_path.startswith(USER_STAGE_PREFIX):
104
117
  return UserStagePathParts(stage_path)
105
118
  return GitStagePathParts(stage_path)
119
+
120
+ @staticmethod
121
+ def split_git_path(path: str):
122
+ # Check if path contains quotes and split it accordingly
123
+ if '/"' in path and '"/' in path:
124
+ if path.count('"') > 2:
125
+ raise UsageError(
126
+ f'Invalid string {path}, too much " in path, expected 2.'
127
+ )
128
+
129
+ path_parts = path.split('"')
130
+ before_quoted_part = GitManager._split_path_without_empty_parts(
131
+ path_parts[0]
132
+ )
133
+
134
+ if path_parts[2] == "/":
135
+ after_quoted_part = []
136
+ else:
137
+ after_quoted_part = GitManager._split_path_without_empty_parts(
138
+ path_parts[2]
139
+ )
140
+
141
+ return [
142
+ *before_quoted_part,
143
+ f'"{path_parts[1]}"',
144
+ *after_quoted_part,
145
+ ]
146
+ else:
147
+ return GitManager._split_path_without_empty_parts(path)
148
+
149
+ @staticmethod
150
+ def _split_path_without_empty_parts(path: str):
151
+ return [e for e in PurePosixPath(path).parts if e != "/"]
@@ -0,0 +1,57 @@
1
+ # Copyright (c) 2024 Snowflake Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ import typer
18
+ import yaml
19
+ from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
20
+ from snowflake.cli.api.output.types import MessageResult
21
+ from snowflake.cli.api.project.definition_conversion import (
22
+ convert_project_definition_to_v2,
23
+ )
24
+ from snowflake.cli.api.project.definition_manager import DefinitionManager
25
+ from snowflake.cli.api.secure_path import SecurePath
26
+
27
+ app = SnowTyperFactory(
28
+ name="helpers",
29
+ help="Helper commands.",
30
+ )
31
+
32
+
33
+ @app.command()
34
+ def v1_to_v2(
35
+ accept_templates: bool = typer.Option(
36
+ False, "-t", "--accept-templates", help="Allows the migration of templates."
37
+ ),
38
+ **options,
39
+ ):
40
+ """Migrates the Snowpark, Streamlit, and Native App project definition files from V1 to V2."""
41
+ manager = DefinitionManager()
42
+ pd = manager.unrendered_project_definition
43
+
44
+ if pd.meets_version_requirement("2"):
45
+ return MessageResult("Project definition is already at version 2.")
46
+
47
+ pd_v2 = convert_project_definition_to_v2(manager.project_root, pd, accept_templates)
48
+
49
+ SecurePath("snowflake.yml").rename("snowflake_V1.yml")
50
+ with open("snowflake.yml", "w") as file:
51
+ yaml.dump(
52
+ pd_v2.model_dump(
53
+ exclude_unset=True, exclude_none=True, mode="json", by_alias=True
54
+ ),
55
+ file,
56
+ )
57
+ return MessageResult("Project definition migrated to version 2.")
@@ -11,3 +11,20 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
+
15
+ from snowflake.cli._plugins.helpers import commands
16
+ from snowflake.cli.api.plugins.command import (
17
+ SNOWCLI_ROOT_COMMAND_PATH,
18
+ CommandSpec,
19
+ CommandType,
20
+ plugin_hook_impl,
21
+ )
22
+
23
+
24
+ @plugin_hook_impl
25
+ def command_spec():
26
+ return CommandSpec(
27
+ parent_command_path=SNOWCLI_ROOT_COMMAND_PATH,
28
+ command_type=CommandType.COMMAND_GROUP,
29
+ typer_instance=commands.app.create_instance(),
30
+ )
@@ -5,6 +5,15 @@ from typing import Callable, List, Optional, TypedDict
5
5
 
6
6
  import typer
7
7
  from click import ClickException, UsageError
8
+ from snowflake.cli._plugins.nativeapp.application_entity_model import (
9
+ ApplicationEntityModel,
10
+ )
11
+ from snowflake.cli._plugins.nativeapp.application_package_entity import (
12
+ ApplicationPackageEntity,
13
+ )
14
+ from snowflake.cli._plugins.nativeapp.application_package_entity_model import (
15
+ ApplicationPackageEntityModel,
16
+ )
8
17
  from snowflake.cli._plugins.nativeapp.common_flags import (
9
18
  ForceOption,
10
19
  InteractiveOption,
@@ -15,9 +24,7 @@ from snowflake.cli._plugins.nativeapp.constants import (
15
24
  COMMENT_COL,
16
25
  NAME_COL,
17
26
  OWNER_COL,
18
- PATCH_COL,
19
27
  SPECIAL_COMMENT,
20
- VERSION_COL,
21
28
  )
22
29
  from snowflake.cli._plugins.nativeapp.exceptions import (
23
30
  ApplicationPackageDoesNotExistError,
@@ -36,13 +43,9 @@ from snowflake.cli._plugins.nativeapp.utils import (
36
43
  )
37
44
  from snowflake.cli._plugins.workspace.action_context import ActionContext
38
45
  from snowflake.cli.api.console.abc import AbstractConsole
39
- from snowflake.cli.api.entities.application_package_entity import (
40
- ApplicationPackageEntity,
41
- )
42
46
  from snowflake.cli.api.entities.common import EntityBase, get_sql_executor
43
47
  from snowflake.cli.api.entities.utils import (
44
48
  drop_generic_object,
45
- ensure_correct_owner,
46
49
  execute_post_deploy_hooks,
47
50
  generic_sql_error_handler,
48
51
  print_messages,
@@ -54,22 +57,11 @@ from snowflake.cli.api.errno import (
54
57
  NOT_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
55
58
  ONLY_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
56
59
  )
57
- from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
58
- from snowflake.cli.api.project.schemas.entities.application_entity_model import (
59
- ApplicationEntityModel,
60
- )
61
- from snowflake.cli.api.project.schemas.entities.application_package_entity_model import (
62
- ApplicationPackageEntityModel,
63
- )
64
60
  from snowflake.cli.api.project.schemas.entities.common import PostDeployHook
65
61
  from snowflake.cli.api.project.util import (
66
62
  extract_schema,
67
- identifier_to_show_like_pattern,
68
- unquote_identifier,
69
63
  )
70
- from snowflake.cli.api.utils.cursor import find_all_rows
71
64
  from snowflake.connector import ProgrammingError
72
- from snowflake.connector.cursor import DictCursor
73
65
 
74
66
  # Reasons why an `alter application ... upgrade` might fail
75
67
  UPGRADE_RESTRICTION_CODES = {
@@ -108,9 +100,9 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
108
100
  app_name = model.fqn.identifier
109
101
  debug_mode = model.debug
110
102
  if model.meta:
111
- app_role = getattr(model.meta, "role", ctx.default_role)
112
- app_warehouse = getattr(model.meta, "warehouse", ctx.default_warehouse)
113
- post_deploy_hooks = getattr(model.meta, "post_deploy", None)
103
+ app_role = model.meta.role or ctx.default_role
104
+ app_warehouse = model.meta.warehouse or ctx.default_warehouse
105
+ post_deploy_hooks = model.meta.post_deploy
114
106
  else:
115
107
  app_role = ctx.default_role
116
108
  app_warehouse = ctx.default_warehouse
@@ -147,6 +139,8 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
147
139
  paths=[],
148
140
  validate=validate,
149
141
  stage_fqn=stage_fqn,
142
+ interactive=interactive,
143
+ force=force,
150
144
  )
151
145
 
152
146
  self.deploy(
@@ -221,10 +215,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
221
215
  )
222
216
  return
223
217
 
224
- # 2. Check for the right owner
225
- ensure_correct_owner(row=show_obj_row, role=app_role, obj_name=app_name)
226
-
227
- # 3. Check if created by the Snowflake CLI
218
+ # 2. Check if created by the Snowflake CLI
228
219
  row_comment = show_obj_row[COMMENT_COL]
229
220
  if row_comment not in ALLOWED_SPECIAL_COMMENTS and needs_confirmation(
230
221
  needs_confirm, auto_yes
@@ -251,7 +242,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
251
242
  # leave behind an orphan app when we get to dropping the package
252
243
  raise typer.Abort()
253
244
 
254
- # 4. Check for application objects owned by the application
245
+ # 3. Check for application objects owned by the application
255
246
  # This query will fail if the application package has already been dropped, so handle this case gracefully
256
247
  has_objects_to_drop = False
257
248
  message_prefix = ""
@@ -329,7 +320,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
329
320
  # If there's nothing to drop, set cascade to an explicit False value
330
321
  cascade = False
331
322
 
332
- # 5. All validations have passed, drop object
323
+ # 4. All validations have passed, drop object
333
324
  drop_generic_object(
334
325
  console=console,
335
326
  object_type="application",
@@ -425,7 +416,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
425
416
  # versioned dev
426
417
  if version:
427
418
  try:
428
- version_exists = cls.get_existing_version_info(
419
+ version_exists = ApplicationPackageEntity.get_existing_version_info(
429
420
  version=version,
430
421
  package_name=package_name,
431
422
  package_role=package_role,
@@ -658,40 +649,3 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
658
649
  return sql_executor.show_specific_object(
659
650
  "applications", app_name, name_col=NAME_COL
660
651
  )
661
-
662
- @staticmethod
663
- def get_existing_version_info(
664
- version: str,
665
- package_name: str,
666
- package_role: str,
667
- ) -> Optional[dict]:
668
- """
669
- Get the latest patch on an existing version by name in the application package.
670
- Executes 'show versions like ... in application package' query and returns
671
- the latest patch in the version as a single row, if one exists. Otherwise,
672
- returns None.
673
- """
674
- sql_executor = get_sql_executor()
675
- with sql_executor.use_role(package_role):
676
- try:
677
- query = f"show versions like {identifier_to_show_like_pattern(version)} in application package {package_name}"
678
- cursor = sql_executor.execute_query(query, cursor_class=DictCursor)
679
-
680
- if cursor.rowcount is None:
681
- raise SnowflakeSQLExecutionError(query)
682
-
683
- matching_rows = find_all_rows(
684
- cursor, lambda row: row[VERSION_COL] == unquote_identifier(version)
685
- )
686
-
687
- if not matching_rows:
688
- return None
689
-
690
- return max(matching_rows, key=lambda row: row[PATCH_COL])
691
-
692
- except ProgrammingError as err:
693
- if err.msg.__contains__("does not exist or not authorized"):
694
- raise ApplicationPackageDoesNotExistError(package_name)
695
- else:
696
- generic_sql_error_handler(err=err, role=package_role)
697
- return None
@@ -17,14 +17,14 @@ from __future__ import annotations
17
17
  from typing import Literal, Optional
18
18
 
19
19
  from pydantic import Field, field_validator
20
- from snowflake.cli.api.project.schemas.entities.application_package_entity_model import (
20
+ from snowflake.cli._plugins.nativeapp.application_package_entity_model import (
21
21
  ApplicationPackageEntityModel,
22
22
  )
23
23
  from snowflake.cli.api.project.schemas.entities.common import (
24
24
  EntityModelBase,
25
+ Identifier,
25
26
  TargetField,
26
27
  )
27
- from snowflake.cli.api.project.schemas.identifier_model import Identifier
28
28
  from snowflake.cli.api.project.schemas.updatable_model import (
29
29
  DiscriminatorField,
30
30
  )