snowflake-cli-labs 3.0.0rc0__py3-none-any.whl → 3.0.0rc1__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/snow_connector.py +18 -11
- snowflake/cli/_plugins/connection/commands.py +3 -2
- 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 +4 -6
- snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +93 -0
- snowflake/cli/_plugins/nativeapp/exceptions.py +3 -3
- snowflake/cli/_plugins/nativeapp/manager.py +29 -58
- snowflake/cli/_plugins/nativeapp/project_model.py +2 -9
- snowflake/cli/_plugins/nativeapp/teardown_processor.py +19 -105
- snowflake/cli/_plugins/snowpark/commands.py +5 -65
- snowflake/cli/_plugins/snowpark/common.py +17 -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 +4 -0
- snowflake/cli/_plugins/workspace/action_context.py +6 -0
- snowflake/cli/_plugins/workspace/commands.py +103 -22
- snowflake/cli/_plugins/workspace/manager.py +20 -4
- snowflake/cli/api/cli_global_context.py +6 -6
- snowflake/cli/api/commands/decorators.py +1 -1
- snowflake/cli/api/commands/flags.py +31 -12
- snowflake/cli/api/commands/snow_typer.py +9 -2
- snowflake/cli/api/config.py +17 -4
- snowflake/cli/api/constants.py +11 -0
- snowflake/cli/api/entities/application_package_entity.py +296 -3
- snowflake/cli/api/entities/common.py +6 -2
- snowflake/cli/api/entities/utils.py +46 -10
- snowflake/cli/api/exceptions.py +12 -2
- snowflake/cli/api/feature_flags.py +0 -2
- snowflake/cli/api/project/definition.py +24 -1
- snowflake/cli/api/project/definition_conversion.py +194 -0
- snowflake/cli/api/project/schemas/entities/application_package_entity_model.py +17 -0
- snowflake/cli/api/project/schemas/project_definition.py +1 -4
- snowflake/cli/api/rendering/jinja.py +2 -16
- snowflake/cli/api/rendering/project_definition_templates.py +1 -1
- snowflake/cli/api/rendering/sql_templates.py +7 -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 +4 -6
- {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc1.dist-info}/METADATA +5 -5
- {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc1.dist-info}/RECORD +51 -49
- {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc1.dist-info}/WHEEL +0 -0
- {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc1.dist-info}/entry_points.txt +0 -0
- {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc1.dist-info}/licenses/LICENSE +0 -0
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
-
import json
|
|
18
17
|
import time
|
|
19
18
|
from abc import ABC, abstractmethod
|
|
20
19
|
from contextlib import contextmanager
|
|
@@ -37,9 +36,7 @@ from snowflake.cli._plugins.nativeapp.constants import (
|
|
|
37
36
|
NAME_COL,
|
|
38
37
|
)
|
|
39
38
|
from snowflake.cli._plugins.nativeapp.exceptions import (
|
|
40
|
-
ApplicationPackageDoesNotExistError,
|
|
41
39
|
NoEventTableForAccount,
|
|
42
|
-
SetupScriptFailedValidation,
|
|
43
40
|
)
|
|
44
41
|
from snowflake.cli._plugins.nativeapp.project_model import (
|
|
45
42
|
NativeAppProjectModel,
|
|
@@ -47,7 +44,6 @@ from snowflake.cli._plugins.nativeapp.project_model import (
|
|
|
47
44
|
from snowflake.cli._plugins.stage.diff import (
|
|
48
45
|
DiffResult,
|
|
49
46
|
)
|
|
50
|
-
from snowflake.cli._plugins.stage.manager import StageManager
|
|
51
47
|
from snowflake.cli.api.console import cli_console as cc
|
|
52
48
|
from snowflake.cli.api.entities.application_package_entity import (
|
|
53
49
|
ApplicationPackageEntity,
|
|
@@ -57,10 +53,6 @@ from snowflake.cli.api.entities.utils import (
|
|
|
57
53
|
generic_sql_error_handler,
|
|
58
54
|
sync_deploy_root_with_stage,
|
|
59
55
|
)
|
|
60
|
-
from snowflake.cli.api.errno import (
|
|
61
|
-
DOES_NOT_EXIST_OR_NOT_AUTHORIZED,
|
|
62
|
-
)
|
|
63
|
-
from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
|
|
64
56
|
from snowflake.cli.api.project.schemas.entities.common import PostDeployHook
|
|
65
57
|
from snowflake.cli.api.project.schemas.native_app.native_app import NativeApp
|
|
66
58
|
from snowflake.cli.api.project.schemas.native_app.path_mapping import PathMapping
|
|
@@ -381,59 +373,38 @@ class NativeAppManager(SqlExecutionMixin):
|
|
|
381
373
|
|
|
382
374
|
return diff
|
|
383
375
|
|
|
384
|
-
def
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
for error in validation_result.get("errors", []):
|
|
395
|
-
# Print them as warnings for now since we're going to be
|
|
396
|
-
# revamping CLI output soon
|
|
397
|
-
cc.warning(_validation_item_to_str(error))
|
|
376
|
+
def deploy_to_scratch_stage_fn(self):
|
|
377
|
+
bundle_map = self.build_bundle()
|
|
378
|
+
self.deploy(
|
|
379
|
+
bundle_map=bundle_map,
|
|
380
|
+
prune=True,
|
|
381
|
+
recursive=True,
|
|
382
|
+
stage_fqn=self.scratch_stage_fqn,
|
|
383
|
+
validate=False,
|
|
384
|
+
print_diff=False,
|
|
385
|
+
)
|
|
398
386
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
387
|
+
def validate(self, use_scratch_stage: bool = False):
|
|
388
|
+
return ApplicationPackageEntity.validate_setup_script(
|
|
389
|
+
console=cc,
|
|
390
|
+
package_name=self.package_name,
|
|
391
|
+
package_role=self.package_role,
|
|
392
|
+
stage_fqn=self.stage_fqn,
|
|
393
|
+
use_scratch_stage=use_scratch_stage,
|
|
394
|
+
scratch_stage_fqn=self.scratch_stage_fqn,
|
|
395
|
+
deploy_to_scratch_stage_fn=self.deploy_to_scratch_stage_fn,
|
|
396
|
+
)
|
|
402
397
|
|
|
403
398
|
def get_validation_result(self, use_scratch_stage: bool):
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
stage_fqn=stage_fqn,
|
|
414
|
-
validate=False,
|
|
415
|
-
print_diff=False,
|
|
416
|
-
)
|
|
417
|
-
prefixed_stage_fqn = StageManager.get_standard_stage_prefix(stage_fqn)
|
|
418
|
-
try:
|
|
419
|
-
cursor = self._execute_query(
|
|
420
|
-
f"call system$validate_native_app_setup('{prefixed_stage_fqn}')"
|
|
421
|
-
)
|
|
422
|
-
except ProgrammingError as err:
|
|
423
|
-
if err.errno == DOES_NOT_EXIST_OR_NOT_AUTHORIZED:
|
|
424
|
-
raise ApplicationPackageDoesNotExistError(self.package_name)
|
|
425
|
-
generic_sql_error_handler(err)
|
|
426
|
-
else:
|
|
427
|
-
if not cursor.rowcount:
|
|
428
|
-
raise SnowflakeSQLExecutionError()
|
|
429
|
-
return json.loads(cursor.fetchone()[0])
|
|
430
|
-
finally:
|
|
431
|
-
if use_scratch_stage:
|
|
432
|
-
cc.step(f"Dropping stage {self.scratch_stage_fqn}.")
|
|
433
|
-
with self.use_role(self.package_role):
|
|
434
|
-
self._execute_query(
|
|
435
|
-
f"drop stage if exists {self.scratch_stage_fqn}"
|
|
436
|
-
)
|
|
399
|
+
return ApplicationPackageEntity.get_validation_result(
|
|
400
|
+
console=cc,
|
|
401
|
+
package_name=self.package_name,
|
|
402
|
+
package_role=self.package_role,
|
|
403
|
+
stage_fqn=self.stage_fqn,
|
|
404
|
+
use_scratch_stage=use_scratch_stage,
|
|
405
|
+
scratch_stage_fqn=self.scratch_stage_fqn,
|
|
406
|
+
deploy_to_scratch_stage_fn=self.deploy_to_scratch_stage_fn,
|
|
407
|
+
)
|
|
437
408
|
|
|
438
409
|
def get_events( # type: ignore [return]
|
|
439
410
|
self,
|
|
@@ -21,6 +21,7 @@ from typing import List, Optional
|
|
|
21
21
|
from snowflake.cli._plugins.nativeapp.artifacts import resolve_without_follow
|
|
22
22
|
from snowflake.cli._plugins.nativeapp.bundle_context import BundleContext
|
|
23
23
|
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
24
|
+
from snowflake.cli.api.entities.common import get_sql_executor
|
|
24
25
|
from snowflake.cli.api.project.definition import (
|
|
25
26
|
default_app_package,
|
|
26
27
|
default_application,
|
|
@@ -34,14 +35,6 @@ from snowflake.cli.api.project.util import (
|
|
|
34
35
|
extract_schema,
|
|
35
36
|
to_identifier,
|
|
36
37
|
)
|
|
37
|
-
from snowflake.connector import DictCursor
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def current_role() -> str:
|
|
41
|
-
conn = get_cli_context().connection
|
|
42
|
-
*_, cursor = conn.execute_string("select current_role()", cursor_class=DictCursor)
|
|
43
|
-
role_result = cursor.fetchone()
|
|
44
|
-
return role_result["CURRENT_ROLE()"]
|
|
45
38
|
|
|
46
39
|
|
|
47
40
|
class NativeAppProjectModel:
|
|
@@ -198,7 +191,7 @@ class NativeAppProjectModel:
|
|
|
198
191
|
def _default_role(self) -> str:
|
|
199
192
|
role = default_role()
|
|
200
193
|
if role is None:
|
|
201
|
-
role = current_role()
|
|
194
|
+
role = get_sql_executor().current_role()
|
|
202
195
|
return role
|
|
203
196
|
|
|
204
197
|
@cached_property
|
|
@@ -22,13 +22,8 @@ import typer
|
|
|
22
22
|
from snowflake.cli._plugins.nativeapp.constants import (
|
|
23
23
|
ALLOWED_SPECIAL_COMMENTS,
|
|
24
24
|
COMMENT_COL,
|
|
25
|
-
EXTERNAL_DISTRIBUTION,
|
|
26
|
-
INTERNAL_DISTRIBUTION,
|
|
27
25
|
OWNER_COL,
|
|
28
26
|
)
|
|
29
|
-
from snowflake.cli._plugins.nativeapp.exceptions import (
|
|
30
|
-
CouldNotDropApplicationPackageWithVersions,
|
|
31
|
-
)
|
|
32
27
|
from snowflake.cli._plugins.nativeapp.manager import (
|
|
33
28
|
NativeAppCommandProcessor,
|
|
34
29
|
NativeAppManager,
|
|
@@ -37,11 +32,15 @@ from snowflake.cli._plugins.nativeapp.utils import (
|
|
|
37
32
|
needs_confirmation,
|
|
38
33
|
)
|
|
39
34
|
from snowflake.cli.api.console import cli_console as cc
|
|
40
|
-
from snowflake.cli.api.entities.
|
|
35
|
+
from snowflake.cli.api.entities.application_package_entity import (
|
|
36
|
+
ApplicationPackageEntity,
|
|
37
|
+
)
|
|
38
|
+
from snowflake.cli.api.entities.utils import (
|
|
39
|
+
drop_generic_object,
|
|
40
|
+
ensure_correct_owner,
|
|
41
|
+
)
|
|
41
42
|
from snowflake.cli.api.errno import APPLICATION_NO_LONGER_AVAILABLE
|
|
42
|
-
from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
|
|
43
43
|
from snowflake.connector import ProgrammingError
|
|
44
|
-
from snowflake.connector.cursor import DictCursor
|
|
45
44
|
|
|
46
45
|
|
|
47
46
|
class NativeAppTeardownProcessor(NativeAppManager, NativeAppCommandProcessor):
|
|
@@ -51,20 +50,13 @@ class NativeAppTeardownProcessor(NativeAppManager, NativeAppCommandProcessor):
|
|
|
51
50
|
def drop_generic_object(
|
|
52
51
|
self, object_type: str, object_name: str, role: str, cascade: bool = False
|
|
53
52
|
):
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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.")
|
|
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
|
+
)
|
|
68
60
|
|
|
69
61
|
def drop_application(
|
|
70
62
|
self, auto_yes: bool, interactive: bool = False, cascade: Optional[bool] = None
|
|
@@ -198,90 +190,12 @@ class NativeAppTeardownProcessor(NativeAppManager, NativeAppCommandProcessor):
|
|
|
198
190
|
return # The application object was successfully dropped, therefore exit gracefully
|
|
199
191
|
|
|
200
192
|
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,
|
|
193
|
+
return ApplicationPackageEntity.drop(
|
|
194
|
+
console=cc,
|
|
195
|
+
package_name=self.package_name,
|
|
196
|
+
package_role=self.package_role,
|
|
197
|
+
force_drop=auto_yes,
|
|
283
198
|
)
|
|
284
|
-
return # The application package was successfully dropped, therefore exit gracefully
|
|
285
199
|
|
|
286
200
|
def process(
|
|
287
201
|
self,
|
|
@@ -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(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
|
+
)
|
|
@@ -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 = {}
|
|
@@ -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:
|