snowflake-cli-labs 2.8.1__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 +11 -7
- snowflake/cli/{plugins → _plugins}/git/manager.py +55 -9
- 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 +2 -2
- 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 +3 -2
- snowflake/cli/{plugins → _plugins}/notebook/manager.py +5 -5
- snowflake/cli/{plugins/nativeapp → _plugins/notebook}/plugin_spec.py +1 -1
- snowflake/cli/{plugins → _plugins}/object/command_aliases.py +4 -4
- snowflake/cli/{plugins → _plugins}/object/commands.py +4 -5
- snowflake/cli/{plugins → _plugins}/object/manager.py +36 -15
- 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 +0 -7
- 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 +2 -2
- 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 +8 -8
- 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 +6 -6
- 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 +44 -11
- 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 +15 -14
- snowflake/cli/{plugins → _plugins}/stage/diff.py +1 -47
- snowflake/cli/{plugins → _plugins}/stage/manager.py +12 -7
- 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 +64 -48
- snowflake/cli/{plugins → _plugins}/streamlit/manager.py +67 -69
- 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 +127 -228
- 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 +28 -5
- 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 +43 -23
- snowflake/cli/api/utils/definition_rendering.py +45 -13
- {snowflake_cli_labs-2.8.1.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 -546
- 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.1.dist-info/RECORD +0 -240
- snowflake_cli_labs-2.8.1.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 → _plugins}/notebook/types.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.1.dist-info → snowflake_cli_labs-3.0.0.dist-info}/WHEEL +0 -0
- {snowflake_cli_labs-2.8.1.dist-info → snowflake_cli_labs-3.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,143 @@
|
|
|
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 abc import ABC, abstractmethod
|
|
18
|
+
from inspect import signature
|
|
19
|
+
from typing import Any, List, Optional, Tuple
|
|
20
|
+
|
|
21
|
+
import typer
|
|
22
|
+
from click import ClickException
|
|
23
|
+
from snowflake.cli.api.exceptions import IncompatibleParametersError
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class _OverrideableParameter(ABC):
|
|
27
|
+
"""
|
|
28
|
+
Class that allows you to generate instances of typer.models.OptionInfo with some default properties while allowing
|
|
29
|
+
specific values to be overridden.
|
|
30
|
+
|
|
31
|
+
Custom parameters:
|
|
32
|
+
- mutually_exclusive (Tuple[str]|List[str]): A list of parameter names that this Option is not compatible with. If this Option has
|
|
33
|
+
a truthy value and any of the other parameters in the mutually_exclusive list has a truthy value, a
|
|
34
|
+
ClickException will be thrown. Note that mutually_exclusive can contain an option's own name but does not require
|
|
35
|
+
it.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
default: Any = ...,
|
|
41
|
+
*param_decls: str,
|
|
42
|
+
mutually_exclusive: Optional[List[str] | Tuple[str]] = None,
|
|
43
|
+
**kwargs,
|
|
44
|
+
):
|
|
45
|
+
self.default = default
|
|
46
|
+
self.param_decls = param_decls
|
|
47
|
+
self.mutually_exclusive = mutually_exclusive
|
|
48
|
+
self.kwargs = kwargs
|
|
49
|
+
|
|
50
|
+
def __call__(self, **kwargs) -> typer.models.ParameterInfo:
|
|
51
|
+
"""
|
|
52
|
+
Returns a typer.models.OptionInfo instance initialized with the specified default values along with any overrides
|
|
53
|
+
from kwargs. Note that if you are overriding param_decls, you must pass an iterable of strings, you cannot use
|
|
54
|
+
positional arguments like you can with typer.Option. Does not modify the original instance.
|
|
55
|
+
"""
|
|
56
|
+
default = kwargs.get("default", self.default)
|
|
57
|
+
param_decls = kwargs.get("param_decls", self.param_decls)
|
|
58
|
+
mutually_exclusive = kwargs.get("mutually_exclusive", self.mutually_exclusive)
|
|
59
|
+
if not isinstance(param_decls, list) and not isinstance(param_decls, tuple):
|
|
60
|
+
raise TypeError("param_decls must be a list or tuple")
|
|
61
|
+
passed_kwargs = self.kwargs.copy()
|
|
62
|
+
passed_kwargs.update(kwargs)
|
|
63
|
+
if passed_kwargs.get("callback", None) or mutually_exclusive:
|
|
64
|
+
passed_kwargs["callback"] = self._callback_factory(
|
|
65
|
+
passed_kwargs.get("callback", None), mutually_exclusive
|
|
66
|
+
)
|
|
67
|
+
for non_kwarg in ["default", "param_decls", "mutually_exclusive"]:
|
|
68
|
+
passed_kwargs.pop(non_kwarg, None)
|
|
69
|
+
|
|
70
|
+
return self.get_parameter(default, *param_decls, **passed_kwargs)
|
|
71
|
+
|
|
72
|
+
@abstractmethod
|
|
73
|
+
def get_parameter(
|
|
74
|
+
self, default: Any = None, *param_decls: str, **kwargs
|
|
75
|
+
) -> typer.models.ParameterInfo:
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
class InvalidCallbackSignature(ClickException):
|
|
79
|
+
def __init__(self, callback):
|
|
80
|
+
super().__init__(
|
|
81
|
+
f"Signature {signature(callback)} is not valid for an OverrideableOption callback function. Must have "
|
|
82
|
+
f"at most one parameter with each of the following types: (typer.Context, typer.CallbackParam, "
|
|
83
|
+
f"Any Other Type)"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def _callback_factory(
|
|
87
|
+
self, callback, mutually_exclusive: Optional[List[str] | Tuple[str]]
|
|
88
|
+
):
|
|
89
|
+
callback = callback if callback else lambda x: x
|
|
90
|
+
|
|
91
|
+
# inspect existing_callback to make sure signature is valid
|
|
92
|
+
existing_params = signature(callback).parameters
|
|
93
|
+
# at most one parameter with each type in [typer.Context, typer.CallbackParam, any other type]
|
|
94
|
+
limits = [
|
|
95
|
+
lambda x: x == typer.Context,
|
|
96
|
+
lambda x: x == typer.CallbackParam,
|
|
97
|
+
lambda x: x != typer.Context and x != typer.CallbackParam,
|
|
98
|
+
]
|
|
99
|
+
for limit in limits:
|
|
100
|
+
if len([v for v in existing_params.values() if limit(v.annotation)]) > 1:
|
|
101
|
+
raise self.InvalidCallbackSignature(callback)
|
|
102
|
+
|
|
103
|
+
def generated_callback(ctx: typer.Context, param: typer.CallbackParam, value):
|
|
104
|
+
if mutually_exclusive:
|
|
105
|
+
for name in mutually_exclusive:
|
|
106
|
+
if value and ctx.params.get(
|
|
107
|
+
name, False
|
|
108
|
+
): # if the current parameter is set to True and a previous parameter is also Truthy
|
|
109
|
+
curr_opt = param.opts[0]
|
|
110
|
+
other_opt = [x for x in ctx.command.params if x.name == name][
|
|
111
|
+
0
|
|
112
|
+
].opts[0]
|
|
113
|
+
raise IncompatibleParametersError([curr_opt, other_opt])
|
|
114
|
+
|
|
115
|
+
# pass args to existing callback based on its signature (this is how Typer infers callback args)
|
|
116
|
+
passed_params = {}
|
|
117
|
+
for existing_param in existing_params:
|
|
118
|
+
annotation = existing_params[existing_param].annotation
|
|
119
|
+
if annotation == typer.Context:
|
|
120
|
+
passed_params[existing_param] = ctx
|
|
121
|
+
elif annotation == typer.CallbackParam:
|
|
122
|
+
passed_params[existing_param] = param
|
|
123
|
+
else:
|
|
124
|
+
passed_params[existing_param] = value
|
|
125
|
+
return callback(**passed_params)
|
|
126
|
+
|
|
127
|
+
return generated_callback
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class OverrideableArgument(_OverrideableParameter):
|
|
131
|
+
def get_parameter(
|
|
132
|
+
self, default: Any = ..., *param_decls: str, **kwargs
|
|
133
|
+
) -> typer.models.ArgumentInfo:
|
|
134
|
+
return typer.Argument(default, *param_decls, **kwargs)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# OverrideableOption doesn't work with flags with type List[Any] and default None, because typer executes the callback
|
|
138
|
+
# function which converts the default value iterating over it, but None is not iterable.
|
|
139
|
+
class OverrideableOption(_OverrideableParameter):
|
|
140
|
+
def get_parameter(
|
|
141
|
+
self, default: Any = ..., *param_decls: str, **kwargs
|
|
142
|
+
) -> typer.models.OptionInfo:
|
|
143
|
+
return typer.Option(default, *param_decls, **kwargs)
|
|
@@ -20,6 +20,7 @@ from functools import wraps
|
|
|
20
20
|
from typing import Any, Callable, Dict, List, Optional, Tuple
|
|
21
21
|
|
|
22
22
|
import typer
|
|
23
|
+
from click import ClickException
|
|
23
24
|
from snowflake.cli.api.commands.decorators import (
|
|
24
25
|
global_options,
|
|
25
26
|
global_options_with_connection,
|
|
@@ -29,10 +30,11 @@ from snowflake.cli.api.commands.execution_metadata import (
|
|
|
29
30
|
ExecutionStatus,
|
|
30
31
|
)
|
|
31
32
|
from snowflake.cli.api.commands.flags import DEFAULT_CONTEXT_SETTINGS
|
|
32
|
-
from snowflake.cli.api.commands.typer_pre_execute import run_pre_execute_commands
|
|
33
33
|
from snowflake.cli.api.exceptions import CommandReturnTypeError
|
|
34
34
|
from snowflake.cli.api.output.types import CommandResult
|
|
35
35
|
from snowflake.cli.api.sanitizers import sanitize_for_terminal
|
|
36
|
+
from snowflake.cli.api.sql_execution import SqlExecutionMixin
|
|
37
|
+
from snowflake.connector import DatabaseError
|
|
36
38
|
|
|
37
39
|
log = logging.getLogger(__name__)
|
|
38
40
|
|
|
@@ -46,6 +48,7 @@ class SnowTyper(typer.Typer):
|
|
|
46
48
|
pretty_exceptions_show_locals=False,
|
|
47
49
|
no_args_is_help=True,
|
|
48
50
|
add_completion=True,
|
|
51
|
+
rich_markup_mode="markdown",
|
|
49
52
|
)
|
|
50
53
|
|
|
51
54
|
@staticmethod
|
|
@@ -70,6 +73,7 @@ class SnowTyper(typer.Typer):
|
|
|
70
73
|
requires_global_options: bool = True,
|
|
71
74
|
requires_connection: bool = False,
|
|
72
75
|
is_enabled: Callable[[], bool] | None = None,
|
|
76
|
+
require_warehouse: bool = False,
|
|
73
77
|
**kwargs,
|
|
74
78
|
):
|
|
75
79
|
"""
|
|
@@ -96,15 +100,15 @@ class SnowTyper(typer.Typer):
|
|
|
96
100
|
def command_callable_decorator(*args, **kw):
|
|
97
101
|
"""Wrapper around command callable. This is what happens at "runtime"."""
|
|
98
102
|
execution = ExecutionMetadata()
|
|
99
|
-
self.pre_execute(execution)
|
|
103
|
+
self.pre_execute(execution, require_warehouse=require_warehouse)
|
|
100
104
|
try:
|
|
101
105
|
result = command_callable(*args, **kw)
|
|
102
106
|
self.process_result(result)
|
|
103
107
|
execution.complete(ExecutionStatus.SUCCESS)
|
|
104
|
-
except
|
|
108
|
+
except BaseException as err:
|
|
105
109
|
execution.complete(ExecutionStatus.FAILURE)
|
|
106
|
-
self.exception_handler(err, execution)
|
|
107
|
-
raise
|
|
110
|
+
exception = self.exception_handler(err, execution)
|
|
111
|
+
raise exception
|
|
108
112
|
finally:
|
|
109
113
|
self.post_execute(execution)
|
|
110
114
|
|
|
@@ -115,22 +119,25 @@ class SnowTyper(typer.Typer):
|
|
|
115
119
|
return custom_command
|
|
116
120
|
|
|
117
121
|
@staticmethod
|
|
118
|
-
def pre_execute(execution: ExecutionMetadata):
|
|
122
|
+
def pre_execute(execution: ExecutionMetadata, require_warehouse: bool = False):
|
|
119
123
|
"""
|
|
120
124
|
Callback executed before running any command callable (after context execution).
|
|
121
125
|
Pay attention to make this method safe to use if performed operations are not necessary
|
|
122
126
|
for executing the command in proper way.
|
|
123
127
|
"""
|
|
124
|
-
from snowflake.cli.
|
|
128
|
+
from snowflake.cli._app.telemetry import log_command_usage
|
|
125
129
|
|
|
126
130
|
log.debug("Executing command pre execution callback")
|
|
127
|
-
run_pre_execute_commands()
|
|
128
131
|
log_command_usage(execution)
|
|
132
|
+
if require_warehouse and not SqlExecutionMixin().session_has_warehouse():
|
|
133
|
+
raise ClickException(
|
|
134
|
+
"The command requires warehouse. No warehouse found in current connection."
|
|
135
|
+
)
|
|
129
136
|
|
|
130
137
|
@staticmethod
|
|
131
138
|
def process_result(result):
|
|
132
139
|
"""Command result processor"""
|
|
133
|
-
from snowflake.cli.
|
|
140
|
+
from snowflake.cli._app.printing import print_result
|
|
134
141
|
|
|
135
142
|
# Because we still have commands like "logs" that do not return anything.
|
|
136
143
|
# We should improve it in future.
|
|
@@ -145,10 +152,13 @@ class SnowTyper(typer.Typer):
|
|
|
145
152
|
"""
|
|
146
153
|
Callback executed on command execution error.
|
|
147
154
|
"""
|
|
148
|
-
from snowflake.cli.
|
|
155
|
+
from snowflake.cli._app.telemetry import log_command_execution_error
|
|
149
156
|
|
|
150
157
|
log.debug("Executing command exception callback")
|
|
151
158
|
log_command_execution_error(exception, execution)
|
|
159
|
+
if isinstance(exception, DatabaseError):
|
|
160
|
+
return ClickException(exception.msg)
|
|
161
|
+
return exception
|
|
152
162
|
|
|
153
163
|
@staticmethod
|
|
154
164
|
def post_execute(execution: ExecutionMetadata):
|
|
@@ -156,7 +166,7 @@ class SnowTyper(typer.Typer):
|
|
|
156
166
|
Callback executed after running any command callable. Pay attention to make this method safe to
|
|
157
167
|
use if performed operations are not necessary for executing the command in proper way.
|
|
158
168
|
"""
|
|
159
|
-
from snowflake.cli.
|
|
169
|
+
from snowflake.cli._app.telemetry import flush_telemetry, log_command_result
|
|
160
170
|
|
|
161
171
|
log.debug("Executing command post execution callback")
|
|
162
172
|
log_command_result(execution)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from typing import List, Optional
|
|
2
|
+
|
|
3
|
+
from click import ClickException
|
|
4
|
+
from snowflake.cli.api.commands.common import Variable
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def parse_key_value_variables(variables: Optional[List[str]]) -> List[Variable]:
|
|
8
|
+
"""Util for parsing key=value input. Useful for commands accepting multiple input options."""
|
|
9
|
+
result: List[Variable] = []
|
|
10
|
+
if not variables:
|
|
11
|
+
return result
|
|
12
|
+
for p in variables:
|
|
13
|
+
if "=" not in p:
|
|
14
|
+
raise ClickException(f"Invalid variable: '{p}'")
|
|
15
|
+
|
|
16
|
+
key, value = p.split("=", 1)
|
|
17
|
+
result.append(Variable(key.strip(), value.strip()))
|
|
18
|
+
return result
|
snowflake/cli/api/config.py
CHANGED
|
@@ -30,7 +30,10 @@ from snowflake.cli.api.exceptions import (
|
|
|
30
30
|
UnsupportedConfigSectionTypeError,
|
|
31
31
|
)
|
|
32
32
|
from snowflake.cli.api.secure_path import SecurePath
|
|
33
|
-
from snowflake.cli.api.secure_utils import
|
|
33
|
+
from snowflake.cli.api.secure_utils import (
|
|
34
|
+
file_permissions_are_strict,
|
|
35
|
+
windows_get_not_whitelisted_users_with_access,
|
|
36
|
+
)
|
|
34
37
|
from snowflake.cli.api.utils.types import try_cast_to_bool
|
|
35
38
|
from snowflake.connector.compat import IS_WINDOWS
|
|
36
39
|
from snowflake.connector.config_manager import CONFIG_MANAGER
|
|
@@ -77,7 +80,7 @@ class ConnectionConfig:
|
|
|
77
80
|
warehouse: Optional[str] = None
|
|
78
81
|
role: Optional[str] = None
|
|
79
82
|
authenticator: Optional[str] = None
|
|
80
|
-
|
|
83
|
+
private_key_file: Optional[str] = None
|
|
81
84
|
token_file_path: Optional[str] = None
|
|
82
85
|
|
|
83
86
|
_other_settings: dict = field(default_factory=lambda: {})
|
|
@@ -115,7 +118,7 @@ def config_init(config_file: Optional[Path]):
|
|
|
115
118
|
Initializes the app configuration. Config provided via cli flag takes precedence.
|
|
116
119
|
If config file does not exist we create an empty one.
|
|
117
120
|
"""
|
|
118
|
-
from snowflake.cli.
|
|
121
|
+
from snowflake.cli._app.loggers import create_initial_loggers
|
|
119
122
|
|
|
120
123
|
if config_file:
|
|
121
124
|
CONFIG_MANAGER.file_path = config_file
|
|
@@ -127,12 +130,21 @@ def config_init(config_file: Optional[Path]):
|
|
|
127
130
|
create_initial_loggers()
|
|
128
131
|
|
|
129
132
|
|
|
130
|
-
def
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
133
|
+
def add_connection_to_proper_file(name: str, connection_config: ConnectionConfig):
|
|
134
|
+
if CONNECTIONS_FILE.exists():
|
|
135
|
+
existing_connections = _read_connections_toml()
|
|
136
|
+
existing_connections.update(
|
|
137
|
+
{name: connection_config.to_dict_of_all_non_empty_values()}
|
|
138
|
+
)
|
|
139
|
+
_update_connections_toml(existing_connections)
|
|
140
|
+
return CONNECTIONS_FILE
|
|
141
|
+
else:
|
|
142
|
+
set_config_value(
|
|
143
|
+
section=CONNECTIONS_SECTION,
|
|
144
|
+
key=name,
|
|
145
|
+
value=connection_config.to_dict_of_all_non_empty_values(),
|
|
146
|
+
)
|
|
147
|
+
return CONFIG_MANAGER.file_path
|
|
136
148
|
|
|
137
149
|
|
|
138
150
|
_DEFAULT_LOGS_CONFIG = {
|
|
@@ -160,6 +172,18 @@ def _read_config_file():
|
|
|
160
172
|
message="Bad owner or permissions.*",
|
|
161
173
|
module="snowflake.connector.config_manager",
|
|
162
174
|
)
|
|
175
|
+
|
|
176
|
+
if not file_permissions_are_strict(CONFIG_MANAGER.file_path):
|
|
177
|
+
users = ", ".join(
|
|
178
|
+
windows_get_not_whitelisted_users_with_access(
|
|
179
|
+
CONFIG_MANAGER.file_path
|
|
180
|
+
)
|
|
181
|
+
)
|
|
182
|
+
warnings.warn(
|
|
183
|
+
f"Unauthorized users ({users}) have access to configuration file {CONFIG_MANAGER.file_path}.\n"
|
|
184
|
+
f'Run `icacls "{CONFIG_MANAGER.file_path}" /deny <USER_ID>:F` on those users to restrict permissions.'
|
|
185
|
+
)
|
|
186
|
+
|
|
163
187
|
try:
|
|
164
188
|
CONFIG_MANAGER.read_config()
|
|
165
189
|
except ConfigSourceError as exception:
|
|
@@ -281,7 +305,7 @@ def _initialise_config(config_file: Path) -> None:
|
|
|
281
305
|
|
|
282
306
|
|
|
283
307
|
def get_env_variable_name(*path, key: str) -> str:
|
|
284
|
-
return "
|
|
308
|
+
return ("_".join(["snowflake", *path, key])).upper()
|
|
285
309
|
|
|
286
310
|
|
|
287
311
|
def get_env_value(*path, key: str) -> str | None:
|
|
@@ -322,8 +346,6 @@ def _dump_config(conf_file_cache: Dict):
|
|
|
322
346
|
|
|
323
347
|
|
|
324
348
|
def _check_default_config_files_permissions() -> None:
|
|
325
|
-
if IS_WINDOWS:
|
|
326
|
-
return
|
|
327
349
|
if CONNECTIONS_FILE.exists() and not file_permissions_are_strict(CONNECTIONS_FILE):
|
|
328
350
|
raise ConfigFileTooWidePermissionsError(CONNECTIONS_FILE)
|
|
329
351
|
if CONFIG_FILE.exists() and not file_permissions_are_strict(CONFIG_FILE):
|
|
@@ -346,3 +368,13 @@ def get_feature_flags_section() -> Dict[str, bool | Literal["UNKNOWN"]]:
|
|
|
346
368
|
return "UNKNOWN"
|
|
347
369
|
|
|
348
370
|
return {k: _bool_or_unknown(v) for k, v in flags.items()}
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def _read_connections_toml() -> dict:
|
|
374
|
+
with open(CONNECTIONS_FILE, "r") as f:
|
|
375
|
+
return tomlkit.loads(f.read()).unwrap()
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def _update_connections_toml(connections: dict):
|
|
379
|
+
with open(CONNECTIONS_FILE, "w") as f:
|
|
380
|
+
f.write(tomlkit.dumps(connections))
|
|
@@ -0,0 +1,216 @@
|
|
|
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
|
+
import asyncio
|
|
18
|
+
import logging
|
|
19
|
+
import re
|
|
20
|
+
import warnings
|
|
21
|
+
from dataclasses import asdict, dataclass, field, fields, replace
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Optional
|
|
24
|
+
|
|
25
|
+
from snowflake.cli.api.config import get_default_connection_name
|
|
26
|
+
from snowflake.cli.api.exceptions import InvalidSchemaError
|
|
27
|
+
from snowflake.connector import SnowflakeConnection
|
|
28
|
+
from snowflake.connector.compat import IS_WINDOWS
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
schema_pattern = re.compile(r".+\..+")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class ConnectionContext:
|
|
37
|
+
# FIXME: can reduce duplication using config.ConnectionConfig
|
|
38
|
+
connection_name: Optional[str] = None
|
|
39
|
+
host: Optional[str] = None
|
|
40
|
+
port: Optional[int] = None
|
|
41
|
+
account: Optional[str] = None
|
|
42
|
+
database: Optional[str] = None
|
|
43
|
+
role: Optional[str] = None
|
|
44
|
+
schema: Optional[str] = None
|
|
45
|
+
user: Optional[str] = None
|
|
46
|
+
password: Optional[str] = field(default=None, repr=False)
|
|
47
|
+
authenticator: Optional[str] = None
|
|
48
|
+
private_key_file: Optional[str] = None
|
|
49
|
+
warehouse: Optional[str] = None
|
|
50
|
+
mfa_passcode: Optional[str] = None
|
|
51
|
+
enable_diag: Optional[bool] = False
|
|
52
|
+
diag_log_path: Optional[Path] = None
|
|
53
|
+
diag_allowlist_path: Optional[Path] = None
|
|
54
|
+
temporary_connection: bool = False
|
|
55
|
+
session_token: Optional[str] = None
|
|
56
|
+
master_token: Optional[str] = None
|
|
57
|
+
token_file_path: Optional[Path] = None
|
|
58
|
+
|
|
59
|
+
VALIDATED_FIELD_NAMES = ["schema"]
|
|
60
|
+
|
|
61
|
+
def present_values_as_dict(self) -> dict:
|
|
62
|
+
"""Dictionary representation of this ConnectionContext for values that are not None"""
|
|
63
|
+
return {k: v for (k, v) in asdict(self).items() if v is not None}
|
|
64
|
+
|
|
65
|
+
def clone(self) -> ConnectionContext:
|
|
66
|
+
return replace(self)
|
|
67
|
+
|
|
68
|
+
def update(self, **updates):
|
|
69
|
+
"""
|
|
70
|
+
Given a dictionary of property (key, value) mappings, update properties
|
|
71
|
+
of this context object with equivalent names to the keys.
|
|
72
|
+
|
|
73
|
+
Raises KeyError if a non-property is specified as a key.
|
|
74
|
+
"""
|
|
75
|
+
field_map = {field.name: field for field in fields(self)}
|
|
76
|
+
for key, value in updates.items():
|
|
77
|
+
# ensure key represents a property
|
|
78
|
+
if key not in field_map:
|
|
79
|
+
raise KeyError(f"{key} is not a field of {self.__class__.__name__}")
|
|
80
|
+
setattr(self, key, value)
|
|
81
|
+
|
|
82
|
+
def __repr__(self) -> str:
|
|
83
|
+
"""Minimal repr where None values have their keys omitted."""
|
|
84
|
+
items = [f"{k}={repr(v)}" for (k, v) in self.present_values_as_dict().items()]
|
|
85
|
+
return f"{self.__class__.__name__}({', '.join(items)})"
|
|
86
|
+
|
|
87
|
+
def __setattr__(self, prop, val):
|
|
88
|
+
"""Runs registered validators before setting fields."""
|
|
89
|
+
if prop in self.VALIDATED_FIELD_NAMES:
|
|
90
|
+
validate = getattr(self, f"validate_{prop}")
|
|
91
|
+
validate(val)
|
|
92
|
+
super().__setattr__(prop, val)
|
|
93
|
+
|
|
94
|
+
def validate_schema(self, value: Optional[str]):
|
|
95
|
+
if (
|
|
96
|
+
value
|
|
97
|
+
and not (value.startswith('"') and value.endswith('"'))
|
|
98
|
+
# if schema is fully qualified name (db.schema)
|
|
99
|
+
and schema_pattern.match(value)
|
|
100
|
+
):
|
|
101
|
+
raise InvalidSchemaError(value)
|
|
102
|
+
|
|
103
|
+
def validate_and_complete(self):
|
|
104
|
+
"""
|
|
105
|
+
Ensure we can create a connection from this context.
|
|
106
|
+
"""
|
|
107
|
+
if not self.temporary_connection and not self.connection_name:
|
|
108
|
+
self.connection_name = get_default_connection_name()
|
|
109
|
+
|
|
110
|
+
def build_connection(self):
|
|
111
|
+
from snowflake.cli._app.snow_connector import connect_to_snowflake
|
|
112
|
+
|
|
113
|
+
# Ignore warnings about bad owner or permissions on Windows
|
|
114
|
+
# Telemetry omit our warning filter from config.py
|
|
115
|
+
if IS_WINDOWS:
|
|
116
|
+
warnings.filterwarnings(
|
|
117
|
+
action="ignore",
|
|
118
|
+
message="Bad owner or permissions.*",
|
|
119
|
+
module="snowflake.connector.config_manager",
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
return connect_to_snowflake(**self.present_values_as_dict())
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class OpenConnectionCache:
|
|
126
|
+
"""
|
|
127
|
+
A connection cache that transparently manages SnowflakeConnection objects
|
|
128
|
+
and is keyed by ConnectionContext objects, e.g. cache[ctx].execute_string(...).
|
|
129
|
+
Connections are automatically closed after CONNECTION_CLEANUP_SEC, but
|
|
130
|
+
are guaranteed to be open (if config is valid) when returned by the cache.
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
connections: dict[str, SnowflakeConnection]
|
|
134
|
+
cleanup_futures: dict[str, asyncio.TimerHandle]
|
|
135
|
+
|
|
136
|
+
CONNECTION_CLEANUP_SEC: float = 10.0 * 60
|
|
137
|
+
"""Connections are closed this many seconds after the last time they are accessed."""
|
|
138
|
+
|
|
139
|
+
def __init__(self):
|
|
140
|
+
self.connections = {}
|
|
141
|
+
self.cleanup_futures = {}
|
|
142
|
+
|
|
143
|
+
def __getitem__(self, ctx):
|
|
144
|
+
if not isinstance(ctx, ConnectionContext):
|
|
145
|
+
raise ValueError(
|
|
146
|
+
f"Expected key to be ConnectionContext but got {repr(ctx)}"
|
|
147
|
+
)
|
|
148
|
+
key = repr(ctx)
|
|
149
|
+
if not self._has_open_connection(key):
|
|
150
|
+
self._insert(key, ctx)
|
|
151
|
+
self._touch(key)
|
|
152
|
+
return self.connections[key]
|
|
153
|
+
|
|
154
|
+
def clear(self):
|
|
155
|
+
"""Closes all connections and resets the cache to its initial state."""
|
|
156
|
+
connection_keys = list(self.connections.keys())
|
|
157
|
+
for key in connection_keys:
|
|
158
|
+
self._cleanup(key)
|
|
159
|
+
|
|
160
|
+
# if any orphaned futures still exist, clean them up too
|
|
161
|
+
for future in self.cleanup_futures.values():
|
|
162
|
+
future.cancel()
|
|
163
|
+
self.cleanup_futures.clear()
|
|
164
|
+
|
|
165
|
+
def _has_open_connection(self, key: str):
|
|
166
|
+
return key in self.connections
|
|
167
|
+
|
|
168
|
+
def _insert(self, key: str, ctx: ConnectionContext):
|
|
169
|
+
try:
|
|
170
|
+
# N.B. build_connection ultimately calls connect_to_snowflake, which
|
|
171
|
+
# interpolates in connection dicts (from config) and environment variables.
|
|
172
|
+
# This means that we could return a stale (incorrect) connection for the
|
|
173
|
+
# given ConnectionContext if get_env_value or get_connection_dict would
|
|
174
|
+
# have returned different values (i.e. env / config have changed).
|
|
175
|
+
self.connections[key] = ctx.build_connection()
|
|
176
|
+
except Exception:
|
|
177
|
+
logger.debug(
|
|
178
|
+
"ConnectionCache: failed to connect using %s; not caching.", key
|
|
179
|
+
)
|
|
180
|
+
raise
|
|
181
|
+
|
|
182
|
+
def _cancel_cleanup_future_if_exists(self, key: str):
|
|
183
|
+
if key in self.cleanup_futures:
|
|
184
|
+
self.cleanup_futures.pop(key).cancel()
|
|
185
|
+
|
|
186
|
+
def _touch(self, key: str):
|
|
187
|
+
"""
|
|
188
|
+
Extend the lifetime of the cached connection at the given key.
|
|
189
|
+
"""
|
|
190
|
+
loop = None
|
|
191
|
+
try:
|
|
192
|
+
loop = asyncio.get_event_loop()
|
|
193
|
+
except RuntimeError:
|
|
194
|
+
# Python 3.11+ will throw when no event loop;
|
|
195
|
+
# Python 3.10 will issue a DeprecationWarning and return None
|
|
196
|
+
pass
|
|
197
|
+
|
|
198
|
+
if not loop:
|
|
199
|
+
logger.debug(
|
|
200
|
+
"ConnectionCache: no event loop; connections will close at exit."
|
|
201
|
+
)
|
|
202
|
+
return
|
|
203
|
+
|
|
204
|
+
self._cancel_cleanup_future_if_exists(key)
|
|
205
|
+
self.cleanup_futures[key] = loop.call_later(
|
|
206
|
+
self.CONNECTION_CLEANUP_SEC, lambda: self._cleanup(key)
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
def _cleanup(self, key: str):
|
|
210
|
+
"""Closes the cached connection at the given key."""
|
|
211
|
+
if key not in self.connections:
|
|
212
|
+
logger.debug("Cleaning up connection %s, but not found in cache!", key)
|
|
213
|
+
|
|
214
|
+
# doesn't cancel in-flight async queries
|
|
215
|
+
self._cancel_cleanup_future_if_exists(key)
|
|
216
|
+
self.connections.pop(key).close()
|
snowflake/cli/api/console/abc.py
CHANGED
|
@@ -20,7 +20,10 @@ from typing import Callable, Iterator, Optional
|
|
|
20
20
|
|
|
21
21
|
from rich import print as rich_print
|
|
22
22
|
from rich.text import Text
|
|
23
|
-
from snowflake.cli.api.cli_global_context import
|
|
23
|
+
from snowflake.cli.api.cli_global_context import (
|
|
24
|
+
_CliGlobalContextAccess,
|
|
25
|
+
get_cli_context,
|
|
26
|
+
)
|
|
24
27
|
|
|
25
28
|
|
|
26
29
|
class AbstractConsole(ABC):
|
|
@@ -34,14 +37,16 @@ class AbstractConsole(ABC):
|
|
|
34
37
|
"""
|
|
35
38
|
|
|
36
39
|
_print_fn: Callable[[str], None]
|
|
37
|
-
_cli_context: _CliGlobalContextAccess
|
|
38
40
|
_in_phase: bool
|
|
39
41
|
|
|
40
42
|
def __init__(self):
|
|
41
43
|
super().__init__()
|
|
42
|
-
self._cli_context = cli_context
|
|
43
44
|
self._in_phase = False
|
|
44
45
|
|
|
46
|
+
@property
|
|
47
|
+
def _cli_context(self) -> _CliGlobalContextAccess:
|
|
48
|
+
return get_cli_context()
|
|
49
|
+
|
|
45
50
|
@property
|
|
46
51
|
def is_silent(self) -> bool:
|
|
47
52
|
"""Returns information whether intermediate output is muted."""
|
snowflake/cli/api/constants.py
CHANGED
|
@@ -77,3 +77,14 @@ VALID_SCOPES = ["database", "schema", "compute-pool"]
|
|
|
77
77
|
DEFAULT_SIZE_LIMIT_MB = 128
|
|
78
78
|
|
|
79
79
|
SF_REST_API_URL_PREFIX = "/api/v2"
|
|
80
|
+
|
|
81
|
+
PROJECT_TEMPLATE_VARIABLE_OPENING = "<%"
|
|
82
|
+
PROJECT_TEMPLATE_VARIABLE_CLOSING = "%>"
|
|
83
|
+
|
|
84
|
+
INIT_TEMPLATE_VARIABLE_OPENING = "<!"
|
|
85
|
+
INIT_TEMPLATE_VARIABLE_CLOSING = "!>"
|
|
86
|
+
|
|
87
|
+
SNOWPARK_SHARED_MIXIN = "snowpark_shared"
|
|
88
|
+
|
|
89
|
+
DEFAULT_ENV_FILE = "environment.yml"
|
|
90
|
+
DEFAULT_PAGES_DIR = "pages"
|