snowflake-cli 3.4.1__py3-none-any.whl → 3.6.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 +13 -1
- snowflake/cli/_app/cli_app.py +1 -10
- snowflake/cli/_app/commands_registration/builtin_plugins.py +7 -1
- snowflake/cli/_app/commands_registration/command_plugins_loader.py +3 -1
- snowflake/cli/_app/commands_registration/commands_registration_with_callbacks.py +3 -3
- snowflake/cli/_app/printing.py +2 -2
- snowflake/cli/_app/snow_connector.py +5 -4
- snowflake/cli/_app/telemetry.py +3 -15
- snowflake/cli/_app/version_check.py +4 -4
- snowflake/cli/_plugins/auth/__init__.py +11 -0
- snowflake/cli/_plugins/auth/keypair/__init__.py +0 -0
- snowflake/cli/_plugins/auth/keypair/commands.py +151 -0
- snowflake/cli/_plugins/auth/keypair/manager.py +331 -0
- snowflake/cli/_plugins/auth/keypair/plugin_spec.py +30 -0
- snowflake/cli/_plugins/connection/commands.py +79 -5
- snowflake/cli/_plugins/helpers/commands.py +3 -4
- snowflake/cli/_plugins/nativeapp/entities/application.py +4 -1
- snowflake/cli/_plugins/nativeapp/sf_sql_facade.py +33 -6
- snowflake/cli/_plugins/notebook/commands.py +3 -4
- snowflake/cli/_plugins/object/command_aliases.py +3 -1
- snowflake/cli/_plugins/object/manager.py +4 -2
- snowflake/cli/_plugins/plugin/commands.py +79 -0
- snowflake/cli/_plugins/plugin/manager.py +74 -0
- snowflake/cli/_plugins/plugin/plugin_spec.py +30 -0
- snowflake/cli/_plugins/project/__init__.py +0 -0
- snowflake/cli/_plugins/project/commands.py +173 -0
- snowflake/cli/{_app/api_impl/plugin/__init__.py → _plugins/project/feature_flags.py} +9 -0
- snowflake/cli/_plugins/project/manager.py +76 -0
- snowflake/cli/_plugins/project/plugin_spec.py +30 -0
- snowflake/cli/_plugins/project/project_entity_model.py +40 -0
- snowflake/cli/_plugins/snowpark/commands.py +2 -1
- snowflake/cli/_plugins/spcs/compute_pool/commands.py +70 -10
- snowflake/cli/_plugins/spcs/compute_pool/compute_pool_entity.py +8 -0
- snowflake/cli/_plugins/spcs/compute_pool/compute_pool_entity_model.py +37 -0
- snowflake/cli/_plugins/spcs/compute_pool/manager.py +45 -0
- snowflake/cli/_plugins/spcs/image_repository/commands.py +29 -0
- snowflake/cli/_plugins/spcs/image_repository/image_repository_entity.py +8 -0
- snowflake/cli/_plugins/spcs/image_repository/image_repository_entity_model.py +8 -0
- snowflake/cli/_plugins/spcs/image_repository/manager.py +1 -1
- snowflake/cli/_plugins/spcs/services/commands.py +53 -0
- snowflake/cli/_plugins/spcs/services/manager.py +114 -0
- snowflake/cli/_plugins/spcs/services/service_entity.py +6 -0
- snowflake/cli/_plugins/spcs/services/service_entity_model.py +45 -0
- snowflake/cli/_plugins/spcs/services/service_project_paths.py +15 -0
- snowflake/cli/_plugins/sql/manager.py +42 -51
- snowflake/cli/_plugins/sql/source_reader.py +230 -0
- snowflake/cli/_plugins/stage/manager.py +10 -4
- snowflake/cli/_plugins/streamlit/commands.py +9 -24
- snowflake/cli/_plugins/streamlit/manager.py +5 -36
- snowflake/cli/api/artifacts/upload.py +51 -0
- snowflake/cli/api/commands/flags.py +35 -10
- snowflake/cli/api/commands/snow_typer.py +12 -0
- snowflake/cli/api/commands/utils.py +2 -0
- snowflake/cli/api/config.py +15 -10
- snowflake/cli/api/constants.py +2 -0
- snowflake/cli/api/errno.py +1 -0
- snowflake/cli/api/exceptions.py +15 -1
- snowflake/cli/api/feature_flags.py +2 -0
- snowflake/cli/api/plugins/plugin_config.py +43 -4
- snowflake/cli/api/project/definition_helper.py +31 -0
- snowflake/cli/api/project/schemas/entities/entities.py +26 -0
- snowflake/cli/api/rest_api.py +2 -3
- snowflake/cli/{_app → api}/secret.py +4 -1
- snowflake/cli/api/secure_path.py +16 -4
- snowflake/cli/api/sql_execution.py +7 -3
- {snowflake_cli-3.4.1.dist-info → snowflake_cli-3.6.0.dist-info}/METADATA +12 -12
- {snowflake_cli-3.4.1.dist-info → snowflake_cli-3.6.0.dist-info}/RECORD +71 -50
- snowflake/cli/_app/api_impl/plugin/plugin_config_provider_impl.py +0 -66
- snowflake/cli/api/__init__.py +0 -48
- /snowflake/cli/{_app/api_impl → _plugins/plugin}/__init__.py +0 -0
- {snowflake_cli-3.4.1.dist-info → snowflake_cli-3.6.0.dist-info}/WHEEL +0 -0
- {snowflake_cli-3.4.1.dist-info → snowflake_cli-3.6.0.dist-info}/entry_points.txt +0 -0
- {snowflake_cli-3.4.1.dist-info → snowflake_cli-3.6.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
-
import tempfile
|
|
18
17
|
from pathlib import Path
|
|
19
18
|
from typing import Any, Callable, Optional
|
|
20
19
|
|
|
@@ -33,6 +32,7 @@ from snowflake.cli.api.connections import ConnectionContext
|
|
|
33
32
|
from snowflake.cli.api.console import cli_console
|
|
34
33
|
from snowflake.cli.api.identifiers import FQN
|
|
35
34
|
from snowflake.cli.api.output.formats import OutputFormat
|
|
35
|
+
from snowflake.cli.api.secret import SecretType
|
|
36
36
|
from snowflake.cli.api.stage_path import StagePath
|
|
37
37
|
|
|
38
38
|
DEFAULT_CONTEXT_SETTINGS = {"help_option_names": ["--help", "-h"]}
|
|
@@ -215,7 +215,7 @@ MasterTokenOption = typer.Option(
|
|
|
215
215
|
TokenFilePathOption = typer.Option(
|
|
216
216
|
None,
|
|
217
217
|
"--token-file-path",
|
|
218
|
-
help="Path to file with an OAuth token
|
|
218
|
+
help="Path to file with an OAuth token to use when connecting to Snowflake.",
|
|
219
219
|
callback=_connection_callback("token_file_path"),
|
|
220
220
|
show_default=False,
|
|
221
221
|
rich_help_panel=_CONNECTION_SECTION,
|
|
@@ -277,7 +277,7 @@ MfaPasscodeOption = typer.Option(
|
|
|
277
277
|
EnableDiagOption = typer.Option(
|
|
278
278
|
False,
|
|
279
279
|
"--enable-diag",
|
|
280
|
-
help="
|
|
280
|
+
help="Whether to generate a connection diagnostic report.",
|
|
281
281
|
callback=_connection_callback("enable_diag"),
|
|
282
282
|
show_default=False,
|
|
283
283
|
is_flag=True,
|
|
@@ -286,20 +286,29 @@ EnableDiagOption = typer.Option(
|
|
|
286
286
|
|
|
287
287
|
# Set default via callback to avoid including tempdir path in generated docs (snow --docs).
|
|
288
288
|
# Use constant instead of None, as None is removed from telemetry data.
|
|
289
|
-
_DIAG_LOG_DEFAULT_VALUE = "<
|
|
289
|
+
_DIAG_LOG_DEFAULT_VALUE = "<system_temporary_directory>"
|
|
290
290
|
|
|
291
291
|
|
|
292
292
|
def _diag_log_path_callback(path: str):
|
|
293
293
|
if path == _DIAG_LOG_DEFAULT_VALUE:
|
|
294
|
+
import tempfile
|
|
295
|
+
|
|
294
296
|
path = tempfile.gettempdir()
|
|
295
|
-
|
|
296
|
-
|
|
297
|
+
|
|
298
|
+
absolute_path = Path(path).absolute()
|
|
299
|
+
if not absolute_path.exists():
|
|
300
|
+
# if the path does not exist the report is not generated
|
|
301
|
+
from snowflake.cli.api.secure_path import SecurePath
|
|
302
|
+
|
|
303
|
+
SecurePath(absolute_path).mkdir(parents=True)
|
|
304
|
+
|
|
305
|
+
return _connection_callback("diag_log_path")(absolute_path)
|
|
297
306
|
|
|
298
307
|
|
|
299
308
|
DiagLogPathOption: Path = typer.Option(
|
|
300
309
|
_DIAG_LOG_DEFAULT_VALUE,
|
|
301
310
|
"--diag-log-path",
|
|
302
|
-
help="
|
|
311
|
+
help="Path for the generated report. Defaults to system temporary directory.",
|
|
303
312
|
callback=_diag_log_path_callback,
|
|
304
313
|
show_default=False,
|
|
305
314
|
rich_help_panel=_CONNECTION_SECTION,
|
|
@@ -307,11 +316,17 @@ DiagLogPathOption: Path = typer.Option(
|
|
|
307
316
|
writable=True,
|
|
308
317
|
)
|
|
309
318
|
|
|
319
|
+
|
|
320
|
+
def _diag_log_allowlist_path_callback(path: str):
|
|
321
|
+
absolute_path = Path(path).absolute() if path else None
|
|
322
|
+
return _connection_callback("diag_allowlist_path")(absolute_path)
|
|
323
|
+
|
|
324
|
+
|
|
310
325
|
DiagAllowlistPathOption: Path = typer.Option(
|
|
311
326
|
None,
|
|
312
327
|
"--diag-allowlist-path",
|
|
313
|
-
help="
|
|
314
|
-
callback=
|
|
328
|
+
help="Path to a JSON file that contains allowlist parameters.",
|
|
329
|
+
callback=_diag_log_allowlist_path_callback,
|
|
315
330
|
show_default=False,
|
|
316
331
|
rich_help_panel=_CONNECTION_SECTION,
|
|
317
332
|
exists=True,
|
|
@@ -487,14 +502,24 @@ class IdentifierStagePathType(click.ParamType):
|
|
|
487
502
|
return StagePath.from_stage_str(value)
|
|
488
503
|
|
|
489
504
|
|
|
505
|
+
class SecretTypeParser(click.ParamType):
|
|
506
|
+
name = "TEXT"
|
|
507
|
+
|
|
508
|
+
def convert(self, value, param, ctx):
|
|
509
|
+
if not isinstance(value, SecretType):
|
|
510
|
+
return SecretType(value)
|
|
511
|
+
return value
|
|
512
|
+
|
|
513
|
+
|
|
490
514
|
def identifier_argument(
|
|
491
515
|
sf_object: str,
|
|
492
516
|
example: str,
|
|
493
517
|
click_type: click.ParamType = IdentifierType(),
|
|
494
518
|
callback: Callable | None = None,
|
|
519
|
+
is_optional: bool = False,
|
|
495
520
|
) -> typer.Argument:
|
|
496
521
|
return typer.Argument(
|
|
497
|
-
...,
|
|
522
|
+
None if is_optional else ...,
|
|
498
523
|
help=f"Identifier of the {sf_object}; for example: {example}",
|
|
499
524
|
show_default=False,
|
|
500
525
|
click_type=click_type,
|
|
@@ -19,6 +19,7 @@ import logging
|
|
|
19
19
|
from functools import wraps
|
|
20
20
|
from typing import Any, Callable, Dict, List, Optional, Tuple
|
|
21
21
|
|
|
22
|
+
import click
|
|
22
23
|
import typer
|
|
23
24
|
from click import ClickException
|
|
24
25
|
from snowflake.cli.api.commands.decorators import (
|
|
@@ -35,10 +36,20 @@ from snowflake.cli.api.output.types import CommandResult
|
|
|
35
36
|
from snowflake.cli.api.sanitizers import sanitize_for_terminal
|
|
36
37
|
from snowflake.cli.api.sql_execution import SqlExecutionMixin
|
|
37
38
|
from snowflake.connector import DatabaseError
|
|
39
|
+
from typer.core import TyperGroup
|
|
38
40
|
|
|
39
41
|
log = logging.getLogger(__name__)
|
|
40
42
|
|
|
41
43
|
|
|
44
|
+
class SortedTyperGroup(TyperGroup):
|
|
45
|
+
def list_commands(self, ctx: click.Context) -> List[str]:
|
|
46
|
+
"""
|
|
47
|
+
From Typer 0.13.0 help items are in order of definition, this function override that approach.
|
|
48
|
+
Returns a list of subcommand names in the alphabetic order.
|
|
49
|
+
"""
|
|
50
|
+
return sorted(self.commands)
|
|
51
|
+
|
|
52
|
+
|
|
42
53
|
class SnowTyper(typer.Typer):
|
|
43
54
|
def __init__(self, /, **kwargs):
|
|
44
55
|
self._sanitize_kwargs(kwargs)
|
|
@@ -49,6 +60,7 @@ class SnowTyper(typer.Typer):
|
|
|
49
60
|
no_args_is_help=True,
|
|
50
61
|
add_completion=True,
|
|
51
62
|
rich_markup_mode="markdown",
|
|
63
|
+
cls=SortedTyperGroup,
|
|
52
64
|
)
|
|
53
65
|
|
|
54
66
|
@staticmethod
|
snowflake/cli/api/config.py
CHANGED
|
@@ -20,7 +20,7 @@ import warnings
|
|
|
20
20
|
from contextlib import contextmanager
|
|
21
21
|
from dataclasses import asdict, dataclass, field
|
|
22
22
|
from pathlib import Path
|
|
23
|
-
from typing import Any, Dict, Optional, Union
|
|
23
|
+
from typing import Any, Dict, List, Optional, Union
|
|
24
24
|
|
|
25
25
|
import tomlkit
|
|
26
26
|
from click import ClickException
|
|
@@ -58,6 +58,7 @@ PLUGINS_SECTION = "plugins"
|
|
|
58
58
|
|
|
59
59
|
LOGS_SECTION_PATH = [CLI_SECTION, LOGS_SECTION]
|
|
60
60
|
PLUGINS_SECTION_PATH = [CLI_SECTION, PLUGINS_SECTION]
|
|
61
|
+
PLUGIN_ENABLED_KEY = "enabled"
|
|
61
62
|
FEATURE_FLAGS_SECTION_PATH = [CLI_SECTION, "features"]
|
|
62
63
|
|
|
63
64
|
CONFIG_MANAGER.add_option(
|
|
@@ -140,8 +141,7 @@ def add_connection_to_proper_file(name: str, connection_config: ConnectionConfig
|
|
|
140
141
|
return CONNECTIONS_FILE
|
|
141
142
|
else:
|
|
142
143
|
set_config_value(
|
|
143
|
-
|
|
144
|
-
key=name,
|
|
144
|
+
path=[CONNECTIONS_SECTION, name],
|
|
145
145
|
value=connection_config.to_dict_of_all_non_empty_values(),
|
|
146
146
|
)
|
|
147
147
|
return CONFIG_MANAGER.file_path
|
|
@@ -200,14 +200,19 @@ def _initialise_logs_section():
|
|
|
200
200
|
conf_file_cache[CLI_SECTION][LOGS_SECTION] = _DEFAULT_LOGS_CONFIG
|
|
201
201
|
|
|
202
202
|
|
|
203
|
-
def set_config_value(
|
|
203
|
+
def set_config_value(path: List[str], value: Any) -> None:
|
|
204
|
+
"""Sets value in config.
|
|
205
|
+
For example to set value "val" to key "key" in section [a.b.c], call
|
|
206
|
+
set_config_value(["a", "b", "c", "key"], "val").
|
|
207
|
+
If you want to override a whole section, value should be a dictionary.
|
|
208
|
+
"""
|
|
204
209
|
with _config_file() as conf_file_cache:
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
210
|
+
current_config_dict = conf_file_cache
|
|
211
|
+
for key in path[:-1]:
|
|
212
|
+
if key not in current_config_dict:
|
|
213
|
+
current_config_dict[key] = {}
|
|
214
|
+
current_config_dict = current_config_dict[key]
|
|
215
|
+
current_config_dict[path[-1]] = value
|
|
211
216
|
|
|
212
217
|
|
|
213
218
|
def get_logs_config() -> dict:
|
snowflake/cli/api/constants.py
CHANGED
|
@@ -47,6 +47,7 @@ class ObjectType(Enum):
|
|
|
47
47
|
NETWORK_RULE = ObjectNames("network-rule", "network rule", "network rules")
|
|
48
48
|
NOTEBOOK = ObjectNames("notebook", "notebook", "notebooks")
|
|
49
49
|
PROCEDURE = ObjectNames("procedure", "procedure", "procedures")
|
|
50
|
+
PROJECT = ObjectNames("project", "project", "projects")
|
|
50
51
|
ROLE = ObjectNames("role", "role", "roles")
|
|
51
52
|
SCHEMA = ObjectNames("schema", "schema", "schemas")
|
|
52
53
|
SERVICE = ObjectNames("service", "service", "services")
|
|
@@ -77,6 +78,7 @@ OBJECT_TO_NAMES = {o.value.cli_name: o.value for o in ObjectType}
|
|
|
77
78
|
UNSUPPORTED_OBJECTS = {
|
|
78
79
|
ObjectType.APPLICATION.value.cli_name,
|
|
79
80
|
ObjectType.APPLICATION_PACKAGE.value.cli_name,
|
|
81
|
+
ObjectType.PROJECT.value.cli_name,
|
|
80
82
|
}
|
|
81
83
|
SUPPORTED_OBJECTS = sorted(OBJECT_TO_NAMES.keys() - UNSUPPORTED_OBJECTS)
|
|
82
84
|
|
snowflake/cli/api/errno.py
CHANGED
|
@@ -78,6 +78,7 @@ MAX_VERSIONS_IN_RELEASE_CHANNEL_REACHED = 512004
|
|
|
78
78
|
MAX_UNBOUND_VERSIONS_REACHED = 512023
|
|
79
79
|
CANNOT_DEREGISTER_VERSION_ASSOCIATED_WITH_CHANNEL = 512021
|
|
80
80
|
TARGET_ACCOUNT_USED_BY_OTHER_RELEASE_DIRECTIVE = 93091
|
|
81
|
+
CANNOT_SET_DEBUG_MODE_WITH_MANIFEST_VERSION = 93362
|
|
81
82
|
|
|
82
83
|
|
|
83
84
|
ERR_JAVASCRIPT_EXECUTION = 100132
|
snowflake/cli/api/exceptions.py
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
17
|
from pathlib import Path
|
|
18
|
-
from typing import Optional
|
|
18
|
+
from typing import List, Optional
|
|
19
19
|
|
|
20
20
|
from click.exceptions import ClickException, UsageError
|
|
21
21
|
from snowflake.cli.api.constants import ObjectType
|
|
@@ -54,6 +54,13 @@ class InvalidPluginConfiguration(ClickException):
|
|
|
54
54
|
return f"Invalid plugin configuration. {self.message}"
|
|
55
55
|
|
|
56
56
|
|
|
57
|
+
class PluginNotInstalledError(ClickException):
|
|
58
|
+
def __init__(self, plugin_name, installed_plugins: List[str]):
|
|
59
|
+
super().__init__(
|
|
60
|
+
f"Plugin {plugin_name} is not installed. Available plugins: {', '.join(installed_plugins)}."
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
57
64
|
class SnowflakeConnectionError(ClickException):
|
|
58
65
|
def __init__(self, snowflake_err: Exception):
|
|
59
66
|
super().__init__(f"Could not connect to Snowflake. Reason: {snowflake_err}")
|
|
@@ -229,3 +236,10 @@ class ShowSpecificObjectMultipleRowsError(RuntimeError):
|
|
|
229
236
|
super().__init__(
|
|
230
237
|
f"Received multiple rows from result of SQL statement: {show_obj_query}. Usage of 'show_specific_object' may not be properly scoped."
|
|
231
238
|
)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class CouldNotSetKeyPairError(ClickException):
|
|
242
|
+
def __init__(self):
|
|
243
|
+
super().__init__(
|
|
244
|
+
"The public key is set already. Use the rotate command instead."
|
|
245
|
+
)
|
|
@@ -67,3 +67,5 @@ class FeatureFlag(FeatureFlagMixin):
|
|
|
67
67
|
"ENABLE_SEPARATE_AUTHENTICATION_POLICY_ID", False
|
|
68
68
|
)
|
|
69
69
|
ENABLE_SNOWPARK_GLOB_SUPPORT = BooleanFlag("ENABLE_SNOWPARK_GLOB_SUPPORT", False)
|
|
70
|
+
ENABLE_SPCS_SERVICE_EVENTS = BooleanFlag("ENABLE_SPCS_SERVICE_EVENTS", False)
|
|
71
|
+
ENABLE_AUTH_KEYPAIR = BooleanFlag("ENABLE_AUTH_KEYPAIR", False)
|
|
@@ -17,6 +17,16 @@ from __future__ import annotations
|
|
|
17
17
|
from dataclasses import dataclass
|
|
18
18
|
from typing import Any, Dict, List
|
|
19
19
|
|
|
20
|
+
from snowflake.cli.api.config import (
|
|
21
|
+
PLUGIN_ENABLED_KEY,
|
|
22
|
+
PLUGINS_SECTION_PATH,
|
|
23
|
+
config_section_exists,
|
|
24
|
+
get_config_section,
|
|
25
|
+
get_config_value,
|
|
26
|
+
get_plugins_config,
|
|
27
|
+
)
|
|
28
|
+
from snowflake.cli.api.exceptions import InvalidPluginConfiguration
|
|
29
|
+
|
|
20
30
|
|
|
21
31
|
@dataclass
|
|
22
32
|
class PluginConfig:
|
|
@@ -25,8 +35,37 @@ class PluginConfig:
|
|
|
25
35
|
|
|
26
36
|
|
|
27
37
|
class PluginConfigProvider:
|
|
28
|
-
|
|
29
|
-
|
|
38
|
+
@staticmethod
|
|
39
|
+
def get_enabled_plugin_names() -> List[str]:
|
|
40
|
+
enabled_plugins = []
|
|
41
|
+
for plugin_name, plugin_config_section in get_plugins_config().items():
|
|
42
|
+
enabled = plugin_config_section.get(PLUGIN_ENABLED_KEY, False)
|
|
43
|
+
_assert_value_is_bool(
|
|
44
|
+
enabled, value_name=PLUGIN_ENABLED_KEY, plugin_name=plugin_name
|
|
45
|
+
)
|
|
46
|
+
if enabled:
|
|
47
|
+
enabled_plugins.append(plugin_name)
|
|
48
|
+
return enabled_plugins
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def get_config(plugin_name: str) -> PluginConfig:
|
|
52
|
+
config_path = PLUGINS_SECTION_PATH + [plugin_name]
|
|
53
|
+
plugin_config = PluginConfig(is_plugin_enabled=False, internal_config={})
|
|
54
|
+
plugin_config.is_plugin_enabled = get_config_value(
|
|
55
|
+
*config_path, key=PLUGIN_ENABLED_KEY, default=False
|
|
56
|
+
)
|
|
57
|
+
_assert_value_is_bool(
|
|
58
|
+
plugin_config.is_plugin_enabled,
|
|
59
|
+
value_name=PLUGIN_ENABLED_KEY,
|
|
60
|
+
plugin_name=plugin_name,
|
|
61
|
+
)
|
|
62
|
+
if config_section_exists(*config_path, "config"):
|
|
63
|
+
plugin_config.internal_config = get_config_section(*config_path, "config")
|
|
64
|
+
return plugin_config
|
|
65
|
+
|
|
30
66
|
|
|
31
|
-
|
|
32
|
-
|
|
67
|
+
def _assert_value_is_bool(value, *, value_name: str, plugin_name: str) -> None:
|
|
68
|
+
if type(value) is not bool:
|
|
69
|
+
raise InvalidPluginConfiguration(
|
|
70
|
+
f'[{plugin_name}]: "{value_name}" must be a boolean'
|
|
71
|
+
)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from click import UsageError
|
|
4
|
+
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
5
|
+
from snowflake.cli.api.constants import ObjectType
|
|
6
|
+
from snowflake.cli.api.exceptions import NoProjectDefinitionError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_entity_from_project_definition(
|
|
10
|
+
entity_type: ObjectType, entity_id: Optional[str] = None
|
|
11
|
+
):
|
|
12
|
+
cli_context = get_cli_context()
|
|
13
|
+
pd = cli_context.project_definition
|
|
14
|
+
entities = pd.get_entities_by_type(entity_type=entity_type.value.cli_name)
|
|
15
|
+
|
|
16
|
+
if not entities:
|
|
17
|
+
raise NoProjectDefinitionError(
|
|
18
|
+
project_type=entity_type.value.sf_name,
|
|
19
|
+
project_root=cli_context.project_root,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
if entity_id and entity_id not in entities:
|
|
23
|
+
raise UsageError(f"No '{entity_id}' entity in project definition file.")
|
|
24
|
+
elif len(entities.keys()) == 1:
|
|
25
|
+
entity_id = list(entities.keys())[0]
|
|
26
|
+
|
|
27
|
+
if entity_id is None:
|
|
28
|
+
raise UsageError(
|
|
29
|
+
f"Multiple {entity_type.value.sf_plural_name} found. Please provide entity id for the operation."
|
|
30
|
+
)
|
|
31
|
+
return entities[entity_id]
|
|
@@ -26,6 +26,10 @@ from snowflake.cli._plugins.nativeapp.entities.application_package import (
|
|
|
26
26
|
)
|
|
27
27
|
from snowflake.cli._plugins.notebook.notebook_entity import NotebookEntity
|
|
28
28
|
from snowflake.cli._plugins.notebook.notebook_entity_model import NotebookEntityModel
|
|
29
|
+
from snowflake.cli._plugins.project.project_entity_model import (
|
|
30
|
+
ProjectEntity,
|
|
31
|
+
ProjectEntityModel,
|
|
32
|
+
)
|
|
29
33
|
from snowflake.cli._plugins.snowpark.snowpark_entity import (
|
|
30
34
|
FunctionEntity,
|
|
31
35
|
ProcedureEntity,
|
|
@@ -34,6 +38,20 @@ from snowflake.cli._plugins.snowpark.snowpark_entity_model import (
|
|
|
34
38
|
FunctionEntityModel,
|
|
35
39
|
ProcedureEntityModel,
|
|
36
40
|
)
|
|
41
|
+
from snowflake.cli._plugins.spcs.compute_pool.compute_pool_entity import (
|
|
42
|
+
ComputePoolEntity,
|
|
43
|
+
)
|
|
44
|
+
from snowflake.cli._plugins.spcs.compute_pool.compute_pool_entity_model import (
|
|
45
|
+
ComputePoolEntityModel,
|
|
46
|
+
)
|
|
47
|
+
from snowflake.cli._plugins.spcs.image_repository.image_repository_entity import (
|
|
48
|
+
ImageRepositoryEntity,
|
|
49
|
+
)
|
|
50
|
+
from snowflake.cli._plugins.spcs.image_repository.image_repository_entity_model import (
|
|
51
|
+
ImageRepositoryEntityModel,
|
|
52
|
+
)
|
|
53
|
+
from snowflake.cli._plugins.spcs.services.service_entity import ServiceEntity
|
|
54
|
+
from snowflake.cli._plugins.spcs.services.service_entity_model import ServiceEntityModel
|
|
37
55
|
from snowflake.cli._plugins.streamlit.streamlit_entity import StreamlitEntity
|
|
38
56
|
from snowflake.cli._plugins.streamlit.streamlit_entity_model import (
|
|
39
57
|
StreamlitEntityModel,
|
|
@@ -44,7 +62,11 @@ Entity = Union[
|
|
|
44
62
|
ApplicationPackageEntity,
|
|
45
63
|
StreamlitEntity,
|
|
46
64
|
ProcedureEntity,
|
|
65
|
+
ProjectEntity,
|
|
47
66
|
FunctionEntity,
|
|
67
|
+
ComputePoolEntity,
|
|
68
|
+
ImageRepositoryEntity,
|
|
69
|
+
ServiceEntity,
|
|
48
70
|
NotebookEntity,
|
|
49
71
|
]
|
|
50
72
|
EntityModel = Union[
|
|
@@ -53,7 +75,11 @@ EntityModel = Union[
|
|
|
53
75
|
StreamlitEntityModel,
|
|
54
76
|
FunctionEntityModel,
|
|
55
77
|
ProcedureEntityModel,
|
|
78
|
+
ComputePoolEntityModel,
|
|
79
|
+
ImageRepositoryEntityModel,
|
|
80
|
+
ServiceEntityModel,
|
|
56
81
|
NotebookEntityModel,
|
|
82
|
+
ProjectEntityModel,
|
|
57
83
|
]
|
|
58
84
|
|
|
59
85
|
ALL_ENTITIES: List[Entity] = [*get_args(Entity)]
|
snowflake/cli/api/rest_api.py
CHANGED
|
@@ -155,9 +155,8 @@ class RestApi:
|
|
|
155
155
|
raise SchemaNotDefinedException(
|
|
156
156
|
"Schema not defined in connection. Please try again with `--schema` flag."
|
|
157
157
|
)
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
# raise SchemaNotExistsException(f"Schema '{schema}' does not exist.")
|
|
158
|
+
if not self._schema_exists(db_name=db, schema_name=schema):
|
|
159
|
+
raise SchemaNotExistsException(f"Schema '{schema}' does not exist.")
|
|
161
160
|
if self.get_endpoint_exists(
|
|
162
161
|
url := f"{SF_REST_API_URL_PREFIX}/databases/{self.conn.database}/schemas/{self.conn.schema}/{plural_object_type}/"
|
|
163
162
|
):
|
snowflake/cli/api/secure_path.py
CHANGED
|
@@ -21,7 +21,7 @@ import shutil
|
|
|
21
21
|
import tempfile
|
|
22
22
|
from contextlib import contextmanager
|
|
23
23
|
from pathlib import Path
|
|
24
|
-
from typing import Optional, Union
|
|
24
|
+
from typing import IO, Any, Generator, Optional, Union
|
|
25
25
|
|
|
26
26
|
from snowflake.cli.api.exceptions import DirectoryIsNotEmptyError, FileTooLargeError
|
|
27
27
|
from snowflake.cli.api.secure_utils import (
|
|
@@ -38,7 +38,7 @@ UNLIMITED = -1
|
|
|
38
38
|
|
|
39
39
|
class SecurePath:
|
|
40
40
|
def __init__(self, path: Union[Path, str]):
|
|
41
|
-
self._path = Path(path)
|
|
41
|
+
self._path = Path(os.path.expanduser(path))
|
|
42
42
|
|
|
43
43
|
def __repr__(self):
|
|
44
44
|
return f'SecurePath("{self._path}")'
|
|
@@ -72,6 +72,12 @@ class SecurePath:
|
|
|
72
72
|
"""
|
|
73
73
|
return SecurePath(self._path.absolute())
|
|
74
74
|
|
|
75
|
+
def resolve(self):
|
|
76
|
+
"""
|
|
77
|
+
Make the path absolute, resolving symlinks
|
|
78
|
+
"""
|
|
79
|
+
return SecurePath(self._path.resolve())
|
|
80
|
+
|
|
75
81
|
def iterdir(self):
|
|
76
82
|
"""
|
|
77
83
|
When the path points to a directory, yield path objects of the directory contents.
|
|
@@ -102,7 +108,7 @@ class SecurePath:
|
|
|
102
108
|
Return True if the path points to a regular file (or a symbolic link pointing to a regular file),
|
|
103
109
|
False if it points to another kind of file.
|
|
104
110
|
"""
|
|
105
|
-
return self._path.is_file()
|
|
111
|
+
return self._path.expanduser().absolute().is_file()
|
|
106
112
|
|
|
107
113
|
def glob(self, pattern: str):
|
|
108
114
|
"""
|
|
@@ -110,6 +116,12 @@ class SecurePath:
|
|
|
110
116
|
"""
|
|
111
117
|
return self._path.glob(pattern)
|
|
112
118
|
|
|
119
|
+
def as_posix(self) -> str:
|
|
120
|
+
"""
|
|
121
|
+
Return the string representation of the path with forward slashes (/) as the path separator.
|
|
122
|
+
"""
|
|
123
|
+
return self._path.as_posix()
|
|
124
|
+
|
|
113
125
|
@property
|
|
114
126
|
def name(self) -> str:
|
|
115
127
|
"""A string representing the final path component."""
|
|
@@ -172,7 +184,7 @@ class SecurePath:
|
|
|
172
184
|
mode="r",
|
|
173
185
|
read_file_limit_mb: Optional[int] = None,
|
|
174
186
|
**open_kwargs,
|
|
175
|
-
):
|
|
187
|
+
) -> Generator[IO[Any], None, None]:
|
|
176
188
|
"""
|
|
177
189
|
Open the file pointed by this path and return a file object, as
|
|
178
190
|
the built-in open() function does.
|
|
@@ -85,9 +85,13 @@ class BaseSqlExecutor:
|
|
|
85
85
|
)
|
|
86
86
|
return stream_generator if return_cursors else list()
|
|
87
87
|
|
|
88
|
+
def execute_string(self, query: str, **kwargs) -> Iterable[SnowflakeCursor]:
|
|
89
|
+
"""Executes a single SQL query and returns the results"""
|
|
90
|
+
return self._execute_string(query, **kwargs)
|
|
91
|
+
|
|
88
92
|
def execute_query(self, query: str, **kwargs):
|
|
89
|
-
"""Executes a single SQL query and returns the result"""
|
|
90
|
-
*_, last_result = list(self.
|
|
93
|
+
"""Executes a single SQL query and returns the last result"""
|
|
94
|
+
*_, last_result = list(self.execute_string(dedent(query), **kwargs))
|
|
91
95
|
return last_result
|
|
92
96
|
|
|
93
97
|
def execute_queries(self, queries: str, **kwargs):
|
|
@@ -95,7 +99,7 @@ class BaseSqlExecutor:
|
|
|
95
99
|
|
|
96
100
|
# Without remove_comments=True, connectors might throw an error if there is a comment at the end of the file
|
|
97
101
|
return list(
|
|
98
|
-
self.
|
|
102
|
+
self.execute_string(dedent(queries), remove_comments=True, **kwargs)
|
|
99
103
|
)
|
|
100
104
|
|
|
101
105
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: snowflake-cli
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.6.0
|
|
4
4
|
Summary: Snowflake CLI
|
|
5
5
|
Project-URL: Source code, https://github.com/snowflakedb/snowflake-cli
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/snowflakedb/snowflake-cli/issues
|
|
@@ -218,32 +218,32 @@ Classifier: Programming Language :: SQL
|
|
|
218
218
|
Classifier: Topic :: Database
|
|
219
219
|
Requires-Python: >=3.10
|
|
220
220
|
Requires-Dist: gitpython==3.1.44
|
|
221
|
-
Requires-Dist: jinja2==3.1.
|
|
221
|
+
Requires-Dist: jinja2==3.1.6
|
|
222
222
|
Requires-Dist: packaging
|
|
223
223
|
Requires-Dist: pip
|
|
224
224
|
Requires-Dist: pluggy==1.5.0
|
|
225
|
-
Requires-Dist: pydantic==2.10.
|
|
225
|
+
Requires-Dist: pydantic==2.10.6
|
|
226
226
|
Requires-Dist: pyyaml==6.0.2
|
|
227
227
|
Requires-Dist: requests==2.32.3
|
|
228
228
|
Requires-Dist: requirements-parser==0.11.0
|
|
229
229
|
Requires-Dist: rich==13.9.4
|
|
230
|
-
Requires-Dist: setuptools==
|
|
231
|
-
Requires-Dist: snowflake-connector-python[secure-local-storage]==3.
|
|
230
|
+
Requires-Dist: setuptools==76.0.0
|
|
231
|
+
Requires-Dist: snowflake-connector-python[secure-local-storage]==3.14.0
|
|
232
232
|
Requires-Dist: snowflake-core==1.0.2; python_version < '3.12'
|
|
233
233
|
Requires-Dist: snowflake-snowpark-python<1.26.0,>=1.15.0; python_version < '3.12'
|
|
234
234
|
Requires-Dist: tomlkit==0.13.2
|
|
235
|
-
Requires-Dist: typer==0.
|
|
235
|
+
Requires-Dist: typer==0.15.2
|
|
236
236
|
Requires-Dist: urllib3<2.4,>=1.24.3
|
|
237
237
|
Provides-Extra: development
|
|
238
|
-
Requires-Dist: coverage==7.6.
|
|
239
|
-
Requires-Dist: factory-boy==3.3.
|
|
240
|
-
Requires-Dist: faker==
|
|
238
|
+
Requires-Dist: coverage==7.6.12; extra == 'development'
|
|
239
|
+
Requires-Dist: factory-boy==3.3.3; extra == 'development'
|
|
240
|
+
Requires-Dist: faker==37.0.0; extra == 'development'
|
|
241
241
|
Requires-Dist: pre-commit>=3.5.0; extra == 'development'
|
|
242
|
+
Requires-Dist: pytest-httpserver==1.1.2; extra == 'development'
|
|
242
243
|
Requires-Dist: pytest-randomly==3.16.0; extra == 'development'
|
|
243
|
-
Requires-Dist: pytest==8.3.
|
|
244
|
-
Requires-Dist: syrupy==4.
|
|
244
|
+
Requires-Dist: pytest==8.3.5; extra == 'development'
|
|
245
|
+
Requires-Dist: syrupy==4.9.0; extra == 'development'
|
|
245
246
|
Provides-Extra: packaging
|
|
246
|
-
Requires-Dist: pyinstaller~=6.10; extra == 'packaging'
|
|
247
247
|
Description-Content-Type: text/markdown
|
|
248
248
|
|
|
249
249
|
<!--
|