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
@@ -15,179 +15,36 @@
15
15
  from __future__ import annotations
16
16
 
17
17
  from pathlib import Path
18
- from textwrap import dedent
19
18
  from typing import Dict, Optional
20
19
 
21
- import typer
22
- from snowflake.cli._plugins.nativeapp.constants import (
23
- ALLOWED_SPECIAL_COMMENTS,
24
- COMMENT_COL,
25
- OWNER_COL,
26
- )
27
20
  from snowflake.cli._plugins.nativeapp.manager import (
28
21
  NativeAppCommandProcessor,
29
22
  NativeAppManager,
30
23
  )
31
- from snowflake.cli._plugins.nativeapp.utils import (
32
- needs_confirmation,
33
- )
34
24
  from snowflake.cli.api.console import cli_console as cc
25
+ from snowflake.cli.api.entities.application_entity import (
26
+ ApplicationEntity,
27
+ )
35
28
  from snowflake.cli.api.entities.application_package_entity import (
36
29
  ApplicationPackageEntity,
37
30
  )
38
- from snowflake.cli.api.entities.utils import (
39
- drop_generic_object,
40
- ensure_correct_owner,
41
- )
42
- from snowflake.cli.api.errno import APPLICATION_NO_LONGER_AVAILABLE
43
- from snowflake.connector import ProgrammingError
44
31
 
45
32
 
46
33
  class NativeAppTeardownProcessor(NativeAppManager, NativeAppCommandProcessor):
47
34
  def __init__(self, project_definition: Dict, project_root: Path):
48
35
  super().__init__(project_definition, project_root)
49
36
 
50
- def drop_generic_object(
51
- self, object_type: str, object_name: str, role: str, cascade: bool = False
52
- ):
53
- return drop_generic_object(
54
- console=cc,
55
- object_type=object_type,
56
- object_name=object_name,
57
- role=role,
58
- cascade=cascade,
59
- )
60
-
61
37
  def drop_application(
62
38
  self, auto_yes: bool, interactive: bool = False, cascade: Optional[bool] = None
63
39
  ):
64
- """
65
- Attempts to drop the application object if all validations and user prompts allow so.
66
- """
67
-
68
- needs_confirm = True
69
-
70
- # 1. If existing application is not found, exit gracefully
71
- show_obj_row = self.get_existing_app_info()
72
- if show_obj_row is None:
73
- cc.warning(
74
- f"Role {self.app_role} does not own any application object with the name {self.app_name}, or the application object does not exist."
75
- )
76
- return
77
-
78
- # 2. Check for the right owner
79
- ensure_correct_owner(
80
- row=show_obj_row, role=self.app_role, obj_name=self.app_name
81
- )
82
-
83
- # 3. Check if created by the Snowflake CLI
84
- row_comment = show_obj_row[COMMENT_COL]
85
- if row_comment not in ALLOWED_SPECIAL_COMMENTS and needs_confirmation(
86
- needs_confirm, auto_yes
87
- ):
88
- should_drop_object = typer.confirm(
89
- dedent(
90
- f"""\
91
- Application object {self.app_name} was not created by Snowflake CLI.
92
- Application object details:
93
- Name: {self.app_name}
94
- Created on: {show_obj_row["created_on"]}
95
- Source: {show_obj_row["source"]}
96
- Owner: {show_obj_row[OWNER_COL]}
97
- Comment: {show_obj_row[COMMENT_COL]}
98
- Version: {show_obj_row["version"]}
99
- Patch: {show_obj_row["patch"]}
100
- Are you sure you want to drop it?
101
- """
102
- )
103
- )
104
- if not should_drop_object:
105
- cc.message(f"Did not drop application object {self.app_name}.")
106
- # The user desires to keep the app, therefore we can't proceed since it would
107
- # leave behind an orphan app when we get to dropping the package
108
- raise typer.Abort()
109
-
110
- # 4. Check for application objects owned by the application
111
- # This query will fail if the application package has already been dropped, so handle this case gracefully
112
- has_objects_to_drop = False
113
- message_prefix = ""
114
- cascade_true_message = ""
115
- cascade_false_message = ""
116
- interactive_prompt = ""
117
- non_interactive_abort = ""
118
- try:
119
- if application_objects := self.get_objects_owned_by_application():
120
- has_objects_to_drop = True
121
- message_prefix = (
122
- f"The following objects are owned by application {self.app_name}"
123
- )
124
- cascade_true_message = f"{message_prefix} and will be dropped:"
125
- cascade_false_message = f"{message_prefix} and will NOT be dropped:"
126
- interactive_prompt = "Would you like to drop these objects in addition to the application? [y/n/ABORT]"
127
- non_interactive_abort = "Re-run teardown again with --cascade or --no-cascade to specify whether these objects should be dropped along with the application"
128
- except ProgrammingError as e:
129
- if e.errno != APPLICATION_NO_LONGER_AVAILABLE:
130
- raise
131
- application_objects = []
132
- message_prefix = f"Could not determine which objects are owned by application {self.app_name}"
133
- has_objects_to_drop = True # potentially, but we don't know what they are
134
- cascade_true_message = (
135
- f"{message_prefix}, an unknown number of objects will be dropped."
136
- )
137
- cascade_false_message = f"{message_prefix}, they will NOT be dropped."
138
- interactive_prompt = f"Would you like to drop an unknown set of objects in addition to the application? [y/n/ABORT]"
139
- non_interactive_abort = f"Re-run teardown again with --cascade or --no-cascade to specify whether any objects should be dropped along with the application."
140
-
141
- if has_objects_to_drop:
142
- if cascade is True:
143
- # If the user explicitly passed the --cascade flag
144
- cc.message(cascade_true_message)
145
- with cc.indented():
146
- for obj in application_objects:
147
- cc.message(self._application_object_to_str(obj))
148
- elif cascade is False:
149
- # If the user explicitly passed the --no-cascade flag
150
- cc.message(cascade_false_message)
151
- with cc.indented():
152
- for obj in application_objects:
153
- cc.message(self._application_object_to_str(obj))
154
- elif interactive:
155
- # If the user didn't pass any cascade flag and the session is interactive
156
- cc.message(message_prefix)
157
- with cc.indented():
158
- for obj in application_objects:
159
- cc.message(self._application_object_to_str(obj))
160
- user_response = typer.prompt(
161
- interactive_prompt,
162
- show_default=False,
163
- default="ABORT",
164
- ).lower()
165
- if user_response in ["y", "yes"]:
166
- cascade = True
167
- elif user_response in ["n", "no"]:
168
- cascade = False
169
- else:
170
- raise typer.Abort()
171
- else:
172
- # Else abort since we don't know what to do and can't ask the user
173
- cc.message(message_prefix)
174
- with cc.indented():
175
- for obj in application_objects:
176
- cc.message(self._application_object_to_str(obj))
177
- cc.message(non_interactive_abort)
178
- raise typer.Abort()
179
- elif cascade is None:
180
- # If there's nothing to drop, set cascade to an explicit False value
181
- cascade = False
182
-
183
- # 5. All validations have passed, drop object
184
- self.drop_generic_object(
185
- object_type="application",
186
- object_name=self.app_name,
187
- role=self.app_role,
40
+ return ApplicationEntity.drop(
41
+ console=cc,
42
+ app_name=self.app_name,
43
+ app_role=self.app_role,
44
+ auto_yes=auto_yes,
45
+ interactive=interactive,
188
46
  cascade=cascade,
189
47
  )
190
- return # The application object was successfully dropped, therefore exit gracefully
191
48
 
192
49
  def drop_package(self, auto_yes: bool):
193
50
  return ApplicationPackageEntity.drop(
@@ -14,14 +14,17 @@
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
+ import inspect
17
18
  from functools import wraps
18
19
  from typing import Any, Dict, Optional, Union
19
20
 
21
+ import typer
20
22
  from click import ClickException
21
23
  from snowflake.cli.api.cli_global_context import (
22
24
  get_cli_context,
23
25
  get_cli_context_manager,
24
26
  )
27
+ from snowflake.cli.api.commands.decorators import _options_decorator_factory
25
28
  from snowflake.cli.api.project.schemas.entities.application_entity_model import (
26
29
  ApplicationEntityModel,
27
30
  )
@@ -48,28 +51,75 @@ def _convert_v2_artifact_to_v1_dict(
48
51
  return v2_artifact
49
52
 
50
53
 
51
- def _pdf_v2_to_v1(v2_definition: DefinitionV20) -> DefinitionV11:
54
+ def _pdf_v2_to_v1(
55
+ v2_definition: DefinitionV20,
56
+ package_entity_id: str = "",
57
+ app_entity_id: str = "",
58
+ ) -> DefinitionV11:
52
59
  pdfv1: Dict[str, Any] = {"definition_version": "1.1", "native_app": {}}
53
60
 
54
61
  app_package_definition: Optional[ApplicationPackageEntityModel] = None
55
62
  app_definition: Optional[ApplicationEntityModel] = None
56
63
 
57
- for key, entity in v2_definition.entities.items():
58
- if entity.get_type() == ApplicationPackageEntityModel.get_type():
59
- if app_package_definition:
60
- raise ClickException(
61
- "More than one application package entity exists in the project definition file."
62
- )
63
- app_package_definition = entity
64
- elif entity.get_type() == ApplicationEntityModel.get_type():
65
- if app_definition:
64
+ # Enumerate all application package and application entities in the project definition
65
+ packages: dict[
66
+ str, ApplicationPackageEntityModel
67
+ ] = v2_definition.get_entities_by_type(ApplicationPackageEntityModel.get_type())
68
+ apps: dict[str, ApplicationEntityModel] = v2_definition.get_entities_by_type(
69
+ ApplicationEntityModel.get_type()
70
+ )
71
+
72
+ # Determine the application entity to convert, there can be zero or one
73
+ if app_entity_id:
74
+ # If the user specified an app entity ID, use that one directly
75
+ app_definition = apps.get(app_entity_id)
76
+ elif len(apps) == 1:
77
+ # Otherwise, if there is only one app entity, fall back to that one
78
+ app_definition = next(iter(apps.values()))
79
+ elif len(apps) > 1:
80
+ # If there are multiple app entities, the user must specify which one to use
81
+ raise ClickException(
82
+ "More than one application entity exists in the project definition file, "
83
+ "specify --app-entity-id to choose which one to operate on."
84
+ )
85
+
86
+ # Infer or verify the package if we have an app entity to convert
87
+ if app_definition:
88
+ target_package = app_definition.from_.target
89
+ if package_entity_id:
90
+ # If the user specified a package entity ID,
91
+ # check that the app entity targets the user-specified package entity
92
+ if target_package != package_entity_id:
66
93
  raise ClickException(
67
- "More than one application entity exists in the project definition file."
94
+ f"The application entity {app_definition.entity_id} does not "
95
+ f"target the application package entity {package_entity_id}. Either"
96
+ f"use --package-entity-id {target_package} to target the correct package entity, "
97
+ f"or omit the --package-entity-id flag to automatically use the package entity "
98
+ f"that the application entity targets."
68
99
  )
69
- app_definition = entity
100
+ elif target_package in packages:
101
+ # If the user didn't target a specific package entity, use the one the app entity targets
102
+ package_entity_id = target_package
103
+
104
+ # Determine the package entity to convert, there must be one
105
+ if package_entity_id:
106
+ # If the user specified a package entity ID (or we inferred one from the app entity), use that one directly
107
+ app_package_definition = packages.get(package_entity_id)
108
+ elif len(packages) == 1:
109
+ # Otherwise, if there is only one package entity, fall back to that one
110
+ app_package_definition = next(iter(packages.values()))
111
+ elif len(packages) > 1:
112
+ # If there are multiple package entities, the user must specify which one to use
113
+ raise ClickException(
114
+ "More than one application package entity exists in the project definition file, "
115
+ "specify --package-entity-id to choose which one to operate on."
116
+ )
117
+
118
+ # If we don't have a package entity to convert, error out since it's not optional
70
119
  if not app_package_definition:
120
+ with_id = f'with ID "{package_entity_id}" ' if package_entity_id else ""
71
121
  raise ClickException(
72
- "Could not find an application package entity in the project definition file."
122
+ f"Could not find an application package entity {with_id}in the project definition file."
73
123
  )
74
124
 
75
125
  # NativeApp
@@ -141,14 +191,38 @@ def nativeapp_definition_v2_to_v1(func):
141
191
 
142
192
  @wraps(func)
143
193
  def wrapper(*args, **kwargs):
144
- original_pdf: DefinitionV20 = get_cli_context().project_definition
194
+ original_pdf: Optional[DefinitionV20] = get_cli_context().project_definition
145
195
  if not original_pdf:
146
196
  raise ValueError(
147
197
  "Project definition could not be found. The nativeapp_definition_v2_to_v1 command decorator assumes with_project_definition() was called before it."
148
198
  )
149
199
  if original_pdf.definition_version == "2":
150
- pdfv1 = _pdf_v2_to_v1(original_pdf)
151
- get_cli_context_manager().set_project_definition(pdfv1)
200
+ package_entity_id = kwargs.get("package_entity_id", "")
201
+ app_entity_id = kwargs.get("app_entity_id", "")
202
+ pdfv1 = _pdf_v2_to_v1(original_pdf, package_entity_id, app_entity_id)
203
+ get_cli_context_manager().override_project_definition = pdfv1
152
204
  return func(*args, **kwargs)
153
205
 
154
- return wrapper
206
+ return _options_decorator_factory(
207
+ wrapper,
208
+ additional_options=[
209
+ inspect.Parameter(
210
+ "package_entity_id",
211
+ inspect.Parameter.KEYWORD_ONLY,
212
+ annotation=Optional[str],
213
+ default=typer.Option(
214
+ default="",
215
+ help="The ID of the package entity on which to operate when definition_version is 2 or higher.",
216
+ ),
217
+ ),
218
+ inspect.Parameter(
219
+ "app_entity_id",
220
+ inspect.Parameter.KEYWORD_ONLY,
221
+ annotation=Optional[str],
222
+ default=typer.Option(
223
+ default="",
224
+ help="The ID of the application entity on which to operate when definition_version is 2 or higher.",
225
+ ),
226
+ ),
227
+ ],
228
+ )
@@ -446,5 +446,5 @@ def describe(
446
446
  def _get_v2_project_definition(cli_context) -> ProjectDefinitionV2:
447
447
  pd = cli_context.project_definition
448
448
  if not pd.meets_version_requirement("2"):
449
- pd = convert_project_definition_to_v2(pd)
449
+ pd = convert_project_definition_to_v2(cli_context.project_root, pd)
450
450
  return pd
@@ -121,13 +121,14 @@ class WheelMetadata:
121
121
  if line.startswith(dep_keyword)
122
122
  ]
123
123
  name = cls._get_name_from_wheel_filename(wheel_path.name)
124
+
124
125
  return cls(name=name, wheel_path=wheel_path, dependencies=dependencies)
125
126
 
126
127
  @staticmethod
127
128
  def _get_name_from_wheel_filename(wheel_filename: str) -> str:
128
129
  # wheel filename is in format {name}-{version}[-{extra info}]
129
130
  # https://peps.python.org/pep-0491/#file-name-convention
130
- return wheel_filename.split("-")[0]
131
+ return wheel_filename.split("-")[0].lower()
131
132
 
132
133
  @staticmethod
133
134
  def to_wheel_name_format(package_name: str) -> str:
@@ -138,7 +138,7 @@ def streamlit_deploy(
138
138
  raise NoProjectDefinitionError(
139
139
  project_type="streamlit", project_root=cli_context.project_root
140
140
  )
141
- pd = convert_project_definition_to_v2(pd)
141
+ pd = convert_project_definition_to_v2(cli_context.project_root, pd)
142
142
 
143
143
  streamlits: Dict[str, StreamlitEntityModel] = pd.get_entities_by_type(
144
144
  entity_type="streamlit"
@@ -18,11 +18,13 @@ import logging
18
18
  from pathlib import Path
19
19
  from typing import List, Optional
20
20
 
21
+ from click import ClickException
21
22
  from snowflake.cli._plugins.connection.util import (
22
23
  MissingConnectionAccountError,
23
24
  MissingConnectionRegionError,
24
25
  make_snowsight_url,
25
26
  )
27
+ from snowflake.cli._plugins.object.manager import ObjectManager
26
28
  from snowflake.cli._plugins.stage.manager import StageManager
27
29
  from snowflake.cli.api.commands.experimental_behaviour import (
28
30
  experimental_behaviour_enabled,
@@ -112,6 +114,13 @@ class StreamlitManager(SqlExecutionMixin):
112
114
 
113
115
  def deploy(self, streamlit: StreamlitEntityModel, replace: bool = False):
114
116
  streamlit_id = streamlit.fqn.using_connection(self._conn)
117
+ if (
118
+ ObjectManager().object_exists(object_type="streamlit", fqn=streamlit_id)
119
+ and not replace
120
+ ):
121
+ raise ClickException(
122
+ f"Streamlit {streamlit.fqn} already exist. If you want to replace it use --replace flag."
123
+ )
115
124
 
116
125
  # for backwards compatibility - quoted stage path might be case-sensitive
117
126
  # https://docs.snowflake.com/en/sql-reference/identifiers-syntax#double-quoted-identifiers
@@ -1,6 +1,6 @@
1
1
  from dataclasses import dataclass
2
2
  from pathlib import Path
3
- from typing import Optional
3
+ from typing import Callable, Optional
4
4
 
5
5
  from snowflake.cli.api.console.abc import AbstractConsole
6
6
 
@@ -15,3 +15,4 @@ class ActionContext:
15
15
  project_root: Path
16
16
  default_role: str
17
17
  default_warehouse: Optional[str]
18
+ get_entity: Callable
@@ -24,6 +24,7 @@ import yaml
24
24
  from snowflake.cli._plugins.nativeapp.artifacts import BundleMap
25
25
  from snowflake.cli._plugins.nativeapp.common_flags import (
26
26
  ForceOption,
27
+ InteractiveOption,
27
28
  ValidateOption,
28
29
  )
29
30
  from snowflake.cli._plugins.workspace.manager import WorkspaceManager
@@ -53,13 +54,14 @@ def migrate(
53
54
  ),
54
55
  **options,
55
56
  ):
56
- """Migrates the Snowpark and Streamlit project definition files from V1 to V2."""
57
- pd = DefinitionManager().unrendered_project_definition
57
+ """Migrates the Snowpark, Streamlit, and Native App project definition files from V1 to V2."""
58
+ manager = DefinitionManager()
59
+ pd = manager.unrendered_project_definition
58
60
 
59
61
  if pd.meets_version_requirement("2"):
60
62
  return MessageResult("Project definition is already at version 2.")
61
63
 
62
- pd_v2 = convert_project_definition_to_v2(pd, accept_templates)
64
+ pd_v2 = convert_project_definition_to_v2(manager.project_root, pd, accept_templates)
63
65
 
64
66
  SecurePath("snowflake.yml").rename("snowflake_V1.yml")
65
67
  with open("snowflake.yml", "w") as file:
@@ -72,16 +74,6 @@ def migrate(
72
74
  return MessageResult("Project definition migrated to version 2.")
73
75
 
74
76
 
75
- @ws.command(requires_connection=True, hidden=True)
76
- @with_project_definition()
77
- def validate(
78
- **options,
79
- ):
80
- """Validates the project definition file."""
81
- # If we get to this point, @with_project_definition() has already validated the PDF schema
82
- return MessageResult("Project definition is valid.")
83
-
84
-
85
77
  @ws.command(requires_connection=True, hidden=True)
86
78
  @with_project_definition()
87
79
  def bundle(
@@ -104,7 +96,7 @@ def bundle(
104
96
  return MessageResult(f"Bundle generated at {bundle_map.deploy_root()}")
105
97
 
106
98
 
107
- @ws.command(requires_connection=True)
99
+ @ws.command(requires_connection=True, hidden=True)
108
100
  @with_project_definition()
109
101
  def deploy(
110
102
  entity_id: str = typer.Option(
@@ -132,6 +124,15 @@ def deploy(
132
124
  unspecified, the command syncs all local changes to the stage."""
133
125
  ).strip(),
134
126
  ),
127
+ from_release_directive: Optional[bool] = typer.Option(
128
+ False,
129
+ "--from-release-directive",
130
+ help=f"""Creates or upgrades an application object to the version and patch specified by the release directive applicable to your Snowflake account.
131
+ The command fails if no release directive exists for your Snowflake account for a given application package, which is determined from the project definition file. Default: unset.""",
132
+ is_flag=True,
133
+ ),
134
+ interactive: bool = InteractiveOption,
135
+ force: Optional[bool] = ForceOption,
135
136
  validate: bool = ValidateOption,
136
137
  **options,
137
138
  ):
@@ -163,24 +164,32 @@ def deploy(
163
164
  recursive=recursive,
164
165
  paths=paths,
165
166
  validate=validate,
167
+ from_release_directive=from_release_directive,
168
+ interactive=interactive,
169
+ force=force,
166
170
  )
167
171
  return MessageResult("Deployed successfully.")
168
172
 
169
173
 
170
- @ws.command(requires_connection=True)
174
+ @ws.command(requires_connection=True, hidden=True)
171
175
  @with_project_definition()
172
176
  def drop(
173
177
  entity_id: str = typer.Option(
174
178
  help=f"""The ID of the entity you want to drop.""",
175
179
  ),
176
180
  # TODO The following options should be generated automatically, depending on the specified entity type
181
+ interactive: bool = InteractiveOption,
177
182
  force: Optional[bool] = ForceOption,
183
+ cascade: Optional[bool] = typer.Option(
184
+ None,
185
+ help=f"""Whether to drop all application objects owned by the application within the account. Default: false.""",
186
+ show_default=False,
187
+ ),
178
188
  **options,
179
189
  ):
180
190
  """
181
191
  Drops the specified entity.
182
192
  """
183
-
184
193
  cli_context = get_cli_context()
185
194
  ws = WorkspaceManager(
186
195
  project_definition=cli_context.project_definition,
@@ -191,4 +200,27 @@ def drop(
191
200
  entity_id,
192
201
  EntityActions.DROP,
193
202
  force_drop=force,
203
+ interactive=interactive,
204
+ cascade=cascade,
205
+ )
206
+
207
+
208
+ @ws.command(requires_connection=True, hidden=True)
209
+ @with_project_definition()
210
+ def validate(
211
+ entity_id: str = typer.Option(
212
+ help=f"""The ID of the entity you want to validate.""",
213
+ ),
214
+ **options,
215
+ ):
216
+ """Validates the specified entity."""
217
+ cli_context = get_cli_context()
218
+ ws = WorkspaceManager(
219
+ project_definition=cli_context.project_definition,
220
+ project_root=cli_context.project_root,
221
+ )
222
+
223
+ ws.perform_action(
224
+ entity_id,
225
+ EntityActions.VALIDATE,
194
226
  )
@@ -64,6 +64,7 @@ class WorkspaceManager:
64
64
  project_root=self.project_root(),
65
65
  default_role=self._default_role,
66
66
  default_warehouse=self.default_warehouse,
67
+ get_entity=self.get_entity,
67
68
  )
68
69
  return entity.perform(action, action_ctx, *args, **kwargs)
69
70
  else: