snowflake-cli-labs 3.0.0rc1__py3-none-any.whl → 3.0.0rc2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/_app/cli_app.py +10 -1
  3. snowflake/cli/_app/snow_connector.py +76 -29
  4. snowflake/cli/_app/telemetry.py +8 -4
  5. snowflake/cli/_app/version_check.py +74 -0
  6. snowflake/cli/_plugins/git/commands.py +55 -14
  7. snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +2 -5
  8. snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +49 -31
  9. snowflake/cli/_plugins/nativeapp/manager.py +46 -87
  10. snowflake/cli/_plugins/nativeapp/run_processor.py +56 -260
  11. snowflake/cli/_plugins/nativeapp/same_account_install_method.py +74 -0
  12. snowflake/cli/_plugins/nativeapp/teardown_processor.py +9 -152
  13. snowflake/cli/_plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +91 -17
  14. snowflake/cli/_plugins/snowpark/commands.py +1 -1
  15. snowflake/cli/_plugins/snowpark/models.py +2 -1
  16. snowflake/cli/_plugins/streamlit/commands.py +1 -1
  17. snowflake/cli/_plugins/streamlit/manager.py +9 -0
  18. snowflake/cli/_plugins/workspace/action_context.py +2 -1
  19. snowflake/cli/_plugins/workspace/commands.py +48 -16
  20. snowflake/cli/_plugins/workspace/manager.py +1 -0
  21. snowflake/cli/api/cli_global_context.py +136 -313
  22. snowflake/cli/api/commands/flags.py +76 -91
  23. snowflake/cli/api/commands/snow_typer.py +6 -4
  24. snowflake/cli/api/config.py +1 -1
  25. snowflake/cli/api/connections.py +214 -0
  26. snowflake/cli/api/console/abc.py +4 -2
  27. snowflake/cli/api/entities/application_entity.py +687 -2
  28. snowflake/cli/api/entities/application_package_entity.py +151 -46
  29. snowflake/cli/api/entities/common.py +1 -0
  30. snowflake/cli/api/entities/utils.py +41 -17
  31. snowflake/cli/api/identifiers.py +3 -0
  32. snowflake/cli/api/project/definition.py +11 -0
  33. snowflake/cli/api/project/definition_conversion.py +171 -13
  34. snowflake/cli/api/project/schemas/entities/common.py +0 -12
  35. snowflake/cli/api/project/schemas/identifier_model.py +2 -2
  36. snowflake/cli/api/project/schemas/project_definition.py +101 -39
  37. snowflake/cli/api/rendering/project_definition_templates.py +4 -0
  38. snowflake/cli/api/rendering/sql_templates.py +7 -0
  39. snowflake/cli/api/utils/definition_rendering.py +3 -1
  40. {snowflake_cli_labs-3.0.0rc1.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/METADATA +6 -6
  41. {snowflake_cli_labs-3.0.0rc1.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/RECORD +44 -42
  42. snowflake/cli/api/commands/typer_pre_execute.py +0 -26
  43. {snowflake_cli_labs-3.0.0rc1.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/WHEEL +0 -0
  44. {snowflake_cli_labs-3.0.0rc1.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/entry_points.txt +0 -0
  45. {snowflake_cli_labs-3.0.0rc1.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/licenses/LICENSE +0 -0
@@ -16,24 +16,15 @@ from __future__ import annotations
16
16
 
17
17
  import time
18
18
  from abc import ABC, abstractmethod
19
- from contextlib import contextmanager
20
19
  from datetime import datetime
21
20
  from functools import cached_property
22
21
  from pathlib import Path
23
22
  from textwrap import dedent
24
- from typing import Generator, List, Optional, TypedDict
23
+ from typing import Generator, List, Optional
25
24
 
26
- from click import ClickException
27
25
  from snowflake.cli._plugins.connection.util import make_snowsight_url
28
26
  from snowflake.cli._plugins.nativeapp.artifacts import (
29
27
  BundleMap,
30
- build_bundle,
31
- )
32
- from snowflake.cli._plugins.nativeapp.codegen.compiler import (
33
- NativeAppCompiler,
34
- )
35
- from snowflake.cli._plugins.nativeapp.constants import (
36
- NAME_COL,
37
28
  )
38
29
  from snowflake.cli._plugins.nativeapp.exceptions import (
39
30
  NoEventTableForAccount,
@@ -45,6 +36,10 @@ from snowflake.cli._plugins.stage.diff import (
45
36
  DiffResult,
46
37
  )
47
38
  from snowflake.cli.api.console import cli_console as cc
39
+ from snowflake.cli.api.entities.application_entity import (
40
+ ApplicationEntity,
41
+ ApplicationOwnedObject,
42
+ )
48
43
  from snowflake.cli.api.entities.application_package_entity import (
49
44
  ApplicationPackageEntity,
50
45
  )
@@ -63,8 +58,6 @@ from snowflake.cli.api.project.util import (
63
58
  from snowflake.cli.api.sql_execution import SqlExecutionMixin
64
59
  from snowflake.connector import DictCursor, ProgrammingError
65
60
 
66
- ApplicationOwnedObject = TypedDict("ApplicationOwnedObject", {"name": str, "type": str})
67
-
68
61
 
69
62
  class NativeAppCommandProcessor(ABC):
70
63
  @abstractmethod
@@ -141,20 +134,8 @@ class NativeAppManager(SqlExecutionMixin):
141
134
  def application_warehouse(self) -> Optional[str]:
142
135
  return self.na_project.application_warehouse
143
136
 
144
- @contextmanager
145
137
  def use_application_warehouse(self):
146
- if self.application_warehouse:
147
- with self.use_warehouse(self.application_warehouse):
148
- yield
149
- else:
150
- raise ClickException(
151
- dedent(
152
- f"""\
153
- Application warehouse cannot be empty.
154
- Please provide a value for it in your connection information or your project definition file.
155
- """
156
- )
157
- )
138
+ return ApplicationEntity.use_application_warehouse(self.application_warehouse)
158
139
 
159
140
  @property
160
141
  def project_identifier(self) -> str:
@@ -219,10 +200,14 @@ class NativeAppManager(SqlExecutionMixin):
219
200
  """
220
201
  Populates the local deploy root from artifact sources.
221
202
  """
222
- bundle_map = build_bundle(self.project_root, self.deploy_root, self.artifacts)
223
- compiler = NativeAppCompiler(self.na_project.get_bundle_context())
224
- compiler.compile_artifacts()
225
- return bundle_map
203
+ return ApplicationPackageEntity.bundle(
204
+ project_root=self.project_root,
205
+ deploy_root=self.deploy_root,
206
+ bundle_root=self.bundle_root,
207
+ generated_root=self.generated_root,
208
+ package_name=self.package_name,
209
+ artifacts=self.artifacts,
210
+ )
226
211
 
227
212
  def sync_deploy_root_with_stage(
228
213
  self,
@@ -249,14 +234,10 @@ class NativeAppManager(SqlExecutionMixin):
249
234
  )
250
235
 
251
236
  def get_existing_app_info(self) -> Optional[dict]:
252
- """
253
- Check for an existing application object by the same name as in project definition, in account.
254
- It executes a 'show applications like' query and returns the result as single row, if one exists.
255
- """
256
- with self.use_role(self.app_role):
257
- return self.show_specific_object(
258
- "applications", self.app_name, name_col=NAME_COL
259
- )
237
+ return ApplicationEntity.get_existing_app_info(
238
+ app_name=self.app_name,
239
+ app_role=self.app_role,
240
+ )
260
241
 
261
242
  def get_existing_app_pkg_info(self) -> Optional[dict]:
262
243
  return ApplicationPackageEntity.get_existing_app_pkg_info(
@@ -264,32 +245,19 @@ class NativeAppManager(SqlExecutionMixin):
264
245
  package_role=self.package_role,
265
246
  )
266
247
 
267
- def get_objects_owned_by_application(self) -> List[ApplicationOwnedObject]:
268
- """
269
- Returns all application objects owned by this application.
270
- """
271
- with self.use_role(self.app_role):
272
- results = self._execute_query(
273
- f"show objects owned by application {self.app_name}"
274
- ).fetchall()
275
- return [{"name": row[1], "type": row[2]} for row in results]
248
+ def get_objects_owned_by_application(self):
249
+ return ApplicationEntity.get_objects_owned_by_application(
250
+ app_name=self.app_name,
251
+ app_role=self.app_role,
252
+ )
276
253
 
277
254
  def _application_objects_to_str(
278
255
  self, application_objects: list[ApplicationOwnedObject]
279
256
  ) -> str:
280
- """
281
- Returns a list in an "(Object Type) Object Name" format. Database-level and schema-level object names are fully qualified:
282
- (COMPUTE_POOL) POOL_NAME
283
- (DATABASE) DB_NAME
284
- (SCHEMA) DB_NAME.PUBLIC
285
- ...
286
- """
287
- return "\n".join(
288
- [self._application_object_to_str(obj) for obj in application_objects]
289
- )
257
+ return ApplicationEntity.application_objects_to_str(application_objects)
290
258
 
291
- def _application_object_to_str(self, obj: ApplicationOwnedObject) -> str:
292
- return f"({obj['type']}) {obj['name']}"
259
+ def _application_object_to_str(self, obj: ApplicationOwnedObject):
260
+ return ApplicationEntity.application_object_to_str(obj)
293
261
 
294
262
  def get_snowsight_url(self) -> str:
295
263
  """Returns the URL that can be used to visit this app via Snowsight."""
@@ -343,35 +311,26 @@ class NativeAppManager(SqlExecutionMixin):
343
311
  validate: bool = True,
344
312
  print_diff: bool = True,
345
313
  ) -> DiffResult:
346
- """app deploy process"""
347
-
348
- # 1. Create an empty application package, if none exists
349
- self.create_app_package()
350
-
351
- with self.use_role(self.package_role):
352
- # 2. now that the application package exists, create shared data
353
- self._apply_package_scripts()
354
-
355
- # 3. Upload files from deploy root local folder to the above stage
356
- stage_fqn = stage_fqn or self.stage_fqn
357
- diff = self.sync_deploy_root_with_stage(
358
- bundle_map=bundle_map,
359
- role=self.package_role,
360
- prune=prune,
361
- recursive=recursive,
362
- stage_fqn=stage_fqn,
363
- local_paths_to_sync=local_paths_to_sync,
364
- print_diff=print_diff,
365
- )
366
-
367
- # 4. Execute post-deploy hooks
368
- with self.use_package_warehouse():
369
- self.execute_package_post_deploy_hooks()
370
-
371
- if validate:
372
- self.validate(use_scratch_stage=False)
373
-
374
- return diff
314
+ return ApplicationPackageEntity.deploy(
315
+ console=cc,
316
+ project_root=self.project_root,
317
+ deploy_root=self.deploy_root,
318
+ bundle_root=self.bundle_root,
319
+ generated_root=self.generated_root,
320
+ artifacts=self.artifacts,
321
+ package_name=self.package_name,
322
+ package_role=self.package_role,
323
+ package_distribution=self.package_distribution,
324
+ prune=prune,
325
+ recursive=recursive,
326
+ paths=local_paths_to_sync,
327
+ print_diff=print_diff,
328
+ validate=validate,
329
+ stage_fqn=stage_fqn or self.stage_fqn,
330
+ package_warehouse=self.package_warehouse,
331
+ post_deploy_hooks=self.package_post_deploy_hooks,
332
+ package_scripts=self.package_scripts,
333
+ )
375
334
 
376
335
  def deploy_to_scratch_stage_fn(self):
377
336
  bundle_map = self.build_bundle()
@@ -15,135 +15,33 @@
15
15
  from __future__ import annotations
16
16
 
17
17
  from pathlib import Path
18
- from textwrap import dedent
19
18
  from typing import Optional
20
19
 
21
20
  import typer
22
- from click import UsageError
23
21
  from snowflake.cli._plugins.nativeapp.artifacts import BundleMap
24
- from snowflake.cli._plugins.nativeapp.constants import (
25
- ALLOWED_SPECIAL_COMMENTS,
26
- COMMENT_COL,
27
- PATCH_COL,
28
- SPECIAL_COMMENT,
29
- VERSION_COL,
30
- )
31
- from snowflake.cli._plugins.nativeapp.exceptions import (
32
- ApplicationCreatedExternallyError,
33
- ApplicationPackageDoesNotExistError,
34
- )
35
22
  from snowflake.cli._plugins.nativeapp.manager import (
36
23
  NativeAppCommandProcessor,
37
24
  NativeAppManager,
38
25
  )
39
26
  from snowflake.cli._plugins.nativeapp.policy import PolicyBase
40
- from snowflake.cli._plugins.nativeapp.project_model import (
41
- NativeAppProjectModel,
27
+ from snowflake.cli._plugins.nativeapp.same_account_install_method import (
28
+ SameAccountInstallMethod,
42
29
  )
43
- from snowflake.cli._plugins.stage.manager import StageManager
44
30
  from snowflake.cli.api.console import cli_console as cc
31
+ from snowflake.cli.api.entities.application_entity import (
32
+ ApplicationEntity,
33
+ )
45
34
  from snowflake.cli.api.entities.utils import (
46
- ensure_correct_owner,
47
35
  generic_sql_error_handler,
48
36
  )
49
37
  from snowflake.cli.api.errno import (
50
38
  APPLICATION_NO_LONGER_AVAILABLE,
51
39
  APPLICATION_OWNS_EXTERNAL_OBJECTS,
52
- CANNOT_UPGRADE_FROM_LOOSE_FILES_TO_VERSION,
53
- CANNOT_UPGRADE_FROM_VERSION_TO_LOOSE_FILES,
54
- NOT_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
55
- ONLY_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
56
40
  )
57
41
  from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
58
42
  from snowflake.cli.api.project.schemas.native_app.native_app import NativeApp
59
- from snowflake.cli.api.project.util import (
60
- identifier_to_show_like_pattern,
61
- unquote_identifier,
62
- )
63
- from snowflake.cli.api.utils.cursor import find_all_rows
64
43
  from snowflake.connector import ProgrammingError
65
- from snowflake.connector.cursor import DictCursor, SnowflakeCursor
66
-
67
- # Reasons why an `alter application ... upgrade` might fail
68
- UPGRADE_RESTRICTION_CODES = {
69
- CANNOT_UPGRADE_FROM_LOOSE_FILES_TO_VERSION,
70
- CANNOT_UPGRADE_FROM_VERSION_TO_LOOSE_FILES,
71
- ONLY_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
72
- NOT_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
73
- APPLICATION_NO_LONGER_AVAILABLE,
74
- }
75
-
76
-
77
- def print_messages(create_or_upgrade_cursor: Optional[SnowflakeCursor]):
78
- """
79
- Shows messages in the console returned by the CREATE or UPGRADE
80
- APPLICATION command.
81
- """
82
- if not create_or_upgrade_cursor:
83
- return
84
-
85
- messages = [row[0] for row in create_or_upgrade_cursor.fetchall()]
86
- for message in messages:
87
- cc.warning(message)
88
- cc.message("")
89
-
90
-
91
- class SameAccountInstallMethod:
92
- _requires_created_by_cli: bool
93
- _from_release_directive: bool
94
- version: Optional[str]
95
- patch: Optional[int]
96
-
97
- def __init__(
98
- self,
99
- requires_created_by_cli: bool,
100
- version: Optional[str] = None,
101
- patch: Optional[int] = None,
102
- from_release_directive: bool = False,
103
- ):
104
- self._requires_created_by_cli = requires_created_by_cli
105
- self.version = version
106
- self.patch = patch
107
- self._from_release_directive = from_release_directive
108
-
109
- @classmethod
110
- def unversioned_dev(cls):
111
- """aka. stage dev aka loose files"""
112
- return cls(True)
113
-
114
- @classmethod
115
- def versioned_dev(cls, version: str, patch: Optional[int] = None):
116
- return cls(False, version, patch)
117
-
118
- @classmethod
119
- def release_directive(cls):
120
- return cls(False, from_release_directive=True)
121
-
122
- @property
123
- def is_dev_mode(self) -> bool:
124
- return not self._from_release_directive
125
-
126
- def using_clause(self, app: NativeAppProjectModel) -> str:
127
- if self._from_release_directive:
128
- return ""
129
-
130
- if self.version:
131
- patch_clause = f"patch {self.patch}" if self.patch else ""
132
- return f"using version {self.version} {patch_clause}"
133
-
134
- stage_name = StageManager.quote_stage_name(app.stage_fqn)
135
- return f"using {stage_name}"
136
-
137
- def ensure_app_usable(self, app: NativeAppProjectModel, show_app_row: dict):
138
- """Raise an exception if we cannot proceed with install given the pre-existing application object"""
139
-
140
- if self._requires_created_by_cli:
141
- if show_app_row[COMMENT_COL] not in ALLOWED_SPECIAL_COMMENTS:
142
- # this application object was not created by this tooling
143
- raise ApplicationCreatedExternallyError(app.app_name)
144
-
145
- # expected owner
146
- ensure_correct_owner(row=show_app_row, role=app.app_role, obj_name=app.app_name)
44
+ from snowflake.connector.cursor import SnowflakeCursor
147
45
 
148
46
 
149
47
  class NativeAppRunProcessor(NativeAppManager, NativeAppCommandProcessor):
@@ -165,35 +63,11 @@ class NativeAppRunProcessor(NativeAppManager, NativeAppCommandProcessor):
165
63
  return show_obj_cursor
166
64
 
167
65
  def get_existing_version_info(self, version: str) -> Optional[dict]:
168
- """
169
- Get the latest patch on an existing version by name in the application package.
170
- Executes 'show versions like ... in application package' query and returns
171
- the latest patch in the version as a single row, if one exists. Otherwise,
172
- returns None.
173
- """
174
- with self.use_role(self.package_role):
175
- try:
176
- query = f"show versions like {identifier_to_show_like_pattern(version)} in application package {self.package_name}"
177
- cursor = self._execute_query(query, cursor_class=DictCursor)
178
-
179
- if cursor.rowcount is None:
180
- raise SnowflakeSQLExecutionError(query)
181
-
182
- matching_rows = find_all_rows(
183
- cursor, lambda row: row[VERSION_COL] == unquote_identifier(version)
184
- )
185
-
186
- if not matching_rows:
187
- return None
188
-
189
- return max(matching_rows, key=lambda row: row[PATCH_COL])
190
-
191
- except ProgrammingError as err:
192
- if err.msg.__contains__("does not exist or not authorized"):
193
- raise ApplicationPackageDoesNotExistError(self.package_name)
194
- else:
195
- generic_sql_error_handler(err=err, role=self.package_role)
196
- return None
66
+ return ApplicationEntity.get_existing_version_info(
67
+ version=version,
68
+ package_name=self.package_name,
69
+ package_role=self.package_role,
70
+ )
197
71
 
198
72
  def drop_application_before_upgrade(
199
73
  self, policy: PolicyBase, is_interactive: bool, cascade: bool = False
@@ -249,90 +123,26 @@ class NativeAppRunProcessor(NativeAppManager, NativeAppCommandProcessor):
249
123
  install_method: SameAccountInstallMethod,
250
124
  is_interactive: bool = False,
251
125
  ):
252
- with self.use_role(self.app_role):
253
-
254
- # 1. Need to use a warehouse to create an application object
255
- with self.use_warehouse(self.application_warehouse):
256
-
257
- # 2. Check for an existing application by the same name
258
- show_app_row = self.get_existing_app_info()
259
-
260
- # 3. If existing application is found, perform a few validations and upgrade the application object.
261
- if show_app_row:
262
-
263
- install_method.ensure_app_usable(self._na_project, show_app_row)
264
-
265
- # If all the above checks are in order, proceed to upgrade
266
- try:
267
- cc.step(
268
- f"Upgrading existing application object {self.app_name}."
269
- )
270
- using_clause = install_method.using_clause(self._na_project)
271
- upgrade_cursor = self._execute_query(
272
- f"alter application {self.app_name} upgrade {using_clause}",
273
- )
274
- print_messages(upgrade_cursor)
275
-
276
- if install_method.is_dev_mode:
277
- # if debug_mode is present (controlled), ensure it is up-to-date
278
- if self.debug_mode is not None:
279
- self._execute_query(
280
- f"alter application {self.app_name} set debug_mode = {self.debug_mode}"
281
- )
282
-
283
- # hooks always executed after a create or upgrade
284
- self.execute_app_post_deploy_hooks()
285
- return
286
-
287
- except ProgrammingError as err:
288
- if err.errno not in UPGRADE_RESTRICTION_CODES:
289
- generic_sql_error_handler(err=err)
290
- else: # The existing application object was created from a different process.
291
- cc.warning(err.msg)
292
- self.drop_application_before_upgrade(policy, is_interactive)
293
-
294
- # 4. With no (more) existing application objects, create an application object using the release directives
295
- cc.step(f"Creating new application object {self.app_name} in account.")
296
-
297
- if self.app_role != self.package_role:
298
- with self.use_role(self.package_role):
299
- self._execute_query(
300
- f"grant install, develop on application package {self.package_name} to role {self.app_role}"
301
- )
302
- self._execute_query(
303
- f"grant usage on schema {self.package_name}.{self.stage_schema} to role {self.app_role}"
304
- )
305
- self._execute_query(
306
- f"grant read on stage {self.stage_fqn} to role {self.app_role}"
307
- )
308
-
309
- try:
310
- # by default, applications are created in debug mode when possible;
311
- # this can be overridden in the project definition
312
- debug_mode_clause = ""
313
- if install_method.is_dev_mode:
314
- initial_debug_mode = (
315
- self.debug_mode if self.debug_mode is not None else True
316
- )
317
- debug_mode_clause = f"debug_mode = {initial_debug_mode}"
318
-
319
- using_clause = install_method.using_clause(self._na_project)
320
- create_cursor = self._execute_query(
321
- dedent(
322
- f"""\
323
- create application {self.app_name}
324
- from application package {self.package_name} {using_clause} {debug_mode_clause}
325
- comment = {SPECIAL_COMMENT}
326
- """
327
- ),
328
- )
329
- print_messages(create_cursor)
330
-
331
- # hooks always executed after a create or upgrade
332
- self.execute_app_post_deploy_hooks()
333
-
334
- except ProgrammingError as err:
335
- generic_sql_error_handler(err)
126
+ def drop_app():
127
+ self.drop_application_before_upgrade(policy, is_interactive)
128
+
129
+ return ApplicationEntity.create_or_upgrade_app(
130
+ console=cc,
131
+ project_root=self.project_root,
132
+ package_name=self.package_name,
133
+ package_role=self.package_role,
134
+ app_name=self.app_name,
135
+ app_role=self.app_role,
136
+ app_warehouse=self.application_warehouse,
137
+ stage_schema=self.stage_schema,
138
+ stage_fqn=self.stage_fqn,
139
+ debug_mode=self.debug_mode,
140
+ policy=policy,
141
+ install_method=install_method,
142
+ is_interactive=is_interactive,
143
+ post_deploy_hooks=self.app_post_deploy_hooks,
144
+ drop_application_before_upgrade=drop_app,
145
+ )
336
146
 
337
147
  def process(
338
148
  self,
@@ -346,46 +156,32 @@ class NativeAppRunProcessor(NativeAppManager, NativeAppCommandProcessor):
346
156
  *args,
347
157
  **kwargs,
348
158
  ):
349
- """
350
- Create or upgrade the application object using the given strategy
351
- (unversioned dev, versioned dev, or same-account release directive).
352
- """
353
-
354
- # same-account release directive
355
- if from_release_directive:
356
- self.create_or_upgrade_app(
357
- policy=policy,
358
- is_interactive=is_interactive,
359
- install_method=SameAccountInstallMethod.release_directive(),
159
+ def deploy_package():
160
+ self.deploy(
161
+ bundle_map=bundle_map, prune=True, recursive=True, validate=validate
360
162
  )
361
- return
362
163
 
363
- # versioned dev
364
- if version:
365
- try:
366
- version_exists = self.get_existing_version_info(version)
367
- if not version_exists:
368
- raise UsageError(
369
- f"Application package {self.package_name} does not have any version {version} defined. Use 'snow app version create' to define a version in the application package first."
370
- )
371
- except ApplicationPackageDoesNotExistError as app_err:
372
- raise UsageError(
373
- f"Application package {self.package_name} does not exist. Use 'snow app version create' to first create an application package and then define a version in it."
374
- )
375
-
376
- self.create_or_upgrade_app(
377
- policy=policy,
378
- install_method=SameAccountInstallMethod.versioned_dev(version, patch),
379
- is_interactive=is_interactive,
380
- )
381
- return
382
-
383
- # unversioned dev
384
- self.deploy(
385
- bundle_map=bundle_map, prune=True, recursive=True, validate=validate
386
- )
387
- self.create_or_upgrade_app(
388
- policy=policy,
164
+ def drop_app():
165
+ self.drop_application_before_upgrade(policy, is_interactive)
166
+
167
+ ApplicationEntity.deploy(
168
+ console=cc,
169
+ project_root=self.project_root,
170
+ app_name=self.app_name,
171
+ app_role=self.app_role,
172
+ app_warehouse=self.application_warehouse,
173
+ package_name=self.package_name,
174
+ package_role=self.package_role,
175
+ stage_schema=self.stage_schema,
176
+ stage_fqn=self.stage_fqn,
177
+ debug_mode=self.debug_mode,
178
+ validate=validate,
179
+ from_release_directive=from_release_directive,
389
180
  is_interactive=is_interactive,
390
- install_method=SameAccountInstallMethod.unversioned_dev(),
181
+ policy=policy,
182
+ version=version,
183
+ patch=patch,
184
+ post_deploy_hooks=self.app_post_deploy_hooks,
185
+ deploy_package=deploy_package,
186
+ drop_application_before_upgrade=drop_app,
391
187
  )
@@ -0,0 +1,74 @@
1
+ from typing import Optional
2
+
3
+ from snowflake.cli._plugins.nativeapp.constants import (
4
+ ALLOWED_SPECIAL_COMMENTS,
5
+ COMMENT_COL,
6
+ )
7
+ from snowflake.cli._plugins.nativeapp.exceptions import (
8
+ ApplicationCreatedExternallyError,
9
+ )
10
+
11
+ # from snowflake.cli._plugins.nativeapp.project_model import NativeAppProjectModel
12
+ from snowflake.cli._plugins.stage.manager import StageManager
13
+ from snowflake.cli.api.entities.utils import ensure_correct_owner
14
+
15
+
16
+ class SameAccountInstallMethod:
17
+ _requires_created_by_cli: bool
18
+ _from_release_directive: bool
19
+ version: Optional[str]
20
+ patch: Optional[int]
21
+
22
+ def __init__(
23
+ self,
24
+ requires_created_by_cli: bool,
25
+ version: Optional[str] = None,
26
+ patch: Optional[int] = None,
27
+ from_release_directive: bool = False,
28
+ ):
29
+ self._requires_created_by_cli = requires_created_by_cli
30
+ self.version = version
31
+ self.patch = patch
32
+ self._from_release_directive = from_release_directive
33
+
34
+ @classmethod
35
+ def unversioned_dev(cls):
36
+ """aka. stage dev aka loose files"""
37
+ return cls(True)
38
+
39
+ @classmethod
40
+ def versioned_dev(cls, version: str, patch: Optional[int] = None):
41
+ return cls(False, version, patch)
42
+
43
+ @classmethod
44
+ def release_directive(cls):
45
+ return cls(False, from_release_directive=True)
46
+
47
+ @property
48
+ def is_dev_mode(self) -> bool:
49
+ return not self._from_release_directive
50
+
51
+ def using_clause(
52
+ self,
53
+ stage_fqn: str,
54
+ ) -> str:
55
+ if self._from_release_directive:
56
+ return ""
57
+
58
+ if self.version:
59
+ patch_clause = f"patch {self.patch}" if self.patch else ""
60
+ return f"using version {self.version} {patch_clause}"
61
+
62
+ stage_name = StageManager.quote_stage_name(stage_fqn)
63
+ return f"using {stage_name}"
64
+
65
+ def ensure_app_usable(self, app_name: str, app_role: str, show_app_row: dict):
66
+ """Raise an exception if we cannot proceed with install given the pre-existing application object"""
67
+
68
+ if self._requires_created_by_cli:
69
+ if show_app_row[COMMENT_COL] not in ALLOWED_SPECIAL_COMMENTS:
70
+ # this application object was not created by this tooling
71
+ raise ApplicationCreatedExternallyError(app_name)
72
+
73
+ # expected owner
74
+ ensure_correct_owner(row=show_app_row, role=app_role, obj_name=app_name)