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
|
@@ -15,273 +15,44 @@
|
|
|
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
|
-
EXTERNAL_DISTRIBUTION,
|
|
26
|
-
INTERNAL_DISTRIBUTION,
|
|
27
|
-
OWNER_COL,
|
|
28
|
-
)
|
|
29
|
-
from snowflake.cli._plugins.nativeapp.exceptions import (
|
|
30
|
-
CouldNotDropApplicationPackageWithVersions,
|
|
31
|
-
)
|
|
32
20
|
from snowflake.cli._plugins.nativeapp.manager import (
|
|
33
21
|
NativeAppCommandProcessor,
|
|
34
22
|
NativeAppManager,
|
|
35
23
|
)
|
|
36
|
-
from snowflake.cli._plugins.nativeapp.utils import (
|
|
37
|
-
needs_confirmation,
|
|
38
|
-
)
|
|
39
24
|
from snowflake.cli.api.console import cli_console as cc
|
|
40
|
-
from snowflake.cli.api.entities.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
from snowflake.
|
|
44
|
-
|
|
25
|
+
from snowflake.cli.api.entities.application_entity import (
|
|
26
|
+
ApplicationEntity,
|
|
27
|
+
)
|
|
28
|
+
from snowflake.cli.api.entities.application_package_entity import (
|
|
29
|
+
ApplicationPackageEntity,
|
|
30
|
+
)
|
|
45
31
|
|
|
46
32
|
|
|
47
33
|
class NativeAppTeardownProcessor(NativeAppManager, NativeAppCommandProcessor):
|
|
48
34
|
def __init__(self, project_definition: Dict, project_root: Path):
|
|
49
35
|
super().__init__(project_definition, project_root)
|
|
50
36
|
|
|
51
|
-
def drop_generic_object(
|
|
52
|
-
self, object_type: str, object_name: str, role: str, cascade: bool = False
|
|
53
|
-
):
|
|
54
|
-
"""
|
|
55
|
-
Drop object using the given role.
|
|
56
|
-
"""
|
|
57
|
-
with self.use_role(role):
|
|
58
|
-
cc.step(f"Dropping {object_type} {object_name} now.")
|
|
59
|
-
drop_query = f"drop {object_type} {object_name}"
|
|
60
|
-
if cascade:
|
|
61
|
-
drop_query += " cascade"
|
|
62
|
-
try:
|
|
63
|
-
self._execute_query(drop_query)
|
|
64
|
-
except:
|
|
65
|
-
raise SnowflakeSQLExecutionError(drop_query)
|
|
66
|
-
|
|
67
|
-
cc.message(f"Dropped {object_type} {object_name} successfully.")
|
|
68
|
-
|
|
69
37
|
def drop_application(
|
|
70
38
|
self, auto_yes: bool, interactive: bool = False, cascade: Optional[bool] = None
|
|
71
39
|
):
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
# 1. If existing application is not found, exit gracefully
|
|
79
|
-
show_obj_row = self.get_existing_app_info()
|
|
80
|
-
if show_obj_row is None:
|
|
81
|
-
cc.warning(
|
|
82
|
-
f"Role {self.app_role} does not own any application object with the name {self.app_name}, or the application object does not exist."
|
|
83
|
-
)
|
|
84
|
-
return
|
|
85
|
-
|
|
86
|
-
# 2. Check for the right owner
|
|
87
|
-
ensure_correct_owner(
|
|
88
|
-
row=show_obj_row, role=self.app_role, obj_name=self.app_name
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
# 3. Check if created by the Snowflake CLI
|
|
92
|
-
row_comment = show_obj_row[COMMENT_COL]
|
|
93
|
-
if row_comment not in ALLOWED_SPECIAL_COMMENTS and needs_confirmation(
|
|
94
|
-
needs_confirm, auto_yes
|
|
95
|
-
):
|
|
96
|
-
should_drop_object = typer.confirm(
|
|
97
|
-
dedent(
|
|
98
|
-
f"""\
|
|
99
|
-
Application object {self.app_name} was not created by Snowflake CLI.
|
|
100
|
-
Application object details:
|
|
101
|
-
Name: {self.app_name}
|
|
102
|
-
Created on: {show_obj_row["created_on"]}
|
|
103
|
-
Source: {show_obj_row["source"]}
|
|
104
|
-
Owner: {show_obj_row[OWNER_COL]}
|
|
105
|
-
Comment: {show_obj_row[COMMENT_COL]}
|
|
106
|
-
Version: {show_obj_row["version"]}
|
|
107
|
-
Patch: {show_obj_row["patch"]}
|
|
108
|
-
Are you sure you want to drop it?
|
|
109
|
-
"""
|
|
110
|
-
)
|
|
111
|
-
)
|
|
112
|
-
if not should_drop_object:
|
|
113
|
-
cc.message(f"Did not drop application object {self.app_name}.")
|
|
114
|
-
# The user desires to keep the app, therefore we can't proceed since it would
|
|
115
|
-
# leave behind an orphan app when we get to dropping the package
|
|
116
|
-
raise typer.Abort()
|
|
117
|
-
|
|
118
|
-
# 4. Check for application objects owned by the application
|
|
119
|
-
# This query will fail if the application package has already been dropped, so handle this case gracefully
|
|
120
|
-
has_objects_to_drop = False
|
|
121
|
-
message_prefix = ""
|
|
122
|
-
cascade_true_message = ""
|
|
123
|
-
cascade_false_message = ""
|
|
124
|
-
interactive_prompt = ""
|
|
125
|
-
non_interactive_abort = ""
|
|
126
|
-
try:
|
|
127
|
-
if application_objects := self.get_objects_owned_by_application():
|
|
128
|
-
has_objects_to_drop = True
|
|
129
|
-
message_prefix = (
|
|
130
|
-
f"The following objects are owned by application {self.app_name}"
|
|
131
|
-
)
|
|
132
|
-
cascade_true_message = f"{message_prefix} and will be dropped:"
|
|
133
|
-
cascade_false_message = f"{message_prefix} and will NOT be dropped:"
|
|
134
|
-
interactive_prompt = "Would you like to drop these objects in addition to the application? [y/n/ABORT]"
|
|
135
|
-
non_interactive_abort = "Re-run teardown again with --cascade or --no-cascade to specify whether these objects should be dropped along with the application"
|
|
136
|
-
except ProgrammingError as e:
|
|
137
|
-
if e.errno != APPLICATION_NO_LONGER_AVAILABLE:
|
|
138
|
-
raise
|
|
139
|
-
application_objects = []
|
|
140
|
-
message_prefix = f"Could not determine which objects are owned by application {self.app_name}"
|
|
141
|
-
has_objects_to_drop = True # potentially, but we don't know what they are
|
|
142
|
-
cascade_true_message = (
|
|
143
|
-
f"{message_prefix}, an unknown number of objects will be dropped."
|
|
144
|
-
)
|
|
145
|
-
cascade_false_message = f"{message_prefix}, they will NOT be dropped."
|
|
146
|
-
interactive_prompt = f"Would you like to drop an unknown set of objects in addition to the application? [y/n/ABORT]"
|
|
147
|
-
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."
|
|
148
|
-
|
|
149
|
-
if has_objects_to_drop:
|
|
150
|
-
if cascade is True:
|
|
151
|
-
# If the user explicitly passed the --cascade flag
|
|
152
|
-
cc.message(cascade_true_message)
|
|
153
|
-
with cc.indented():
|
|
154
|
-
for obj in application_objects:
|
|
155
|
-
cc.message(self._application_object_to_str(obj))
|
|
156
|
-
elif cascade is False:
|
|
157
|
-
# If the user explicitly passed the --no-cascade flag
|
|
158
|
-
cc.message(cascade_false_message)
|
|
159
|
-
with cc.indented():
|
|
160
|
-
for obj in application_objects:
|
|
161
|
-
cc.message(self._application_object_to_str(obj))
|
|
162
|
-
elif interactive:
|
|
163
|
-
# If the user didn't pass any cascade flag and the session is interactive
|
|
164
|
-
cc.message(message_prefix)
|
|
165
|
-
with cc.indented():
|
|
166
|
-
for obj in application_objects:
|
|
167
|
-
cc.message(self._application_object_to_str(obj))
|
|
168
|
-
user_response = typer.prompt(
|
|
169
|
-
interactive_prompt,
|
|
170
|
-
show_default=False,
|
|
171
|
-
default="ABORT",
|
|
172
|
-
).lower()
|
|
173
|
-
if user_response in ["y", "yes"]:
|
|
174
|
-
cascade = True
|
|
175
|
-
elif user_response in ["n", "no"]:
|
|
176
|
-
cascade = False
|
|
177
|
-
else:
|
|
178
|
-
raise typer.Abort()
|
|
179
|
-
else:
|
|
180
|
-
# Else abort since we don't know what to do and can't ask the user
|
|
181
|
-
cc.message(message_prefix)
|
|
182
|
-
with cc.indented():
|
|
183
|
-
for obj in application_objects:
|
|
184
|
-
cc.message(self._application_object_to_str(obj))
|
|
185
|
-
cc.message(non_interactive_abort)
|
|
186
|
-
raise typer.Abort()
|
|
187
|
-
elif cascade is None:
|
|
188
|
-
# If there's nothing to drop, set cascade to an explicit False value
|
|
189
|
-
cascade = False
|
|
190
|
-
|
|
191
|
-
# 5. All validations have passed, drop object
|
|
192
|
-
self.drop_generic_object(
|
|
193
|
-
object_type="application",
|
|
194
|
-
object_name=self.app_name,
|
|
195
|
-
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,
|
|
196
46
|
cascade=cascade,
|
|
197
47
|
)
|
|
198
|
-
return # The application object was successfully dropped, therefore exit gracefully
|
|
199
48
|
|
|
200
49
|
def drop_package(self, auto_yes: bool):
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
# 1. If existing application package is not found, exit gracefully
|
|
207
|
-
show_obj_row = self.get_existing_app_pkg_info()
|
|
208
|
-
if show_obj_row is None:
|
|
209
|
-
cc.warning(
|
|
210
|
-
f"Role {self.package_role} does not own any application package with the name {self.package_name}, or the application package does not exist."
|
|
211
|
-
)
|
|
212
|
-
return
|
|
213
|
-
|
|
214
|
-
# 2. Check for the right owner
|
|
215
|
-
ensure_correct_owner(
|
|
216
|
-
row=show_obj_row, role=self.package_role, obj_name=self.package_name
|
|
217
|
-
)
|
|
218
|
-
|
|
219
|
-
with self.use_role(self.package_role):
|
|
220
|
-
# 3. Check for versions in the application package
|
|
221
|
-
show_versions_query = (
|
|
222
|
-
f"show versions in application package {self.package_name}"
|
|
223
|
-
)
|
|
224
|
-
show_versions_cursor = self._execute_query(
|
|
225
|
-
show_versions_query, cursor_class=DictCursor
|
|
226
|
-
)
|
|
227
|
-
if show_versions_cursor.rowcount is None:
|
|
228
|
-
raise SnowflakeSQLExecutionError(show_versions_query)
|
|
229
|
-
|
|
230
|
-
if show_versions_cursor.rowcount > 0:
|
|
231
|
-
# allow dropping a package with versions when --force is set
|
|
232
|
-
if not auto_yes:
|
|
233
|
-
raise CouldNotDropApplicationPackageWithVersions(
|
|
234
|
-
"Drop versions first, or use --force to override."
|
|
235
|
-
)
|
|
236
|
-
|
|
237
|
-
# 4. Check distribution of the existing application package
|
|
238
|
-
actual_distribution = self.get_app_pkg_distribution_in_snowflake
|
|
239
|
-
if not self.verify_project_distribution(actual_distribution):
|
|
240
|
-
cc.warning(
|
|
241
|
-
f"Continuing to execute `snow app teardown` on application package {self.package_name} with distribution '{actual_distribution}'."
|
|
242
|
-
)
|
|
243
|
-
|
|
244
|
-
# 5. If distribution is internal, check if created by the Snowflake CLI
|
|
245
|
-
row_comment = show_obj_row[COMMENT_COL]
|
|
246
|
-
if actual_distribution == INTERNAL_DISTRIBUTION:
|
|
247
|
-
if row_comment in ALLOWED_SPECIAL_COMMENTS:
|
|
248
|
-
needs_confirm = False
|
|
249
|
-
else:
|
|
250
|
-
if needs_confirmation(needs_confirm, auto_yes):
|
|
251
|
-
cc.warning(
|
|
252
|
-
f"Application package {self.package_name} was not created by Snowflake CLI."
|
|
253
|
-
)
|
|
254
|
-
else:
|
|
255
|
-
if needs_confirmation(needs_confirm, auto_yes):
|
|
256
|
-
cc.warning(
|
|
257
|
-
f"Application package {self.package_name} in your Snowflake account has distribution property '{EXTERNAL_DISTRIBUTION}' and could be associated with one or more of your listings on Snowflake Marketplace."
|
|
258
|
-
)
|
|
259
|
-
|
|
260
|
-
if needs_confirmation(needs_confirm, auto_yes):
|
|
261
|
-
should_drop_object = typer.confirm(
|
|
262
|
-
dedent(
|
|
263
|
-
f"""\
|
|
264
|
-
Application package details:
|
|
265
|
-
Name: {self.app_name}
|
|
266
|
-
Created on: {show_obj_row["created_on"]}
|
|
267
|
-
Distribution: {actual_distribution}
|
|
268
|
-
Owner: {show_obj_row[OWNER_COL]}
|
|
269
|
-
Comment: {show_obj_row[COMMENT_COL]}
|
|
270
|
-
Are you sure you want to drop it?
|
|
271
|
-
"""
|
|
272
|
-
)
|
|
273
|
-
)
|
|
274
|
-
if not should_drop_object:
|
|
275
|
-
cc.message(f"Did not drop application package {self.package_name}.")
|
|
276
|
-
return # The user desires to keep the application package, therefore exit gracefully
|
|
277
|
-
|
|
278
|
-
# All validations have passed, drop object
|
|
279
|
-
self.drop_generic_object(
|
|
280
|
-
object_type="application package",
|
|
281
|
-
object_name=self.package_name,
|
|
282
|
-
role=self.package_role,
|
|
50
|
+
return ApplicationPackageEntity.drop(
|
|
51
|
+
console=cc,
|
|
52
|
+
package_name=self.package_name,
|
|
53
|
+
package_role=self.package_role,
|
|
54
|
+
force_drop=auto_yes,
|
|
283
55
|
)
|
|
284
|
-
return # The application package was successfully dropped, therefore exit gracefully
|
|
285
56
|
|
|
286
57
|
def process(
|
|
287
58
|
self,
|
|
@@ -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(
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
151
|
-
|
|
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
|
|
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
|
+
)
|
|
@@ -75,7 +75,6 @@ from snowflake.cli.api.constants import (
|
|
|
75
75
|
DEFAULT_SIZE_LIMIT_MB,
|
|
76
76
|
)
|
|
77
77
|
from snowflake.cli.api.exceptions import (
|
|
78
|
-
NoProjectDefinitionError,
|
|
79
78
|
SecretsWithoutExternalAccessIntegrationError,
|
|
80
79
|
)
|
|
81
80
|
from snowflake.cli.api.identifiers import FQN
|
|
@@ -85,6 +84,9 @@ from snowflake.cli.api.output.types import (
|
|
|
85
84
|
MessageResult,
|
|
86
85
|
SingleQueryResult,
|
|
87
86
|
)
|
|
87
|
+
from snowflake.cli.api.project.definition_conversion import (
|
|
88
|
+
convert_project_definition_to_v2,
|
|
89
|
+
)
|
|
88
90
|
from snowflake.cli.api.project.schemas.entities.snowpark_entity import (
|
|
89
91
|
FunctionEntityModel,
|
|
90
92
|
ProcedureEntityModel,
|
|
@@ -93,10 +95,6 @@ from snowflake.cli.api.project.schemas.project_definition import (
|
|
|
93
95
|
ProjectDefinition,
|
|
94
96
|
ProjectDefinitionV2,
|
|
95
97
|
)
|
|
96
|
-
from snowflake.cli.api.project.schemas.snowpark.callable import (
|
|
97
|
-
FunctionSchema,
|
|
98
|
-
ProcedureSchema,
|
|
99
|
-
)
|
|
100
98
|
from snowflake.cli.api.secure_path import SecurePath
|
|
101
99
|
from snowflake.connector import DictCursor, ProgrammingError
|
|
102
100
|
from snowflake.connector.cursor import SnowflakeCursor
|
|
@@ -123,7 +121,7 @@ LikeOption = like_option(
|
|
|
123
121
|
)
|
|
124
122
|
|
|
125
123
|
|
|
126
|
-
@app.command("deploy", requires_connection=True)
|
|
124
|
+
@app.command("deploy", requires_connection=True, require_warehouse=True)
|
|
127
125
|
@with_project_definition()
|
|
128
126
|
def deploy(
|
|
129
127
|
replace: bool = ReplaceOption(
|
|
@@ -445,66 +443,8 @@ def describe(
|
|
|
445
443
|
)
|
|
446
444
|
|
|
447
445
|
|
|
448
|
-
def migrate_v1_snowpark_to_v2(pd: ProjectDefinition):
|
|
449
|
-
if not pd.snowpark:
|
|
450
|
-
raise NoProjectDefinitionError(
|
|
451
|
-
project_type="snowpark", project_root=get_cli_context().project_root
|
|
452
|
-
)
|
|
453
|
-
|
|
454
|
-
artifact_mapping = {"src": pd.snowpark.src}
|
|
455
|
-
if pd.snowpark.project_name:
|
|
456
|
-
artifact_mapping["dest"] = pd.snowpark.project_name
|
|
457
|
-
|
|
458
|
-
snowpark_shared_mixin = "snowpark_shared"
|
|
459
|
-
data: dict = {
|
|
460
|
-
"definition_version": "2",
|
|
461
|
-
"mixins": {
|
|
462
|
-
snowpark_shared_mixin: {
|
|
463
|
-
"stage": pd.snowpark.stage_name,
|
|
464
|
-
"artifacts": [artifact_mapping],
|
|
465
|
-
}
|
|
466
|
-
},
|
|
467
|
-
"entities": {},
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
for index, entity in enumerate([*pd.snowpark.procedures, *pd.snowpark.functions]):
|
|
471
|
-
identifier = {"name": entity.name}
|
|
472
|
-
if entity.database is not None:
|
|
473
|
-
identifier["database"] = entity.database
|
|
474
|
-
if entity.schema_name is not None:
|
|
475
|
-
identifier["schema"] = entity.schema_name
|
|
476
|
-
|
|
477
|
-
if entity.name.startswith("<%") and entity.name.endswith("%>"):
|
|
478
|
-
entity_name = f"snowpark_entity_{index}"
|
|
479
|
-
else:
|
|
480
|
-
entity_name = entity.name
|
|
481
|
-
|
|
482
|
-
v2_entity = {
|
|
483
|
-
"type": "function" if isinstance(entity, FunctionSchema) else "procedure",
|
|
484
|
-
"stage": pd.snowpark.stage_name,
|
|
485
|
-
"handler": entity.handler,
|
|
486
|
-
"returns": entity.returns,
|
|
487
|
-
"signature": entity.signature,
|
|
488
|
-
"runtime": entity.runtime,
|
|
489
|
-
"external_access_integrations": entity.external_access_integrations,
|
|
490
|
-
"secrets": entity.secrets,
|
|
491
|
-
"imports": entity.imports,
|
|
492
|
-
"identifier": identifier,
|
|
493
|
-
"meta": {"use_mixins": [snowpark_shared_mixin]},
|
|
494
|
-
}
|
|
495
|
-
if isinstance(entity, ProcedureSchema):
|
|
496
|
-
v2_entity["execute_as_caller"] = entity.execute_as_caller
|
|
497
|
-
|
|
498
|
-
data["entities"][entity_name] = v2_entity
|
|
499
|
-
|
|
500
|
-
if hasattr(pd, "env") and pd.env:
|
|
501
|
-
data["env"] = {k: v for k, v in pd.env.items()}
|
|
502
|
-
|
|
503
|
-
return ProjectDefinitionV2(**data)
|
|
504
|
-
|
|
505
|
-
|
|
506
446
|
def _get_v2_project_definition(cli_context) -> ProjectDefinitionV2:
|
|
507
447
|
pd = cli_context.project_definition
|
|
508
448
|
if not pd.meets_version_requirement("2"):
|
|
509
|
-
pd =
|
|
449
|
+
pd = convert_project_definition_to_v2(cli_context.project_root, pd)
|
|
510
450
|
return pd
|
|
@@ -23,7 +23,13 @@ from click import UsageError
|
|
|
23
23
|
from snowflake.cli._plugins.snowpark.models import Requirement
|
|
24
24
|
from snowflake.cli._plugins.snowpark.snowpark_project_paths import Artefact
|
|
25
25
|
from snowflake.cli.api.console import cli_console
|
|
26
|
-
from snowflake.cli.api.constants import
|
|
26
|
+
from snowflake.cli.api.constants import (
|
|
27
|
+
INIT_TEMPLATE_VARIABLE_CLOSING,
|
|
28
|
+
INIT_TEMPLATE_VARIABLE_OPENING,
|
|
29
|
+
PROJECT_TEMPLATE_VARIABLE_CLOSING,
|
|
30
|
+
PROJECT_TEMPLATE_VARIABLE_OPENING,
|
|
31
|
+
ObjectType,
|
|
32
|
+
)
|
|
27
33
|
from snowflake.cli.api.project.schemas.entities.snowpark_entity import (
|
|
28
34
|
ProcedureEntityModel,
|
|
29
35
|
SnowparkEntityModel,
|
|
@@ -250,3 +256,13 @@ def _compare_imports(
|
|
|
250
256
|
}
|
|
251
257
|
|
|
252
258
|
return project_imports != object_imports
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def is_name_a_templated_one(name: str) -> bool:
|
|
262
|
+
return (
|
|
263
|
+
PROJECT_TEMPLATE_VARIABLE_OPENING in name
|
|
264
|
+
and PROJECT_TEMPLATE_VARIABLE_CLOSING in name
|
|
265
|
+
) or (
|
|
266
|
+
INIT_TEMPLATE_VARIABLE_OPENING in name
|
|
267
|
+
and INIT_TEMPLATE_VARIABLE_CLOSING in name
|
|
268
|
+
)
|
|
@@ -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:
|
|
@@ -18,12 +18,9 @@ import logging
|
|
|
18
18
|
from dataclasses import dataclass
|
|
19
19
|
from typing import Dict, List, Set
|
|
20
20
|
|
|
21
|
-
import requests
|
|
22
|
-
from click import ClickException
|
|
23
21
|
from packaging.requirements import InvalidRequirement
|
|
24
22
|
from packaging.requirements import Requirement as PkgRequirement
|
|
25
23
|
from packaging.version import InvalidVersion, parse
|
|
26
|
-
from requests import HTTPError
|
|
27
24
|
from snowflake.cli._plugins.snowpark.models import Requirement
|
|
28
25
|
from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
|
|
29
26
|
from snowflake.cli.api.secure_path import SecurePath
|
|
@@ -170,23 +167,13 @@ class AnacondaPackagesManager(SqlExecutionMixin):
|
|
|
170
167
|
"https://repo.anaconda.com/pkgs/snowflake/channeldata.json"
|
|
171
168
|
)
|
|
172
169
|
|
|
173
|
-
# TODO in v3.0: Keep only SQL query, remove fallback to JSON with channel's metadata
|
|
174
170
|
def find_packages_available_in_snowflake_anaconda(self) -> AnacondaPackages:
|
|
175
171
|
"""
|
|
176
172
|
Finds python packages available in Snowflake to use in functions and stored procedures.
|
|
177
173
|
It tries to get the list of packages using SQL query
|
|
178
174
|
but if the try fails then the fallback is to parse JSON containing info about Snowflake's Anaconda channel.
|
|
179
175
|
"""
|
|
180
|
-
|
|
181
|
-
packages = self._query_snowflake_for_available_packages()
|
|
182
|
-
except Exception as ex:
|
|
183
|
-
log.warning(
|
|
184
|
-
"Cannot fetch available packages information from Snowflake. "
|
|
185
|
-
"Please check your connection configuration. "
|
|
186
|
-
"Fallback to Anaconda channel metadata."
|
|
187
|
-
)
|
|
188
|
-
log.debug("Available packages query failure: %s", ex.__str__(), exc_info=ex)
|
|
189
|
-
packages = self._get_available_packages_from_anaconda_channel_info()
|
|
176
|
+
packages = self._query_snowflake_for_available_packages()
|
|
190
177
|
return AnacondaPackages(packages)
|
|
191
178
|
|
|
192
179
|
def _query_snowflake_for_available_packages(self) -> dict[str, AvailablePackage]:
|
|
@@ -210,24 +197,3 @@ class AnacondaPackagesManager(SqlExecutionMixin):
|
|
|
210
197
|
snowflake_name=package_name, versions={version}
|
|
211
198
|
)
|
|
212
199
|
return packages
|
|
213
|
-
|
|
214
|
-
def _get_available_packages_from_anaconda_channel_info(
|
|
215
|
-
self,
|
|
216
|
-
) -> dict[str, AvailablePackage]:
|
|
217
|
-
try:
|
|
218
|
-
response = requests.get(self._snowflake_channel_url)
|
|
219
|
-
response.raise_for_status()
|
|
220
|
-
packages = {}
|
|
221
|
-
for key, package in response.json()["packages"].items():
|
|
222
|
-
if not (version := package.get("version")):
|
|
223
|
-
continue
|
|
224
|
-
package_name = package.get("name", key)
|
|
225
|
-
standardized_name = Requirement.standardize_name(package_name)
|
|
226
|
-
packages[standardized_name] = AvailablePackage(
|
|
227
|
-
snowflake_name=package_name, versions={version}
|
|
228
|
-
)
|
|
229
|
-
return packages
|
|
230
|
-
except HTTPError as err:
|
|
231
|
-
raise ClickException(
|
|
232
|
-
f"Accessing Snowflake Anaconda channel failed. Reason {err}"
|
|
233
|
-
)
|
|
@@ -73,8 +73,7 @@ def execute_sql(
|
|
|
73
73
|
Query to execute can be specified using query option, filename option (all queries from file will be executed)
|
|
74
74
|
or via stdin by piping output from other command. For example `cat my.sql | snow sql -i`.
|
|
75
75
|
|
|
76
|
-
The command supports variable substitution that happens on client-side.
|
|
77
|
-
syntax are supported.
|
|
76
|
+
The command supports variable substitution that happens on client-side.
|
|
78
77
|
"""
|
|
79
78
|
|
|
80
79
|
data = {}
|