snowflake-cli-labs 2.8.0rc1__py3-none-any.whl → 3.0.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 → _app}/__main__.py +1 -1
- snowflake/cli/{app → _app}/cli_app.py +22 -13
- snowflake/cli/{app → _app}/commands_registration/builtin_plugins.py +15 -19
- snowflake/cli/{app → _app}/commands_registration/command_plugins_loader.py +9 -9
- snowflake/cli/{app → _app}/commands_registration/commands_registration_with_callbacks.py +4 -4
- snowflake/cli/{app → _app}/commands_registration/exception_logging.py +2 -2
- snowflake/cli/{app → _app}/commands_registration/typer_registration.py +2 -2
- snowflake/cli/{app → _app}/dev/docs/commands_docs_generator.py +30 -12
- snowflake/cli/{app → _app}/dev/docs/generator.py +3 -3
- snowflake/cli/{app → _app}/dev/docs/project_definition_docs_generator.py +4 -4
- snowflake/cli/{app → _app}/dev/docs/templates/usage.rst.jinja2 +14 -4
- snowflake/cli/{app → _app}/main_typer.py +2 -2
- snowflake/cli/{app → _app}/printing.py +2 -2
- snowflake/cli/_app/secret.py +9 -0
- snowflake/cli/{app → _app}/snow_connector.py +127 -61
- snowflake/cli/{app → _app}/telemetry.py +38 -7
- snowflake/cli/_app/version_check.py +74 -0
- snowflake/cli/{plugins → _plugins}/connection/commands.py +34 -11
- snowflake/cli/_plugins/connection/plugin_spec.py +30 -0
- snowflake/cli/{plugins → _plugins}/connection/util.py +16 -0
- snowflake/cli/{plugins → _plugins}/cortex/commands.py +54 -49
- snowflake/cli/{plugins → _plugins}/cortex/constants.py +1 -1
- snowflake/cli/{plugins → _plugins}/cortex/manager.py +5 -5
- snowflake/cli/{plugins → _plugins}/cortex/plugin_spec.py +1 -1
- snowflake/cli/{plugins → _plugins}/git/commands.py +79 -26
- snowflake/cli/{plugins → _plugins}/git/manager.py +72 -17
- snowflake/cli/{plugins → _plugins}/git/plugin_spec.py +1 -1
- snowflake/cli/_plugins/helpers/commands.py +90 -0
- snowflake/cli/{plugins/notebook → _plugins/helpers}/plugin_spec.py +1 -1
- snowflake/cli/{plugins → _plugins}/init/commands.py +10 -6
- snowflake/cli/{plugins → _plugins}/init/plugin_spec.py +1 -1
- snowflake/cli/{plugins → _plugins}/nativeapp/artifacts.py +24 -9
- snowflake/cli/_plugins/nativeapp/bundle_context.py +31 -0
- snowflake/cli/{plugins → _plugins}/nativeapp/codegen/artifact_processor.py +4 -4
- snowflake/cli/{plugins → _plugins}/nativeapp/codegen/compiler.py +37 -18
- snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +249 -0
- snowflake/cli/{plugins → _plugins}/nativeapp/codegen/setup/setup_driver.py.source +5 -2
- snowflake/cli/{plugins → _plugins}/nativeapp/codegen/snowpark/extension_function_utils.py +5 -5
- snowflake/cli/{plugins → _plugins}/nativeapp/codegen/snowpark/models.py +1 -1
- snowflake/cli/{plugins → _plugins}/nativeapp/codegen/snowpark/python_processor.py +29 -34
- snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +114 -0
- snowflake/cli/{plugins → _plugins}/nativeapp/commands.py +252 -132
- snowflake/cli/{plugins → _plugins}/nativeapp/common_flags.py +1 -1
- snowflake/cli/_plugins/nativeapp/entities/application.py +878 -0
- snowflake/cli/_plugins/nativeapp/entities/application_package.py +1392 -0
- snowflake/cli/{plugins → _plugins}/nativeapp/exceptions.py +3 -12
- snowflake/cli/_plugins/nativeapp/manager.py +415 -0
- snowflake/cli/{plugins/connection → _plugins/nativeapp}/plugin_spec.py +1 -1
- snowflake/cli/{plugins → _plugins}/nativeapp/policy.py +3 -0
- snowflake/cli/{plugins → _plugins}/nativeapp/project_model.py +36 -20
- snowflake/cli/_plugins/nativeapp/run_processor.py +184 -0
- snowflake/cli/_plugins/nativeapp/same_account_install_method.py +70 -0
- snowflake/cli/_plugins/nativeapp/teardown_processor.py +70 -0
- snowflake/cli/_plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +262 -0
- snowflake/cli/{plugins → _plugins}/nativeapp/version/commands.py +20 -49
- snowflake/cli/_plugins/nativeapp/version/version_processor.py +98 -0
- snowflake/cli/{plugins → _plugins}/notebook/commands.py +8 -6
- snowflake/cli/{plugins → _plugins}/notebook/manager.py +14 -14
- snowflake/cli/{plugins/nativeapp → _plugins/notebook}/plugin_spec.py +1 -1
- snowflake/cli/{plugins → _plugins}/notebook/types.py +0 -1
- snowflake/cli/{plugins → _plugins}/object/command_aliases.py +6 -5
- snowflake/cli/{plugins → _plugins}/object/commands.py +16 -10
- snowflake/cli/{plugins → _plugins}/object/manager.py +43 -21
- snowflake/cli/{plugins → _plugins}/object/plugin_spec.py +1 -1
- snowflake/cli/_plugins/snowpark/commands.py +450 -0
- snowflake/cli/_plugins/snowpark/common.py +268 -0
- snowflake/cli/{plugins → _plugins}/snowpark/models.py +2 -8
- snowflake/cli/{plugins → _plugins}/snowpark/package/anaconda_packages.py +2 -36
- snowflake/cli/{plugins → _plugins}/snowpark/package/commands.py +13 -74
- snowflake/cli/{plugins → _plugins}/snowpark/package/manager.py +4 -3
- snowflake/cli/{plugins → _plugins}/snowpark/package_utils.py +5 -5
- snowflake/cli/_plugins/snowpark/plugin_spec.py +30 -0
- snowflake/cli/_plugins/snowpark/snowpark_entity.py +29 -0
- snowflake/cli/_plugins/snowpark/snowpark_entity_model.py +173 -0
- snowflake/cli/_plugins/snowpark/snowpark_project_paths.py +109 -0
- snowflake/cli/{plugins → _plugins}/snowpark/snowpark_shared.py +0 -36
- snowflake/cli/{plugins → _plugins}/snowpark/zipper.py +16 -8
- snowflake/cli/{plugins → _plugins}/spcs/__init__.py +5 -7
- snowflake/cli/{plugins → _plugins}/spcs/compute_pool/commands.py +29 -28
- snowflake/cli/{plugins → _plugins}/spcs/compute_pool/manager.py +3 -3
- snowflake/cli/{plugins → _plugins}/spcs/image_registry/commands.py +3 -3
- snowflake/cli/{plugins → _plugins}/spcs/image_repository/commands.py +25 -19
- snowflake/cli/{plugins → _plugins}/spcs/image_repository/manager.py +1 -1
- snowflake/cli/{plugins → _plugins}/spcs/plugin_spec.py +1 -1
- snowflake/cli/{plugins → _plugins}/spcs/services/commands.py +66 -32
- snowflake/cli/{plugins → _plugins}/spcs/services/manager.py +43 -5
- snowflake/cli/{plugins → _plugins}/sql/commands.py +20 -17
- snowflake/cli/{plugins → _plugins}/sql/manager.py +1 -1
- snowflake/cli/{plugins → _plugins}/sql/plugin_spec.py +1 -1
- snowflake/cli/{plugins → _plugins}/stage/commands.py +20 -17
- snowflake/cli/{plugins → _plugins}/stage/diff.py +1 -47
- snowflake/cli/{plugins → _plugins}/stage/manager.py +62 -24
- snowflake/cli/{plugins → _plugins}/stage/plugin_spec.py +1 -1
- snowflake/cli/_plugins/stage/utils.py +54 -0
- snowflake/cli/{plugins → _plugins}/streamlit/commands.py +71 -62
- snowflake/cli/{plugins → _plugins}/streamlit/manager.py +68 -70
- snowflake/cli/_plugins/streamlit/plugin_spec.py +30 -0
- snowflake/cli/_plugins/streamlit/streamlit_entity.py +12 -0
- snowflake/cli/_plugins/streamlit/streamlit_entity_model.py +66 -0
- snowflake/cli/_plugins/workspace/action_context.py +18 -0
- snowflake/cli/_plugins/workspace/commands.py +306 -0
- snowflake/cli/_plugins/workspace/manager.py +74 -0
- snowflake/cli/_plugins/workspace/plugin_spec.py +30 -0
- snowflake/cli/api/cli_global_context.py +152 -295
- snowflake/cli/api/commands/common.py +25 -0
- snowflake/cli/api/commands/decorators.py +19 -4
- snowflake/cli/api/commands/experimental_behaviour.py +2 -3
- snowflake/cli/api/commands/flags.py +143 -222
- snowflake/cli/api/commands/overrideable_parameter.py +143 -0
- snowflake/cli/api/commands/snow_typer.py +21 -11
- snowflake/cli/api/commands/utils.py +18 -0
- snowflake/cli/api/config.py +44 -12
- snowflake/cli/api/connections.py +216 -0
- snowflake/cli/api/console/abc.py +8 -3
- snowflake/cli/api/constants.py +11 -0
- snowflake/cli/api/entities/common.py +56 -0
- snowflake/cli/api/entities/utils.py +370 -0
- snowflake/cli/api/errno.py +1 -0
- snowflake/cli/api/exceptions.py +31 -5
- snowflake/cli/api/feature_flags.py +0 -1
- snowflake/cli/api/identifiers.py +45 -9
- snowflake/cli/api/metrics.py +92 -0
- snowflake/cli/api/project/definition.py +48 -6
- snowflake/cli/api/project/definition_conversion.py +400 -0
- snowflake/cli/api/project/definition_manager.py +16 -5
- snowflake/cli/api/project/project_verification.py +3 -3
- snowflake/cli/api/project/schemas/entities/common.py +91 -16
- snowflake/cli/api/project/schemas/entities/entities.py +37 -6
- snowflake/cli/api/project/schemas/project_definition.py +180 -49
- snowflake/cli/api/project/schemas/updatable_model.py +11 -3
- snowflake/cli/api/project/schemas/v1/__init__.py +0 -0
- snowflake/cli/api/project/schemas/{identifier_model.py → v1/identifier_model.py} +3 -1
- snowflake/cli/api/project/schemas/v1/native_app/__init__.py +0 -0
- snowflake/cli/api/project/schemas/{native_app → v1/native_app}/application.py +8 -9
- snowflake/cli/api/project/schemas/{native_app → v1/native_app}/native_app.py +4 -4
- snowflake/cli/api/project/schemas/{native_app → v1/native_app}/package.py +7 -1
- snowflake/cli/api/project/schemas/v1/snowpark/__init__.py +0 -0
- snowflake/cli/api/project/schemas/{snowpark → v1/snowpark}/callable.py +2 -2
- snowflake/cli/api/project/schemas/{snowpark → v1/snowpark}/snowpark.py +2 -2
- snowflake/cli/api/project/schemas/v1/streamlit/__init__.py +0 -0
- snowflake/cli/api/project/schemas/{streamlit → v1/streamlit}/streamlit.py +2 -1
- snowflake/cli/api/project/util.py +23 -6
- snowflake/cli/api/rendering/jinja.py +14 -8
- snowflake/cli/api/rendering/project_definition_templates.py +5 -1
- snowflake/cli/api/rendering/sql_templates.py +56 -11
- snowflake/cli/api/rest_api.py +11 -5
- snowflake/cli/api/secure_path.py +16 -18
- snowflake/cli/api/secure_utils.py +90 -1
- snowflake/cli/api/sql_execution.py +47 -27
- snowflake/cli/api/utils/definition_rendering.py +45 -13
- {snowflake_cli_labs-2.8.0rc1.dist-info → snowflake_cli_labs-3.0.0.dist-info}/METADATA +20 -18
- snowflake_cli_labs-3.0.0.dist-info/RECORD +242 -0
- snowflake_cli_labs-3.0.0.dist-info/entry_points.txt +2 -0
- snowflake/cli/api/commands/project_initialisation.py +0 -65
- snowflake/cli/api/commands/typer_pre_execute.py +0 -26
- snowflake/cli/api/project/schemas/entities/application_entity.py +0 -44
- snowflake/cli/api/project/schemas/entities/application_package_entity.py +0 -66
- snowflake/cli/app/build_and_push.sh +0 -8
- snowflake/cli/plugins/nativeapp/codegen/setup/native_app_setup_processor.py +0 -172
- snowflake/cli/plugins/nativeapp/init.py +0 -345
- snowflake/cli/plugins/nativeapp/manager.py +0 -823
- snowflake/cli/plugins/nativeapp/run_processor.py +0 -389
- snowflake/cli/plugins/nativeapp/teardown_processor.py +0 -301
- snowflake/cli/plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +0 -135
- snowflake/cli/plugins/nativeapp/version/version_processor.py +0 -362
- snowflake/cli/plugins/object_stage_deprecated/__init__.py +0 -15
- snowflake/cli/plugins/object_stage_deprecated/commands.py +0 -122
- snowflake/cli/plugins/object_stage_deprecated/plugin_spec.py +0 -32
- snowflake/cli/plugins/snowpark/commands.py +0 -548
- snowflake/cli/plugins/snowpark/common.py +0 -307
- snowflake/cli/plugins/snowpark/manager.py +0 -109
- snowflake/cli/plugins/snowpark/plugin_spec.py +0 -30
- snowflake/cli/plugins/snowpark/snowpark_package_paths.py +0 -65
- snowflake/cli/plugins/spcs/jobs/commands.py +0 -78
- snowflake/cli/plugins/spcs/jobs/manager.py +0 -53
- snowflake/cli/plugins/streamlit/__init__.py +0 -13
- snowflake/cli/plugins/streamlit/plugin_spec.py +0 -30
- snowflake/cli/plugins/workspace/__init__.py +0 -13
- snowflake/cli/plugins/workspace/commands.py +0 -35
- snowflake/cli/plugins/workspace/plugin_spec.py +0 -30
- snowflake/cli/templates/default_snowpark/.gitignore +0 -4
- snowflake/cli/templates/default_snowpark/app/common.py +0 -2
- snowflake/cli/templates/default_snowpark/app/functions.py +0 -15
- snowflake/cli/templates/default_snowpark/app/procedures.py +0 -22
- snowflake/cli/templates/default_snowpark/requirements.txt +0 -1
- snowflake/cli/templates/default_snowpark/snowflake.yml +0 -23
- snowflake/cli/templates/default_streamlit/.gitignore +0 -4
- snowflake/cli/templates/default_streamlit/common/hello.py +0 -2
- snowflake/cli/templates/default_streamlit/environment.yml +0 -6
- snowflake/cli/templates/default_streamlit/pages/my_page.py +0 -3
- snowflake/cli/templates/default_streamlit/snowflake.yml +0 -10
- snowflake/cli/templates/default_streamlit/streamlit_app.py +0 -4
- snowflake_cli_labs-2.8.0rc1.dist-info/RECORD +0 -240
- snowflake_cli_labs-2.8.0rc1.dist-info/entry_points.txt +0 -2
- /snowflake/cli/{app → _app}/__init__.py +0 -0
- /snowflake/cli/{api/project/schemas/native_app → _app/api_impl}/__init__.py +0 -0
- /snowflake/cli/{api/project/schemas/snowpark → _app/api_impl/plugin}/__init__.py +0 -0
- /snowflake/cli/{app → _app}/api_impl/plugin/plugin_config_provider_impl.py +0 -0
- /snowflake/cli/{app → _app}/commands_registration/__init__.py +0 -0
- /snowflake/cli/{app → _app}/commands_registration/threadsafe.py +0 -0
- /snowflake/cli/{app → _app}/constants.py +0 -0
- /snowflake/cli/{api/project/schemas/streamlit → _app/dev}/__init__.py +0 -0
- /snowflake/cli/{app → _app}/dev/commands_structure.py +0 -0
- /snowflake/cli/{app/api_impl → _app/dev/docs}/__init__.py +0 -0
- /snowflake/cli/{app → _app}/dev/docs/project_definition_generate_json_schema.py +0 -0
- /snowflake/cli/{app → _app}/dev/docs/template_utils.py +0 -0
- /snowflake/cli/{app → _app}/dev/docs/templates/definition_description.rst.jinja2 +0 -0
- /snowflake/cli/{app → _app}/dev/docs/templates/overview.rst.jinja2 +0 -0
- /snowflake/cli/{app → _app}/dev/pycharm_remote_debug.py +0 -0
- /snowflake/cli/{app → _app}/loggers.py +0 -0
- /snowflake/cli/{app/api_impl/plugin → _plugins}/__init__.py +0 -0
- /snowflake/cli/{app/dev → _plugins/connection}/__init__.py +0 -0
- /snowflake/cli/{app/dev/docs → _plugins/cortex}/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/cortex/types.py +0 -0
- /snowflake/cli/{plugins → _plugins/git}/__init__.py +0 -0
- /snowflake/cli/{plugins/connection → _plugins/helpers}/__init__.py +0 -0
- /snowflake/cli/{plugins/cortex → _plugins/init}/__init__.py +0 -0
- /snowflake/cli/{plugins/git → _plugins/nativeapp}/__init__.py +0 -0
- /snowflake/cli/{plugins/init → _plugins/nativeapp/codegen}/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/nativeapp/codegen/sandbox.py +0 -0
- /snowflake/cli/{plugins → _plugins}/nativeapp/codegen/snowpark/callback_source.py.jinja +0 -0
- /snowflake/cli/{plugins → _plugins}/nativeapp/constants.py +0 -0
- /snowflake/cli/{templates/default_snowpark/app → _plugins/nativeapp/entities}/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/nativeapp/feature_flags.py +0 -0
- /snowflake/cli/{plugins → _plugins}/nativeapp/utils.py +0 -0
- /snowflake/cli/{plugins/nativeapp → _plugins/nativeapp/version}/__init__.py +0 -0
- /snowflake/cli/{plugins/nativeapp/codegen → _plugins/notebook}/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/notebook/exceptions.py +0 -0
- /snowflake/cli/{plugins/nativeapp/version → _plugins/object}/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/object/common.py +0 -0
- /snowflake/cli/{plugins/notebook → _plugins/snowpark}/__init__.py +0 -0
- /snowflake/cli/{plugins/object → _plugins/snowpark/package}/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/snowpark/package/utils.py +0 -0
- /snowflake/cli/{plugins → _plugins}/spcs/common.py +0 -0
- /snowflake/cli/{plugins/snowpark → _plugins/spcs/compute_pool}/__init__.py +0 -0
- /snowflake/cli/{plugins/snowpark/package → _plugins/spcs/image_registry}/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/spcs/image_registry/manager.py +0 -0
- /snowflake/cli/{plugins/spcs/compute_pool → _plugins/spcs/image_repository}/__init__.py +0 -0
- /snowflake/cli/{plugins/spcs/image_registry → _plugins/spcs/services}/__init__.py +0 -0
- /snowflake/cli/{plugins/spcs/image_repository → _plugins/sql}/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/sql/snowsql_templating.py +0 -0
- /snowflake/cli/{plugins/spcs/jobs → _plugins/stage}/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/stage/md5.py +0 -0
- /snowflake/cli/{plugins/spcs/services → _plugins/streamlit}/__init__.py +0 -0
- /snowflake/cli/{plugins/sql → _plugins/workspace}/__init__.py +0 -0
- /snowflake/cli/{plugins/stage → api/project/schemas/entities}/__init__.py +0 -0
- /snowflake/cli/api/project/schemas/{native_app → v1/native_app}/path_mapping.py +0 -0
- /snowflake/cli/api/project/schemas/{snowpark → v1/snowpark}/argument.py +0 -0
- {snowflake_cli_labs-2.8.0rc1.dist-info → snowflake_cli_labs-3.0.0.dist-info}/WHEEL +0 -0
- {snowflake_cli_labs-2.8.0rc1.dist-info → snowflake_cli_labs-3.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -34,6 +34,9 @@ UNQUOTED_IDENTIFIER_REGEX = r"([a-zA-Z_])([a-zA-Z0-9_$]{0,254})"
|
|
|
34
34
|
QUOTED_IDENTIFIER_REGEX = r'"((""|[^"]){0,255})"'
|
|
35
35
|
VALID_IDENTIFIER_REGEX = f"(?:{UNQUOTED_IDENTIFIER_REGEX}|{QUOTED_IDENTIFIER_REGEX})"
|
|
36
36
|
|
|
37
|
+
# An env var that is used to suffix the names of some account-level resources
|
|
38
|
+
TEST_RESOURCE_SUFFIX_VAR = "SNOWFLAKE_CLI_TEST_RESOURCE_SUFFIX"
|
|
39
|
+
|
|
37
40
|
|
|
38
41
|
def encode_uri_component(s: str) -> str:
|
|
39
42
|
"""
|
|
@@ -191,12 +194,6 @@ def extract_schema(qualified_name: str):
|
|
|
191
194
|
return None
|
|
192
195
|
|
|
193
196
|
|
|
194
|
-
def generate_user_env(username: str) -> dict:
|
|
195
|
-
return {
|
|
196
|
-
"USER": username,
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
|
|
200
197
|
def first_set_env(*keys: str):
|
|
201
198
|
for k in keys:
|
|
202
199
|
v = os.getenv(k)
|
|
@@ -259,3 +256,23 @@ def identifier_to_show_like_pattern(identifier: str) -> str:
|
|
|
259
256
|
matching this identifier
|
|
260
257
|
"""
|
|
261
258
|
return f"'{escape_like_pattern(unquote_identifier(identifier))}'"
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def append_test_resource_suffix(identifier: str) -> str:
|
|
262
|
+
"""
|
|
263
|
+
Append a suffix that should be added to specified account-level resources.
|
|
264
|
+
|
|
265
|
+
This is an internal concern that is currently only used in tests
|
|
266
|
+
to isolate concurrent runs and to add the test name to resources.
|
|
267
|
+
"""
|
|
268
|
+
suffix = os.environ.get(TEST_RESOURCE_SUFFIX_VAR, "")
|
|
269
|
+
if identifier_to_str(identifier).endswith(identifier_to_str(suffix)):
|
|
270
|
+
# If the suffix has already been added, don't add it again
|
|
271
|
+
return identifier
|
|
272
|
+
if is_valid_quoted_identifier(identifier) or is_valid_quoted_identifier(suffix):
|
|
273
|
+
# If either identifier is already quoted, use concat_identifier
|
|
274
|
+
# to add the suffix inside the quotes
|
|
275
|
+
return concat_identifiers([identifier, suffix])
|
|
276
|
+
# Otherwise just append the string, don't add quotes
|
|
277
|
+
# in case the user doesn't want them
|
|
278
|
+
return f"{identifier}{suffix}"
|
|
@@ -17,7 +17,7 @@ from __future__ import annotations
|
|
|
17
17
|
|
|
18
18
|
from pathlib import Path
|
|
19
19
|
from textwrap import dedent
|
|
20
|
-
from typing import Dict, Optional
|
|
20
|
+
from typing import Any, Dict, Optional
|
|
21
21
|
|
|
22
22
|
import jinja2
|
|
23
23
|
from jinja2 import Environment, StrictUndefined, loaders
|
|
@@ -82,8 +82,18 @@ class IgnoreAttrEnvironment(Environment):
|
|
|
82
82
|
return self.undefined(obj=obj, name=argument)
|
|
83
83
|
|
|
84
84
|
|
|
85
|
+
def get_basic_jinja_env(loader: Optional[loaders.BaseLoader] = None) -> Environment:
|
|
86
|
+
return env_bootstrap(
|
|
87
|
+
IgnoreAttrEnvironment(
|
|
88
|
+
loader=loader or loaders.BaseLoader(),
|
|
89
|
+
keep_trailing_newline=True,
|
|
90
|
+
undefined=StrictUndefined,
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
85
95
|
def jinja_render_from_file(
|
|
86
|
-
template_path: Path, data: Dict, output_file_path: Optional[Path] = None
|
|
96
|
+
template_path: Path, data: Dict[str, Any], output_file_path: Optional[Path] = None
|
|
87
97
|
) -> Optional[str]:
|
|
88
98
|
"""
|
|
89
99
|
Renders a jinja template and outputs either the rendered contents as string or writes to a file.
|
|
@@ -96,12 +106,8 @@ def jinja_render_from_file(
|
|
|
96
106
|
Returns:
|
|
97
107
|
None if file path is provided, else returns the rendered string.
|
|
98
108
|
"""
|
|
99
|
-
env =
|
|
100
|
-
|
|
101
|
-
loader=loaders.FileSystemLoader(template_path.parent),
|
|
102
|
-
keep_trailing_newline=True,
|
|
103
|
-
undefined=StrictUndefined,
|
|
104
|
-
)
|
|
109
|
+
env = get_basic_jinja_env(
|
|
110
|
+
loader=loaders.FileSystemLoader(template_path.parent.as_posix())
|
|
105
111
|
)
|
|
106
112
|
loaded_template = env.get_template(template_path.name)
|
|
107
113
|
rendered_result = loaded_template.render(**data)
|
|
@@ -24,7 +24,11 @@ _YML_TEMPLATE_START = "<%"
|
|
|
24
24
|
_YML_TEMPLATE_END = "%>"
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
def
|
|
27
|
+
def has_client_side_templates(template_content: str) -> bool:
|
|
28
|
+
return _YML_TEMPLATE_START in template_content
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_client_side_jinja_env() -> Environment:
|
|
28
32
|
_random_block = "___very___unique___block___to___disable___logic___blocks___"
|
|
29
33
|
return env_bootstrap(
|
|
30
34
|
IgnoreAttrEnvironment(
|
|
@@ -17,8 +17,11 @@ from __future__ import annotations
|
|
|
17
17
|
from typing import Dict, Optional
|
|
18
18
|
|
|
19
19
|
from click import ClickException
|
|
20
|
-
from jinja2 import StrictUndefined, loaders
|
|
21
|
-
from snowflake.cli.api.cli_global_context import
|
|
20
|
+
from jinja2 import Environment, StrictUndefined, loaders, meta
|
|
21
|
+
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
22
|
+
from snowflake.cli.api.console.console import cli_console
|
|
23
|
+
from snowflake.cli.api.exceptions import InvalidTemplate
|
|
24
|
+
from snowflake.cli.api.metrics import CLICounterField
|
|
22
25
|
from snowflake.cli.api.rendering.jinja import (
|
|
23
26
|
CONTEXT_KEY,
|
|
24
27
|
FUNCTION_KEY,
|
|
@@ -26,26 +29,62 @@ from snowflake.cli.api.rendering.jinja import (
|
|
|
26
29
|
env_bootstrap,
|
|
27
30
|
)
|
|
28
31
|
|
|
29
|
-
_SQL_TEMPLATE_START = "
|
|
30
|
-
_SQL_TEMPLATE_END = "
|
|
32
|
+
_SQL_TEMPLATE_START = "<%"
|
|
33
|
+
_SQL_TEMPLATE_END = "%>"
|
|
34
|
+
_OLD_SQL_TEMPLATE_START = "&{"
|
|
35
|
+
_OLD_SQL_TEMPLATE_END = "}"
|
|
31
36
|
RESERVED_KEYS = [CONTEXT_KEY, FUNCTION_KEY]
|
|
32
37
|
|
|
33
38
|
|
|
34
|
-
def
|
|
39
|
+
def _get_sql_jinja_env(template_start: str, template_end: str) -> Environment:
|
|
35
40
|
_random_block = "___very___unique___block___to___disable___logic___blocks___"
|
|
36
41
|
return env_bootstrap(
|
|
37
42
|
IgnoreAttrEnvironment(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
variable_end_string=_SQL_TEMPLATE_END,
|
|
43
|
+
variable_start_string=template_start,
|
|
44
|
+
variable_end_string=template_end,
|
|
45
|
+
loader=loaders.BaseLoader(),
|
|
42
46
|
block_start_string=_random_block,
|
|
43
47
|
block_end_string=_random_block,
|
|
48
|
+
keep_trailing_newline=True,
|
|
44
49
|
undefined=StrictUndefined,
|
|
45
50
|
)
|
|
46
51
|
)
|
|
47
52
|
|
|
48
53
|
|
|
54
|
+
def _does_template_have_env_syntax(env: Environment, template_content: str) -> bool:
|
|
55
|
+
template = env.parse(template_content)
|
|
56
|
+
return bool(meta.find_undeclared_variables(template))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def has_sql_templates(template_content: str) -> bool:
|
|
60
|
+
return (
|
|
61
|
+
_OLD_SQL_TEMPLATE_START in template_content
|
|
62
|
+
or _SQL_TEMPLATE_START in template_content
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def choose_sql_jinja_env_based_on_template_syntax(
|
|
67
|
+
template_content: str, reference_name: Optional[str] = None
|
|
68
|
+
) -> Environment:
|
|
69
|
+
old_syntax_env = _get_sql_jinja_env(_OLD_SQL_TEMPLATE_START, _OLD_SQL_TEMPLATE_END)
|
|
70
|
+
new_syntax_env = _get_sql_jinja_env(_SQL_TEMPLATE_START, _SQL_TEMPLATE_END)
|
|
71
|
+
has_old_syntax = _does_template_have_env_syntax(old_syntax_env, template_content)
|
|
72
|
+
has_new_syntax = _does_template_have_env_syntax(new_syntax_env, template_content)
|
|
73
|
+
reference_name_str = f" in {reference_name}" if reference_name else ""
|
|
74
|
+
if has_old_syntax and has_new_syntax:
|
|
75
|
+
raise InvalidTemplate(
|
|
76
|
+
f"The SQL query{reference_name_str} mixes {_OLD_SQL_TEMPLATE_START} ... {_OLD_SQL_TEMPLATE_END} syntax"
|
|
77
|
+
f" and {_SQL_TEMPLATE_START} ... {_SQL_TEMPLATE_END} syntax."
|
|
78
|
+
)
|
|
79
|
+
if has_old_syntax:
|
|
80
|
+
cli_console.warning(
|
|
81
|
+
f"Warning: {_OLD_SQL_TEMPLATE_START} ... {_OLD_SQL_TEMPLATE_END} syntax{reference_name_str} is deprecated."
|
|
82
|
+
f" Use {_SQL_TEMPLATE_START} ... {_SQL_TEMPLATE_END} syntax instead."
|
|
83
|
+
)
|
|
84
|
+
return old_syntax_env
|
|
85
|
+
return new_syntax_env
|
|
86
|
+
|
|
87
|
+
|
|
49
88
|
def snowflake_sql_jinja_render(content: str, data: Dict | None = None) -> str:
|
|
50
89
|
data = data or {}
|
|
51
90
|
|
|
@@ -55,6 +94,12 @@ def snowflake_sql_jinja_render(content: str, data: Dict | None = None) -> str:
|
|
|
55
94
|
f"{reserved_key} in user defined data. The `{reserved_key}` variable is reserved for CLI usage."
|
|
56
95
|
)
|
|
57
96
|
|
|
58
|
-
context_data =
|
|
97
|
+
context_data = get_cli_context().template_context
|
|
59
98
|
context_data.update(data)
|
|
60
|
-
|
|
99
|
+
env = choose_sql_jinja_env_based_on_template_syntax(content)
|
|
100
|
+
|
|
101
|
+
get_cli_context().metrics.set_counter(
|
|
102
|
+
CLICounterField.SQL_TEMPLATES, int(has_sql_templates(content))
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
return env.from_string(content).render(context_data)
|
snowflake/cli/api/rest_api.py
CHANGED
|
@@ -21,8 +21,9 @@ from typing import Any, Dict, Optional
|
|
|
21
21
|
from click import ClickException
|
|
22
22
|
from snowflake.cli.api.constants import SF_REST_API_URL_PREFIX
|
|
23
23
|
from snowflake.connector.connection import SnowflakeConnection
|
|
24
|
-
from snowflake.connector.errors import BadRequest
|
|
24
|
+
from snowflake.connector.errors import BadRequest
|
|
25
25
|
from snowflake.connector.network import SnowflakeRestful
|
|
26
|
+
from snowflake.connector.vendored.requests.exceptions import HTTPError
|
|
26
27
|
|
|
27
28
|
log = logging.getLogger(__name__)
|
|
28
29
|
|
|
@@ -47,10 +48,10 @@ class RestApi:
|
|
|
47
48
|
Check whether [get] endpoint exists under given URL.
|
|
48
49
|
"""
|
|
49
50
|
try:
|
|
50
|
-
|
|
51
|
-
return
|
|
52
|
-
except
|
|
53
|
-
if
|
|
51
|
+
self.send_rest_request(url, method="get")
|
|
52
|
+
return True
|
|
53
|
+
except HTTPError as err:
|
|
54
|
+
if err.response.status_code == 404:
|
|
54
55
|
return False
|
|
55
56
|
raise err
|
|
56
57
|
|
|
@@ -60,6 +61,10 @@ class RestApi:
|
|
|
60
61
|
return bool(result)
|
|
61
62
|
except BadRequest:
|
|
62
63
|
return False
|
|
64
|
+
except HTTPError as err:
|
|
65
|
+
if err.response.status_code == 404:
|
|
66
|
+
return False
|
|
67
|
+
raise err
|
|
63
68
|
|
|
64
69
|
def send_rest_request(
|
|
65
70
|
self, url: str, method: str, data: Optional[Dict[str, Any]] = None
|
|
@@ -91,6 +96,7 @@ class RestApi:
|
|
|
91
96
|
token=self.rest.token,
|
|
92
97
|
data=json.dumps(data if data else {}),
|
|
93
98
|
no_retry=True,
|
|
99
|
+
raise_raw_http_failure=True,
|
|
94
100
|
)
|
|
95
101
|
|
|
96
102
|
def _database_exists(self, db_name: str) -> bool:
|
snowflake/cli/api/secure_path.py
CHANGED
|
@@ -24,6 +24,12 @@ from pathlib import Path
|
|
|
24
24
|
from typing import Optional, Union
|
|
25
25
|
|
|
26
26
|
from snowflake.cli.api.exceptions import DirectoryIsNotEmptyError, FileTooLargeError
|
|
27
|
+
from snowflake.cli.api.secure_utils import (
|
|
28
|
+
chmod as secure_chmod,
|
|
29
|
+
)
|
|
30
|
+
from snowflake.cli.api.secure_utils import (
|
|
31
|
+
restrict_file_permissions,
|
|
32
|
+
)
|
|
27
33
|
|
|
28
34
|
log = logging.getLogger(__name__)
|
|
29
35
|
|
|
@@ -47,6 +53,12 @@ class SecurePath:
|
|
|
47
53
|
"""
|
|
48
54
|
return self._path
|
|
49
55
|
|
|
56
|
+
def chmod(self, permissions_mask: int) -> None:
|
|
57
|
+
"""
|
|
58
|
+
Change the file mode and permissions, like os.chmod().
|
|
59
|
+
"""
|
|
60
|
+
secure_chmod(self._path, permissions_mask)
|
|
61
|
+
|
|
50
62
|
@property
|
|
51
63
|
def parent(self):
|
|
52
64
|
"""
|
|
@@ -97,28 +109,11 @@ class SecurePath:
|
|
|
97
109
|
"""A string representing the final path component."""
|
|
98
110
|
return self._path.name
|
|
99
111
|
|
|
100
|
-
def chmod(self, permissions_mask: int) -> None:
|
|
101
|
-
"""
|
|
102
|
-
Change the file mode and permissions, like os.chmod().
|
|
103
|
-
"""
|
|
104
|
-
log.info(
|
|
105
|
-
"Update permissions of file %s to %s", self._path, oct(permissions_mask)
|
|
106
|
-
)
|
|
107
|
-
self._path.chmod(permissions_mask)
|
|
108
|
-
|
|
109
112
|
def restrict_permissions(self) -> None:
|
|
110
113
|
"""
|
|
111
114
|
Restrict file/directory permissions to owner-only.
|
|
112
115
|
"""
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
owner_permissions = (
|
|
116
|
-
# https://docs.python.org/3/library/stat.html
|
|
117
|
-
stat.S_IRUSR # readable by owner
|
|
118
|
-
| stat.S_IWUSR # writeable by owner
|
|
119
|
-
| stat.S_IXUSR # executable by owner
|
|
120
|
-
)
|
|
121
|
-
self.chmod(self._path.stat().st_mode & owner_permissions)
|
|
116
|
+
restrict_file_permissions(self._path)
|
|
122
117
|
|
|
123
118
|
def touch(self, permissions_mask: int = 0o600, exist_ok: bool = True) -> None:
|
|
124
119
|
"""
|
|
@@ -349,6 +344,9 @@ class SecurePath:
|
|
|
349
344
|
):
|
|
350
345
|
raise FileTooLargeError(self._path.resolve(), size_limit_in_mb)
|
|
351
346
|
|
|
347
|
+
def rename(self, new_name: Union[str | Path]):
|
|
348
|
+
self._path.rename(new_name)
|
|
349
|
+
|
|
352
350
|
|
|
353
351
|
def _raise_file_exists_error(path: Path):
|
|
354
352
|
raise FileExistsError(errno.EEXIST, os.strerror(errno.EEXIST), path)
|
|
@@ -12,11 +12,64 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
+
import logging
|
|
15
16
|
import stat
|
|
16
17
|
from pathlib import Path
|
|
18
|
+
from typing import List
|
|
17
19
|
|
|
20
|
+
from snowflake.connector.compat import IS_WINDOWS
|
|
18
21
|
|
|
19
|
-
|
|
22
|
+
log = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _get_windows_whitelisted_users():
|
|
26
|
+
# whitelisted users list obtained in consultation with prodsec: CASEC-9627
|
|
27
|
+
import os
|
|
28
|
+
|
|
29
|
+
return [
|
|
30
|
+
"SYSTEM",
|
|
31
|
+
"Administrators",
|
|
32
|
+
"Network",
|
|
33
|
+
"Domain Admins",
|
|
34
|
+
"Domain Users",
|
|
35
|
+
os.getlogin(),
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _run_icacls(file_path: Path) -> str:
|
|
40
|
+
import subprocess
|
|
41
|
+
|
|
42
|
+
return subprocess.check_output(["icacls", str(file_path)], text=True)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _windows_permissions_are_denied(permission_codes: str) -> bool:
|
|
46
|
+
# according to https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/icacls
|
|
47
|
+
return "(DENY)" in permission_codes or "(N)" in permission_codes
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def windows_get_not_whitelisted_users_with_access(file_path: Path) -> List[str]:
|
|
51
|
+
import re
|
|
52
|
+
|
|
53
|
+
# according to https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/icacls
|
|
54
|
+
icacls_output_regex = (
|
|
55
|
+
rf"({re.escape(str(file_path))})?.*\\(?P<user>.*):(?P<permissions>[(A-Z),]+)"
|
|
56
|
+
)
|
|
57
|
+
whitelisted_users = _get_windows_whitelisted_users()
|
|
58
|
+
|
|
59
|
+
users_with_access = []
|
|
60
|
+
for permission in re.finditer(icacls_output_regex, _run_icacls(file_path)):
|
|
61
|
+
if (permission.group("user") not in whitelisted_users) and (
|
|
62
|
+
not _windows_permissions_are_denied(permission.group("permissions"))
|
|
63
|
+
):
|
|
64
|
+
users_with_access.append(permission.group("user"))
|
|
65
|
+
return list(set(users_with_access))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _windows_file_permissions_are_strict(file_path: Path) -> bool:
|
|
69
|
+
return windows_get_not_whitelisted_users_with_access(file_path) == []
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _unix_file_permissions_are_strict(file_path: Path) -> bool:
|
|
20
73
|
accessible_by_others = (
|
|
21
74
|
# https://docs.python.org/3/library/stat.html
|
|
22
75
|
stat.S_IRGRP # readable by group
|
|
@@ -27,3 +80,39 @@ def file_permissions_are_strict(file_path: Path) -> bool:
|
|
|
27
80
|
| stat.S_IXOTH # executable by others
|
|
28
81
|
)
|
|
29
82
|
return (file_path.stat().st_mode & accessible_by_others) == 0
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def file_permissions_are_strict(file_path: Path) -> bool:
|
|
86
|
+
if IS_WINDOWS:
|
|
87
|
+
return _windows_file_permissions_are_strict(file_path)
|
|
88
|
+
return _unix_file_permissions_are_strict(file_path)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def chmod(path: Path, permissions_mask: int) -> None:
|
|
92
|
+
log.info("Update permissions of file %s to %s", path, oct(permissions_mask))
|
|
93
|
+
path.chmod(permissions_mask)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _unix_restrict_file_permissions(path: Path) -> None:
|
|
97
|
+
owner_permissions = (
|
|
98
|
+
# https://docs.python.org/3/library/stat.html
|
|
99
|
+
stat.S_IRUSR # readable by owner
|
|
100
|
+
| stat.S_IWUSR # writeable by owner
|
|
101
|
+
| stat.S_IXUSR # executable by owner
|
|
102
|
+
)
|
|
103
|
+
chmod(path, path.stat().st_mode & owner_permissions)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _windows_restrict_file_permissions(path: Path) -> None:
|
|
107
|
+
import subprocess
|
|
108
|
+
|
|
109
|
+
for user in windows_get_not_whitelisted_users_with_access(path):
|
|
110
|
+
log.info("Removing permissions of user %s from file %s", user, path)
|
|
111
|
+
subprocess.run(["icacls", str(path), "/DENY", f"{user}:F"])
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def restrict_file_permissions(file_path: Path) -> None:
|
|
115
|
+
if IS_WINDOWS:
|
|
116
|
+
_windows_restrict_file_permissions(file_path)
|
|
117
|
+
else:
|
|
118
|
+
_unix_restrict_file_permissions(file_path)
|
|
@@ -21,7 +21,7 @@ from io import StringIO
|
|
|
21
21
|
from textwrap import dedent
|
|
22
22
|
from typing import Iterable, Optional, Tuple
|
|
23
23
|
|
|
24
|
-
from snowflake.cli.api.cli_global_context import
|
|
24
|
+
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
25
25
|
from snowflake.cli.api.console import cli_console
|
|
26
26
|
from snowflake.cli.api.constants import ObjectType
|
|
27
27
|
from snowflake.cli.api.exceptions import (
|
|
@@ -35,27 +35,21 @@ from snowflake.cli.api.project.util import (
|
|
|
35
35
|
unquote_identifier,
|
|
36
36
|
)
|
|
37
37
|
from snowflake.cli.api.utils.cursor import find_first_row
|
|
38
|
+
from snowflake.connector import SnowflakeConnection
|
|
38
39
|
from snowflake.connector.cursor import DictCursor, SnowflakeCursor
|
|
39
40
|
from snowflake.connector.errors import ProgrammingError
|
|
40
41
|
|
|
41
42
|
|
|
42
|
-
class
|
|
43
|
-
def __init__(self):
|
|
43
|
+
class SqlExecutor:
|
|
44
|
+
def __init__(self, connection: SnowflakeConnection | None = None):
|
|
44
45
|
self._snowpark_session = None
|
|
46
|
+
self._connection = connection
|
|
45
47
|
|
|
46
48
|
@property
|
|
47
|
-
def _conn(self):
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
49
|
+
def _conn(self) -> SnowflakeConnection:
|
|
50
|
+
if self._connection:
|
|
51
|
+
return self._connection
|
|
52
|
+
return get_cli_context().connection
|
|
59
53
|
|
|
60
54
|
@cached_property
|
|
61
55
|
def _log(self):
|
|
@@ -88,6 +82,12 @@ class SqlExecutionMixin:
|
|
|
88
82
|
def _execute_queries(self, queries: str, **kwargs):
|
|
89
83
|
return list(self._execute_string(dedent(queries), **kwargs))
|
|
90
84
|
|
|
85
|
+
def execute_query(self, query: str, **kwargs):
|
|
86
|
+
return self._execute_query(query, **kwargs)
|
|
87
|
+
|
|
88
|
+
def execute_queries(self, queries: str, **kwargs):
|
|
89
|
+
return self._execute_queries(queries, **kwargs)
|
|
90
|
+
|
|
91
91
|
def use(self, object_type: ObjectType, name: str):
|
|
92
92
|
try:
|
|
93
93
|
self._execute_query(f"use {object_type.value.sf_name} {name}")
|
|
@@ -97,16 +97,16 @@ class SqlExecutionMixin:
|
|
|
97
97
|
f"Could not use {object_type} {name}. Object does not exist, or operation cannot be performed."
|
|
98
98
|
)
|
|
99
99
|
|
|
100
|
+
def current_role(self) -> str:
|
|
101
|
+
return self._execute_query(f"select current_role()").fetchone()[0]
|
|
102
|
+
|
|
100
103
|
@contextmanager
|
|
101
104
|
def use_role(self, new_role: str):
|
|
102
105
|
"""
|
|
103
106
|
Switches to a different role for a while, then switches back.
|
|
104
107
|
This is a no-op if the requested role is already active.
|
|
105
108
|
"""
|
|
106
|
-
|
|
107
|
-
f"select current_role()", cursor_class=DictCursor
|
|
108
|
-
).fetchone()
|
|
109
|
-
prev_role = role_result["CURRENT_ROLE()"]
|
|
109
|
+
prev_role = self.current_role()
|
|
110
110
|
is_different_role = new_role.lower() != prev_role.lower()
|
|
111
111
|
if is_different_role:
|
|
112
112
|
self._log.debug("Assuming different role: %s", new_role)
|
|
@@ -117,6 +117,12 @@ class SqlExecutionMixin:
|
|
|
117
117
|
if is_different_role:
|
|
118
118
|
self._execute_query(f"use role {prev_role}")
|
|
119
119
|
|
|
120
|
+
def session_has_warehouse(self) -> bool:
|
|
121
|
+
result = self._execute_query(
|
|
122
|
+
"select current_warehouse() is not null"
|
|
123
|
+
).fetchone()
|
|
124
|
+
return bool(result[0])
|
|
125
|
+
|
|
120
126
|
@contextmanager
|
|
121
127
|
def use_warehouse(self, new_wh: str):
|
|
122
128
|
"""
|
|
@@ -125,12 +131,10 @@ class SqlExecutionMixin:
|
|
|
125
131
|
If there is no default warehouse in the account, it will throw an error.
|
|
126
132
|
"""
|
|
127
133
|
|
|
128
|
-
wh_result = self._execute_query(
|
|
129
|
-
f"select current_warehouse()", cursor_class=DictCursor
|
|
130
|
-
).fetchone()
|
|
134
|
+
wh_result = self._execute_query(f"select current_warehouse()").fetchone()
|
|
131
135
|
# If user has an assigned default warehouse, prev_wh will contain a value even if the warehouse is suspended.
|
|
132
136
|
try:
|
|
133
|
-
prev_wh = wh_result[
|
|
137
|
+
prev_wh = wh_result[0]
|
|
134
138
|
except:
|
|
135
139
|
prev_wh = None
|
|
136
140
|
|
|
@@ -147,11 +151,11 @@ class SqlExecutionMixin:
|
|
|
147
151
|
self.use(object_type=ObjectType.WAREHOUSE, name=prev_wh)
|
|
148
152
|
|
|
149
153
|
def create_password_secret(
|
|
150
|
-
self, name:
|
|
154
|
+
self, name: FQN, username: str, password: str
|
|
151
155
|
) -> SnowflakeCursor:
|
|
152
156
|
return self._execute_query(
|
|
153
157
|
f"""
|
|
154
|
-
create secret {name}
|
|
158
|
+
create secret {name.sql_identifier}
|
|
155
159
|
type = password
|
|
156
160
|
username = '{username}'
|
|
157
161
|
password = '{password}'
|
|
@@ -159,11 +163,11 @@ class SqlExecutionMixin:
|
|
|
159
163
|
)
|
|
160
164
|
|
|
161
165
|
def create_api_integration(
|
|
162
|
-
self, name:
|
|
166
|
+
self, name: FQN, api_provider: str, allowed_prefix: str, secret: Optional[str]
|
|
163
167
|
) -> SnowflakeCursor:
|
|
164
168
|
return self._execute_query(
|
|
165
169
|
f"""
|
|
166
|
-
create api integration {name}
|
|
170
|
+
create api integration {name.sql_identifier}
|
|
167
171
|
api_provider = {api_provider}
|
|
168
172
|
api_allowed_prefixes = ('{allowed_prefix}')
|
|
169
173
|
allowed_authentication_secrets = ({secret if secret else ''})
|
|
@@ -254,6 +258,22 @@ class SqlExecutionMixin:
|
|
|
254
258
|
return show_obj_row
|
|
255
259
|
|
|
256
260
|
|
|
261
|
+
class SqlExecutionMixin(SqlExecutor):
|
|
262
|
+
def __init__(self, *args, **kwargs):
|
|
263
|
+
super().__init__(*args, **kwargs)
|
|
264
|
+
self._snowpark_session = None
|
|
265
|
+
|
|
266
|
+
@property
|
|
267
|
+
def snowpark_session(self):
|
|
268
|
+
if not self._snowpark_session:
|
|
269
|
+
from snowflake.snowpark.session import Session
|
|
270
|
+
|
|
271
|
+
self._snowpark_session = Session.builder.configs(
|
|
272
|
+
{"connection": self._conn}
|
|
273
|
+
).create()
|
|
274
|
+
return self._snowpark_session
|
|
275
|
+
|
|
276
|
+
|
|
257
277
|
class VerboseCursor(SnowflakeCursor):
|
|
258
278
|
def execute(self, command: str, *args, **kwargs):
|
|
259
279
|
cli_console.message(command)
|