snowflake-cli 3.7.2__py3-none-any.whl → 3.8.0__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 +14 -0
- snowflake/cli/_app/telemetry.py +11 -0
- snowflake/cli/_plugins/connection/commands.py +4 -2
- snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +1 -1
- snowflake/cli/_plugins/nativeapp/entities/application_package.py +20 -7
- snowflake/cli/_plugins/nativeapp/sf_sql_facade.py +5 -3
- snowflake/cli/_plugins/project/commands.py +16 -6
- snowflake/cli/_plugins/snowpark/common.py +31 -0
- snowflake/cli/_plugins/snowpark/package/anaconda_packages.py +3 -0
- snowflake/cli/_plugins/snowpark/snowpark_entity.py +21 -1
- snowflake/cli/_plugins/snowpark/snowpark_entity_model.py +23 -1
- snowflake/cli/_plugins/spcs/common.py +7 -0
- snowflake/cli/_plugins/spcs/image_repository/commands.py +7 -2
- snowflake/cli/_plugins/spcs/image_repository/manager.py +6 -2
- snowflake/cli/_plugins/spcs/services/commands.py +2 -2
- snowflake/cli/_plugins/spcs/services/manager.py +36 -1
- snowflake/cli/_plugins/sql/commands.py +57 -6
- snowflake/cli/_plugins/sql/lexer/__init__.py +7 -0
- snowflake/cli/_plugins/sql/lexer/completer.py +12 -0
- snowflake/cli/_plugins/sql/lexer/functions.py +421 -0
- snowflake/cli/_plugins/sql/lexer/keywords.py +529 -0
- snowflake/cli/_plugins/sql/lexer/lexer.py +56 -0
- snowflake/cli/_plugins/sql/lexer/types.py +37 -0
- snowflake/cli/_plugins/sql/manager.py +43 -9
- snowflake/cli/_plugins/sql/repl.py +221 -0
- snowflake/cli/_plugins/sql/snowsql_commands.py +331 -0
- snowflake/cli/_plugins/sql/statement_reader.py +296 -0
- snowflake/cli/_plugins/streamlit/commands.py +30 -15
- snowflake/cli/_plugins/streamlit/manager.py +0 -183
- snowflake/cli/_plugins/streamlit/streamlit_entity.py +163 -23
- snowflake/cli/api/artifacts/upload.py +5 -0
- snowflake/cli/api/artifacts/utils.py +0 -2
- snowflake/cli/api/cli_global_context.py +7 -3
- snowflake/cli/api/commands/decorators.py +70 -0
- snowflake/cli/api/commands/flags.py +95 -3
- snowflake/cli/api/config.py +10 -0
- snowflake/cli/api/connections.py +10 -0
- snowflake/cli/api/console/abc.py +8 -2
- snowflake/cli/api/console/console.py +16 -0
- snowflake/cli/api/console/enum.py +1 -1
- snowflake/cli/api/entities/common.py +99 -10
- snowflake/cli/api/entities/utils.py +1 -0
- snowflake/cli/api/feature_flags.py +6 -0
- snowflake/cli/api/project/project_paths.py +5 -0
- snowflake/cli/api/rendering/sql_templates.py +2 -1
- snowflake/cli/api/sql_execution.py +16 -4
- snowflake/cli/api/utils/path_utils.py +15 -0
- snowflake/cli/api/utils/python_api_utils.py +12 -0
- {snowflake_cli-3.7.2.dist-info → snowflake_cli-3.8.0.dist-info}/METADATA +10 -6
- {snowflake_cli-3.7.2.dist-info → snowflake_cli-3.8.0.dist-info}/RECORD +54 -46
- snowflake/cli/_plugins/nativeapp/feature_flags.py +0 -28
- snowflake/cli/_plugins/sql/source_reader.py +0 -230
- {snowflake_cli-3.7.2.dist-info → snowflake_cli-3.8.0.dist-info}/WHEEL +0 -0
- {snowflake_cli-3.7.2.dist-info → snowflake_cli-3.8.0.dist-info}/entry_points.txt +0 -0
- {snowflake_cli-3.7.2.dist-info → snowflake_cli-3.8.0.dist-info}/licenses/LICENSE +0 -0
snowflake/cli/__about__.py
CHANGED
|
@@ -64,6 +64,16 @@ SUPPORTED_ENV_OVERRIDES = [
|
|
|
64
64
|
"session_token",
|
|
65
65
|
"master_token",
|
|
66
66
|
"token_file_path",
|
|
67
|
+
"oauth_client_id",
|
|
68
|
+
"oauth_client_secret",
|
|
69
|
+
"oauth_authorization_url",
|
|
70
|
+
"oauth_token_request_url",
|
|
71
|
+
"oauth_redirect_uri",
|
|
72
|
+
"oauth_scope",
|
|
73
|
+
"oauth_disable_pkce",
|
|
74
|
+
"oauth_enable_refresh_tokens",
|
|
75
|
+
"oauth_enable_single_use_refresh_tokens",
|
|
76
|
+
"client_store_temporary_credential",
|
|
67
77
|
]
|
|
68
78
|
|
|
69
79
|
# mapping of found key -> key to set
|
|
@@ -103,14 +113,18 @@ def connect_to_snowflake(
|
|
|
103
113
|
temporary_connection, using_session_token, using_master_token
|
|
104
114
|
)
|
|
105
115
|
|
|
116
|
+
connection_parameters = {}
|
|
106
117
|
if connection_name:
|
|
107
118
|
connection_parameters = {
|
|
108
119
|
_resolve_alias(k): v
|
|
109
120
|
for k, v in get_connection_dict(connection_name).items()
|
|
110
121
|
}
|
|
122
|
+
|
|
111
123
|
elif temporary_connection:
|
|
112
124
|
connection_parameters = {} # we will apply overrides in next step
|
|
113
125
|
|
|
126
|
+
connection_parameters["using_session_keep_alive"] = True
|
|
127
|
+
|
|
114
128
|
# Apply overrides to connection details
|
|
115
129
|
# (1) Command line override case
|
|
116
130
|
for key, value in overrides.items():
|
snowflake/cli/_app/telemetry.py
CHANGED
|
@@ -75,6 +75,7 @@ class CLITelemetryField(Enum):
|
|
|
75
75
|
IS_CLI_EXCEPTION = "is_cli_exception"
|
|
76
76
|
# Project context
|
|
77
77
|
PROJECT_DEFINITION_VERSION = "project_definition_version"
|
|
78
|
+
MODE = "mode"
|
|
78
79
|
|
|
79
80
|
|
|
80
81
|
class TelemetryEvent(Enum):
|
|
@@ -151,9 +152,19 @@ def _find_command_info() -> TelemetryDict:
|
|
|
151
152
|
"format", OutputFormat.TABLE
|
|
152
153
|
).value,
|
|
153
154
|
CLITelemetryField.PROJECT_DEFINITION_VERSION: str(_get_definition_version()),
|
|
155
|
+
CLITelemetryField.MODE: _get_cli_running_mode(),
|
|
154
156
|
}
|
|
155
157
|
|
|
156
158
|
|
|
159
|
+
def _get_cli_running_mode() -> str:
|
|
160
|
+
try:
|
|
161
|
+
if get_cli_context().is_repl:
|
|
162
|
+
return "repl"
|
|
163
|
+
except Exception:
|
|
164
|
+
pass
|
|
165
|
+
return "cmd"
|
|
166
|
+
|
|
167
|
+
|
|
157
168
|
def _get_definition_version() -> str | None:
|
|
158
169
|
try:
|
|
159
170
|
cli_context = get_cli_context()
|
|
@@ -90,9 +90,11 @@ class EmptyInput:
|
|
|
90
90
|
return "optional"
|
|
91
91
|
|
|
92
92
|
|
|
93
|
-
def
|
|
93
|
+
def _mask_sensitive_parameters(connection_params: dict):
|
|
94
94
|
if "password" in connection_params:
|
|
95
95
|
connection_params["password"] = "****"
|
|
96
|
+
if "oauth_client_secret" in connection_params:
|
|
97
|
+
connection_params["oauth_client_secret"] = "****"
|
|
96
98
|
return connection_params
|
|
97
99
|
|
|
98
100
|
|
|
@@ -106,7 +108,7 @@ def list_connections(**options) -> CommandResult:
|
|
|
106
108
|
result = (
|
|
107
109
|
{
|
|
108
110
|
"connection_name": connection_name,
|
|
109
|
-
"parameters":
|
|
111
|
+
"parameters": _mask_sensitive_parameters(
|
|
110
112
|
connection_config.to_dict_of_known_non_empty_values()
|
|
111
113
|
),
|
|
112
114
|
"is_default": connection_name == default_connection,
|
|
@@ -35,10 +35,10 @@ from snowflake.cli._plugins.nativeapp.codegen.sandbox import (
|
|
|
35
35
|
SandboxEnvBuilder,
|
|
36
36
|
execute_script_in_sandbox,
|
|
37
37
|
)
|
|
38
|
-
from snowflake.cli._plugins.nativeapp.feature_flags import FeatureFlag
|
|
39
38
|
from snowflake.cli._plugins.stage.diff import to_stage_path
|
|
40
39
|
from snowflake.cli.api.artifacts.bundle_map import BundleMap
|
|
41
40
|
from snowflake.cli.api.console import cli_console as cc
|
|
41
|
+
from snowflake.cli.api.feature_flags import FeatureFlag
|
|
42
42
|
from snowflake.cli.api.project.schemas.entities.common import (
|
|
43
43
|
PathMapping,
|
|
44
44
|
ProcessorMapping,
|
|
@@ -44,7 +44,6 @@ from snowflake.cli._plugins.nativeapp.exceptions import (
|
|
|
44
44
|
ObjectPropertyNotFoundError,
|
|
45
45
|
SetupScriptFailedValidation,
|
|
46
46
|
)
|
|
47
|
-
from snowflake.cli._plugins.nativeapp.feature_flags import FeatureFlag
|
|
48
47
|
from snowflake.cli._plugins.nativeapp.policy import (
|
|
49
48
|
AllowAlwaysPolicy,
|
|
50
49
|
AskAlwaysPolicy,
|
|
@@ -88,6 +87,7 @@ from snowflake.cli.api.entities.utils import (
|
|
|
88
87
|
)
|
|
89
88
|
from snowflake.cli.api.errno import DOES_NOT_EXIST_OR_NOT_AUTHORIZED
|
|
90
89
|
from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
|
|
90
|
+
from snowflake.cli.api.feature_flags import FeatureFlag
|
|
91
91
|
from snowflake.cli.api.project.schemas.entities.common import (
|
|
92
92
|
EntityModelBaseWithArtifacts,
|
|
93
93
|
Identifier,
|
|
@@ -190,6 +190,10 @@ class ApplicationPackageEntityModel(EntityModelBaseWithArtifacts):
|
|
|
190
190
|
title="Entities that will be bundled and deployed as part of this application package",
|
|
191
191
|
default=[],
|
|
192
192
|
)
|
|
193
|
+
enable_release_channels: bool = Field(
|
|
194
|
+
title="Enable release channels for this application package",
|
|
195
|
+
default=False,
|
|
196
|
+
)
|
|
193
197
|
|
|
194
198
|
@field_validator("children")
|
|
195
199
|
@classmethod
|
|
@@ -1550,24 +1554,33 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
|
|
|
1550
1554
|
def _get_enable_release_channels_flag(self) -> Optional[bool]:
|
|
1551
1555
|
"""
|
|
1552
1556
|
Returns the requested value of enable_release_channels flag for the application package.
|
|
1553
|
-
It retrieves the value from
|
|
1557
|
+
It retrieves the value from snowflake.yml (and from the configuration file),
|
|
1558
|
+
and checks that the feature is enabled in the account.
|
|
1554
1559
|
If return value is None, it means do not explicitly set the flag.
|
|
1555
1560
|
"""
|
|
1556
|
-
|
|
1561
|
+
value_from_snowflake_yml = self.model.enable_release_channels
|
|
1562
|
+
feature_flag_from_config = FeatureFlag.ENABLE_RELEASE_CHANNELS.is_enabled()
|
|
1563
|
+
if feature_flag_from_config and not value_from_snowflake_yml:
|
|
1564
|
+
self._workspace_ctx.console.warning(
|
|
1565
|
+
f"{FeatureFlag.ENABLE_RELEASE_CHANNELS.name} value in config.toml is deprecated."
|
|
1566
|
+
f" Set [enable_release_channels] for the application package in snowflake.yml instead."
|
|
1567
|
+
)
|
|
1568
|
+
enable_release_channels = value_from_snowflake_yml or feature_flag_from_config
|
|
1569
|
+
|
|
1557
1570
|
feature_enabled_in_account = (
|
|
1558
1571
|
get_snowflake_facade().get_ui_parameter(
|
|
1559
1572
|
UIParameter.NA_FEATURE_RELEASE_CHANNELS, "ENABLED"
|
|
1560
1573
|
)
|
|
1561
1574
|
== "ENABLED"
|
|
1562
1575
|
)
|
|
1563
|
-
|
|
1564
|
-
if feature_flag_from_config is not None and not feature_enabled_in_account:
|
|
1576
|
+
if enable_release_channels and not feature_enabled_in_account:
|
|
1565
1577
|
self._workspace_ctx.console.warning(
|
|
1566
|
-
f"Ignoring
|
|
1578
|
+
f"Ignoring [enable_release_channels] value because "
|
|
1579
|
+
"release channels are not enabled in the current account."
|
|
1567
1580
|
)
|
|
1568
1581
|
return None
|
|
1569
1582
|
|
|
1570
|
-
return
|
|
1583
|
+
return enable_release_channels
|
|
1571
1584
|
|
|
1572
1585
|
def create_app_package(self) -> None:
|
|
1573
1586
|
"""
|
|
@@ -148,6 +148,7 @@ class SnowflakeSQLFacade:
|
|
|
148
148
|
yield
|
|
149
149
|
return
|
|
150
150
|
|
|
151
|
+
name = to_identifier(name)
|
|
151
152
|
try:
|
|
152
153
|
current_obj_result_row = self._sql_executor.execute_query(
|
|
153
154
|
f"select current_{object_type}()"
|
|
@@ -158,7 +159,7 @@ class SnowflakeSQLFacade:
|
|
|
158
159
|
)
|
|
159
160
|
|
|
160
161
|
try:
|
|
161
|
-
prev_obj = current_obj_result_row[0]
|
|
162
|
+
prev_obj = to_identifier(current_obj_result_row[0])
|
|
162
163
|
except IndexError:
|
|
163
164
|
prev_obj = None
|
|
164
165
|
|
|
@@ -230,12 +231,13 @@ class SnowflakeSQLFacade:
|
|
|
230
231
|
with self._use_role_optional(role_to_use):
|
|
231
232
|
try:
|
|
232
233
|
self._sql_executor.execute_query(
|
|
233
|
-
f"grant {comma_separated_privileges} on {object_type_and_name} to role {role_to_grant}"
|
|
234
|
+
f"grant {comma_separated_privileges} on {object_type_and_name} to role {to_identifier(role_to_grant)}"
|
|
234
235
|
)
|
|
235
236
|
except Exception as err:
|
|
236
237
|
handle_unclassified_error(
|
|
237
238
|
err,
|
|
238
|
-
f"Failed to grant {comma_separated_privileges} on {object_type_and_name}
|
|
239
|
+
f"Failed to grant {comma_separated_privileges} on {object_type_and_name}"
|
|
240
|
+
f" to role {to_identifier(role_to_grant)}.",
|
|
239
241
|
)
|
|
240
242
|
|
|
241
243
|
def execute_user_script(
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
from typing import List, Optional
|
|
16
16
|
|
|
17
17
|
import typer
|
|
18
|
-
from click import ClickException
|
|
19
18
|
from snowflake.cli._plugins.object.command_aliases import add_object_command_aliases
|
|
20
19
|
from snowflake.cli._plugins.object.commands import scope_option
|
|
21
20
|
from snowflake.cli._plugins.object.manager import ObjectManager
|
|
@@ -38,6 +37,7 @@ from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
|
|
|
38
37
|
from snowflake.cli.api.commands.utils import get_entity_for_operation
|
|
39
38
|
from snowflake.cli.api.console.console import cli_console
|
|
40
39
|
from snowflake.cli.api.constants import ObjectType
|
|
40
|
+
from snowflake.cli.api.exceptions import CliError
|
|
41
41
|
from snowflake.cli.api.identifiers import FQN
|
|
42
42
|
from snowflake.cli.api.output.types import MessageResult, QueryResult, SingleQueryResult
|
|
43
43
|
|
|
@@ -73,7 +73,7 @@ add_object_command_aliases(
|
|
|
73
73
|
help_example='`list --like "my%"` lists all projects that begin with “my”'
|
|
74
74
|
),
|
|
75
75
|
scope_option=scope_option(help_example="`list --in database my_db`"),
|
|
76
|
-
ommit_commands=["
|
|
76
|
+
ommit_commands=["create", "describe"],
|
|
77
77
|
)
|
|
78
78
|
|
|
79
79
|
|
|
@@ -133,11 +133,11 @@ def create(
|
|
|
133
133
|
)
|
|
134
134
|
om = ObjectManager()
|
|
135
135
|
if om.object_exists(object_type="project", fqn=project.fqn):
|
|
136
|
-
raise
|
|
136
|
+
raise CliError(f"Project '{project.fqn}' already exists.")
|
|
137
137
|
if not no_version and om.object_exists(
|
|
138
138
|
object_type="stage", fqn=FQN.from_stage(project.stage)
|
|
139
139
|
):
|
|
140
|
-
raise
|
|
140
|
+
raise CliError(f"Stage '{project.stage}' already exists.")
|
|
141
141
|
|
|
142
142
|
pm = ProjectManager()
|
|
143
143
|
with cli_console.phase(f"Creating project '{project.fqn}'"):
|
|
@@ -154,17 +154,22 @@ def create(
|
|
|
154
154
|
@with_project_definition()
|
|
155
155
|
def add_version(
|
|
156
156
|
entity_id: str = entity_argument("project"),
|
|
157
|
-
_from: Optional[str] = from_option(
|
|
157
|
+
_from: Optional[str] = from_option(),
|
|
158
158
|
_alias: Optional[str] = typer.Option(
|
|
159
159
|
None, "--alias", help="Alias for the version.", show_default=False
|
|
160
160
|
),
|
|
161
161
|
comment: Optional[str] = typer.Option(
|
|
162
162
|
None, "--comment", help="Version comment.", show_default=False
|
|
163
163
|
),
|
|
164
|
-
prune: bool = PruneOption(
|
|
164
|
+
prune: bool = PruneOption(default=True),
|
|
165
165
|
**options,
|
|
166
166
|
):
|
|
167
167
|
"""Uploads local files to Snowflake and cerates a new project version."""
|
|
168
|
+
if _from is not None and prune:
|
|
169
|
+
cli_console.warning(
|
|
170
|
+
"When `--from` option is used, `--prune` option will be ignored and files from stage will be used as they are."
|
|
171
|
+
)
|
|
172
|
+
prune = False
|
|
168
173
|
cli_context = get_cli_context()
|
|
169
174
|
project: ProjectEntityModel = get_entity_for_operation(
|
|
170
175
|
cli_context=cli_context,
|
|
@@ -172,6 +177,11 @@ def add_version(
|
|
|
172
177
|
project_definition=cli_context.project_definition,
|
|
173
178
|
entity_type="project",
|
|
174
179
|
)
|
|
180
|
+
om = ObjectManager()
|
|
181
|
+
if not om.object_exists(object_type="project", fqn=project.fqn):
|
|
182
|
+
raise CliError(
|
|
183
|
+
f"Project '{project.fqn}' does not exist. Use `project create` command first"
|
|
184
|
+
)
|
|
175
185
|
ProjectManager().add_version(
|
|
176
186
|
project=project,
|
|
177
187
|
prune=prune,
|
|
@@ -103,6 +103,21 @@ class SnowparkObjectManager(SqlExecutionMixin):
|
|
|
103
103
|
if isinstance(entity, ProcedureEntityModel) and entity.execute_as_caller:
|
|
104
104
|
query.append("execute as caller")
|
|
105
105
|
|
|
106
|
+
if entity.artifact_repository and entity.artifact_repository_packages:
|
|
107
|
+
packages = [f"'{item}'" for item in entity.artifact_repository_packages]
|
|
108
|
+
query.extend(
|
|
109
|
+
[
|
|
110
|
+
f"ARTIFACT_REPOSITORY= {entity.artifact_repository}",
|
|
111
|
+
f"ARTIFACT_REPOSITORY_PACKAGES=({','.join(packages)})",
|
|
112
|
+
]
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
if entity.resource_constraint:
|
|
116
|
+
constraints = ",".join(
|
|
117
|
+
f"{key}='{value}'" for key, value in entity.resource_constraint.items()
|
|
118
|
+
)
|
|
119
|
+
query.append(f"RESOURCE_CONSTRAINT=({constraints})")
|
|
120
|
+
|
|
106
121
|
return self.execute_query("\n".join(query))
|
|
107
122
|
|
|
108
123
|
def deploy_entity(
|
|
@@ -184,6 +199,22 @@ def _check_if_replace_is_required(
|
|
|
184
199
|
log.info("Runtime versions do not match. Replacing the %s", object_type)
|
|
185
200
|
return True
|
|
186
201
|
|
|
202
|
+
if entity.resource_constraint != resource_json.get("resource_constraint", None):
|
|
203
|
+
log.info("Resource constraints do not match. Replacing the %s", object_type)
|
|
204
|
+
return True
|
|
205
|
+
|
|
206
|
+
if entity.artifact_repository != resource_json.get("artifact_repository", None):
|
|
207
|
+
log.info("Artifact repository does not match. Replacing the %s", object_type)
|
|
208
|
+
return True
|
|
209
|
+
|
|
210
|
+
if entity.artifact_repository_packages != resource_json.get(
|
|
211
|
+
"artifact_repository_packages", None
|
|
212
|
+
):
|
|
213
|
+
log.info(
|
|
214
|
+
"Artifact repository packages do not match. Replacing the %s", object_type
|
|
215
|
+
)
|
|
216
|
+
return True
|
|
217
|
+
|
|
187
218
|
if isinstance(entity, ProcedureEntityModel):
|
|
188
219
|
if resource_json.get("execute as", "OWNER") != (
|
|
189
220
|
"CALLER" if entity.execute_as_caller else "OWNER"
|
|
@@ -88,6 +88,9 @@ class AnacondaPackages:
|
|
|
88
88
|
return False
|
|
89
89
|
if skip_version_check or not package.specs:
|
|
90
90
|
return True
|
|
91
|
+
if any(spec[0] == "!=" for spec in package.specs):
|
|
92
|
+
# Snowflake doesn't support '!=' so we need to resolve this requirement externally
|
|
93
|
+
return False
|
|
91
94
|
return self._packages[package.name].is_required_version_available(package)
|
|
92
95
|
|
|
93
96
|
def package_latest_version(self, package: Requirement) -> str | None:
|
|
@@ -3,7 +3,6 @@ from pathlib import Path
|
|
|
3
3
|
from typing import Generic, List, Optional, TypeVar
|
|
4
4
|
|
|
5
5
|
from click import ClickException
|
|
6
|
-
from snowflake.cli._plugins.nativeapp.feature_flags import FeatureFlag
|
|
7
6
|
from snowflake.cli._plugins.snowpark import package_utils
|
|
8
7
|
from snowflake.cli._plugins.snowpark.common import (
|
|
9
8
|
DEFAULT_RUNTIME,
|
|
@@ -25,6 +24,7 @@ from snowflake.cli._plugins.snowpark.snowpark_project_paths import SnowparkProje
|
|
|
25
24
|
from snowflake.cli._plugins.snowpark.zipper import zip_dir
|
|
26
25
|
from snowflake.cli._plugins.workspace.context import ActionContext
|
|
27
26
|
from snowflake.cli.api.entities.common import EntityBase
|
|
27
|
+
from snowflake.cli.api.feature_flags import FeatureFlag
|
|
28
28
|
from snowflake.cli.api.secure_path import SecurePath
|
|
29
29
|
from snowflake.connector import ProgrammingError
|
|
30
30
|
|
|
@@ -176,6 +176,17 @@ class SnowparkEntity(EntityBase[Generic[T]]):
|
|
|
176
176
|
if self.model.type == "procedure" and self.model.execute_as_caller:
|
|
177
177
|
query.append("EXECUTE AS CALLER")
|
|
178
178
|
|
|
179
|
+
if self.model.artifact_repository and self.model.artifact_repository_packages:
|
|
180
|
+
packages = [f"'{item}'" for item in self.model.artifact_repository_packages]
|
|
181
|
+
query.extend(
|
|
182
|
+
[
|
|
183
|
+
f"ARTIFACT_REPOSITORY= {self.model.artifact_repository} ",
|
|
184
|
+
f"ARTIFACT_REPOSITORY_PACKAGES=({','.join(packages)})",
|
|
185
|
+
]
|
|
186
|
+
)
|
|
187
|
+
if self.model.resource_constraint:
|
|
188
|
+
query.append(self._get_resource_constraints_sql())
|
|
189
|
+
|
|
179
190
|
return "\n".join(query)
|
|
180
191
|
|
|
181
192
|
def get_execute_sql(self, execution_arguments: List[str] | None = None):
|
|
@@ -237,6 +248,15 @@ class SnowparkEntity(EntityBase[Generic[T]]):
|
|
|
237
248
|
|
|
238
249
|
return download_result
|
|
239
250
|
|
|
251
|
+
def _get_resource_constraints_sql(self) -> str:
|
|
252
|
+
if self.model.resource_constraint:
|
|
253
|
+
constraints = ",".join(
|
|
254
|
+
f"{key}='{value}'"
|
|
255
|
+
for key, value in self.model.resource_constraint.items()
|
|
256
|
+
)
|
|
257
|
+
return f"RESOURCE_CONSTRAINT=({constraints})"
|
|
258
|
+
return ""
|
|
259
|
+
|
|
240
260
|
|
|
241
261
|
class FunctionEntity(SnowparkEntity[FunctionEntityModel]):
|
|
242
262
|
"""
|
|
@@ -17,7 +17,7 @@ from __future__ import annotations
|
|
|
17
17
|
import glob
|
|
18
18
|
from typing import List, Literal, Optional, Union
|
|
19
19
|
|
|
20
|
-
from pydantic import Field, field_validator
|
|
20
|
+
from pydantic import Field, field_validator, model_validator
|
|
21
21
|
from snowflake.cli.api.feature_flags import FeatureFlag
|
|
22
22
|
from snowflake.cli.api.identifiers import FQN
|
|
23
23
|
from snowflake.cli.api.project.schemas.entities.common import (
|
|
@@ -50,6 +50,17 @@ class SnowparkEntityModel(
|
|
|
50
50
|
)
|
|
51
51
|
stage: str = Field(title="Stage in which artifacts will be stored")
|
|
52
52
|
|
|
53
|
+
artifact_repository: Optional[str] = Field(
|
|
54
|
+
default=None, title="Artifact repository to be used"
|
|
55
|
+
)
|
|
56
|
+
artifact_repository_packages: Optional[List[str]] = Field(
|
|
57
|
+
default=None, title="Packages to be installed from artifact repository"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
resource_constraint: Optional[dict] = Field(
|
|
61
|
+
default=None, title="Resource constraints for the function/procedure"
|
|
62
|
+
)
|
|
63
|
+
|
|
53
64
|
@field_validator("artifacts")
|
|
54
65
|
@classmethod
|
|
55
66
|
def _convert_artifacts(cls, artifacts: Union[dict, str]):
|
|
@@ -75,6 +86,17 @@ class SnowparkEntityModel(
|
|
|
75
86
|
return str(runtime_input)
|
|
76
87
|
return runtime_input
|
|
77
88
|
|
|
89
|
+
@model_validator(mode="before")
|
|
90
|
+
@classmethod
|
|
91
|
+
def check_artifact_repository(cls, values: dict) -> dict:
|
|
92
|
+
artifact_repository = values.get("artifact_repository")
|
|
93
|
+
artifact_repository_packages = values.get("artifact_repository_packages")
|
|
94
|
+
if artifact_repository_packages and not artifact_repository:
|
|
95
|
+
raise ValueError(
|
|
96
|
+
"You specified Artifact_repository_packages without setting Artifact_repository.",
|
|
97
|
+
)
|
|
98
|
+
return values
|
|
99
|
+
|
|
78
100
|
@property
|
|
79
101
|
def udf_sproc_identifier(self) -> UdfSprocIdentifier:
|
|
80
102
|
return UdfSprocIdentifier.from_definition(self)
|
|
@@ -181,6 +181,13 @@ def build_time_clauses(
|
|
|
181
181
|
return since_clause, until_clause
|
|
182
182
|
|
|
183
183
|
|
|
184
|
+
def build_db_and_schema_clause(database_name: str, schema_name: str | None) -> str:
|
|
185
|
+
return f"""
|
|
186
|
+
and resource_attributes:"snow.database.name" = '{database_name}'
|
|
187
|
+
and resource_attributes:"snow.schema.name" = '{schema_name or 'PUBLIC'}'
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
|
|
184
191
|
def format_event_row(event_dict: dict) -> dict:
|
|
185
192
|
try:
|
|
186
193
|
resource_attributes = json.loads(event_dict.get("RESOURCE_ATTRIBUTES", "{}"))
|
|
@@ -75,7 +75,7 @@ add_object_command_aliases(
|
|
|
75
75
|
object_type=ObjectType.IMAGE_REPOSITORY,
|
|
76
76
|
name_argument=REPO_NAME_ARGUMENT,
|
|
77
77
|
like_option=like_option(
|
|
78
|
-
help_example='
|
|
78
|
+
help_example='`--like "my%"` lists all image repositories that begin with “my”.'
|
|
79
79
|
),
|
|
80
80
|
scope_option=scope_option(help_example="`list --in database my_db`"),
|
|
81
81
|
ommit_commands=["describe"],
|
|
@@ -126,10 +126,15 @@ def deploy(
|
|
|
126
126
|
@app.command("list-images", requires_connection=True)
|
|
127
127
|
def list_images(
|
|
128
128
|
name: FQN = REPO_NAME_ARGUMENT,
|
|
129
|
+
like_option: Optional[str] = like_option(
|
|
130
|
+
help_example='`--like "my%"` lists all image repositories that begin with “my”.'
|
|
131
|
+
),
|
|
129
132
|
**options,
|
|
130
133
|
) -> CollectionResult:
|
|
131
134
|
"""Lists images in the given repository."""
|
|
132
|
-
return QueryResult(
|
|
135
|
+
return QueryResult(
|
|
136
|
+
ImageRepositoryManager().list_images(name.identifier, like_option)
|
|
137
|
+
)
|
|
133
138
|
|
|
134
139
|
|
|
135
140
|
@app.command("list-tags", requires_connection=True, deprecated=True)
|
|
@@ -83,5 +83,9 @@ class ImageRepositoryManager(SqlExecutionMixin):
|
|
|
83
83
|
e, ObjectType.IMAGE_REPOSITORY, name, replace_available=True
|
|
84
84
|
)
|
|
85
85
|
|
|
86
|
-
def list_images(self, repo_name: str) -> SnowflakeCursor:
|
|
87
|
-
|
|
86
|
+
def list_images(self, repo_name: str, like_option: str) -> SnowflakeCursor:
|
|
87
|
+
if like_option:
|
|
88
|
+
query = f"show images like '{like_option}' in image repository {repo_name}"
|
|
89
|
+
else:
|
|
90
|
+
query = f"show images in image repository {repo_name}"
|
|
91
|
+
return self.execute_query(query)
|
|
@@ -437,7 +437,7 @@ def metrics(
|
|
|
437
437
|
manager = ServiceManager()
|
|
438
438
|
if since or until:
|
|
439
439
|
metrics = manager.get_all_metrics(
|
|
440
|
-
service_name=name
|
|
440
|
+
service_name=name,
|
|
441
441
|
container_name=container_name,
|
|
442
442
|
instance_id=instance_id,
|
|
443
443
|
since=since,
|
|
@@ -446,7 +446,7 @@ def metrics(
|
|
|
446
446
|
)
|
|
447
447
|
else:
|
|
448
448
|
metrics = manager.get_latest_metrics(
|
|
449
|
-
service_name=name
|
|
449
|
+
service_name=name,
|
|
450
450
|
container_name=container_name,
|
|
451
451
|
instance_id=instance_id,
|
|
452
452
|
show_all_columns=show_all_columns,
|
|
@@ -26,6 +26,7 @@ from snowflake.cli._plugins.spcs.common import (
|
|
|
26
26
|
EVENT_COLUMN_NAMES,
|
|
27
27
|
NoPropertiesProvidedError,
|
|
28
28
|
SPCSEventTableError,
|
|
29
|
+
build_db_and_schema_clause,
|
|
29
30
|
build_resource_clause,
|
|
30
31
|
build_time_clauses,
|
|
31
32
|
filter_log_timestamp,
|
|
@@ -197,6 +198,8 @@ class ServiceManager(SqlExecutionMixin):
|
|
|
197
198
|
if not artifacts:
|
|
198
199
|
raise ValueError("Service needs to have artifacts to deploy")
|
|
199
200
|
|
|
201
|
+
service_project_paths.remove_up_bundle_root()
|
|
202
|
+
SecurePath(service_project_paths.bundle_root).mkdir(parents=True, exist_ok=True)
|
|
200
203
|
bundle_map = bundle_artifacts(service_project_paths, artifacts)
|
|
201
204
|
for absolute_src, absolute_dest in bundle_map.all_mappings(
|
|
202
205
|
absolute=True, expand_directories=True
|
|
@@ -208,6 +211,7 @@ class ServiceManager(SqlExecutionMixin):
|
|
|
208
211
|
stage_manager.put(
|
|
209
212
|
local_path=absolute_dest, stage_path=stage_path, overwrite=True
|
|
210
213
|
)
|
|
214
|
+
service_project_paths.clean_up_output()
|
|
211
215
|
|
|
212
216
|
def execute_job(
|
|
213
217
|
self,
|
|
@@ -388,13 +392,14 @@ class ServiceManager(SqlExecutionMixin):
|
|
|
388
392
|
|
|
389
393
|
def get_all_metrics(
|
|
390
394
|
self,
|
|
391
|
-
service_name: str,
|
|
395
|
+
service_name: FQN | str,
|
|
392
396
|
instance_id: str,
|
|
393
397
|
container_name: str,
|
|
394
398
|
since: str | datetime | None = None,
|
|
395
399
|
until: str | datetime | None = None,
|
|
396
400
|
show_all_columns: bool = False,
|
|
397
401
|
):
|
|
402
|
+
service_name, database, schema = parse_service_details(service_name)
|
|
398
403
|
|
|
399
404
|
account_event_table = self.get_account_event_table()
|
|
400
405
|
resource_clause = build_resource_clause(
|
|
@@ -402,11 +407,18 @@ class ServiceManager(SqlExecutionMixin):
|
|
|
402
407
|
)
|
|
403
408
|
since_clause, until_clause = build_time_clauses(since, until)
|
|
404
409
|
|
|
410
|
+
db_and_schema_clause = ""
|
|
411
|
+
if database:
|
|
412
|
+
db_and_schema_clause = build_db_and_schema_clause(
|
|
413
|
+
database_name=database, schema_name=schema
|
|
414
|
+
)
|
|
415
|
+
|
|
405
416
|
query = f"""\
|
|
406
417
|
select *
|
|
407
418
|
from {account_event_table}
|
|
408
419
|
where (
|
|
409
420
|
{resource_clause}
|
|
421
|
+
{db_and_schema_clause}
|
|
410
422
|
{since_clause}
|
|
411
423
|
{until_clause}
|
|
412
424
|
)
|
|
@@ -438,12 +450,19 @@ class ServiceManager(SqlExecutionMixin):
|
|
|
438
450
|
container_name: str,
|
|
439
451
|
show_all_columns: bool = False,
|
|
440
452
|
):
|
|
453
|
+
service_name, database, schema = parse_service_details(service_name)
|
|
441
454
|
|
|
442
455
|
account_event_table = self.get_account_event_table()
|
|
443
456
|
resource_clause = build_resource_clause(
|
|
444
457
|
service_name, instance_id, container_name
|
|
445
458
|
)
|
|
446
459
|
|
|
460
|
+
db_and_schema_clause = ""
|
|
461
|
+
if database:
|
|
462
|
+
db_and_schema_clause = build_db_and_schema_clause(
|
|
463
|
+
database_name=database, schema_name=schema
|
|
464
|
+
)
|
|
465
|
+
|
|
447
466
|
query = f"""
|
|
448
467
|
with rankedmetrics as (
|
|
449
468
|
select
|
|
@@ -457,6 +476,7 @@ class ServiceManager(SqlExecutionMixin):
|
|
|
457
476
|
record_type = 'METRIC'
|
|
458
477
|
and scope['name'] = 'snow.spcs.platform'
|
|
459
478
|
and {resource_clause}
|
|
479
|
+
{db_and_schema_clause}
|
|
460
480
|
and timestamp > dateadd('hour', -1, current_timestamp)
|
|
461
481
|
)
|
|
462
482
|
select *
|
|
@@ -580,3 +600,18 @@ class ServiceManager(SqlExecutionMixin):
|
|
|
580
600
|
unset_list = [property_name for property_name, value in property_pairs if value]
|
|
581
601
|
query = f"alter service {service_name} unset {','.join(unset_list)}"
|
|
582
602
|
return self.execute_query(query)
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
def parse_service_details(
|
|
606
|
+
service_identifier: str | FQN,
|
|
607
|
+
) -> tuple[str, str | None, str | None]:
|
|
608
|
+
if isinstance(service_identifier, FQN):
|
|
609
|
+
name = service_identifier.name
|
|
610
|
+
database = service_identifier.database
|
|
611
|
+
schema = service_identifier.schema
|
|
612
|
+
else:
|
|
613
|
+
name = service_identifier
|
|
614
|
+
database = None
|
|
615
|
+
schema = None
|
|
616
|
+
|
|
617
|
+
return name, database, schema
|