snowflake-cli 3.5.0__py3-none-any.whl → 3.7.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/commands_registration/builtin_plugins.py +4 -0
- snowflake/cli/_app/loggers.py +2 -2
- snowflake/cli/_app/snow_connector.py +7 -6
- 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 +78 -1
- snowflake/cli/_plugins/helpers/commands.py +25 -1
- snowflake/cli/_plugins/helpers/snowsl_vars_reader.py +133 -0
- snowflake/cli/_plugins/init/commands.py +9 -6
- snowflake/cli/_plugins/logs/__init__.py +0 -0
- snowflake/cli/_plugins/logs/commands.py +105 -0
- snowflake/cli/_plugins/logs/manager.py +107 -0
- snowflake/cli/_plugins/logs/plugin_spec.py +16 -0
- snowflake/cli/_plugins/logs/utils.py +60 -0
- 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 -0
- snowflake/cli/_plugins/notebook/notebook_entity.py +16 -27
- snowflake/cli/_plugins/object/command_aliases.py +3 -1
- snowflake/cli/_plugins/object/manager.py +4 -2
- snowflake/cli/_plugins/project/commands.py +89 -48
- snowflake/cli/_plugins/project/manager.py +57 -23
- snowflake/cli/_plugins/project/project_entity_model.py +22 -3
- snowflake/cli/_plugins/snowpark/commands.py +15 -2
- snowflake/cli/_plugins/spcs/compute_pool/commands.py +17 -5
- snowflake/cli/_plugins/sql/manager.py +43 -52
- snowflake/cli/_plugins/sql/source_reader.py +230 -0
- snowflake/cli/_plugins/stage/manager.py +25 -12
- snowflake/cli/_plugins/streamlit/commands.py +3 -0
- snowflake/cli/_plugins/streamlit/manager.py +19 -15
- snowflake/cli/api/artifacts/upload.py +30 -34
- snowflake/cli/api/artifacts/utils.py +8 -6
- snowflake/cli/api/cli_global_context.py +7 -2
- snowflake/cli/api/commands/decorators.py +11 -2
- snowflake/cli/api/commands/flags.py +35 -4
- snowflake/cli/api/commands/snow_typer.py +20 -2
- snowflake/cli/api/config.py +5 -3
- snowflake/cli/api/constants.py +2 -0
- snowflake/cli/api/entities/utils.py +29 -16
- snowflake/cli/api/errno.py +1 -0
- snowflake/cli/api/exceptions.py +75 -27
- snowflake/cli/api/feature_flags.py +1 -0
- snowflake/cli/api/identifiers.py +2 -0
- snowflake/cli/api/plugins/plugin_config.py +2 -2
- snowflake/cli/api/project/schemas/template.py +3 -3
- snowflake/cli/api/rendering/project_templates.py +3 -3
- snowflake/cli/api/rendering/sql_templates.py +2 -2
- 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 +8 -4
- snowflake/cli/api/utils/definition_rendering.py +14 -8
- snowflake/cli/api/utils/templating_functions.py +4 -4
- {snowflake_cli-3.5.0.dist-info → snowflake_cli-3.7.0.dist-info}/METADATA +11 -11
- {snowflake_cli-3.5.0.dist-info → snowflake_cli-3.7.0.dist-info}/RECORD +64 -52
- {snowflake_cli-3.5.0.dist-info → snowflake_cli-3.7.0.dist-info}/WHEEL +0 -0
- {snowflake_cli-3.5.0.dist-info → snowflake_cli-3.7.0.dist-info}/entry_points.txt +0 -0
- {snowflake_cli-3.5.0.dist-info → snowflake_cli-3.7.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -32,6 +32,7 @@ from snowflake.cli.api.connections import ConnectionContext
|
|
|
32
32
|
from snowflake.cli.api.console import cli_console
|
|
33
33
|
from snowflake.cli.api.identifiers import FQN
|
|
34
34
|
from snowflake.cli.api.output.formats import OutputFormat
|
|
35
|
+
from snowflake.cli.api.secret import SecretType
|
|
35
36
|
from snowflake.cli.api.stage_path import StagePath
|
|
36
37
|
|
|
37
38
|
DEFAULT_CONTEXT_SETTINGS = {"help_option_names": ["--help", "-h"]}
|
|
@@ -214,7 +215,7 @@ MasterTokenOption = typer.Option(
|
|
|
214
215
|
TokenFilePathOption = typer.Option(
|
|
215
216
|
None,
|
|
216
217
|
"--token-file-path",
|
|
217
|
-
help="Path to file with an OAuth token
|
|
218
|
+
help="Path to file with an OAuth token to use when connecting to Snowflake.",
|
|
218
219
|
callback=_connection_callback("token_file_path"),
|
|
219
220
|
show_default=False,
|
|
220
221
|
rich_help_panel=_CONNECTION_SECTION,
|
|
@@ -324,7 +325,7 @@ def _diag_log_allowlist_path_callback(path: str):
|
|
|
324
325
|
DiagAllowlistPathOption: Path = typer.Option(
|
|
325
326
|
None,
|
|
326
327
|
"--diag-allowlist-path",
|
|
327
|
-
help="Path to a JSON file
|
|
328
|
+
help="Path to a JSON file that contains allowlist parameters.",
|
|
328
329
|
callback=_diag_log_allowlist_path_callback,
|
|
329
330
|
show_default=False,
|
|
330
331
|
rich_help_panel=_CONNECTION_SECTION,
|
|
@@ -370,6 +371,17 @@ DebugOption = typer.Option(
|
|
|
370
371
|
rich_help_panel=_CLI_BEHAVIOUR,
|
|
371
372
|
)
|
|
372
373
|
|
|
374
|
+
EnhancedExitCodesOption = typer.Option(
|
|
375
|
+
False,
|
|
376
|
+
"--enhanced-exit-codes",
|
|
377
|
+
help="Differentiate exit error codes based on failure type.",
|
|
378
|
+
callback=_context_callback("enhanced_exit_codes"),
|
|
379
|
+
is_flag=True,
|
|
380
|
+
rich_help_panel=_CLI_BEHAVIOUR,
|
|
381
|
+
is_eager=True,
|
|
382
|
+
envvar="SNOWFLAKE_ENHANCED_EXIT_CODES",
|
|
383
|
+
)
|
|
384
|
+
|
|
373
385
|
|
|
374
386
|
# If IfExistsOption, IfNotExistsOption, or ReplaceOption are used with names other than those in CREATE_MODE_OPTION_NAMES,
|
|
375
387
|
# you must also override mutually_exclusive if you want to retain the validation that at most one of these flags is
|
|
@@ -411,9 +423,19 @@ OnErrorOption = typer.Option(
|
|
|
411
423
|
|
|
412
424
|
NoInteractiveOption = typer.Option(False, "--no-interactive", help="Disable prompting.")
|
|
413
425
|
|
|
426
|
+
PruneOption = OverrideableOption(
|
|
427
|
+
False,
|
|
428
|
+
"--prune",
|
|
429
|
+
help=f"Delete files that exist in the stage, but not in the local filesystem.",
|
|
430
|
+
show_default=False,
|
|
431
|
+
)
|
|
432
|
+
|
|
414
433
|
|
|
415
|
-
def entity_argument(entity_type: str) -> typer.Argument:
|
|
416
|
-
|
|
434
|
+
def entity_argument(entity_type: str, required=False) -> typer.Argument:
|
|
435
|
+
_help = f"ID of {entity_type} entity."
|
|
436
|
+
if not required:
|
|
437
|
+
return typer.Argument(None, help=_help)
|
|
438
|
+
return typer.Argument(..., help=_help, show_default=False)
|
|
417
439
|
|
|
418
440
|
|
|
419
441
|
def variables_option(description: str):
|
|
@@ -501,6 +523,15 @@ class IdentifierStagePathType(click.ParamType):
|
|
|
501
523
|
return StagePath.from_stage_str(value)
|
|
502
524
|
|
|
503
525
|
|
|
526
|
+
class SecretTypeParser(click.ParamType):
|
|
527
|
+
name = "TEXT"
|
|
528
|
+
|
|
529
|
+
def convert(self, value, param, ctx):
|
|
530
|
+
if not isinstance(value, SecretType):
|
|
531
|
+
return SecretType(value)
|
|
532
|
+
return value
|
|
533
|
+
|
|
534
|
+
|
|
504
535
|
def identifier_argument(
|
|
505
536
|
sf_object: str,
|
|
506
537
|
example: str,
|
|
@@ -31,7 +31,13 @@ from snowflake.cli.api.commands.execution_metadata import (
|
|
|
31
31
|
ExecutionStatus,
|
|
32
32
|
)
|
|
33
33
|
from snowflake.cli.api.commands.flags import DEFAULT_CONTEXT_SETTINGS
|
|
34
|
-
from snowflake.cli.api.exceptions import
|
|
34
|
+
from snowflake.cli.api.exceptions import (
|
|
35
|
+
BaseCliError,
|
|
36
|
+
CliArgumentError,
|
|
37
|
+
CliError,
|
|
38
|
+
CliSqlError,
|
|
39
|
+
CommandReturnTypeError,
|
|
40
|
+
)
|
|
35
41
|
from snowflake.cli.api.output.types import CommandResult
|
|
36
42
|
from snowflake.cli.api.sanitizers import sanitize_for_terminal
|
|
37
43
|
from snowflake.cli.api.sql_execution import SqlExecutionMixin
|
|
@@ -168,8 +174,20 @@ class SnowTyper(typer.Typer):
|
|
|
168
174
|
|
|
169
175
|
log.debug("Executing command exception callback")
|
|
170
176
|
log_command_execution_error(exception, execution)
|
|
177
|
+
exception = SnowTyper._cli_base_exception_migration_dispatcher(exception)
|
|
178
|
+
return exception
|
|
179
|
+
|
|
180
|
+
@staticmethod
|
|
181
|
+
def _cli_base_exception_migration_dispatcher(exception):
|
|
182
|
+
"""Handler used for dispatching exception until migration completed."""
|
|
183
|
+
if isinstance(
|
|
184
|
+
exception, (BaseCliError, CliError, CliArgumentError, CliSqlError)
|
|
185
|
+
):
|
|
186
|
+
return exception
|
|
187
|
+
|
|
171
188
|
if isinstance(exception, DatabaseError):
|
|
172
|
-
return
|
|
189
|
+
return CliSqlError(exception.msg)
|
|
190
|
+
|
|
173
191
|
return exception
|
|
174
192
|
|
|
175
193
|
@staticmethod
|
snowflake/cli/api/config.py
CHANGED
|
@@ -26,7 +26,7 @@ import tomlkit
|
|
|
26
26
|
from click import ClickException
|
|
27
27
|
from snowflake.cli.api.exceptions import (
|
|
28
28
|
ConfigFileTooWidePermissionsError,
|
|
29
|
-
|
|
29
|
+
MissingConfigurationError,
|
|
30
30
|
UnsupportedConfigSectionTypeError,
|
|
31
31
|
)
|
|
32
32
|
from snowflake.cli.api.secure_path import SecurePath
|
|
@@ -252,7 +252,9 @@ def get_connection_dict(connection_name: str) -> dict:
|
|
|
252
252
|
try:
|
|
253
253
|
return get_config_section(CONNECTIONS_SECTION, connection_name)
|
|
254
254
|
except KeyError:
|
|
255
|
-
raise
|
|
255
|
+
raise MissingConfigurationError(
|
|
256
|
+
f"Connection {connection_name} is not configured"
|
|
257
|
+
)
|
|
256
258
|
|
|
257
259
|
|
|
258
260
|
def get_default_connection_name() -> str:
|
|
@@ -262,7 +264,7 @@ def get_default_connection_name() -> str:
|
|
|
262
264
|
def get_default_connection_dict() -> dict:
|
|
263
265
|
def_connection_name = get_default_connection_name()
|
|
264
266
|
if not connection_exists(def_connection_name):
|
|
265
|
-
raise
|
|
267
|
+
raise MissingConfigurationError(
|
|
266
268
|
f"Couldn't find connection for default connection `{def_connection_name}`. "
|
|
267
269
|
f"Specify connection name or configure default connection."
|
|
268
270
|
)
|
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
|
|
|
@@ -19,7 +19,10 @@ from snowflake.cli._plugins.stage.diff import (
|
|
|
19
19
|
sync_local_diff_with_stage,
|
|
20
20
|
to_stage_path,
|
|
21
21
|
)
|
|
22
|
-
from snowflake.cli._plugins.stage.manager import
|
|
22
|
+
from snowflake.cli._plugins.stage.manager import (
|
|
23
|
+
StageManager,
|
|
24
|
+
StagePathParts,
|
|
25
|
+
)
|
|
23
26
|
from snowflake.cli._plugins.stage.utils import print_diff_to_console
|
|
24
27
|
from snowflake.cli.api.artifacts.bundle_map import BundleMap
|
|
25
28
|
from snowflake.cli.api.cli_global_context import get_cli_context, span
|
|
@@ -33,6 +36,7 @@ from snowflake.cli.api.exceptions import (
|
|
|
33
36
|
NoWarehouseSelectedInSessionError,
|
|
34
37
|
SnowflakeSQLExecutionError,
|
|
35
38
|
)
|
|
39
|
+
from snowflake.cli.api.identifiers import FQN
|
|
36
40
|
from snowflake.cli.api.metrics import CLICounterField
|
|
37
41
|
from snowflake.cli.api.project.schemas.entities.common import PostDeployHook
|
|
38
42
|
from snowflake.cli.api.rendering.sql_templates import (
|
|
@@ -79,12 +83,12 @@ def _get_stage_paths_to_sync(
|
|
|
79
83
|
def sync_deploy_root_with_stage(
|
|
80
84
|
console: AbstractConsole,
|
|
81
85
|
deploy_root: Path,
|
|
82
|
-
package_name: str,
|
|
83
86
|
bundle_map: BundleMap,
|
|
84
|
-
role: str,
|
|
85
87
|
prune: bool,
|
|
86
88
|
recursive: bool,
|
|
87
|
-
stage_path:
|
|
89
|
+
stage_path: StagePathParts,
|
|
90
|
+
role: str | None = None,
|
|
91
|
+
package_name: str | None = None,
|
|
88
92
|
local_paths_to_sync: List[Path] | None = None,
|
|
89
93
|
print_diff: bool = True,
|
|
90
94
|
) -> DiffResult:
|
|
@@ -99,6 +103,8 @@ def sync_deploy_root_with_stage(
|
|
|
99
103
|
recursive (bool): Whether to traverse directories recursively.
|
|
100
104
|
stage_path (DefaultStagePathParts): stage path object.
|
|
101
105
|
|
|
106
|
+
package_name (str): supported for Native App compatibility. Should be None out of Native App context.
|
|
107
|
+
|
|
102
108
|
local_paths_to_sync (List[Path], optional): List of local paths to sync. Defaults to None to sync all
|
|
103
109
|
local paths. Note that providing an empty list here is equivalent to None.
|
|
104
110
|
print_diff (bool): Whether to print the diff between the local files and the remote stage. Defaults to True
|
|
@@ -106,17 +112,24 @@ def sync_deploy_root_with_stage(
|
|
|
106
112
|
Returns:
|
|
107
113
|
A `DiffResult` instance describing the changes that were performed.
|
|
108
114
|
"""
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
115
|
+
if not package_name:
|
|
116
|
+
# ensure stage exists
|
|
117
|
+
stage_fqn = FQN.from_stage(stage_path.stage)
|
|
118
|
+
console.step(f"Creating stage {stage_fqn} if not exists.")
|
|
119
|
+
StageManager().create(fqn=stage_fqn)
|
|
120
|
+
else:
|
|
121
|
+
# ensure stage exists - nativeapp behavior
|
|
122
|
+
sql_facade = get_snowflake_facade()
|
|
123
|
+
schema = stage_path.schema
|
|
124
|
+
stage_fqn = stage_path.stage
|
|
125
|
+
# Does a stage already exist within the application package, or we need to create one?
|
|
126
|
+
# Using "if not exists" should take care of either case.
|
|
127
|
+
console.step(
|
|
128
|
+
f"Checking if stage {stage_fqn} exists, or creating a new one if none exists."
|
|
129
|
+
)
|
|
130
|
+
if not sql_facade.stage_exists(stage_fqn):
|
|
131
|
+
sql_facade.create_schema(schema, database=package_name)
|
|
132
|
+
sql_facade.create_stage(stage_fqn)
|
|
120
133
|
|
|
121
134
|
# Perform a diff operation and display results to the user for informational purposes
|
|
122
135
|
if print_diff:
|
|
@@ -178,7 +191,7 @@ def sync_deploy_root_with_stage(
|
|
|
178
191
|
if print_diff:
|
|
179
192
|
print_diff_to_console(diff, bundle_map)
|
|
180
193
|
|
|
181
|
-
# Upload diff-ed files to
|
|
194
|
+
# Upload diff-ed files to the stage
|
|
182
195
|
if diff.has_changes():
|
|
183
196
|
console.step(
|
|
184
197
|
"Updating the Snowflake stage from your local %s directory."
|
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
|
@@ -22,46 +22,87 @@ from snowflake.cli.api.constants import ObjectType
|
|
|
22
22
|
from snowflake.connector.compat import IS_WINDOWS
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
class
|
|
25
|
+
class BaseCliError(ClickException):
|
|
26
|
+
"""Base Cli Exception.
|
|
27
|
+
|
|
28
|
+
0 Everything ran smoothly.
|
|
29
|
+
1 Something went wrong with the client.
|
|
30
|
+
2 Something went wrong with command line arguments.
|
|
31
|
+
3 Cli could not connect to server.
|
|
32
|
+
4 Cli could not communicate properly with server.
|
|
33
|
+
5 The enhanced_exit_codes parameter was set and Cli exited because of error.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, *args, **kwargs):
|
|
37
|
+
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
38
|
+
|
|
39
|
+
if not get_cli_context().enhanced_exit_codes:
|
|
40
|
+
self.exit_code = kwargs.pop("exit_code", 1)
|
|
41
|
+
super().__init__(*args, **kwargs)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class CliError(BaseCliError):
|
|
45
|
+
"""Generic Cli Error - to be used in favour of ClickException."""
|
|
46
|
+
|
|
47
|
+
exit_code = 1
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class CliArgumentError(BaseCliError):
|
|
51
|
+
exit_code = 2
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class CliConnectionError(BaseCliError):
|
|
55
|
+
exit_code = 3
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class CliCommunicationError(BaseCliError):
|
|
59
|
+
exit_code = 4
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class CliSqlError(BaseCliError):
|
|
63
|
+
exit_code = 5
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class EnvironmentVariableNotFoundError(CliError):
|
|
26
67
|
def __init__(self, env_variable_name: str):
|
|
27
68
|
super().__init__(f"Environment variable {env_variable_name} not found")
|
|
28
69
|
|
|
29
70
|
|
|
30
|
-
class
|
|
71
|
+
class MissingConfigurationError(CliError):
|
|
31
72
|
pass
|
|
32
73
|
|
|
33
74
|
|
|
34
|
-
class CycleDetectedError(
|
|
75
|
+
class CycleDetectedError(CliError):
|
|
35
76
|
pass
|
|
36
77
|
|
|
37
78
|
|
|
38
|
-
class
|
|
79
|
+
class InvalidTemplateError(CliError):
|
|
39
80
|
pass
|
|
40
81
|
|
|
41
82
|
|
|
42
|
-
class
|
|
83
|
+
class InvalidConnectionConfigurationError(CliError):
|
|
43
84
|
def format_message(self):
|
|
44
85
|
return f"Invalid connection configuration. {self.message}"
|
|
45
86
|
|
|
46
87
|
|
|
47
|
-
class
|
|
88
|
+
class InvalidLogsConfigurationError(CliError):
|
|
48
89
|
def format_message(self):
|
|
49
90
|
return f"Invalid logs configuration. {self.message}"
|
|
50
91
|
|
|
51
92
|
|
|
52
|
-
class
|
|
93
|
+
class InvalidPluginConfigurationError(CliError):
|
|
53
94
|
def format_message(self):
|
|
54
95
|
return f"Invalid plugin configuration. {self.message}"
|
|
55
96
|
|
|
56
97
|
|
|
57
|
-
class PluginNotInstalledError(
|
|
98
|
+
class PluginNotInstalledError(CliError):
|
|
58
99
|
def __init__(self, plugin_name, installed_plugins: List[str]):
|
|
59
100
|
super().__init__(
|
|
60
101
|
f"Plugin {plugin_name} is not installed. Available plugins: {', '.join(installed_plugins)}."
|
|
61
102
|
)
|
|
62
103
|
|
|
63
104
|
|
|
64
|
-
class SnowflakeConnectionError(
|
|
105
|
+
class SnowflakeConnectionError(CliError):
|
|
65
106
|
def __init__(self, snowflake_err: Exception):
|
|
66
107
|
super().__init__(f"Could not connect to Snowflake. Reason: {snowflake_err}")
|
|
67
108
|
|
|
@@ -71,17 +112,17 @@ class UnsupportedConfigSectionTypeError(Exception):
|
|
|
71
112
|
super().__init__(f"Unsupported configuration section type {section_type}")
|
|
72
113
|
|
|
73
114
|
|
|
74
|
-
class OutputDataTypeError(
|
|
115
|
+
class OutputDataTypeError(CliError):
|
|
75
116
|
def __init__(self, got_type: type, expected_type: type):
|
|
76
117
|
super().__init__(f"Got {got_type} type but expected {expected_type}")
|
|
77
118
|
|
|
78
119
|
|
|
79
|
-
class CommandReturnTypeError(
|
|
120
|
+
class CommandReturnTypeError(CliError):
|
|
80
121
|
def __init__(self, got_type: type):
|
|
81
122
|
super().__init__(f"Commands have to return OutputData type, but got {got_type}")
|
|
82
123
|
|
|
83
124
|
|
|
84
|
-
class SnowflakeSQLExecutionError(
|
|
125
|
+
class SnowflakeSQLExecutionError(CliError):
|
|
85
126
|
"""
|
|
86
127
|
Could not successfully execute the Snowflake SQL statements.
|
|
87
128
|
"""
|
|
@@ -95,7 +136,7 @@ class SnowflakeSQLExecutionError(ClickException):
|
|
|
95
136
|
)
|
|
96
137
|
|
|
97
138
|
|
|
98
|
-
class ObjectAlreadyExistsError(
|
|
139
|
+
class ObjectAlreadyExistsError(CliError):
|
|
99
140
|
def __init__(
|
|
100
141
|
self,
|
|
101
142
|
object_type: ObjectType,
|
|
@@ -108,45 +149,45 @@ class ObjectAlreadyExistsError(ClickException):
|
|
|
108
149
|
super().__init__(msg)
|
|
109
150
|
|
|
110
151
|
|
|
111
|
-
class NoProjectDefinitionError(
|
|
152
|
+
class NoProjectDefinitionError(CliError):
|
|
112
153
|
def __init__(self, project_type: str, project_root: str | Path):
|
|
113
154
|
super().__init__(
|
|
114
155
|
f"No {project_type} project definition found in {project_root}"
|
|
115
156
|
)
|
|
116
157
|
|
|
117
158
|
|
|
118
|
-
class InvalidProjectDefinitionVersionError(
|
|
159
|
+
class InvalidProjectDefinitionVersionError(CliError):
|
|
119
160
|
def __init__(self, expected_version: str, actual_version: str):
|
|
120
161
|
super().__init__(
|
|
121
162
|
f"This command only supports definition version {expected_version}, got {actual_version}."
|
|
122
163
|
)
|
|
123
164
|
|
|
124
165
|
|
|
125
|
-
class InvalidSchemaError(
|
|
166
|
+
class InvalidSchemaError(CliError):
|
|
126
167
|
def __init__(self, schema: str):
|
|
127
168
|
super().__init__(f"Invalid schema {schema}")
|
|
128
169
|
|
|
129
170
|
|
|
130
|
-
class SecretsWithoutExternalAccessIntegrationError(
|
|
171
|
+
class SecretsWithoutExternalAccessIntegrationError(CliError):
|
|
131
172
|
def __init__(self, object_name: str):
|
|
132
173
|
super().__init__(
|
|
133
174
|
f"{object_name} defined with secrets but without external integration."
|
|
134
175
|
)
|
|
135
176
|
|
|
136
177
|
|
|
137
|
-
class FileTooLargeError(
|
|
178
|
+
class FileTooLargeError(CliError):
|
|
138
179
|
def __init__(self, path: Path, size_limit_in_kb: int):
|
|
139
180
|
super().__init__(
|
|
140
181
|
f"File {path} is too large (size limit: {size_limit_in_kb} KB)"
|
|
141
182
|
)
|
|
142
183
|
|
|
143
184
|
|
|
144
|
-
class DirectoryIsNotEmptyError(
|
|
185
|
+
class DirectoryIsNotEmptyError(CliError):
|
|
145
186
|
def __init__(self, path: Path):
|
|
146
187
|
super().__init__(f"Directory '{path}' is not empty")
|
|
147
188
|
|
|
148
189
|
|
|
149
|
-
class ConfigFileTooWidePermissionsError(
|
|
190
|
+
class ConfigFileTooWidePermissionsError(CliError):
|
|
150
191
|
def __init__(self, path: Path):
|
|
151
192
|
change_permissons_command = (
|
|
152
193
|
f'icacls "{path}" /deny <USER_ID>:F'
|
|
@@ -162,26 +203,26 @@ class ConfigFileTooWidePermissionsError(ClickException):
|
|
|
162
203
|
super().__init__(msg)
|
|
163
204
|
|
|
164
205
|
|
|
165
|
-
class DatabaseNotProvidedError(
|
|
206
|
+
class DatabaseNotProvidedError(CliError):
|
|
166
207
|
def __init__(self):
|
|
167
208
|
super().__init__(
|
|
168
209
|
"Database not specified. Please update connection to add `database` parameter, or re-run command using `--database` option. Use `snow connection list` to list existing connections."
|
|
169
210
|
)
|
|
170
211
|
|
|
171
212
|
|
|
172
|
-
class SchemaNotProvidedError(
|
|
213
|
+
class SchemaNotProvidedError(CliError):
|
|
173
214
|
def __init__(self):
|
|
174
215
|
super().__init__(
|
|
175
216
|
"Schema not specified. Please update connection to add `schema` parameter, or re-run command using `--schema` option. Use `snow connection list` to list existing connections."
|
|
176
217
|
)
|
|
177
218
|
|
|
178
219
|
|
|
179
|
-
class FQNNameError(
|
|
220
|
+
class FQNNameError(CliError):
|
|
180
221
|
def __init__(self, name: str):
|
|
181
222
|
super().__init__(f"Specified name '{name}' is not valid name.")
|
|
182
223
|
|
|
183
224
|
|
|
184
|
-
class FQNInconsistencyError(
|
|
225
|
+
class FQNInconsistencyError(CliError):
|
|
185
226
|
def __init__(self, part: str, name: str):
|
|
186
227
|
super().__init__(
|
|
187
228
|
f"{part.capitalize()} provided but name '{name}' is fully qualified name."
|
|
@@ -206,7 +247,7 @@ class UnmetParametersError(UsageError):
|
|
|
206
247
|
)
|
|
207
248
|
|
|
208
249
|
|
|
209
|
-
class NoWarehouseSelectedInSessionError(
|
|
250
|
+
class NoWarehouseSelectedInSessionError(CliError):
|
|
210
251
|
def __init__(self, msg: str):
|
|
211
252
|
super().__init__(
|
|
212
253
|
"Received the following error message while executing SQL statement:\n"
|
|
@@ -215,7 +256,7 @@ class NoWarehouseSelectedInSessionError(ClickException):
|
|
|
215
256
|
)
|
|
216
257
|
|
|
217
258
|
|
|
218
|
-
class DoesNotExistOrUnauthorizedError(
|
|
259
|
+
class DoesNotExistOrUnauthorizedError(CliError):
|
|
219
260
|
def __init__(self, msg: str):
|
|
220
261
|
super().__init__(
|
|
221
262
|
"Received the following error message while executing SQL statement:\n"
|
|
@@ -224,7 +265,7 @@ class DoesNotExistOrUnauthorizedError(ClickException):
|
|
|
224
265
|
)
|
|
225
266
|
|
|
226
267
|
|
|
227
|
-
class CouldNotUseObjectError(
|
|
268
|
+
class CouldNotUseObjectError(CliError):
|
|
228
269
|
def __init__(self, object_type: ObjectType, name: str):
|
|
229
270
|
super().__init__(
|
|
230
271
|
f"Could not use {object_type} {name}. Object does not exist, or operation cannot be performed."
|
|
@@ -236,3 +277,10 @@ class ShowSpecificObjectMultipleRowsError(RuntimeError):
|
|
|
236
277
|
super().__init__(
|
|
237
278
|
f"Received multiple rows from result of SQL statement: {show_obj_query}. Usage of 'show_specific_object' may not be properly scoped."
|
|
238
279
|
)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class CouldNotSetKeyPairError(CliError):
|
|
283
|
+
def __init__(self):
|
|
284
|
+
super().__init__(
|
|
285
|
+
"The public key is set already. Use the rotate command instead."
|
|
286
|
+
)
|
|
@@ -68,3 +68,4 @@ class FeatureFlag(FeatureFlagMixin):
|
|
|
68
68
|
)
|
|
69
69
|
ENABLE_SNOWPARK_GLOB_SUPPORT = BooleanFlag("ENABLE_SNOWPARK_GLOB_SUPPORT", False)
|
|
70
70
|
ENABLE_SPCS_SERVICE_EVENTS = BooleanFlag("ENABLE_SPCS_SERVICE_EVENTS", False)
|
|
71
|
+
ENABLE_AUTH_KEYPAIR = BooleanFlag("ENABLE_AUTH_KEYPAIR", False)
|
snowflake/cli/api/identifiers.py
CHANGED
|
@@ -25,7 +25,7 @@ from snowflake.cli.api.config import (
|
|
|
25
25
|
get_config_value,
|
|
26
26
|
get_plugins_config,
|
|
27
27
|
)
|
|
28
|
-
from snowflake.cli.api.exceptions import
|
|
28
|
+
from snowflake.cli.api.exceptions import InvalidPluginConfigurationError
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
@dataclass
|
|
@@ -66,6 +66,6 @@ class PluginConfigProvider:
|
|
|
66
66
|
|
|
67
67
|
def _assert_value_is_bool(value, *, value_name: str, plugin_name: str) -> None:
|
|
68
68
|
if type(value) is not bool:
|
|
69
|
-
raise
|
|
69
|
+
raise InvalidPluginConfigurationError(
|
|
70
70
|
f'[{plugin_name}]: "{value_name}" must be a boolean'
|
|
71
71
|
)
|
|
@@ -19,7 +19,7 @@ from typing import Any, List, Literal, Optional, Union
|
|
|
19
19
|
import typer
|
|
20
20
|
from click import ClickException
|
|
21
21
|
from pydantic import BaseModel, Field
|
|
22
|
-
from snowflake.cli.api.exceptions import
|
|
22
|
+
from snowflake.cli.api.exceptions import InvalidTemplateError
|
|
23
23
|
from snowflake.cli.api.secure_path import SecurePath
|
|
24
24
|
|
|
25
25
|
|
|
@@ -68,10 +68,10 @@ class Template(BaseModel):
|
|
|
68
68
|
for path_in_template in self.files_to_render:
|
|
69
69
|
full_path = template_root / path_in_template
|
|
70
70
|
if not full_path.exists():
|
|
71
|
-
raise
|
|
71
|
+
raise InvalidTemplateError(
|
|
72
72
|
f"[files_to_render] contains not-existing file: {path_in_template}"
|
|
73
73
|
)
|
|
74
74
|
if full_path.is_dir():
|
|
75
|
-
raise
|
|
75
|
+
raise InvalidTemplateError(
|
|
76
76
|
f"[files_to_render] contains a dictionary: {path_in_template}"
|
|
77
77
|
)
|
|
@@ -24,7 +24,7 @@ from jinja2 import (
|
|
|
24
24
|
UndefinedError,
|
|
25
25
|
loaders,
|
|
26
26
|
)
|
|
27
|
-
from snowflake.cli.api.exceptions import
|
|
27
|
+
from snowflake.cli.api.exceptions import InvalidTemplateError
|
|
28
28
|
from snowflake.cli.api.rendering.jinja import IgnoreAttrEnvironment, env_bootstrap
|
|
29
29
|
from snowflake.cli.api.secure_path import SecurePath
|
|
30
30
|
|
|
@@ -90,9 +90,9 @@ def render_template_files(
|
|
|
90
90
|
full_path = template_root / path
|
|
91
91
|
full_path.write_text(rendered_result)
|
|
92
92
|
except TemplateSyntaxError as err:
|
|
93
|
-
raise
|
|
93
|
+
raise InvalidTemplateError(
|
|
94
94
|
f"Invalid template syntax in line {err.lineno} of file {path}:\n"
|
|
95
95
|
f"{err.message}"
|
|
96
96
|
)
|
|
97
97
|
except UndefinedError as err:
|
|
98
|
-
raise
|
|
98
|
+
raise InvalidTemplateError(err.message)
|
|
@@ -20,7 +20,7 @@ from click import ClickException
|
|
|
20
20
|
from jinja2 import Environment, StrictUndefined, loaders, meta
|
|
21
21
|
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
22
22
|
from snowflake.cli.api.console.console import cli_console
|
|
23
|
-
from snowflake.cli.api.exceptions import
|
|
23
|
+
from snowflake.cli.api.exceptions import InvalidTemplateError
|
|
24
24
|
from snowflake.cli.api.metrics import CLICounterField
|
|
25
25
|
from snowflake.cli.api.rendering.jinja import (
|
|
26
26
|
CONTEXT_KEY,
|
|
@@ -72,7 +72,7 @@ def choose_sql_jinja_env_based_on_template_syntax(
|
|
|
72
72
|
has_new_syntax = _does_template_have_env_syntax(new_syntax_env, template_content)
|
|
73
73
|
reference_name_str = f" in {reference_name}" if reference_name else ""
|
|
74
74
|
if has_old_syntax and has_new_syntax:
|
|
75
|
-
raise
|
|
75
|
+
raise InvalidTemplateError(
|
|
76
76
|
f"The SQL query{reference_name_str} mixes {_OLD_SQL_TEMPLATE_START} ... {_OLD_SQL_TEMPLATE_END} syntax"
|
|
77
77
|
f" and {_SQL_TEMPLATE_START} ... {_SQL_TEMPLATE_END} syntax."
|
|
78
78
|
)
|
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
|
):
|