snowflake-cli-labs 2.6.0rc0__py3-none-any.whl → 2.7.0rc0__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/api/cli_global_context.py +9 -0
- snowflake/cli/api/commands/decorators.py +9 -4
- snowflake/cli/api/commands/execution_metadata.py +40 -0
- snowflake/cli/api/commands/flags.py +45 -36
- snowflake/cli/api/commands/project_initialisation.py +4 -1
- snowflake/cli/api/commands/snow_typer.py +20 -9
- snowflake/cli/api/config.py +3 -0
- snowflake/cli/api/errno.py +27 -0
- snowflake/cli/api/feature_flags.py +1 -0
- snowflake/cli/api/identifiers.py +20 -3
- snowflake/cli/api/output/types.py +9 -0
- snowflake/cli/api/project/definition_manager.py +2 -2
- snowflake/cli/api/project/project_verification.py +23 -0
- snowflake/cli/api/project/schemas/entities/application_entity.py +50 -0
- snowflake/cli/api/project/schemas/entities/application_package_entity.py +63 -0
- snowflake/cli/api/project/schemas/entities/common.py +85 -0
- snowflake/cli/api/project/schemas/entities/entities.py +30 -0
- snowflake/cli/api/project/schemas/project_definition.py +114 -22
- snowflake/cli/api/project/schemas/streamlit/streamlit.py +5 -4
- snowflake/cli/api/project/schemas/template.py +77 -0
- snowflake/cli/{plugins/nativeapp/errno.py → api/rendering/__init__.py} +0 -2
- snowflake/cli/api/{utils/rendering.py → rendering/jinja.py} +3 -48
- snowflake/cli/api/rendering/project_definition_templates.py +39 -0
- snowflake/cli/api/rendering/project_templates.py +97 -0
- snowflake/cli/api/rendering/sql_templates.py +56 -0
- snowflake/cli/api/rest_api.py +84 -25
- snowflake/cli/api/sql_execution.py +40 -1
- snowflake/cli/api/utils/definition_rendering.py +8 -5
- snowflake/cli/app/cli_app.py +0 -2
- snowflake/cli/app/commands_registration/builtin_plugins.py +4 -0
- snowflake/cli/app/dev/docs/project_definition_docs_generator.py +2 -2
- snowflake/cli/app/loggers.py +10 -6
- snowflake/cli/app/printing.py +17 -7
- snowflake/cli/app/snow_connector.py +9 -1
- snowflake/cli/app/telemetry.py +41 -2
- snowflake/cli/plugins/connection/commands.py +4 -3
- snowflake/cli/plugins/connection/util.py +73 -18
- snowflake/cli/plugins/cortex/commands.py +2 -1
- snowflake/cli/plugins/git/commands.py +20 -4
- snowflake/cli/plugins/git/manager.py +44 -20
- snowflake/cli/plugins/init/__init__.py +13 -0
- snowflake/cli/plugins/init/commands.py +242 -0
- snowflake/cli/plugins/init/plugin_spec.py +30 -0
- snowflake/cli/plugins/nativeapp/codegen/artifact_processor.py +40 -0
- snowflake/cli/plugins/nativeapp/codegen/compiler.py +57 -27
- snowflake/cli/plugins/nativeapp/codegen/sandbox.py +99 -10
- snowflake/cli/plugins/nativeapp/codegen/setup/native_app_setup_processor.py +172 -0
- snowflake/cli/plugins/nativeapp/codegen/setup/setup_driver.py.source +56 -0
- snowflake/cli/plugins/nativeapp/codegen/snowpark/python_processor.py +21 -21
- snowflake/cli/plugins/nativeapp/commands.py +69 -6
- snowflake/cli/plugins/nativeapp/constants.py +0 -6
- snowflake/cli/plugins/nativeapp/exceptions.py +37 -12
- snowflake/cli/plugins/nativeapp/init.py +1 -1
- snowflake/cli/plugins/nativeapp/manager.py +114 -39
- snowflake/cli/plugins/nativeapp/project_model.py +8 -4
- snowflake/cli/plugins/nativeapp/run_processor.py +117 -102
- snowflake/cli/plugins/nativeapp/teardown_processor.py +7 -2
- snowflake/cli/plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +146 -0
- snowflake/cli/plugins/nativeapp/version/commands.py +19 -3
- snowflake/cli/plugins/nativeapp/version/version_processor.py +11 -3
- snowflake/cli/plugins/object/commands.py +1 -1
- snowflake/cli/plugins/object/manager.py +2 -15
- snowflake/cli/plugins/snowpark/commands.py +34 -26
- snowflake/cli/plugins/snowpark/common.py +88 -27
- snowflake/cli/plugins/snowpark/manager.py +16 -5
- snowflake/cli/plugins/snowpark/models.py +6 -0
- snowflake/cli/plugins/sql/commands.py +3 -5
- snowflake/cli/plugins/sql/manager.py +1 -1
- snowflake/cli/plugins/stage/commands.py +2 -2
- snowflake/cli/plugins/stage/diff.py +4 -2
- snowflake/cli/plugins/stage/manager.py +290 -86
- snowflake/cli/plugins/streamlit/commands.py +20 -6
- snowflake/cli/plugins/streamlit/manager.py +29 -27
- snowflake/cli/plugins/workspace/__init__.py +13 -0
- snowflake/cli/plugins/workspace/commands.py +35 -0
- snowflake/cli/plugins/workspace/plugin_spec.py +30 -0
- snowflake/cli/templates/default_snowpark/app/__init__.py +0 -13
- snowflake/cli/templates/default_snowpark/app/common.py +0 -15
- snowflake/cli/templates/default_snowpark/app/functions.py +0 -14
- snowflake/cli/templates/default_snowpark/app/procedures.py +0 -14
- snowflake/cli/templates/default_streamlit/common/hello.py +0 -15
- snowflake/cli/templates/default_streamlit/pages/my_page.py +0 -14
- snowflake/cli/templates/default_streamlit/streamlit_app.py +0 -14
- {snowflake_cli_labs-2.6.0rc0.dist-info → snowflake_cli_labs-2.7.0rc0.dist-info}/METADATA +7 -6
- {snowflake_cli_labs-2.6.0rc0.dist-info → snowflake_cli_labs-2.7.0rc0.dist-info}/RECORD +89 -69
- {snowflake_cli_labs-2.6.0rc0.dist-info → snowflake_cli_labs-2.7.0rc0.dist-info}/WHEEL +0 -0
- {snowflake_cli_labs-2.6.0rc0.dist-info → snowflake_cli_labs-2.7.0rc0.dist-info}/entry_points.txt +0 -0
- {snowflake_cli_labs-2.6.0rc0.dist-info → snowflake_cli_labs-2.7.0rc0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Copyright (c) 2024 Snowflake Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Dict, Optional
|
|
18
|
+
|
|
19
|
+
from click import ClickException
|
|
20
|
+
from jinja2 import StrictUndefined, loaders
|
|
21
|
+
from snowflake.cli.api.cli_global_context import cli_context
|
|
22
|
+
from snowflake.cli.api.rendering.jinja import (
|
|
23
|
+
CONTEXT_KEY,
|
|
24
|
+
IgnoreAttrEnvironment,
|
|
25
|
+
env_bootstrap,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
_SQL_TEMPLATE_START = "&{"
|
|
29
|
+
_SQL_TEMPLATE_END = "}"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_sql_cli_jinja_env(*, loader: Optional[loaders.BaseLoader] = None):
|
|
33
|
+
_random_block = "___very___unique___block___to___disable___logic___blocks___"
|
|
34
|
+
return env_bootstrap(
|
|
35
|
+
IgnoreAttrEnvironment(
|
|
36
|
+
loader=loader or loaders.BaseLoader(),
|
|
37
|
+
keep_trailing_newline=True,
|
|
38
|
+
variable_start_string=_SQL_TEMPLATE_START,
|
|
39
|
+
variable_end_string=_SQL_TEMPLATE_END,
|
|
40
|
+
block_start_string=_random_block,
|
|
41
|
+
block_end_string=_random_block,
|
|
42
|
+
undefined=StrictUndefined,
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def snowflake_sql_jinja_render(content: str, data: Dict | None = None) -> str:
|
|
48
|
+
data = data or {}
|
|
49
|
+
if CONTEXT_KEY in data:
|
|
50
|
+
raise ClickException(
|
|
51
|
+
f"{CONTEXT_KEY} in user defined data. The `{CONTEXT_KEY}` variable is reserved for CLI usage."
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
context_data = cli_context.template_context
|
|
55
|
+
context_data.update(data)
|
|
56
|
+
return get_sql_cli_jinja_env().from_string(content).render(**context_data)
|
snowflake/cli/api/rest_api.py
CHANGED
|
@@ -16,16 +16,27 @@ from __future__ import annotations
|
|
|
16
16
|
|
|
17
17
|
import json
|
|
18
18
|
import logging
|
|
19
|
-
from typing import Any, Dict,
|
|
19
|
+
from typing import Any, Dict, Optional
|
|
20
20
|
|
|
21
|
+
from click import ClickException
|
|
21
22
|
from snowflake.cli.api.constants import SF_REST_API_URL_PREFIX
|
|
22
23
|
from snowflake.connector.connection import SnowflakeConnection
|
|
23
|
-
from snowflake.connector.errors import InterfaceError
|
|
24
|
+
from snowflake.connector.errors import BadRequest, InterfaceError
|
|
24
25
|
from snowflake.connector.network import SnowflakeRestful
|
|
25
26
|
|
|
26
27
|
log = logging.getLogger(__name__)
|
|
27
28
|
|
|
28
29
|
|
|
30
|
+
def _pluralize_object_type(object_type: str) -> str:
|
|
31
|
+
"""
|
|
32
|
+
Pluralize object type without depending on OBJECT_TO_NAMES.
|
|
33
|
+
"""
|
|
34
|
+
if object_type.endswith("y"):
|
|
35
|
+
return object_type[:-1].lower() + "ies"
|
|
36
|
+
else:
|
|
37
|
+
return object_type.lower() + "s"
|
|
38
|
+
|
|
39
|
+
|
|
29
40
|
class RestApi:
|
|
30
41
|
def __init__(self, connection: SnowflakeConnection):
|
|
31
42
|
self.conn = connection
|
|
@@ -43,6 +54,13 @@ class RestApi:
|
|
|
43
54
|
return False
|
|
44
55
|
raise err
|
|
45
56
|
|
|
57
|
+
def _fetch_endpoint_exists(self, url: str) -> bool:
|
|
58
|
+
try:
|
|
59
|
+
result = self.send_rest_request(url, method="get")
|
|
60
|
+
return bool(result)
|
|
61
|
+
except BadRequest:
|
|
62
|
+
return False
|
|
63
|
+
|
|
46
64
|
def send_rest_request(
|
|
47
65
|
self, url: str, method: str, data: Optional[Dict[str, Any]] = None
|
|
48
66
|
):
|
|
@@ -75,12 +93,18 @@ class RestApi:
|
|
|
75
93
|
no_retry=True,
|
|
76
94
|
)
|
|
77
95
|
|
|
78
|
-
def
|
|
79
|
-
|
|
80
|
-
|
|
96
|
+
def _database_exists(self, db_name: str) -> bool:
|
|
97
|
+
url = f"{SF_REST_API_URL_PREFIX}/databases/{db_name}"
|
|
98
|
+
return self._fetch_endpoint_exists(url)
|
|
99
|
+
|
|
100
|
+
def _schema_exists(self, db_name: str, schema_name: str) -> bool:
|
|
101
|
+
url = f"{SF_REST_API_URL_PREFIX}/databases/{db_name}/schemas/{schema_name}"
|
|
102
|
+
return self._fetch_endpoint_exists(url)
|
|
103
|
+
|
|
104
|
+
def determine_url_for_create_query(self, object_type: str) -> str:
|
|
81
105
|
"""
|
|
82
106
|
Determine an url for creating an object of given type via REST API.
|
|
83
|
-
|
|
107
|
+
If URL cannot be determined, the function throws CannotDetermineCreateURLException exception.
|
|
84
108
|
|
|
85
109
|
URLs we check:
|
|
86
110
|
* /api/v2/<type>/
|
|
@@ -92,22 +116,57 @@ class RestApi:
|
|
|
92
116
|
To check whether an URL exists, we send read-only GET request (LIST endpoint,
|
|
93
117
|
which should imply CREATE endpoint).
|
|
94
118
|
"""
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
119
|
+
plural_object_type = _pluralize_object_type(object_type)
|
|
120
|
+
|
|
121
|
+
if self.get_endpoint_exists(
|
|
122
|
+
url := f"{SF_REST_API_URL_PREFIX}/{plural_object_type}/"
|
|
123
|
+
):
|
|
124
|
+
return url
|
|
125
|
+
|
|
126
|
+
db = self.conn.database
|
|
127
|
+
if not db:
|
|
128
|
+
raise DatabaseNotDefinedException(
|
|
129
|
+
"Database not defined in connection. Please try again with `--database` flag."
|
|
130
|
+
)
|
|
131
|
+
if not self._database_exists(db):
|
|
132
|
+
raise DatabaseNotExistsException(f"Database '{db}' does not exist.")
|
|
133
|
+
if self.get_endpoint_exists(
|
|
134
|
+
url := f"{SF_REST_API_URL_PREFIX}/databases/{db}/{plural_object_type}/"
|
|
135
|
+
):
|
|
136
|
+
return url
|
|
137
|
+
|
|
138
|
+
schema = self.conn.schema
|
|
139
|
+
if not schema:
|
|
140
|
+
raise SchemaNotDefinedException(
|
|
141
|
+
"Schema not defined in connection. Please try again with `--schema` flag."
|
|
142
|
+
)
|
|
143
|
+
if not self._schema_exists(db_name=db, schema_name=schema):
|
|
144
|
+
raise SchemaNotExistsException(f"Schema '{schema}' does not exist.")
|
|
145
|
+
if self.get_endpoint_exists(
|
|
146
|
+
url := f"{SF_REST_API_URL_PREFIX}/databases/{self.conn.database}/schemas/{self.conn.schema}/{plural_object_type}/"
|
|
147
|
+
):
|
|
148
|
+
return url
|
|
149
|
+
|
|
150
|
+
raise CannotDetermineCreateURLException(
|
|
151
|
+
f"Create operation for type {object_type} is not supported. Try using `sql -q 'CREATE ...'` command."
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class DatabaseNotDefinedException(ClickException):
|
|
156
|
+
pass
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class SchemaNotDefinedException(ClickException):
|
|
160
|
+
pass
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class DatabaseNotExistsException(ClickException):
|
|
164
|
+
pass
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class SchemaNotExistsException(ClickException):
|
|
168
|
+
pass
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class CannotDetermineCreateURLException(ClickException):
|
|
172
|
+
pass
|
|
@@ -41,12 +41,22 @@ from snowflake.connector.errors import ProgrammingError
|
|
|
41
41
|
|
|
42
42
|
class SqlExecutionMixin:
|
|
43
43
|
def __init__(self):
|
|
44
|
-
|
|
44
|
+
self._snowpark_session = None
|
|
45
45
|
|
|
46
46
|
@property
|
|
47
47
|
def _conn(self):
|
|
48
48
|
return cli_context.connection
|
|
49
49
|
|
|
50
|
+
@property
|
|
51
|
+
def snowpark_session(self):
|
|
52
|
+
if not self._snowpark_session:
|
|
53
|
+
from snowflake.snowpark.session import Session
|
|
54
|
+
|
|
55
|
+
self._snowpark_session = Session.builder.configs(
|
|
56
|
+
{"connection": self._conn}
|
|
57
|
+
).create()
|
|
58
|
+
return self._snowpark_session
|
|
59
|
+
|
|
50
60
|
@cached_property
|
|
51
61
|
def _log(self):
|
|
52
62
|
return logging.getLogger(__name__)
|
|
@@ -107,6 +117,35 @@ class SqlExecutionMixin:
|
|
|
107
117
|
if is_different_role:
|
|
108
118
|
self._execute_query(f"use role {prev_role}")
|
|
109
119
|
|
|
120
|
+
@contextmanager
|
|
121
|
+
def use_warehouse(self, new_wh: str):
|
|
122
|
+
"""
|
|
123
|
+
Switches to a different warehouse for a while, then switches back.
|
|
124
|
+
This is a no-op if the requested warehouse is already active.
|
|
125
|
+
If there is no default warehouse in the account, it will throw an error.
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
wh_result = self._execute_query(
|
|
129
|
+
f"select current_warehouse()", cursor_class=DictCursor
|
|
130
|
+
).fetchone()
|
|
131
|
+
# If user has an assigned default warehouse, prev_wh will contain a value even if the warehouse is suspended.
|
|
132
|
+
try:
|
|
133
|
+
prev_wh = wh_result["CURRENT_WAREHOUSE()"]
|
|
134
|
+
except:
|
|
135
|
+
prev_wh = None
|
|
136
|
+
|
|
137
|
+
# new_wh is not None, and should already be a valid identifier, no additional check is performed here.
|
|
138
|
+
is_different_wh = new_wh != prev_wh
|
|
139
|
+
try:
|
|
140
|
+
if is_different_wh:
|
|
141
|
+
self._log.debug("Using warehouse: %s", new_wh)
|
|
142
|
+
self.use(object_type=ObjectType.WAREHOUSE, name=new_wh)
|
|
143
|
+
yield
|
|
144
|
+
finally:
|
|
145
|
+
if prev_wh and is_different_wh:
|
|
146
|
+
self._log.debug("Switching back to warehouse: %s", prev_wh)
|
|
147
|
+
self.use(object_type=ObjectType.WAREHOUSE, name=prev_wh)
|
|
148
|
+
|
|
110
149
|
def create_password_secret(
|
|
111
150
|
self, name: str, username: str, password: str
|
|
112
151
|
) -> SnowflakeCursor:
|
|
@@ -22,13 +22,16 @@ from packaging.version import Version
|
|
|
22
22
|
from snowflake.cli.api.console import cli_console as cc
|
|
23
23
|
from snowflake.cli.api.exceptions import CycleDetectedError, InvalidTemplate
|
|
24
24
|
from snowflake.cli.api.project.schemas.project_definition import (
|
|
25
|
-
ProjectDefinition,
|
|
26
25
|
ProjectProperties,
|
|
26
|
+
build_project_definition,
|
|
27
|
+
)
|
|
28
|
+
from snowflake.cli.api.rendering.jinja import CONTEXT_KEY
|
|
29
|
+
from snowflake.cli.api.rendering.project_definition_templates import (
|
|
30
|
+
get_project_definition_cli_jinja_env,
|
|
27
31
|
)
|
|
28
32
|
from snowflake.cli.api.utils.dict_utils import traverse
|
|
29
33
|
from snowflake.cli.api.utils.graph import Graph, Node
|
|
30
34
|
from snowflake.cli.api.utils.models import ProjectEnvironment
|
|
31
|
-
from snowflake.cli.api.utils.rendering import CONTEXT_KEY, get_snowflake_cli_jinja_env
|
|
32
35
|
from snowflake.cli.api.utils.types import Context, Definition
|
|
33
36
|
|
|
34
37
|
|
|
@@ -286,7 +289,7 @@ def render_definition_template(
|
|
|
286
289
|
return ProjectProperties(None, {CONTEXT_KEY: {"env": environment_overrides}})
|
|
287
290
|
|
|
288
291
|
project_context = {CONTEXT_KEY: definition}
|
|
289
|
-
template_env = TemplatedEnvironment(
|
|
292
|
+
template_env = TemplatedEnvironment(get_project_definition_cli_jinja_env())
|
|
290
293
|
|
|
291
294
|
if "definition_version" not in definition or Version(
|
|
292
295
|
definition["definition_version"]
|
|
@@ -301,7 +304,7 @@ def render_definition_template(
|
|
|
301
304
|
# also warn on Exception, as it means the user is incorrectly attempting to use templating
|
|
302
305
|
_template_version_warning()
|
|
303
306
|
|
|
304
|
-
project_definition =
|
|
307
|
+
project_definition = build_project_definition(**original_definition)
|
|
305
308
|
project_context[CONTEXT_KEY]["env"] = environment_overrides
|
|
306
309
|
return ProjectProperties(project_definition, project_context)
|
|
307
310
|
|
|
@@ -337,5 +340,5 @@ def render_definition_template(
|
|
|
337
340
|
|
|
338
341
|
definition["env"] = ProjectEnvironment(default_env, override_env)
|
|
339
342
|
project_context[CONTEXT_KEY] = definition
|
|
340
|
-
project_definition =
|
|
343
|
+
project_definition = build_project_definition(**definition)
|
|
341
344
|
return ProjectProperties(project_definition, project_context)
|
snowflake/cli/app/cli_app.py
CHANGED
|
@@ -30,7 +30,6 @@ from snowflake.cli.api.config import config_init
|
|
|
30
30
|
from snowflake.cli.api.output.formats import OutputFormat
|
|
31
31
|
from snowflake.cli.api.output.types import CollectionResult
|
|
32
32
|
from snowflake.cli.api.secure_path import SecurePath
|
|
33
|
-
from snowflake.cli.app import loggers
|
|
34
33
|
from snowflake.cli.app.api_impl.plugin.plugin_config_provider_impl import (
|
|
35
34
|
PluginConfigProviderImpl,
|
|
36
35
|
)
|
|
@@ -89,7 +88,6 @@ def _commands_registration_callback(value: bool):
|
|
|
89
88
|
@_commands_registration.before
|
|
90
89
|
def _config_init_callback(configuration_file: Optional[Path]):
|
|
91
90
|
config_init(configuration_file)
|
|
92
|
-
loggers.create_initial_loggers()
|
|
93
91
|
|
|
94
92
|
|
|
95
93
|
@_commands_registration.before
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
from snowflake.cli.plugins.connection import plugin_spec as connection_plugin_spec
|
|
16
16
|
from snowflake.cli.plugins.cortex import plugin_spec as cortex_plugin_spec
|
|
17
17
|
from snowflake.cli.plugins.git import plugin_spec as git_plugin_spec
|
|
18
|
+
from snowflake.cli.plugins.init import plugin_spec as init_plugin_spec
|
|
18
19
|
from snowflake.cli.plugins.nativeapp import plugin_spec as nativeapp_plugin_spec
|
|
19
20
|
from snowflake.cli.plugins.notebook import plugin_spec as notebook_plugin_spec
|
|
20
21
|
from snowflake.cli.plugins.object import plugin_spec as object_plugin_spec
|
|
@@ -28,6 +29,7 @@ from snowflake.cli.plugins.spcs import plugin_spec as spcs_plugin_spec
|
|
|
28
29
|
from snowflake.cli.plugins.sql import plugin_spec as sql_plugin_spec
|
|
29
30
|
from snowflake.cli.plugins.stage import plugin_spec as stage_plugin_spec
|
|
30
31
|
from snowflake.cli.plugins.streamlit import plugin_spec as streamlit_plugin_spec
|
|
32
|
+
from snowflake.cli.plugins.workspace import plugin_spec as workspace_plugin_spec
|
|
31
33
|
|
|
32
34
|
|
|
33
35
|
# plugin name to plugin spec
|
|
@@ -45,6 +47,8 @@ def get_builtin_plugin_name_to_plugin_spec():
|
|
|
45
47
|
"notebook": notebook_plugin_spec,
|
|
46
48
|
"object-stage-deprecated": object_stage_deprecated_plugin_spec,
|
|
47
49
|
"cortex": cortex_plugin_spec,
|
|
50
|
+
"init": init_plugin_spec,
|
|
51
|
+
"workspace": workspace_plugin_spec,
|
|
48
52
|
}
|
|
49
53
|
|
|
50
54
|
return plugin_specs
|
|
@@ -18,7 +18,7 @@ import logging
|
|
|
18
18
|
from typing import Any, Dict
|
|
19
19
|
|
|
20
20
|
from pydantic.json_schema import model_json_schema
|
|
21
|
-
from snowflake.cli.api.project.schemas.project_definition import
|
|
21
|
+
from snowflake.cli.api.project.schemas.project_definition import DefinitionV11
|
|
22
22
|
from snowflake.cli.api.secure_path import SecurePath
|
|
23
23
|
from snowflake.cli.app.dev.docs.project_definition_generate_json_schema import (
|
|
24
24
|
ProjectDefinitionGenerateJsonSchema,
|
|
@@ -39,7 +39,7 @@ def generate_project_definition_docs(root: SecurePath):
|
|
|
39
39
|
|
|
40
40
|
root.mkdir(exist_ok=True)
|
|
41
41
|
list_of_sections = model_json_schema(
|
|
42
|
-
|
|
42
|
+
DefinitionV11, schema_generator=ProjectDefinitionGenerateJsonSchema
|
|
43
43
|
)["result"]
|
|
44
44
|
for section in list_of_sections:
|
|
45
45
|
_render_definition_description(root, section)
|
snowflake/cli/app/loggers.py
CHANGED
|
@@ -20,9 +20,6 @@ from dataclasses import asdict, dataclass, field
|
|
|
20
20
|
from typing import Any, Dict, List
|
|
21
21
|
|
|
22
22
|
import typer
|
|
23
|
-
from snowflake.cli.api.config import (
|
|
24
|
-
get_logs_config,
|
|
25
|
-
)
|
|
26
23
|
from snowflake.cli.api.exceptions import InvalidLogsConfiguration
|
|
27
24
|
from snowflake.cli.api.secure_path import SecurePath
|
|
28
25
|
from snowflake.connector.errors import ConfigSourceError
|
|
@@ -103,6 +100,10 @@ def _remove_underscore_prefixes_from_keys(d: Dict[str, Any]) -> None:
|
|
|
103
100
|
|
|
104
101
|
class FileLogsConfig:
|
|
105
102
|
def __init__(self, debug: bool) -> None:
|
|
103
|
+
from snowflake.cli.api.config import (
|
|
104
|
+
get_logs_config,
|
|
105
|
+
)
|
|
106
|
+
|
|
106
107
|
config = get_logs_config()
|
|
107
108
|
|
|
108
109
|
self.path: SecurePath = SecurePath(config["path"])
|
|
@@ -142,8 +143,9 @@ def create_initial_loggers():
|
|
|
142
143
|
config = InitialLoggingConfig()
|
|
143
144
|
try:
|
|
144
145
|
file_logs_config = FileLogsConfig(debug=False)
|
|
145
|
-
|
|
146
|
-
|
|
146
|
+
if file_logs_config.save_logs:
|
|
147
|
+
config.handlers["file"]["filename"] = file_logs_config.filename
|
|
148
|
+
_configurate_logging(config)
|
|
147
149
|
except ConfigSourceError:
|
|
148
150
|
pass
|
|
149
151
|
|
|
@@ -181,7 +183,9 @@ def create_loggers(verbose: bool, debug: bool):
|
|
|
181
183
|
else:
|
|
182
184
|
# We need to remove handler definition - otherwise it creates file even if `save_logs` is False
|
|
183
185
|
del config.handlers["file"]
|
|
184
|
-
config.loggers
|
|
186
|
+
for logger in config.loggers.values():
|
|
187
|
+
if "file" in logger.handlers:
|
|
188
|
+
logger.handlers.remove("file")
|
|
185
189
|
|
|
186
190
|
config.loggers["snowflake.cli"].level = global_log_level
|
|
187
191
|
config.loggers["snowflake"].level = global_log_level
|
snowflake/cli/app/printing.py
CHANGED
|
@@ -34,6 +34,7 @@ from snowflake.cli.api.output.types import (
|
|
|
34
34
|
MessageResult,
|
|
35
35
|
MultipleResults,
|
|
36
36
|
ObjectResult,
|
|
37
|
+
StreamResult,
|
|
37
38
|
)
|
|
38
39
|
from snowflake.cli.api.sanitizers import sanitize_for_terminal
|
|
39
40
|
|
|
@@ -89,7 +90,7 @@ def _print_multiple_table_results(obj: CollectionResult):
|
|
|
89
90
|
for item in items:
|
|
90
91
|
table.add_row(*[str(i) for i in item.values()])
|
|
91
92
|
# Add separator between tables
|
|
92
|
-
rich_print()
|
|
93
|
+
rich_print(flush=True)
|
|
93
94
|
|
|
94
95
|
|
|
95
96
|
def is_structured_format(output_format):
|
|
@@ -98,12 +99,21 @@ def is_structured_format(output_format):
|
|
|
98
99
|
|
|
99
100
|
def print_structured(result: CommandResult):
|
|
100
101
|
"""Handles outputs like json, yml and other structured and parsable formats."""
|
|
102
|
+
printed_end_line = False
|
|
101
103
|
if isinstance(result, MultipleResults):
|
|
102
104
|
_stream_json(result)
|
|
105
|
+
elif isinstance(result, StreamResult):
|
|
106
|
+
# A StreamResult prints each value onto its own line
|
|
107
|
+
# instead of joining all the values into a JSON array
|
|
108
|
+
for r in result.result:
|
|
109
|
+
json.dump(r, sys.stdout, cls=CustomJSONEncoder)
|
|
110
|
+
print(flush=True)
|
|
111
|
+
printed_end_line = True
|
|
103
112
|
else:
|
|
104
113
|
json.dump(result, sys.stdout, cls=CustomJSONEncoder, indent=4)
|
|
105
114
|
# Adds empty line at the end
|
|
106
|
-
|
|
115
|
+
if not printed_end_line:
|
|
116
|
+
print(flush=True)
|
|
107
117
|
|
|
108
118
|
|
|
109
119
|
def _stream_json(result):
|
|
@@ -130,11 +140,11 @@ def _stream_json(result):
|
|
|
130
140
|
def print_unstructured(obj: CommandResult | None):
|
|
131
141
|
"""Handles outputs like table, plain text and other unstructured types."""
|
|
132
142
|
if not obj:
|
|
133
|
-
rich_print("Done")
|
|
143
|
+
rich_print("Done", flush=True)
|
|
134
144
|
elif not obj.result:
|
|
135
|
-
rich_print("No data")
|
|
145
|
+
rich_print("No data", flush=True)
|
|
136
146
|
elif isinstance(obj, MessageResult):
|
|
137
|
-
rich_print(sanitize_for_terminal(obj.message))
|
|
147
|
+
rich_print(sanitize_for_terminal(obj.message), flush=True)
|
|
138
148
|
else:
|
|
139
149
|
if isinstance(obj, ObjectResult):
|
|
140
150
|
_print_single_table(obj)
|
|
@@ -152,14 +162,14 @@ def _print_single_table(obj):
|
|
|
152
162
|
table.add_row(
|
|
153
163
|
sanitize_for_terminal(str(key)), sanitize_for_terminal(str(value))
|
|
154
164
|
)
|
|
155
|
-
rich_print(table)
|
|
165
|
+
rich_print(table, flush=True)
|
|
156
166
|
|
|
157
167
|
|
|
158
168
|
def print_result(cmd_result: CommandResult, output_format: OutputFormat | None = None):
|
|
159
169
|
output_format = output_format or _get_format_type()
|
|
160
170
|
if is_structured_format(output_format):
|
|
161
171
|
print_structured(cmd_result)
|
|
162
|
-
elif isinstance(cmd_result, MultipleResults):
|
|
172
|
+
elif isinstance(cmd_result, (MultipleResults, StreamResult)):
|
|
163
173
|
for res in cmd_result.result:
|
|
164
174
|
print_result(res)
|
|
165
175
|
elif (
|
|
@@ -21,7 +21,12 @@ from typing import Dict, Optional
|
|
|
21
21
|
|
|
22
22
|
import snowflake.connector
|
|
23
23
|
from click.exceptions import ClickException
|
|
24
|
-
from snowflake.cli.api.
|
|
24
|
+
from snowflake.cli.api.cli_global_context import cli_context
|
|
25
|
+
from snowflake.cli.api.config import (
|
|
26
|
+
get_connection_dict,
|
|
27
|
+
get_default_connection_dict,
|
|
28
|
+
get_default_connection_name,
|
|
29
|
+
)
|
|
25
30
|
from snowflake.cli.api.constants import DEFAULT_SIZE_LIMIT_MB
|
|
26
31
|
from snowflake.cli.api.exceptions import (
|
|
27
32
|
InvalidConnectionConfiguration,
|
|
@@ -70,6 +75,9 @@ def connect_to_snowflake(
|
|
|
70
75
|
connection_parameters = {} # we will apply overrides in next step
|
|
71
76
|
else:
|
|
72
77
|
connection_parameters = get_default_connection_dict()
|
|
78
|
+
cli_context.connection_context.set_connection_name(
|
|
79
|
+
get_default_connection_name()
|
|
80
|
+
)
|
|
73
81
|
|
|
74
82
|
# Apply overrides to connection details
|
|
75
83
|
for key, value in overrides.items():
|
snowflake/cli/app/telemetry.py
CHANGED
|
@@ -22,6 +22,7 @@ from typing import Any, Dict, Union
|
|
|
22
22
|
import click
|
|
23
23
|
from snowflake.cli.__about__ import VERSION
|
|
24
24
|
from snowflake.cli.api.cli_global_context import cli_context
|
|
25
|
+
from snowflake.cli.api.commands.execution_metadata import ExecutionMetadata
|
|
25
26
|
from snowflake.cli.api.config import get_feature_flags_section
|
|
26
27
|
from snowflake.cli.api.output.formats import OutputFormat
|
|
27
28
|
from snowflake.cli.api.utils.error_handling import ignore_exceptions
|
|
@@ -44,19 +45,25 @@ class CLITelemetryField(Enum):
|
|
|
44
45
|
COMMAND = "command"
|
|
45
46
|
COMMAND_GROUP = "command_group"
|
|
46
47
|
COMMAND_FLAGS = "command_flags"
|
|
48
|
+
COMMAND_EXECUTION_ID = "command_execution_id"
|
|
49
|
+
COMMAND_RESULT_STATUS = "command_result_status"
|
|
47
50
|
COMMAND_OUTPUT_TYPE = "command_output_type"
|
|
51
|
+
COMMAND_EXECUTION_TIME = "command_execution_time"
|
|
48
52
|
# Configuration
|
|
49
53
|
CONFIG_FEATURE_FLAGS = "config_feature_flags"
|
|
50
54
|
# Information
|
|
51
55
|
EVENT = "event"
|
|
52
56
|
ERROR_MSG = "error_msg"
|
|
53
57
|
ERROR_TYPE = "error_type"
|
|
58
|
+
IS_CLI_EXCEPTION = "is_cli_exception"
|
|
54
59
|
# Project context
|
|
55
60
|
PROJECT_DEFINITION_VERSION = "project_definition_version"
|
|
56
61
|
|
|
57
62
|
|
|
58
63
|
class TelemetryEvent(Enum):
|
|
59
64
|
CMD_EXECUTION = "executing_command"
|
|
65
|
+
CMD_EXECUTION_ERROR = "error_executing_command"
|
|
66
|
+
CMD_EXECUTION_RESULT = "result_executing_command"
|
|
60
67
|
|
|
61
68
|
|
|
62
69
|
TelemetryDict = Dict[Union[CLITelemetryField, TelemetryField], Any]
|
|
@@ -141,8 +148,40 @@ _telemetry = CLITelemetryClient(ctx=cli_context)
|
|
|
141
148
|
|
|
142
149
|
|
|
143
150
|
@ignore_exceptions()
|
|
144
|
-
def log_command_usage():
|
|
145
|
-
_telemetry.send(
|
|
151
|
+
def log_command_usage(execution: ExecutionMetadata):
|
|
152
|
+
_telemetry.send(
|
|
153
|
+
{
|
|
154
|
+
TelemetryField.KEY_TYPE: TelemetryEvent.CMD_EXECUTION.value,
|
|
155
|
+
CLITelemetryField.COMMAND_EXECUTION_ID: execution.execution_id,
|
|
156
|
+
}
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@ignore_exceptions()
|
|
161
|
+
def log_command_result(execution: ExecutionMetadata):
|
|
162
|
+
_telemetry.send(
|
|
163
|
+
{
|
|
164
|
+
TelemetryField.KEY_TYPE: TelemetryEvent.CMD_EXECUTION_RESULT.value,
|
|
165
|
+
CLITelemetryField.COMMAND_EXECUTION_ID: execution.execution_id,
|
|
166
|
+
CLITelemetryField.COMMAND_RESULT_STATUS: execution.status.value,
|
|
167
|
+
CLITelemetryField.COMMAND_EXECUTION_TIME: execution.get_duration(),
|
|
168
|
+
}
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@ignore_exceptions()
|
|
173
|
+
def log_command_execution_error(exception: Exception, execution: ExecutionMetadata):
|
|
174
|
+
exception_type: str = type(exception).__name__
|
|
175
|
+
is_cli_exception: bool = issubclass(exception.__class__, click.ClickException)
|
|
176
|
+
_telemetry.send(
|
|
177
|
+
{
|
|
178
|
+
TelemetryField.KEY_TYPE: TelemetryEvent.CMD_EXECUTION_ERROR.value,
|
|
179
|
+
CLITelemetryField.COMMAND_EXECUTION_ID: execution.execution_id,
|
|
180
|
+
CLITelemetryField.ERROR_TYPE: exception_type,
|
|
181
|
+
CLITelemetryField.IS_CLI_EXCEPTION: is_cli_exception,
|
|
182
|
+
CLITelemetryField.COMMAND_EXECUTION_TIME: execution.get_duration(),
|
|
183
|
+
}
|
|
184
|
+
)
|
|
146
185
|
|
|
147
186
|
|
|
148
187
|
@ignore_exceptions()
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
17
|
import logging
|
|
18
|
+
import os.path
|
|
18
19
|
|
|
19
20
|
import typer
|
|
20
21
|
from click import ClickException, Context, Parameter # type: ignore
|
|
@@ -300,9 +301,9 @@ def test(
|
|
|
300
301
|
}
|
|
301
302
|
|
|
302
303
|
if conn_ctx.enable_diag:
|
|
303
|
-
result[
|
|
304
|
-
|
|
305
|
-
|
|
304
|
+
result["Diag Report Location"] = os.path.join(
|
|
305
|
+
conn_ctx.diag_log_path, "SnowflakeConnectionTestReport.txt"
|
|
306
|
+
)
|
|
306
307
|
|
|
307
308
|
return ObjectResult(result)
|
|
308
309
|
|