snowflake-cli-labs 3.0.0rc0__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 (66) 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 +91 -37
  4. snowflake/cli/_app/telemetry.py +8 -4
  5. snowflake/cli/_app/version_check.py +74 -0
  6. snowflake/cli/_plugins/connection/commands.py +3 -2
  7. snowflake/cli/_plugins/git/commands.py +55 -14
  8. snowflake/cli/_plugins/git/manager.py +14 -6
  9. snowflake/cli/_plugins/nativeapp/codegen/compiler.py +18 -2
  10. snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +123 -42
  11. snowflake/cli/_plugins/nativeapp/codegen/setup/setup_driver.py.source +5 -2
  12. snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +6 -11
  13. snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +111 -0
  14. snowflake/cli/_plugins/nativeapp/exceptions.py +3 -3
  15. snowflake/cli/_plugins/nativeapp/manager.py +74 -144
  16. snowflake/cli/_plugins/nativeapp/project_model.py +2 -9
  17. snowflake/cli/_plugins/nativeapp/run_processor.py +56 -260
  18. snowflake/cli/_plugins/nativeapp/same_account_install_method.py +74 -0
  19. snowflake/cli/_plugins/nativeapp/teardown_processor.py +17 -246
  20. snowflake/cli/_plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +91 -17
  21. snowflake/cli/_plugins/snowpark/commands.py +5 -65
  22. snowflake/cli/_plugins/snowpark/common.py +17 -1
  23. snowflake/cli/_plugins/snowpark/models.py +2 -1
  24. snowflake/cli/_plugins/snowpark/package/anaconda_packages.py +1 -35
  25. snowflake/cli/_plugins/sql/commands.py +1 -2
  26. snowflake/cli/_plugins/stage/commands.py +2 -2
  27. snowflake/cli/_plugins/stage/manager.py +46 -15
  28. snowflake/cli/_plugins/streamlit/commands.py +4 -63
  29. snowflake/cli/_plugins/streamlit/manager.py +13 -0
  30. snowflake/cli/_plugins/workspace/action_context.py +7 -0
  31. snowflake/cli/_plugins/workspace/commands.py +145 -32
  32. snowflake/cli/_plugins/workspace/manager.py +21 -4
  33. snowflake/cli/api/cli_global_context.py +136 -313
  34. snowflake/cli/api/commands/decorators.py +1 -1
  35. snowflake/cli/api/commands/flags.py +106 -102
  36. snowflake/cli/api/commands/snow_typer.py +15 -6
  37. snowflake/cli/api/config.py +18 -5
  38. snowflake/cli/api/connections.py +214 -0
  39. snowflake/cli/api/console/abc.py +4 -2
  40. snowflake/cli/api/constants.py +11 -0
  41. snowflake/cli/api/entities/application_entity.py +687 -2
  42. snowflake/cli/api/entities/application_package_entity.py +407 -9
  43. snowflake/cli/api/entities/common.py +7 -2
  44. snowflake/cli/api/entities/utils.py +80 -20
  45. snowflake/cli/api/exceptions.py +12 -2
  46. snowflake/cli/api/feature_flags.py +0 -2
  47. snowflake/cli/api/identifiers.py +3 -0
  48. snowflake/cli/api/project/definition.py +35 -1
  49. snowflake/cli/api/project/definition_conversion.py +352 -0
  50. snowflake/cli/api/project/schemas/entities/application_package_entity_model.py +17 -0
  51. snowflake/cli/api/project/schemas/entities/common.py +0 -12
  52. snowflake/cli/api/project/schemas/identifier_model.py +2 -2
  53. snowflake/cli/api/project/schemas/project_definition.py +102 -43
  54. snowflake/cli/api/rendering/jinja.py +2 -16
  55. snowflake/cli/api/rendering/project_definition_templates.py +5 -1
  56. snowflake/cli/api/rendering/sql_templates.py +14 -4
  57. snowflake/cli/api/secure_path.py +13 -18
  58. snowflake/cli/api/secure_utils.py +90 -1
  59. snowflake/cli/api/sql_execution.py +13 -0
  60. snowflake/cli/api/utils/definition_rendering.py +7 -7
  61. {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/METADATA +9 -9
  62. {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/RECORD +65 -61
  63. snowflake/cli/api/commands/typer_pre_execute.py +0 -26
  64. {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/WHEEL +0 -0
  65. {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/entry_points.txt +0 -0
  66. {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/licenses/LICENSE +0 -0
@@ -37,7 +37,7 @@ from snowflake.cli.api.commands.flags import (
37
37
  ExecuteVariablesOption,
38
38
  OnErrorOption,
39
39
  PatternOption,
40
- identifier_argument,
40
+ identifier_stage_argument,
41
41
  like_option,
42
42
  )
43
43
  from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
@@ -59,7 +59,7 @@ app = SnowTyperFactory(
59
59
  help="Manages stages.",
60
60
  )
61
61
 
62
- StageNameArgument = identifier_argument(sf_object="stage", example="@my_stage")
62
+ StageNameArgument = identifier_stage_argument(sf_object="stage", example="@my_stage")
63
63
 
64
64
  add_object_command_aliases(
65
65
  app=app,
@@ -65,14 +65,21 @@ class StagePathParts:
65
65
  stage_name: str
66
66
  is_directory: bool
67
67
 
68
- @staticmethod
69
- def get_directory(stage_path: str) -> str:
68
+ @classmethod
69
+ def get_directory(cls, stage_path: str) -> str:
70
70
  return "/".join(Path(stage_path).parts[1:])
71
71
 
72
72
  @property
73
73
  def path(self) -> str:
74
74
  raise NotImplementedError
75
75
 
76
+ @property
77
+ def full_path(self) -> str:
78
+ raise NotImplementedError
79
+
80
+ def replace_stage_prefix(self, file_path: str) -> str:
81
+ raise NotImplementedError
82
+
76
83
  def add_stage_prefix(self, file_path: str) -> str:
77
84
  raise NotImplementedError
78
85
 
@@ -112,24 +119,27 @@ class DefaultStagePathParts(StagePathParts):
112
119
  self.directory = self.get_directory(stage_path)
113
120
  self.stage = StageManager.get_stage_from_path(stage_path)
114
121
  stage_name = self.stage.split(".")[-1]
115
- if stage_name.startswith("@"):
116
- stage_name = stage_name[1:]
122
+ stage_name = stage_name[1:] if stage_name.startswith("@") else stage_name
117
123
  self.stage_name = stage_name
118
124
  self.is_directory = True if stage_path.endswith("/") else False
119
125
 
120
126
  @property
121
127
  def path(self) -> str:
122
- return (
123
- f"{self.stage_name}{self.directory}"
124
- if self.stage_name.endswith("/")
125
- else f"{self.stage_name}/{self.directory}"
126
- )
128
+ return f"{self.stage_name.rstrip('/')}/{self.directory}"
127
129
 
128
- def add_stage_prefix(self, file_path: str) -> str:
130
+ @property
131
+ def full_path(self) -> str:
132
+ return f"{self.stage.rstrip('/')}/{self.directory}"
133
+
134
+ def replace_stage_prefix(self, file_path: str) -> str:
129
135
  stage = Path(self.stage).parts[0]
130
136
  file_path_without_prefix = Path(file_path).parts[1:]
131
137
  return f"{stage}/{'/'.join(file_path_without_prefix)}"
132
138
 
139
+ def add_stage_prefix(self, file_path: str) -> str:
140
+ stage = self.stage.rstrip("/")
141
+ return f"{stage}/{file_path.lstrip('/')}"
142
+
133
143
  def get_directory_from_file_path(self, file_path: str) -> List[str]:
134
144
  stage_path_length = len(Path(self.directory).parts)
135
145
  return list(Path(file_path).parts[1 + stage_path_length : -1])
@@ -146,14 +156,29 @@ class UserStagePathParts(StagePathParts):
146
156
 
147
157
  def __init__(self, stage_path: str):
148
158
  self.directory = self.get_directory(stage_path)
149
- self.stage = "@~"
150
- self.stage_name = "@~"
159
+ self.stage = USER_STAGE_PREFIX
160
+ self.stage_name = USER_STAGE_PREFIX
151
161
  self.is_directory = True if stage_path.endswith("/") else False
152
162
 
163
+ @classmethod
164
+ def get_directory(cls, stage_path: str) -> str:
165
+ if Path(stage_path).parts[0] == USER_STAGE_PREFIX:
166
+ return super().get_directory(stage_path)
167
+ return stage_path
168
+
153
169
  @property
154
170
  def path(self) -> str:
155
171
  return f"{self.directory}"
156
172
 
173
+ @property
174
+ def full_path(self) -> str:
175
+ return f"{self.stage}/{self.directory}"
176
+
177
+ def replace_stage_prefix(self, file_path: str) -> str:
178
+ if Path(file_path).parts[0] == self.stage_name:
179
+ return file_path
180
+ return f"{self.stage}/{file_path}"
181
+
157
182
  def add_stage_prefix(self, file_path: str) -> str:
158
183
  return f"{self.stage}/{file_path}"
159
184
 
@@ -241,7 +266,7 @@ class StageManager(SqlExecutionMixin):
241
266
  self._assure_is_existing_directory(dest_directory)
242
267
 
243
268
  result = self._execute_query(
244
- f"get {self.quote_stage_name(stage_path_parts.add_stage_prefix(file_path))} {self._to_uri(f'{dest_directory}/')} parallel={parallel}"
269
+ f"get {self.quote_stage_name(stage_path_parts.replace_stage_prefix(file_path))} {self._to_uri(f'{dest_directory}/')} parallel={parallel}"
245
270
  )
246
271
  results.append(result)
247
272
 
@@ -321,8 +346,14 @@ class StageManager(SqlExecutionMixin):
321
346
  stage_path_parts = self._stage_path_part_factory(stage_path)
322
347
  all_files_list = self._get_files_list_from_stage(stage_path_parts)
323
348
 
349
+ all_files_with_stage_name_prefix = [
350
+ stage_path_parts.get_directory(file) for file in all_files_list
351
+ ]
352
+
324
353
  # filter files from stage if match stage_path pattern
325
- filtered_file_list = self._filter_files_list(stage_path_parts, all_files_list)
354
+ filtered_file_list = self._filter_files_list(
355
+ stage_path_parts, all_files_with_stage_name_prefix
356
+ )
326
357
 
327
358
  if not filtered_file_list:
328
359
  raise ClickException(f"No files matched pattern '{stage_path}'")
@@ -378,7 +409,7 @@ class StageManager(SqlExecutionMixin):
378
409
  if not stage_path_parts.directory:
379
410
  return self._filter_supported_files(files_on_stage)
380
411
 
381
- stage_path = stage_path_parts.path.lower()
412
+ stage_path = stage_path_parts.directory
382
413
 
383
414
  # Exact file path was provided if stage_path in file list
384
415
  if stage_path in files_on_stage:
@@ -46,13 +46,12 @@ from snowflake.cli.api.output.types import (
46
46
  MessageResult,
47
47
  SingleQueryResult,
48
48
  )
49
+ from snowflake.cli.api.project.definition_conversion import (
50
+ convert_project_definition_to_v2,
51
+ )
49
52
  from snowflake.cli.api.project.schemas.entities.streamlit_entity_model import (
50
53
  StreamlitEntityModel,
51
54
  )
52
- from snowflake.cli.api.project.schemas.project_definition import (
53
- ProjectDefinition,
54
- ProjectDefinitionV2,
55
- )
56
55
 
57
56
  app = SnowTyperFactory(
58
57
  name="streamlit",
@@ -139,7 +138,7 @@ def streamlit_deploy(
139
138
  raise NoProjectDefinitionError(
140
139
  project_type="streamlit", project_root=cli_context.project_root
141
140
  )
142
- pd = migrate_v1_streamlit_to_v2(pd)
141
+ pd = convert_project_definition_to_v2(cli_context.project_root, pd)
143
142
 
144
143
  streamlits: Dict[str, StreamlitEntityModel] = pd.get_entities_by_type(
145
144
  entity_type="streamlit"
@@ -171,64 +170,6 @@ def streamlit_deploy(
171
170
  return MessageResult(f"Streamlit successfully deployed and available under {url}")
172
171
 
173
172
 
174
- def migrate_v1_streamlit_to_v2(pd: ProjectDefinition):
175
- default_env_file = "environment.yml"
176
- default_pages_dir = "pages"
177
-
178
- # Process env file
179
- environment_file = pd.streamlit.env_file
180
- if environment_file and not Path(environment_file).exists():
181
- raise ClickException(f"Provided file {environment_file} does not exist")
182
- elif environment_file is None and Path(default_env_file).exists():
183
- environment_file = default_env_file
184
- # Process pages dir
185
- pages_dir = pd.streamlit.pages_dir
186
- if pages_dir and not Path(pages_dir).exists():
187
- raise ClickException(f"Provided file {pages_dir} does not exist")
188
- elif pages_dir is None and Path(default_pages_dir).exists():
189
- pages_dir = default_pages_dir
190
-
191
- # Build V2 definition
192
- artifacts = [
193
- pd.streamlit.main_file,
194
- environment_file,
195
- pages_dir,
196
- ]
197
- artifacts = [a for a in artifacts if a is not None]
198
- if pd.streamlit.additional_source_files:
199
- artifacts.extend(pd.streamlit.additional_source_files)
200
-
201
- identifier = {"name": pd.streamlit.name}
202
- if pd.streamlit.schema_name:
203
- identifier["schema"] = pd.streamlit.schema_name
204
- if pd.streamlit.database:
205
- identifier["database"] = pd.streamlit.database
206
-
207
- if pd.streamlit.name.startswith("<%") and pd.streamlit.name.endswith("%>"):
208
- streamlit_name = "streamlit_entity_1"
209
- else:
210
- streamlit_name = pd.streamlit.name
211
-
212
- data = {
213
- "definition_version": "2",
214
- "entities": {
215
- streamlit_name: {
216
- "type": "streamlit",
217
- "identifier": identifier,
218
- "title": pd.streamlit.title,
219
- "query_warehouse": pd.streamlit.query_warehouse,
220
- "main_file": str(pd.streamlit.main_file),
221
- "pages_dir": str(pd.streamlit.pages_dir),
222
- "stage": pd.streamlit.stage,
223
- "artifacts": artifacts,
224
- }
225
- },
226
- }
227
- if hasattr(pd, "env") and pd.env:
228
- data["env"] = {k: v for k, v in pd.env.items()}
229
- return ProjectDefinitionV2(**data)
230
-
231
-
232
173
  @app.command("get-url", requires_connection=True)
233
174
  def get_url(
234
175
  name: FQN = StreamlitNameArgument,
@@ -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,
@@ -57,6 +59,10 @@ class StreamlitManager(SqlExecutionMixin):
57
59
  stage_manager = StageManager()
58
60
  for file in artifacts:
59
61
  if file.is_dir():
62
+ if not any(file.iterdir()):
63
+ cli_console.warning(f"Skipping empty directory: {file}")
64
+ continue
65
+
60
66
  stage_manager.put(
61
67
  f"{file.joinpath('*')}", f"{root_location}/{file}", 4, True
62
68
  )
@@ -108,6 +114,13 @@ class StreamlitManager(SqlExecutionMixin):
108
114
 
109
115
  def deploy(self, streamlit: StreamlitEntityModel, replace: bool = False):
110
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
+ )
111
124
 
112
125
  # for backwards compatibility - quoted stage path might be case-sensitive
113
126
  # https://docs.snowflake.com/en/sql-reference/identifiers-syntax#double-quoted-identifiers
@@ -1,5 +1,8 @@
1
1
  from dataclasses import dataclass
2
2
  from pathlib import Path
3
+ from typing import Callable, Optional
4
+
5
+ from snowflake.cli.api.console.abc import AbstractConsole
3
6
 
4
7
 
5
8
  @dataclass
@@ -8,4 +11,8 @@ class ActionContext:
8
11
  An object that is passed to each action when called by WorkspaceManager
9
12
  """
10
13
 
14
+ console: AbstractConsole
11
15
  project_root: Path
16
+ default_role: str
17
+ default_warehouse: Optional[str]
18
+ get_entity: Callable
@@ -15,19 +15,28 @@
15
15
  from __future__ import annotations
16
16
 
17
17
  import logging
18
+ from pathlib import Path
19
+ from textwrap import dedent
20
+ from typing import List, Optional
18
21
 
19
22
  import typer
20
23
  import yaml
21
- from click import ClickException
22
24
  from snowflake.cli._plugins.nativeapp.artifacts import BundleMap
23
- from snowflake.cli._plugins.snowpark.commands import migrate_v1_snowpark_to_v2
24
- from snowflake.cli._plugins.streamlit.commands import migrate_v1_streamlit_to_v2
25
+ from snowflake.cli._plugins.nativeapp.common_flags import (
26
+ ForceOption,
27
+ InteractiveOption,
28
+ ValidateOption,
29
+ )
25
30
  from snowflake.cli._plugins.workspace.manager import WorkspaceManager
26
31
  from snowflake.cli.api.cli_global_context import get_cli_context
27
32
  from snowflake.cli.api.commands.decorators import with_project_definition
28
33
  from snowflake.cli.api.commands.snow_typer import SnowTyper
29
34
  from snowflake.cli.api.entities.common import EntityActions
35
+ from snowflake.cli.api.exceptions import IncompatibleParametersError
30
36
  from snowflake.cli.api.output.types import MessageResult
37
+ from snowflake.cli.api.project.definition_conversion import (
38
+ convert_project_definition_to_v2,
39
+ )
31
40
  from snowflake.cli.api.project.definition_manager import DefinitionManager
32
41
  from snowflake.cli.api.secure_path import SecurePath
33
42
 
@@ -45,30 +54,14 @@ def migrate(
45
54
  ),
46
55
  **options,
47
56
  ):
48
- """Migrates the Snowpark and Streamlit project definition files form V1 to V2."""
49
- 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
50
60
 
51
61
  if pd.meets_version_requirement("2"):
52
62
  return MessageResult("Project definition is already at version 2.")
53
63
 
54
- if "<% ctx." in str(pd):
55
- if not accept_templates:
56
- raise ClickException(
57
- "Project definition contains templates. They may not be migrated correctly, and require manual migration."
58
- "You can try again with --accept-templates option, to attempt automatic migration."
59
- )
60
- log.warning(
61
- "Your V1 definition contains templates. We cannot guarantee the correctness of the migration."
62
- )
63
-
64
- if pd.streamlit:
65
- pd_v2 = migrate_v1_streamlit_to_v2(pd)
66
- elif pd.snowpark:
67
- pd_v2 = migrate_v1_snowpark_to_v2(pd)
68
- else:
69
- raise ValueError(
70
- "Only Snowpark and Streamlit entities are supported for migration."
71
- )
64
+ pd_v2 = convert_project_definition_to_v2(manager.project_root, pd, accept_templates)
72
65
 
73
66
  SecurePath("snowflake.yml").rename("snowflake_V1.yml")
74
67
  with open("snowflake.yml", "w") as file:
@@ -83,25 +76,80 @@ def migrate(
83
76
 
84
77
  @ws.command(requires_connection=True, hidden=True)
85
78
  @with_project_definition()
86
- def validate(
79
+ def bundle(
80
+ entity_id: str = typer.Option(
81
+ help=f"""The ID of the entity you want to bundle.""",
82
+ ),
87
83
  **options,
88
84
  ):
89
- """Validates the project definition file."""
90
- # If we get to this point, @with_project_definition() has already validated the PDF schema
91
- return MessageResult("Project definition is valid.")
85
+ """
86
+ Prepares a local folder with the configured artifacts of the specified entity.
87
+ """
88
+
89
+ cli_context = get_cli_context()
90
+ ws = WorkspaceManager(
91
+ project_definition=cli_context.project_definition,
92
+ project_root=cli_context.project_root,
93
+ )
94
+
95
+ bundle_map: BundleMap = ws.perform_action(entity_id, EntityActions.BUNDLE)
96
+ return MessageResult(f"Bundle generated at {bundle_map.deploy_root()}")
92
97
 
93
98
 
94
99
  @ws.command(requires_connection=True, hidden=True)
95
100
  @with_project_definition()
96
- def bundle(
101
+ def deploy(
97
102
  entity_id: str = typer.Option(
98
- help=f"""The ID of the entity you want to bundle.""",
103
+ help=f"""The ID of the entity you want to deploy.""",
104
+ ),
105
+ # TODO The following options should be generated automatically, depending on the specified entity type
106
+ prune: Optional[bool] = typer.Option(
107
+ default=None,
108
+ help=f"""Whether to delete specified files from the stage if they don't exist locally. If set, the command deletes files that exist in the stage, but not in the local filesystem. This option cannot be used when paths are specified.""",
109
+ ),
110
+ recursive: Optional[bool] = typer.Option(
111
+ None,
112
+ "--recursive/--no-recursive",
113
+ "-r",
114
+ help=f"""Whether to traverse and deploy files from subdirectories. If set, the command deploys all files and subdirectories; otherwise, only files in the current directory are deployed.""",
99
115
  ),
116
+ paths: Optional[List[Path]] = typer.Argument(
117
+ default=None,
118
+ show_default=False,
119
+ help=dedent(
120
+ f"""
121
+ Paths, relative to the the project root, of files or directories you want to upload to a stage. If a file is
122
+ specified, it must match one of the artifacts src pattern entries in snowflake.yml. If a directory is
123
+ specified, it will be searched for subfolders or files to deploy based on artifacts src pattern entries. If
124
+ unspecified, the command syncs all local changes to the stage."""
125
+ ).strip(),
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,
136
+ validate: bool = ValidateOption,
100
137
  **options,
101
138
  ):
102
139
  """
103
- Prepares a local folder with the configured artifacts of the specified entity.
140
+ Deploys the specified entity.
104
141
  """
142
+ if prune is None and recursive is None and not paths:
143
+ prune = True
144
+ recursive = True
145
+ else:
146
+ if prune is None:
147
+ prune = False
148
+ if recursive is None:
149
+ recursive = False
150
+
151
+ if paths and prune:
152
+ raise IncompatibleParametersError(["paths", "--prune"])
105
153
 
106
154
  cli_context = get_cli_context()
107
155
  ws = WorkspaceManager(
@@ -109,5 +157,70 @@ def bundle(
109
157
  project_root=cli_context.project_root,
110
158
  )
111
159
 
112
- bundle_map: BundleMap = ws.perform_action(entity_id, EntityActions.BUNDLE)
113
- return MessageResult(f"Bundle generated at {bundle_map.deploy_root()}")
160
+ ws.perform_action(
161
+ entity_id,
162
+ EntityActions.DEPLOY,
163
+ prune=prune,
164
+ recursive=recursive,
165
+ paths=paths,
166
+ validate=validate,
167
+ from_release_directive=from_release_directive,
168
+ interactive=interactive,
169
+ force=force,
170
+ )
171
+ return MessageResult("Deployed successfully.")
172
+
173
+
174
+ @ws.command(requires_connection=True, hidden=True)
175
+ @with_project_definition()
176
+ def drop(
177
+ entity_id: str = typer.Option(
178
+ help=f"""The ID of the entity you want to drop.""",
179
+ ),
180
+ # TODO The following options should be generated automatically, depending on the specified entity type
181
+ interactive: bool = InteractiveOption,
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
+ ),
188
+ **options,
189
+ ):
190
+ """
191
+ Drops the specified entity.
192
+ """
193
+ cli_context = get_cli_context()
194
+ ws = WorkspaceManager(
195
+ project_definition=cli_context.project_definition,
196
+ project_root=cli_context.project_root,
197
+ )
198
+
199
+ ws.perform_action(
200
+ entity_id,
201
+ EntityActions.DROP,
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,
226
+ )
@@ -2,8 +2,11 @@ from pathlib import Path
2
2
  from typing import Dict
3
3
 
4
4
  from snowflake.cli._plugins.workspace.action_context import ActionContext
5
- from snowflake.cli.api.entities.common import EntityActions
5
+ from snowflake.cli.api.cli_global_context import get_cli_context
6
+ from snowflake.cli.api.console import cli_console as cc
7
+ from snowflake.cli.api.entities.common import EntityActions, get_sql_executor
6
8
  from snowflake.cli.api.exceptions import InvalidProjectDefinitionVersionError
9
+ from snowflake.cli.api.project.definition import default_role
7
10
  from snowflake.cli.api.project.schemas.entities.entities import (
8
11
  Entity,
9
12
  v2_entity_model_to_entity_map,
@@ -12,6 +15,7 @@ from snowflake.cli.api.project.schemas.project_definition import (
12
15
  DefinitionV20,
13
16
  ProjectDefinition,
14
17
  )
18
+ from snowflake.cli.api.project.util import to_identifier
15
19
 
16
20
 
17
21
  class WorkspaceManager:
@@ -27,6 +31,13 @@ class WorkspaceManager:
27
31
  self._entities_cache: Dict[str, Entity] = {}
28
32
  self._project_definition: DefinitionV20 = project_definition
29
33
  self._project_root = project_root
34
+ self._default_role = default_role()
35
+ if self._default_role is None:
36
+ self._default_role = get_sql_executor().current_role()
37
+ self.default_warehouse = None
38
+ cli_context = get_cli_context()
39
+ if cli_context.connection.warehouse:
40
+ self.default_warehouse = to_identifier(cli_context.connection.warehouse)
30
41
 
31
42
  def get_entity(self, entity_id: str):
32
43
  """
@@ -42,14 +53,20 @@ class WorkspaceManager:
42
53
  self._entities_cache[entity_id] = entity_cls(entity_model)
43
54
  return self._entities_cache[entity_id]
44
55
 
45
- def perform_action(self, entity_id: str, action: EntityActions):
56
+ def perform_action(self, entity_id: str, action: EntityActions, *args, **kwargs):
46
57
  """
47
58
  Instantiates an entity of the given ID and calls the given action on it.
48
59
  """
49
60
  entity = self.get_entity(entity_id)
50
61
  if entity.supports(action):
51
- action_ctx = ActionContext(project_root=self.project_root())
52
- return entity.perform(action, action_ctx)
62
+ action_ctx = ActionContext(
63
+ console=cc,
64
+ project_root=self.project_root(),
65
+ default_role=self._default_role,
66
+ default_warehouse=self.default_warehouse,
67
+ get_entity=self.get_entity,
68
+ )
69
+ return entity.perform(action, action_ctx, *args, **kwargs)
53
70
  else:
54
71
  raise ValueError(f'This entity type does not support "{action.value}"')
55
72