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.
- snowflake/cli/__about__.py +1 -1
- snowflake/cli/_app/cli_app.py +10 -1
- snowflake/cli/_app/snow_connector.py +91 -37
- snowflake/cli/_app/telemetry.py +8 -4
- snowflake/cli/_app/version_check.py +74 -0
- snowflake/cli/_plugins/connection/commands.py +3 -2
- snowflake/cli/_plugins/git/commands.py +55 -14
- snowflake/cli/_plugins/git/manager.py +14 -6
- snowflake/cli/_plugins/nativeapp/codegen/compiler.py +18 -2
- snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +123 -42
- snowflake/cli/_plugins/nativeapp/codegen/setup/setup_driver.py.source +5 -2
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +6 -11
- snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +111 -0
- snowflake/cli/_plugins/nativeapp/exceptions.py +3 -3
- snowflake/cli/_plugins/nativeapp/manager.py +74 -144
- snowflake/cli/_plugins/nativeapp/project_model.py +2 -9
- snowflake/cli/_plugins/nativeapp/run_processor.py +56 -260
- snowflake/cli/_plugins/nativeapp/same_account_install_method.py +74 -0
- snowflake/cli/_plugins/nativeapp/teardown_processor.py +17 -246
- snowflake/cli/_plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +91 -17
- snowflake/cli/_plugins/snowpark/commands.py +5 -65
- snowflake/cli/_plugins/snowpark/common.py +17 -1
- snowflake/cli/_plugins/snowpark/models.py +2 -1
- snowflake/cli/_plugins/snowpark/package/anaconda_packages.py +1 -35
- snowflake/cli/_plugins/sql/commands.py +1 -2
- snowflake/cli/_plugins/stage/commands.py +2 -2
- snowflake/cli/_plugins/stage/manager.py +46 -15
- snowflake/cli/_plugins/streamlit/commands.py +4 -63
- snowflake/cli/_plugins/streamlit/manager.py +13 -0
- snowflake/cli/_plugins/workspace/action_context.py +7 -0
- snowflake/cli/_plugins/workspace/commands.py +145 -32
- snowflake/cli/_plugins/workspace/manager.py +21 -4
- snowflake/cli/api/cli_global_context.py +136 -313
- snowflake/cli/api/commands/decorators.py +1 -1
- snowflake/cli/api/commands/flags.py +106 -102
- snowflake/cli/api/commands/snow_typer.py +15 -6
- snowflake/cli/api/config.py +18 -5
- snowflake/cli/api/connections.py +214 -0
- snowflake/cli/api/console/abc.py +4 -2
- snowflake/cli/api/constants.py +11 -0
- snowflake/cli/api/entities/application_entity.py +687 -2
- snowflake/cli/api/entities/application_package_entity.py +407 -9
- snowflake/cli/api/entities/common.py +7 -2
- snowflake/cli/api/entities/utils.py +80 -20
- snowflake/cli/api/exceptions.py +12 -2
- snowflake/cli/api/feature_flags.py +0 -2
- snowflake/cli/api/identifiers.py +3 -0
- snowflake/cli/api/project/definition.py +35 -1
- snowflake/cli/api/project/definition_conversion.py +352 -0
- snowflake/cli/api/project/schemas/entities/application_package_entity_model.py +17 -0
- snowflake/cli/api/project/schemas/entities/common.py +0 -12
- snowflake/cli/api/project/schemas/identifier_model.py +2 -2
- snowflake/cli/api/project/schemas/project_definition.py +102 -43
- snowflake/cli/api/rendering/jinja.py +2 -16
- snowflake/cli/api/rendering/project_definition_templates.py +5 -1
- snowflake/cli/api/rendering/sql_templates.py +14 -4
- snowflake/cli/api/secure_path.py +13 -18
- snowflake/cli/api/secure_utils.py +90 -1
- snowflake/cli/api/sql_execution.py +13 -0
- snowflake/cli/api/utils/definition_rendering.py +7 -7
- {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/METADATA +9 -9
- {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/RECORD +65 -61
- snowflake/cli/api/commands/typer_pre_execute.py +0 -26
- {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/WHEEL +0 -0
- {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/entry_points.txt +0 -0
- {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
|
-
|
|
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 =
|
|
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
|
-
@
|
|
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
|
-
|
|
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.
|
|
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(
|
|
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.
|
|
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 =
|
|
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.
|
|
24
|
-
|
|
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
|
|
49
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
"""
|
|
90
|
-
|
|
91
|
-
|
|
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
|
|
101
|
+
def deploy(
|
|
97
102
|
entity_id: str = typer.Option(
|
|
98
|
-
help=f"""The ID of the entity you want to
|
|
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
|
-
|
|
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
|
-
|
|
113
|
-
|
|
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.
|
|
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(
|
|
52
|
-
|
|
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
|
|