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,56 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import Generic, Type, TypeVar, get_args
|
|
3
|
+
|
|
4
|
+
from snowflake.cli._plugins.workspace.action_context import ActionContext
|
|
5
|
+
from snowflake.cli.api.sql_execution import SqlExecutor
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class EntityActions(str, Enum):
|
|
9
|
+
BUNDLE = "action_bundle"
|
|
10
|
+
DEPLOY = "action_deploy"
|
|
11
|
+
DROP = "action_drop"
|
|
12
|
+
VALIDATE = "action_validate"
|
|
13
|
+
|
|
14
|
+
VERSION_LIST = "action_version_list"
|
|
15
|
+
VERSION_CREATE = "action_version_create"
|
|
16
|
+
VERSION_DROP = "action_version_drop"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
T = TypeVar("T")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class EntityBase(Generic[T]):
|
|
23
|
+
"""
|
|
24
|
+
Base class for the fully-featured entity classes.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, entity_model: T):
|
|
28
|
+
self._entity_model = entity_model
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def get_entity_model_type(cls) -> Type[T]:
|
|
32
|
+
"""
|
|
33
|
+
Returns the generic model class specified in each entity class.
|
|
34
|
+
|
|
35
|
+
For example, calling ApplicationEntity.get_entity_model_type() will return the ApplicationEntityModel class.
|
|
36
|
+
"""
|
|
37
|
+
return get_args(cls.__orig_bases__[0])[0] # type: ignore[attr-defined]
|
|
38
|
+
|
|
39
|
+
def supports(self, action: EntityActions) -> bool:
|
|
40
|
+
"""
|
|
41
|
+
Checks whether this entity supports the given action. An entity is considered to support an action if it implements a method with the action name.
|
|
42
|
+
"""
|
|
43
|
+
return callable(getattr(self, action, None))
|
|
44
|
+
|
|
45
|
+
def perform(
|
|
46
|
+
self, action: EntityActions, action_ctx: ActionContext, *args, **kwargs
|
|
47
|
+
):
|
|
48
|
+
"""
|
|
49
|
+
Performs the requested action.
|
|
50
|
+
"""
|
|
51
|
+
return getattr(self, action)(action_ctx, *args, **kwargs)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_sql_executor() -> SqlExecutor:
|
|
55
|
+
"""Returns an SQL Executor that uses the connection from the current CLI context"""
|
|
56
|
+
return SqlExecutor()
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from textwrap import dedent
|
|
4
|
+
from typing import Any, List, NoReturn, Optional
|
|
5
|
+
|
|
6
|
+
import jinja2
|
|
7
|
+
from click import ClickException
|
|
8
|
+
from snowflake.cli._plugins.nativeapp.artifacts import (
|
|
9
|
+
BundleMap,
|
|
10
|
+
resolve_without_follow,
|
|
11
|
+
)
|
|
12
|
+
from snowflake.cli._plugins.nativeapp.exceptions import (
|
|
13
|
+
InvalidTemplateInFileError,
|
|
14
|
+
MissingScriptError,
|
|
15
|
+
)
|
|
16
|
+
from snowflake.cli._plugins.nativeapp.utils import verify_exists, verify_no_directories
|
|
17
|
+
from snowflake.cli._plugins.stage.diff import (
|
|
18
|
+
DiffResult,
|
|
19
|
+
StagePath,
|
|
20
|
+
compute_stage_diff,
|
|
21
|
+
preserve_from_diff,
|
|
22
|
+
sync_local_diff_with_stage,
|
|
23
|
+
to_stage_path,
|
|
24
|
+
)
|
|
25
|
+
from snowflake.cli._plugins.stage.utils import print_diff_to_console
|
|
26
|
+
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
27
|
+
from snowflake.cli.api.console.abc import AbstractConsole
|
|
28
|
+
from snowflake.cli.api.entities.common import get_sql_executor
|
|
29
|
+
from snowflake.cli.api.errno import (
|
|
30
|
+
DOES_NOT_EXIST_OR_CANNOT_BE_PERFORMED,
|
|
31
|
+
NO_WAREHOUSE_SELECTED_IN_SESSION,
|
|
32
|
+
)
|
|
33
|
+
from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
|
|
34
|
+
from snowflake.cli.api.metrics import CLICounterField
|
|
35
|
+
from snowflake.cli.api.project.schemas.entities.common import PostDeployHook
|
|
36
|
+
from snowflake.cli.api.rendering.sql_templates import (
|
|
37
|
+
choose_sql_jinja_env_based_on_template_syntax,
|
|
38
|
+
)
|
|
39
|
+
from snowflake.cli.api.secure_path import UNLIMITED, SecurePath
|
|
40
|
+
from snowflake.connector import ProgrammingError
|
|
41
|
+
from snowflake.connector.cursor import SnowflakeCursor
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def generic_sql_error_handler(
|
|
45
|
+
err: ProgrammingError, role: Optional[str] = None, warehouse: Optional[str] = None
|
|
46
|
+
) -> NoReturn:
|
|
47
|
+
# Potential refactor: If moving away from Python 3.8 and 3.9 to >= 3.10, use match ... case
|
|
48
|
+
if err.errno == DOES_NOT_EXIST_OR_CANNOT_BE_PERFORMED:
|
|
49
|
+
raise ProgrammingError(
|
|
50
|
+
msg=dedent(
|
|
51
|
+
f"""\
|
|
52
|
+
Received error message '{err.msg}' while executing SQL statement.
|
|
53
|
+
'{role}' may not have access to warehouse '{warehouse}'.
|
|
54
|
+
Please grant usage privilege on warehouse to this role.
|
|
55
|
+
"""
|
|
56
|
+
),
|
|
57
|
+
errno=err.errno,
|
|
58
|
+
)
|
|
59
|
+
elif err.errno == NO_WAREHOUSE_SELECTED_IN_SESSION:
|
|
60
|
+
raise ProgrammingError(
|
|
61
|
+
msg=dedent(
|
|
62
|
+
f"""\
|
|
63
|
+
Received error message '{err.msg}' while executing SQL statement.
|
|
64
|
+
Please provide a warehouse for the active session role in your project definition file, config.toml file, or via command line.
|
|
65
|
+
"""
|
|
66
|
+
),
|
|
67
|
+
errno=err.errno,
|
|
68
|
+
)
|
|
69
|
+
elif "does not exist or not authorized" in err.msg:
|
|
70
|
+
raise ProgrammingError(
|
|
71
|
+
msg=dedent(
|
|
72
|
+
f"""\
|
|
73
|
+
Received error message '{err.msg}' while executing SQL statement.
|
|
74
|
+
Please check the name of the resource you are trying to query or the permissions of the role you are using to run the query.
|
|
75
|
+
"""
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
raise err
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _get_stage_paths_to_sync(
|
|
82
|
+
local_paths_to_sync: List[Path], deploy_root: Path
|
|
83
|
+
) -> List[StagePath]:
|
|
84
|
+
"""
|
|
85
|
+
Takes a list of paths (files and directories), returning a list of all files recursively relative to the deploy root.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
stage_paths = []
|
|
89
|
+
for path in local_paths_to_sync:
|
|
90
|
+
if path.is_dir():
|
|
91
|
+
for current_dir, _dirs, files in os.walk(path):
|
|
92
|
+
for file in files:
|
|
93
|
+
deploy_path = Path(current_dir, file).relative_to(deploy_root)
|
|
94
|
+
stage_paths.append(to_stage_path(deploy_path))
|
|
95
|
+
else:
|
|
96
|
+
stage_paths.append(to_stage_path(path.relative_to(deploy_root)))
|
|
97
|
+
return stage_paths
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def sync_deploy_root_with_stage(
|
|
101
|
+
console: AbstractConsole,
|
|
102
|
+
deploy_root: Path,
|
|
103
|
+
package_name: str,
|
|
104
|
+
stage_schema: str,
|
|
105
|
+
bundle_map: BundleMap,
|
|
106
|
+
role: str,
|
|
107
|
+
prune: bool,
|
|
108
|
+
recursive: bool,
|
|
109
|
+
stage_fqn: str,
|
|
110
|
+
local_paths_to_sync: List[Path] | None = None,
|
|
111
|
+
print_diff: bool = True,
|
|
112
|
+
) -> DiffResult:
|
|
113
|
+
"""
|
|
114
|
+
Ensures that the files on our remote stage match the artifacts we have in
|
|
115
|
+
the local filesystem.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
bundle_map (BundleMap): The artifact mapping computed by the `build_bundle` function.
|
|
119
|
+
role (str): The name of the role to use for queries and commands.
|
|
120
|
+
prune (bool): Whether to prune artifacts from the stage that don't exist locally.
|
|
121
|
+
recursive (bool): Whether to traverse directories recursively.
|
|
122
|
+
stage_fqn (str): The name of the stage to diff against and upload to.
|
|
123
|
+
local_paths_to_sync (List[Path], optional): List of local paths to sync. Defaults to None to sync all
|
|
124
|
+
local paths. Note that providing an empty list here is equivalent to None.
|
|
125
|
+
print_diff (bool): Whether to print the diff between the local files and the remote stage. Defaults to True
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
A `DiffResult` instance describing the changes that were performed.
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
sql_executor = get_sql_executor()
|
|
132
|
+
# Does a stage already exist within the application package, or we need to create one?
|
|
133
|
+
# Using "if not exists" should take care of either case.
|
|
134
|
+
console.step(
|
|
135
|
+
f"Checking if stage {stage_fqn} exists, or creating a new one if none exists."
|
|
136
|
+
)
|
|
137
|
+
with sql_executor.use_role(role):
|
|
138
|
+
sql_executor.execute_query(
|
|
139
|
+
f"create schema if not exists {package_name}.{stage_schema}"
|
|
140
|
+
)
|
|
141
|
+
sql_executor.execute_query(
|
|
142
|
+
f"""
|
|
143
|
+
create stage if not exists {stage_fqn}
|
|
144
|
+
encryption = (TYPE = 'SNOWFLAKE_SSE')
|
|
145
|
+
DIRECTORY = (ENABLE = TRUE)"""
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# Perform a diff operation and display results to the user for informational purposes
|
|
149
|
+
if print_diff:
|
|
150
|
+
console.step(
|
|
151
|
+
"Performing a diff between the Snowflake stage and your local deploy_root ('%s') directory."
|
|
152
|
+
% deploy_root.resolve()
|
|
153
|
+
)
|
|
154
|
+
diff: DiffResult = compute_stage_diff(deploy_root, stage_fqn)
|
|
155
|
+
|
|
156
|
+
if local_paths_to_sync:
|
|
157
|
+
# Deploying specific files/directories
|
|
158
|
+
resolved_paths_to_sync = [
|
|
159
|
+
resolve_without_follow(p) for p in local_paths_to_sync
|
|
160
|
+
]
|
|
161
|
+
if not recursive:
|
|
162
|
+
verify_no_directories(resolved_paths_to_sync)
|
|
163
|
+
|
|
164
|
+
deploy_paths_to_sync = []
|
|
165
|
+
for resolved_path in resolved_paths_to_sync:
|
|
166
|
+
verify_exists(resolved_path)
|
|
167
|
+
deploy_paths = bundle_map.to_deploy_paths(resolved_path)
|
|
168
|
+
if not deploy_paths:
|
|
169
|
+
if resolved_path.is_dir() and recursive:
|
|
170
|
+
# No direct artifact mapping found for this path. Check to see
|
|
171
|
+
# if there are subpaths of this directory that are matches. We
|
|
172
|
+
# loop over sources because it's likely a much smaller list
|
|
173
|
+
# than the project directory.
|
|
174
|
+
for src in bundle_map.all_sources(absolute=True):
|
|
175
|
+
if resolved_path in src.parents:
|
|
176
|
+
# There is a source that contains this path, get its dest path(s)
|
|
177
|
+
deploy_paths.extend(bundle_map.to_deploy_paths(src))
|
|
178
|
+
|
|
179
|
+
if not deploy_paths:
|
|
180
|
+
raise ClickException(f"No artifact found for {resolved_path}")
|
|
181
|
+
deploy_paths_to_sync.extend(deploy_paths)
|
|
182
|
+
|
|
183
|
+
stage_paths_to_sync = _get_stage_paths_to_sync(
|
|
184
|
+
deploy_paths_to_sync, resolve_without_follow(deploy_root)
|
|
185
|
+
)
|
|
186
|
+
diff = preserve_from_diff(diff, stage_paths_to_sync)
|
|
187
|
+
else:
|
|
188
|
+
# Full deploy
|
|
189
|
+
if not recursive:
|
|
190
|
+
verify_no_directories(deploy_root.resolve().iterdir())
|
|
191
|
+
|
|
192
|
+
if not prune:
|
|
193
|
+
files_not_removed = [str(path) for path in diff.only_on_stage]
|
|
194
|
+
diff.only_on_stage = []
|
|
195
|
+
|
|
196
|
+
if len(files_not_removed) > 0:
|
|
197
|
+
files_not_removed_str = "\n".join(files_not_removed)
|
|
198
|
+
console.warning(
|
|
199
|
+
f"The following files exist only on the stage:\n{files_not_removed_str}\n\nUse the --prune flag to delete them from the stage."
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
if print_diff:
|
|
203
|
+
print_diff_to_console(diff, bundle_map)
|
|
204
|
+
|
|
205
|
+
# Upload diff-ed files to application package stage
|
|
206
|
+
if diff.has_changes():
|
|
207
|
+
console.step(
|
|
208
|
+
"Updating the Snowflake stage from your local %s directory."
|
|
209
|
+
% deploy_root.resolve(),
|
|
210
|
+
)
|
|
211
|
+
sync_local_diff_with_stage(
|
|
212
|
+
role=role,
|
|
213
|
+
deploy_root_path=deploy_root,
|
|
214
|
+
diff_result=diff,
|
|
215
|
+
stage_fqn=stage_fqn,
|
|
216
|
+
)
|
|
217
|
+
return diff
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _execute_sql_script(
|
|
221
|
+
script_content: str,
|
|
222
|
+
database_name: Optional[str] = None,
|
|
223
|
+
) -> None:
|
|
224
|
+
"""
|
|
225
|
+
Executing the provided SQL script content.
|
|
226
|
+
This assumes that a relevant warehouse is already active.
|
|
227
|
+
If database_name is passed in, it will be used first.
|
|
228
|
+
"""
|
|
229
|
+
try:
|
|
230
|
+
sql_executor = get_sql_executor()
|
|
231
|
+
if database_name is not None:
|
|
232
|
+
sql_executor.execute_query(f"use database {database_name}")
|
|
233
|
+
sql_executor.execute_queries(script_content)
|
|
234
|
+
except ProgrammingError as err:
|
|
235
|
+
generic_sql_error_handler(err)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def execute_post_deploy_hooks(
|
|
239
|
+
console: AbstractConsole,
|
|
240
|
+
project_root: Path,
|
|
241
|
+
post_deploy_hooks: Optional[List[PostDeployHook]],
|
|
242
|
+
deployed_object_type: str,
|
|
243
|
+
database_name: str,
|
|
244
|
+
) -> None:
|
|
245
|
+
"""
|
|
246
|
+
Executes post-deploy hooks for the given object type.
|
|
247
|
+
While executing SQL post deploy hooks, it first switches to the database provided in the input.
|
|
248
|
+
All post deploy scripts templates will first be expanded using the global template context.
|
|
249
|
+
"""
|
|
250
|
+
if not post_deploy_hooks:
|
|
251
|
+
return
|
|
252
|
+
|
|
253
|
+
get_cli_context().metrics.set_counter(CLICounterField.POST_DEPLOY_SCRIPTS, 1)
|
|
254
|
+
|
|
255
|
+
with console.phase(f"Executing {deployed_object_type} post-deploy actions"):
|
|
256
|
+
sql_scripts_paths = []
|
|
257
|
+
for hook in post_deploy_hooks:
|
|
258
|
+
if hook.sql_script:
|
|
259
|
+
sql_scripts_paths.append(hook.sql_script)
|
|
260
|
+
else:
|
|
261
|
+
raise ValueError(
|
|
262
|
+
f"Unsupported {deployed_object_type} post-deploy hook type: {hook}"
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
scripts_content_list = render_script_templates(
|
|
266
|
+
project_root,
|
|
267
|
+
get_cli_context().template_context,
|
|
268
|
+
sql_scripts_paths,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
for index, sql_script_path in enumerate(sql_scripts_paths):
|
|
272
|
+
console.step(f"Executing SQL script: {sql_script_path}")
|
|
273
|
+
_execute_sql_script(
|
|
274
|
+
script_content=scripts_content_list[index],
|
|
275
|
+
database_name=database_name,
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def render_script_templates(
|
|
280
|
+
project_root: Path,
|
|
281
|
+
jinja_context: dict[str, Any],
|
|
282
|
+
scripts: List[str],
|
|
283
|
+
override_env: Optional[jinja2.Environment] = None,
|
|
284
|
+
) -> List[str]:
|
|
285
|
+
"""
|
|
286
|
+
Input:
|
|
287
|
+
- project_root: path to project root
|
|
288
|
+
- jinja_context: a dictionary with the jinja context
|
|
289
|
+
- scripts: list of script paths relative to the project root
|
|
290
|
+
- override_env: optional jinja environment to use for rendering,
|
|
291
|
+
if not provided, the environment will be chosen based on the template syntax
|
|
292
|
+
Returns:
|
|
293
|
+
- List of rendered scripts content
|
|
294
|
+
Size of the return list is the same as the size of the input scripts list.
|
|
295
|
+
"""
|
|
296
|
+
return [
|
|
297
|
+
render_script_template(project_root, jinja_context, script, override_env)
|
|
298
|
+
for script in scripts
|
|
299
|
+
]
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def render_script_template(
|
|
303
|
+
project_root: Path,
|
|
304
|
+
jinja_context: dict[str, Any],
|
|
305
|
+
script: str,
|
|
306
|
+
override_env: Optional[jinja2.Environment] = None,
|
|
307
|
+
) -> str:
|
|
308
|
+
script_full_path = SecurePath(project_root) / script
|
|
309
|
+
try:
|
|
310
|
+
template_content = script_full_path.read_text(file_size_limit_mb=UNLIMITED)
|
|
311
|
+
env = override_env or choose_sql_jinja_env_based_on_template_syntax(
|
|
312
|
+
template_content, reference_name=script
|
|
313
|
+
)
|
|
314
|
+
return env.from_string(template_content).render(jinja_context)
|
|
315
|
+
|
|
316
|
+
except FileNotFoundError as e:
|
|
317
|
+
raise MissingScriptError(script) from e
|
|
318
|
+
|
|
319
|
+
except jinja2.TemplateSyntaxError as e:
|
|
320
|
+
raise InvalidTemplateInFileError(script, e, e.lineno) from e
|
|
321
|
+
|
|
322
|
+
except jinja2.UndefinedError as e:
|
|
323
|
+
raise InvalidTemplateInFileError(script, e) from e
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def validation_item_to_str(item: dict[str, str | int]):
|
|
327
|
+
s = item["message"]
|
|
328
|
+
if item["errorCode"]:
|
|
329
|
+
s = f"{s} (error code {item['errorCode']})"
|
|
330
|
+
return s
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def drop_generic_object(
|
|
334
|
+
console: AbstractConsole,
|
|
335
|
+
object_type: str,
|
|
336
|
+
object_name: str,
|
|
337
|
+
role: str,
|
|
338
|
+
cascade: bool = False,
|
|
339
|
+
):
|
|
340
|
+
"""
|
|
341
|
+
Drop object using the given role.
|
|
342
|
+
"""
|
|
343
|
+
sql_executor = get_sql_executor()
|
|
344
|
+
with sql_executor.use_role(role):
|
|
345
|
+
console.step(f"Dropping {object_type} {object_name} now.")
|
|
346
|
+
drop_query = f"drop {object_type} {object_name}"
|
|
347
|
+
if cascade:
|
|
348
|
+
drop_query += " cascade"
|
|
349
|
+
try:
|
|
350
|
+
sql_executor.execute_query(drop_query)
|
|
351
|
+
except:
|
|
352
|
+
raise SnowflakeSQLExecutionError(drop_query)
|
|
353
|
+
|
|
354
|
+
console.message(f"Dropped {object_type} {object_name} successfully.")
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def print_messages(
|
|
358
|
+
console: AbstractConsole, create_or_upgrade_cursor: Optional[SnowflakeCursor]
|
|
359
|
+
):
|
|
360
|
+
"""
|
|
361
|
+
Shows messages in the console returned by the CREATE or UPGRADE
|
|
362
|
+
APPLICATION command.
|
|
363
|
+
"""
|
|
364
|
+
if not create_or_upgrade_cursor:
|
|
365
|
+
return
|
|
366
|
+
|
|
367
|
+
messages = [row[0] for row in create_or_upgrade_cursor.fetchall()]
|
|
368
|
+
for message in messages:
|
|
369
|
+
console.warning(message)
|
|
370
|
+
console.message("")
|
snowflake/cli/api/errno.py
CHANGED
snowflake/cli/api/exceptions.py
CHANGED
|
@@ -17,8 +17,9 @@ from __future__ import annotations
|
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
from typing import Optional
|
|
19
19
|
|
|
20
|
-
from click.exceptions import ClickException
|
|
20
|
+
from click.exceptions import ClickException, UsageError
|
|
21
21
|
from snowflake.cli.api.constants import ObjectType
|
|
22
|
+
from snowflake.connector.compat import IS_WINDOWS
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
class EnvironmentVariableNotFoundError(ClickException):
|
|
@@ -101,9 +102,16 @@ class ObjectAlreadyExistsError(ClickException):
|
|
|
101
102
|
|
|
102
103
|
|
|
103
104
|
class NoProjectDefinitionError(ClickException):
|
|
104
|
-
def __init__(self, project_type: str,
|
|
105
|
+
def __init__(self, project_type: str, project_root: str | Path):
|
|
105
106
|
super().__init__(
|
|
106
|
-
f"No {project_type} project definition found in {
|
|
107
|
+
f"No {project_type} project definition found in {project_root}"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class InvalidProjectDefinitionVersionError(ClickException):
|
|
112
|
+
def __init__(self, expected_version: str, actual_version: str):
|
|
113
|
+
super().__init__(
|
|
114
|
+
f"This command only supports definition version {expected_version}, got {actual_version}."
|
|
107
115
|
)
|
|
108
116
|
|
|
109
117
|
|
|
@@ -133,9 +141,18 @@ class DirectoryIsNotEmptyError(ClickException):
|
|
|
133
141
|
|
|
134
142
|
class ConfigFileTooWidePermissionsError(ClickException):
|
|
135
143
|
def __init__(self, path: Path):
|
|
136
|
-
|
|
137
|
-
f'
|
|
144
|
+
change_permissons_command = (
|
|
145
|
+
f'icacls "{path}" /deny <USER_ID>:F'
|
|
146
|
+
if IS_WINDOWS
|
|
147
|
+
else f'chmod 0600 "{path}"'
|
|
138
148
|
)
|
|
149
|
+
msg = f"Configuration file {path} has too wide permissions, run `{change_permissons_command}`."
|
|
150
|
+
if IS_WINDOWS:
|
|
151
|
+
msg += (
|
|
152
|
+
f'\nTo check which users have access to the file run `icacls "{path}"`.'
|
|
153
|
+
"Run the above command for all users except you and administrators."
|
|
154
|
+
)
|
|
155
|
+
super().__init__(msg)
|
|
139
156
|
|
|
140
157
|
|
|
141
158
|
class DatabaseNotProvidedError(ClickException):
|
|
@@ -162,3 +179,12 @@ class FQNInconsistencyError(ClickException):
|
|
|
162
179
|
super().__init__(
|
|
163
180
|
f"{part.capitalize()} provided but name '{name}' is fully qualified name."
|
|
164
181
|
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class IncompatibleParametersError(UsageError):
|
|
185
|
+
def __init__(self, options: list[str]):
|
|
186
|
+
options_with_quotes = [f"'{option}'" for option in options]
|
|
187
|
+
comma_separated_options = ", ".join(options_with_quotes[:-1])
|
|
188
|
+
super().__init__(
|
|
189
|
+
f"Parameters {comma_separated_options} and {options_with_quotes[-1]} are incompatible and cannot be used simultaneously."
|
|
190
|
+
)
|
snowflake/cli/api/identifiers.py
CHANGED
|
@@ -17,9 +17,10 @@ from __future__ import annotations
|
|
|
17
17
|
import re
|
|
18
18
|
|
|
19
19
|
from click import ClickException
|
|
20
|
-
from snowflake.cli.api.cli_global_context import cli_context
|
|
21
20
|
from snowflake.cli.api.exceptions import FQNInconsistencyError, FQNNameError
|
|
22
|
-
from snowflake.cli.api.project.schemas.identifier_model import
|
|
21
|
+
from snowflake.cli.api.project.schemas.v1.identifier_model import (
|
|
22
|
+
ObjectIdentifierBaseModel,
|
|
23
|
+
)
|
|
23
24
|
from snowflake.cli.api.project.util import VALID_IDENTIFIER_REGEX, identifier_for_url
|
|
24
25
|
|
|
25
26
|
|
|
@@ -123,11 +124,11 @@ class FQN:
|
|
|
123
124
|
return cls.from_string(name)
|
|
124
125
|
|
|
125
126
|
@classmethod
|
|
126
|
-
def
|
|
127
|
+
def from_identifier_model_v1(cls, model: ObjectIdentifierBaseModel) -> "FQN":
|
|
127
128
|
"""Create an instance from object model."""
|
|
128
129
|
if not isinstance(model, ObjectIdentifierBaseModel):
|
|
129
130
|
raise ClickException(
|
|
130
|
-
f"Expected {type(ObjectIdentifierBaseModel)}, got {model}."
|
|
131
|
+
f"Expected {type(ObjectIdentifierBaseModel).__name__}, got {model}."
|
|
131
132
|
)
|
|
132
133
|
|
|
133
134
|
fqn = cls.from_string(model.name)
|
|
@@ -139,6 +140,23 @@ class FQN:
|
|
|
139
140
|
|
|
140
141
|
return fqn.set_database(model.database).set_schema(model.schema_name)
|
|
141
142
|
|
|
143
|
+
@classmethod
|
|
144
|
+
def from_identifier_model_v2(cls, model) -> "FQN":
|
|
145
|
+
"""Create an instance from object model."""
|
|
146
|
+
from snowflake.cli.api.project.schemas.entities.common import Identifier
|
|
147
|
+
|
|
148
|
+
if not isinstance(model, Identifier):
|
|
149
|
+
raise ClickException(f"Expected {type(Identifier).__name__}, got {model}.")
|
|
150
|
+
|
|
151
|
+
fqn = cls.from_string(model.name)
|
|
152
|
+
|
|
153
|
+
if fqn.database and model.database:
|
|
154
|
+
raise FQNInconsistencyError("database", model.name)
|
|
155
|
+
if fqn.schema and model.schema_:
|
|
156
|
+
raise FQNInconsistencyError("schema", model.name)
|
|
157
|
+
|
|
158
|
+
return fqn.set_database(model.database).set_schema(model.schema_)
|
|
159
|
+
|
|
142
160
|
def set_database(self, database: str | None) -> "FQN":
|
|
143
161
|
if database:
|
|
144
162
|
self._database = database
|
|
@@ -164,4 +182,9 @@ class FQN:
|
|
|
164
182
|
|
|
165
183
|
def using_context(self) -> "FQN":
|
|
166
184
|
"""Update the instance with database and schema from connection in current cli context."""
|
|
167
|
-
|
|
185
|
+
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
186
|
+
|
|
187
|
+
return self.using_connection(get_cli_context().connection)
|
|
188
|
+
|
|
189
|
+
def to_dict(self) -> dict:
|
|
190
|
+
return {"name": self.name, "schema": self.schema, "database": self.database}
|
|
@@ -0,0 +1,92 @@
|
|
|
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 typing import Dict, Optional
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class _TypePrefix:
|
|
19
|
+
FEATURES = "features"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class _DomainPrefix:
|
|
23
|
+
GLOBAL = "global"
|
|
24
|
+
APP = "app"
|
|
25
|
+
SQL = "sql"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class CLICounterField:
|
|
29
|
+
"""
|
|
30
|
+
for each counter field we're adopting a convention of
|
|
31
|
+
<type>.<domain>.<name>
|
|
32
|
+
for example, if we're tracking a global feature, then the field name would be
|
|
33
|
+
features.global.feature_name
|
|
34
|
+
|
|
35
|
+
The metrics API is implemented to be generic, but we are adopting a convention
|
|
36
|
+
for feature tracking with the following model for a given command execution:
|
|
37
|
+
* counter not present -> feature is not available
|
|
38
|
+
* counter == 0 -> feature is available, but not used
|
|
39
|
+
* counter == 1 -> feature is used
|
|
40
|
+
this makes it easy to compute percentages for feature dashboards in Snowsight
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
TEMPLATES_PROCESSOR = (
|
|
44
|
+
f"{_TypePrefix.FEATURES}.{_DomainPrefix.GLOBAL}.templates_processor"
|
|
45
|
+
)
|
|
46
|
+
SQL_TEMPLATES = f"{_TypePrefix.FEATURES}.{_DomainPrefix.SQL}.sql_templates"
|
|
47
|
+
PDF_TEMPLATES = f"{_TypePrefix.FEATURES}.{_DomainPrefix.GLOBAL}.pdf_templates"
|
|
48
|
+
SNOWPARK_PROCESSOR = (
|
|
49
|
+
f"{_TypePrefix.FEATURES}.{_DomainPrefix.APP}.snowpark_processor"
|
|
50
|
+
)
|
|
51
|
+
POST_DEPLOY_SCRIPTS = (
|
|
52
|
+
f"{_TypePrefix.FEATURES}.{_DomainPrefix.APP}.post_deploy_scripts"
|
|
53
|
+
)
|
|
54
|
+
PACKAGE_SCRIPTS = f"{_TypePrefix.FEATURES}.{_DomainPrefix.APP}.package_scripts"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class CLIMetrics:
|
|
58
|
+
"""
|
|
59
|
+
Class to track various metrics across the execution of a command
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self):
|
|
63
|
+
self._counters: Dict[str, int] = {}
|
|
64
|
+
|
|
65
|
+
def __eq__(self, other):
|
|
66
|
+
if isinstance(other, CLIMetrics):
|
|
67
|
+
return self._counters == other._counters
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
def get_counter(self, name: str) -> Optional[int]:
|
|
71
|
+
return self._counters.get(name)
|
|
72
|
+
|
|
73
|
+
def set_counter(self, name: str, value: int) -> None:
|
|
74
|
+
self._counters[name] = value
|
|
75
|
+
|
|
76
|
+
def set_counter_default(self, name: str, value: int) -> None:
|
|
77
|
+
"""
|
|
78
|
+
sets the counter if it does not already exist
|
|
79
|
+
"""
|
|
80
|
+
if name not in self._counters:
|
|
81
|
+
self.set_counter(name, value)
|
|
82
|
+
|
|
83
|
+
def increment_counter(self, name: str, value: int = 1) -> None:
|
|
84
|
+
if name not in self._counters:
|
|
85
|
+
self.set_counter(name, value)
|
|
86
|
+
else:
|
|
87
|
+
self._counters[name] += value
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def counters(self) -> Dict[str, int]:
|
|
91
|
+
# return a copy of the original dict to avoid mutating the original
|
|
92
|
+
return self._counters.copy()
|