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
|
@@ -1,823 +0,0 @@
|
|
|
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 json
|
|
18
|
-
import os
|
|
19
|
-
from abc import ABC, abstractmethod
|
|
20
|
-
from contextlib import contextmanager
|
|
21
|
-
from functools import cached_property
|
|
22
|
-
from pathlib import Path
|
|
23
|
-
from textwrap import dedent
|
|
24
|
-
from typing import Any, List, NoReturn, Optional, TypedDict
|
|
25
|
-
|
|
26
|
-
import jinja2
|
|
27
|
-
from click import ClickException
|
|
28
|
-
from snowflake.cli.api.cli_global_context import cli_context
|
|
29
|
-
from snowflake.cli.api.console import cli_console as cc
|
|
30
|
-
from snowflake.cli.api.errno import (
|
|
31
|
-
DOES_NOT_EXIST_OR_CANNOT_BE_PERFORMED,
|
|
32
|
-
DOES_NOT_EXIST_OR_NOT_AUTHORIZED,
|
|
33
|
-
NO_WAREHOUSE_SELECTED_IN_SESSION,
|
|
34
|
-
)
|
|
35
|
-
from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
|
|
36
|
-
from snowflake.cli.api.project.schemas.native_app.application import (
|
|
37
|
-
PostDeployHook,
|
|
38
|
-
)
|
|
39
|
-
from snowflake.cli.api.project.schemas.native_app.native_app import NativeApp
|
|
40
|
-
from snowflake.cli.api.project.schemas.native_app.path_mapping import PathMapping
|
|
41
|
-
from snowflake.cli.api.project.util import (
|
|
42
|
-
identifier_for_url,
|
|
43
|
-
unquote_identifier,
|
|
44
|
-
)
|
|
45
|
-
from snowflake.cli.api.rendering.sql_templates import (
|
|
46
|
-
get_sql_cli_jinja_env,
|
|
47
|
-
)
|
|
48
|
-
from snowflake.cli.api.secure_path import UNLIMITED, SecurePath
|
|
49
|
-
from snowflake.cli.api.sql_execution import SqlExecutionMixin
|
|
50
|
-
from snowflake.cli.plugins.connection.util import make_snowsight_url
|
|
51
|
-
from snowflake.cli.plugins.nativeapp.artifacts import (
|
|
52
|
-
BundleMap,
|
|
53
|
-
build_bundle,
|
|
54
|
-
resolve_without_follow,
|
|
55
|
-
)
|
|
56
|
-
from snowflake.cli.plugins.nativeapp.codegen.compiler import (
|
|
57
|
-
NativeAppCompiler,
|
|
58
|
-
)
|
|
59
|
-
from snowflake.cli.plugins.nativeapp.constants import (
|
|
60
|
-
ALLOWED_SPECIAL_COMMENTS,
|
|
61
|
-
COMMENT_COL,
|
|
62
|
-
INTERNAL_DISTRIBUTION,
|
|
63
|
-
NAME_COL,
|
|
64
|
-
OWNER_COL,
|
|
65
|
-
SPECIAL_COMMENT,
|
|
66
|
-
)
|
|
67
|
-
from snowflake.cli.plugins.nativeapp.exceptions import (
|
|
68
|
-
ApplicationPackageAlreadyExistsError,
|
|
69
|
-
ApplicationPackageDoesNotExistError,
|
|
70
|
-
InvalidScriptError,
|
|
71
|
-
MissingScriptError,
|
|
72
|
-
NoEventTableForAccount,
|
|
73
|
-
SetupScriptFailedValidation,
|
|
74
|
-
UnexpectedOwnerError,
|
|
75
|
-
)
|
|
76
|
-
from snowflake.cli.plugins.nativeapp.project_model import (
|
|
77
|
-
NativeAppProjectModel,
|
|
78
|
-
)
|
|
79
|
-
from snowflake.cli.plugins.nativeapp.utils import verify_exists, verify_no_directories
|
|
80
|
-
from snowflake.cli.plugins.stage.diff import (
|
|
81
|
-
DiffResult,
|
|
82
|
-
StagePath,
|
|
83
|
-
compute_stage_diff,
|
|
84
|
-
preserve_from_diff,
|
|
85
|
-
print_diff_to_console,
|
|
86
|
-
sync_local_diff_with_stage,
|
|
87
|
-
to_stage_path,
|
|
88
|
-
)
|
|
89
|
-
from snowflake.cli.plugins.stage.manager import StageManager
|
|
90
|
-
from snowflake.connector import DictCursor, ProgrammingError
|
|
91
|
-
|
|
92
|
-
ApplicationOwnedObject = TypedDict("ApplicationOwnedObject", {"name": str, "type": str})
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def generic_sql_error_handler(
|
|
96
|
-
err: ProgrammingError, role: Optional[str] = None, warehouse: Optional[str] = None
|
|
97
|
-
) -> NoReturn:
|
|
98
|
-
# Potential refactor: If moving away from Python 3.8 and 3.9 to >= 3.10, use match ... case
|
|
99
|
-
if err.errno == DOES_NOT_EXIST_OR_CANNOT_BE_PERFORMED:
|
|
100
|
-
raise ProgrammingError(
|
|
101
|
-
msg=dedent(
|
|
102
|
-
f"""\
|
|
103
|
-
Received error message '{err.msg}' while executing SQL statement.
|
|
104
|
-
'{role}' may not have access to warehouse '{warehouse}'.
|
|
105
|
-
Please grant usage privilege on warehouse to this role.
|
|
106
|
-
"""
|
|
107
|
-
),
|
|
108
|
-
errno=err.errno,
|
|
109
|
-
)
|
|
110
|
-
elif err.errno == NO_WAREHOUSE_SELECTED_IN_SESSION:
|
|
111
|
-
raise ProgrammingError(
|
|
112
|
-
msg=dedent(
|
|
113
|
-
f"""\
|
|
114
|
-
Received error message '{err.msg}' while executing SQL statement.
|
|
115
|
-
Please provide a warehouse for the active session role in your project definition file, config.toml file, or via command line.
|
|
116
|
-
"""
|
|
117
|
-
),
|
|
118
|
-
errno=err.errno,
|
|
119
|
-
)
|
|
120
|
-
elif "does not exist or not authorized" in err.msg:
|
|
121
|
-
raise ProgrammingError(
|
|
122
|
-
msg=dedent(
|
|
123
|
-
f"""\
|
|
124
|
-
Received error message '{err.msg}' while executing SQL statement.
|
|
125
|
-
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.
|
|
126
|
-
"""
|
|
127
|
-
)
|
|
128
|
-
)
|
|
129
|
-
raise err
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
def ensure_correct_owner(row: dict, role: str, obj_name: str) -> None:
|
|
133
|
-
"""
|
|
134
|
-
Check if an object has the right owner role
|
|
135
|
-
"""
|
|
136
|
-
actual_owner = row[
|
|
137
|
-
OWNER_COL
|
|
138
|
-
].upper() # Because unquote_identifier() always returns uppercase str
|
|
139
|
-
if actual_owner != unquote_identifier(role):
|
|
140
|
-
raise UnexpectedOwnerError(obj_name, role, actual_owner)
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
def _get_stage_paths_to_sync(
|
|
144
|
-
local_paths_to_sync: List[Path], deploy_root: Path
|
|
145
|
-
) -> List[StagePath]:
|
|
146
|
-
"""
|
|
147
|
-
Takes a list of paths (files and directories), returning a list of all files recursively relative to the deploy root.
|
|
148
|
-
"""
|
|
149
|
-
|
|
150
|
-
stage_paths = []
|
|
151
|
-
for path in local_paths_to_sync:
|
|
152
|
-
if path.is_dir():
|
|
153
|
-
for current_dir, _dirs, files in os.walk(path):
|
|
154
|
-
for file in files:
|
|
155
|
-
deploy_path = Path(current_dir, file).relative_to(deploy_root)
|
|
156
|
-
stage_paths.append(to_stage_path(deploy_path))
|
|
157
|
-
else:
|
|
158
|
-
stage_paths.append(to_stage_path(path.relative_to(deploy_root)))
|
|
159
|
-
return stage_paths
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
class NativeAppCommandProcessor(ABC):
|
|
163
|
-
@abstractmethod
|
|
164
|
-
def process(self, *args, **kwargs):
|
|
165
|
-
pass
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
class NativeAppManager(SqlExecutionMixin):
|
|
169
|
-
"""
|
|
170
|
-
Base class with frequently used functionality already implemented and ready to be used by related subclasses.
|
|
171
|
-
"""
|
|
172
|
-
|
|
173
|
-
def __init__(self, project_definition: NativeApp, project_root: Path):
|
|
174
|
-
super().__init__()
|
|
175
|
-
self._na_project = NativeAppProjectModel(
|
|
176
|
-
project_definition=project_definition,
|
|
177
|
-
project_root=project_root,
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
@property
|
|
181
|
-
def na_project(self) -> NativeAppProjectModel:
|
|
182
|
-
return self._na_project
|
|
183
|
-
|
|
184
|
-
@property
|
|
185
|
-
def project_root(self) -> Path:
|
|
186
|
-
return self.na_project.project_root
|
|
187
|
-
|
|
188
|
-
@property
|
|
189
|
-
def definition(self) -> NativeApp:
|
|
190
|
-
return self.na_project.definition
|
|
191
|
-
|
|
192
|
-
@property
|
|
193
|
-
def artifacts(self) -> List[PathMapping]:
|
|
194
|
-
return self.na_project.artifacts
|
|
195
|
-
|
|
196
|
-
@property
|
|
197
|
-
def bundle_root(self) -> Path:
|
|
198
|
-
return self.na_project.bundle_root
|
|
199
|
-
|
|
200
|
-
@property
|
|
201
|
-
def deploy_root(self) -> Path:
|
|
202
|
-
return self.na_project.deploy_root
|
|
203
|
-
|
|
204
|
-
@property
|
|
205
|
-
def generated_root(self) -> Path:
|
|
206
|
-
return self.na_project.generated_root
|
|
207
|
-
|
|
208
|
-
@property
|
|
209
|
-
def package_scripts(self) -> List[str]:
|
|
210
|
-
return self.na_project.package_scripts
|
|
211
|
-
|
|
212
|
-
@property
|
|
213
|
-
def stage_fqn(self) -> str:
|
|
214
|
-
return self.na_project.stage_fqn
|
|
215
|
-
|
|
216
|
-
@property
|
|
217
|
-
def scratch_stage_fqn(self) -> str:
|
|
218
|
-
return self.na_project.scratch_stage_fqn
|
|
219
|
-
|
|
220
|
-
@property
|
|
221
|
-
def stage_schema(self) -> Optional[str]:
|
|
222
|
-
return self.na_project.stage_schema
|
|
223
|
-
|
|
224
|
-
@property
|
|
225
|
-
def package_warehouse(self) -> Optional[str]:
|
|
226
|
-
return self.na_project.package_warehouse
|
|
227
|
-
|
|
228
|
-
@contextmanager
|
|
229
|
-
def use_package_warehouse(self):
|
|
230
|
-
if self.package_warehouse:
|
|
231
|
-
with self.use_warehouse(self.package_warehouse):
|
|
232
|
-
yield
|
|
233
|
-
else:
|
|
234
|
-
raise ClickException(
|
|
235
|
-
dedent(
|
|
236
|
-
f"""\
|
|
237
|
-
Application package warehouse cannot be empty.
|
|
238
|
-
Please provide a value for it in your connection information or your project definition file.
|
|
239
|
-
"""
|
|
240
|
-
)
|
|
241
|
-
)
|
|
242
|
-
|
|
243
|
-
@property
|
|
244
|
-
def application_warehouse(self) -> Optional[str]:
|
|
245
|
-
return self.na_project.application_warehouse
|
|
246
|
-
|
|
247
|
-
@contextmanager
|
|
248
|
-
def use_application_warehouse(self):
|
|
249
|
-
if self.application_warehouse:
|
|
250
|
-
with self.use_warehouse(self.application_warehouse):
|
|
251
|
-
yield
|
|
252
|
-
else:
|
|
253
|
-
raise ClickException(
|
|
254
|
-
dedent(
|
|
255
|
-
f"""\
|
|
256
|
-
Application warehouse cannot be empty.
|
|
257
|
-
Please provide a value for it in your connection information or your project definition file.
|
|
258
|
-
"""
|
|
259
|
-
)
|
|
260
|
-
)
|
|
261
|
-
|
|
262
|
-
@property
|
|
263
|
-
def project_identifier(self) -> str:
|
|
264
|
-
return self.na_project.project_identifier
|
|
265
|
-
|
|
266
|
-
@property
|
|
267
|
-
def package_name(self) -> str:
|
|
268
|
-
return self.na_project.package_name
|
|
269
|
-
|
|
270
|
-
@property
|
|
271
|
-
def package_role(self) -> str:
|
|
272
|
-
return self.na_project.package_role
|
|
273
|
-
|
|
274
|
-
@property
|
|
275
|
-
def package_distribution(self) -> str:
|
|
276
|
-
return self.na_project.package_distribution
|
|
277
|
-
|
|
278
|
-
@property
|
|
279
|
-
def app_name(self) -> str:
|
|
280
|
-
return self.na_project.app_name
|
|
281
|
-
|
|
282
|
-
@property
|
|
283
|
-
def app_role(self) -> str:
|
|
284
|
-
return self.na_project.app_role
|
|
285
|
-
|
|
286
|
-
@property
|
|
287
|
-
def app_post_deploy_hooks(self) -> Optional[List[PostDeployHook]]:
|
|
288
|
-
return self.na_project.app_post_deploy_hooks
|
|
289
|
-
|
|
290
|
-
@property
|
|
291
|
-
def package_post_deploy_hooks(self) -> Optional[List[PostDeployHook]]:
|
|
292
|
-
return self.na_project.package_post_deploy_hooks
|
|
293
|
-
|
|
294
|
-
@property
|
|
295
|
-
def debug_mode(self) -> bool:
|
|
296
|
-
return self.na_project.debug_mode
|
|
297
|
-
|
|
298
|
-
@cached_property
|
|
299
|
-
def get_app_pkg_distribution_in_snowflake(self) -> str:
|
|
300
|
-
"""
|
|
301
|
-
Returns the 'distribution' attribute of a 'describe application package' SQL query, in lowercase.
|
|
302
|
-
"""
|
|
303
|
-
with self.use_role(self.package_role):
|
|
304
|
-
try:
|
|
305
|
-
desc_cursor = self._execute_query(
|
|
306
|
-
f"describe application package {self.package_name}"
|
|
307
|
-
)
|
|
308
|
-
except ProgrammingError as err:
|
|
309
|
-
generic_sql_error_handler(err)
|
|
310
|
-
|
|
311
|
-
if desc_cursor.rowcount is None or desc_cursor.rowcount == 0:
|
|
312
|
-
raise SnowflakeSQLExecutionError()
|
|
313
|
-
else:
|
|
314
|
-
for row in desc_cursor:
|
|
315
|
-
if row[0].lower() == "distribution":
|
|
316
|
-
return row[1].lower()
|
|
317
|
-
raise ProgrammingError(
|
|
318
|
-
msg=dedent(
|
|
319
|
-
f"""\
|
|
320
|
-
Could not find the 'distribution' attribute for application package {self.package_name} in the output of SQL query:
|
|
321
|
-
'describe application package {self.package_name}'
|
|
322
|
-
"""
|
|
323
|
-
)
|
|
324
|
-
)
|
|
325
|
-
|
|
326
|
-
@cached_property
|
|
327
|
-
def account_event_table(self) -> str:
|
|
328
|
-
query = "show parameters like 'event_table' in account"
|
|
329
|
-
results = self._execute_query(query, cursor_class=DictCursor)
|
|
330
|
-
return next((r["value"] for r in results if r["key"] == "EVENT_TABLE"), "")
|
|
331
|
-
|
|
332
|
-
def verify_project_distribution(
|
|
333
|
-
self, expected_distribution: Optional[str] = None
|
|
334
|
-
) -> bool:
|
|
335
|
-
"""
|
|
336
|
-
Returns true if the 'distribution' attribute of an existing application package in snowflake
|
|
337
|
-
is the same as the the attribute specified in project definition file.
|
|
338
|
-
"""
|
|
339
|
-
actual_distribution = (
|
|
340
|
-
expected_distribution
|
|
341
|
-
if expected_distribution
|
|
342
|
-
else self.get_app_pkg_distribution_in_snowflake
|
|
343
|
-
)
|
|
344
|
-
project_def_distribution = self.package_distribution.lower()
|
|
345
|
-
if actual_distribution != project_def_distribution:
|
|
346
|
-
cc.warning(
|
|
347
|
-
dedent(
|
|
348
|
-
f"""\
|
|
349
|
-
Application package {self.package_name} in your Snowflake account has distribution property {actual_distribution},
|
|
350
|
-
which does not match the value specified in project definition file: {project_def_distribution}.
|
|
351
|
-
"""
|
|
352
|
-
)
|
|
353
|
-
)
|
|
354
|
-
return False
|
|
355
|
-
return True
|
|
356
|
-
|
|
357
|
-
def build_bundle(self) -> BundleMap:
|
|
358
|
-
"""
|
|
359
|
-
Populates the local deploy root from artifact sources.
|
|
360
|
-
"""
|
|
361
|
-
bundle_map = build_bundle(self.project_root, self.deploy_root, self.artifacts)
|
|
362
|
-
compiler = NativeAppCompiler(
|
|
363
|
-
na_project=self.na_project,
|
|
364
|
-
)
|
|
365
|
-
compiler.compile_artifacts()
|
|
366
|
-
return bundle_map
|
|
367
|
-
|
|
368
|
-
def sync_deploy_root_with_stage(
|
|
369
|
-
self,
|
|
370
|
-
bundle_map: BundleMap,
|
|
371
|
-
role: str,
|
|
372
|
-
prune: bool,
|
|
373
|
-
recursive: bool,
|
|
374
|
-
stage_fqn: str,
|
|
375
|
-
local_paths_to_sync: List[Path] | None = None,
|
|
376
|
-
print_diff: bool = True,
|
|
377
|
-
) -> DiffResult:
|
|
378
|
-
"""
|
|
379
|
-
Ensures that the files on our remote stage match the artifacts we have in
|
|
380
|
-
the local filesystem.
|
|
381
|
-
|
|
382
|
-
Args:
|
|
383
|
-
bundle_map (BundleMap): The artifact mapping computed by the `build_bundle` function.
|
|
384
|
-
role (str): The name of the role to use for queries and commands.
|
|
385
|
-
prune (bool): Whether to prune artifacts from the stage that don't exist locally.
|
|
386
|
-
recursive (bool): Whether to traverse directories recursively.
|
|
387
|
-
stage_fqn (str): The name of the stage to diff against and upload to.
|
|
388
|
-
local_paths_to_sync (List[Path], optional): List of local paths to sync. Defaults to None to sync all
|
|
389
|
-
local paths. Note that providing an empty list here is equivalent to None.
|
|
390
|
-
print_diff (bool): Whether to print the diff between the local files and the remote stage. Defaults to True
|
|
391
|
-
|
|
392
|
-
Returns:
|
|
393
|
-
A `DiffResult` instance describing the changes that were performed.
|
|
394
|
-
"""
|
|
395
|
-
|
|
396
|
-
# Does a stage already exist within the application package, or we need to create one?
|
|
397
|
-
# Using "if not exists" should take care of either case.
|
|
398
|
-
cc.step(
|
|
399
|
-
f"Checking if stage {stage_fqn} exists, or creating a new one if none exists."
|
|
400
|
-
)
|
|
401
|
-
with self.use_role(role):
|
|
402
|
-
self._execute_query(
|
|
403
|
-
f"create schema if not exists {self.package_name}.{self.stage_schema}"
|
|
404
|
-
)
|
|
405
|
-
self._execute_query(
|
|
406
|
-
f"""
|
|
407
|
-
create stage if not exists {stage_fqn}
|
|
408
|
-
encryption = (TYPE = 'SNOWFLAKE_SSE')
|
|
409
|
-
DIRECTORY = (ENABLE = TRUE)"""
|
|
410
|
-
)
|
|
411
|
-
|
|
412
|
-
# Perform a diff operation and display results to the user for informational purposes
|
|
413
|
-
if print_diff:
|
|
414
|
-
cc.step(
|
|
415
|
-
"Performing a diff between the Snowflake stage and your local deploy_root ('%s') directory."
|
|
416
|
-
% self.deploy_root.resolve()
|
|
417
|
-
)
|
|
418
|
-
diff: DiffResult = compute_stage_diff(self.deploy_root, stage_fqn)
|
|
419
|
-
|
|
420
|
-
if local_paths_to_sync:
|
|
421
|
-
# Deploying specific files/directories
|
|
422
|
-
resolved_paths_to_sync = [
|
|
423
|
-
resolve_without_follow(p) for p in local_paths_to_sync
|
|
424
|
-
]
|
|
425
|
-
if not recursive:
|
|
426
|
-
verify_no_directories(resolved_paths_to_sync)
|
|
427
|
-
|
|
428
|
-
deploy_paths_to_sync = []
|
|
429
|
-
for resolved_path in resolved_paths_to_sync:
|
|
430
|
-
verify_exists(resolved_path)
|
|
431
|
-
deploy_paths = bundle_map.to_deploy_paths(resolved_path)
|
|
432
|
-
if not deploy_paths:
|
|
433
|
-
if resolved_path.is_dir() and recursive:
|
|
434
|
-
# No direct artifact mapping found for this path. Check to see
|
|
435
|
-
# if there are subpaths of this directory that are matches. We
|
|
436
|
-
# loop over sources because it's likely a much smaller list
|
|
437
|
-
# than the project directory.
|
|
438
|
-
for src in bundle_map.all_sources(absolute=True):
|
|
439
|
-
if resolved_path in src.parents:
|
|
440
|
-
# There is a source that contains this path, get its dest path(s)
|
|
441
|
-
deploy_paths.extend(bundle_map.to_deploy_paths(src))
|
|
442
|
-
|
|
443
|
-
if not deploy_paths:
|
|
444
|
-
raise ClickException(f"No artifact found for {resolved_path}")
|
|
445
|
-
deploy_paths_to_sync.extend(deploy_paths)
|
|
446
|
-
|
|
447
|
-
stage_paths_to_sync = _get_stage_paths_to_sync(
|
|
448
|
-
deploy_paths_to_sync, resolve_without_follow(self.deploy_root)
|
|
449
|
-
)
|
|
450
|
-
diff = preserve_from_diff(diff, stage_paths_to_sync)
|
|
451
|
-
else:
|
|
452
|
-
# Full deploy
|
|
453
|
-
if not recursive:
|
|
454
|
-
verify_no_directories(self.deploy_root.resolve().iterdir())
|
|
455
|
-
|
|
456
|
-
if not prune:
|
|
457
|
-
files_not_removed = [str(path) for path in diff.only_on_stage]
|
|
458
|
-
diff.only_on_stage = []
|
|
459
|
-
|
|
460
|
-
if len(files_not_removed) > 0:
|
|
461
|
-
files_not_removed_str = "\n".join(files_not_removed)
|
|
462
|
-
cc.warning(
|
|
463
|
-
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."
|
|
464
|
-
)
|
|
465
|
-
|
|
466
|
-
if print_diff:
|
|
467
|
-
print_diff_to_console(diff, bundle_map)
|
|
468
|
-
|
|
469
|
-
# Upload diff-ed files to application package stage
|
|
470
|
-
if diff.has_changes():
|
|
471
|
-
cc.step(
|
|
472
|
-
"Updating the Snowflake stage from your local %s directory."
|
|
473
|
-
% self.deploy_root.resolve(),
|
|
474
|
-
)
|
|
475
|
-
sync_local_diff_with_stage(
|
|
476
|
-
role=role,
|
|
477
|
-
deploy_root_path=self.deploy_root,
|
|
478
|
-
diff_result=diff,
|
|
479
|
-
stage_fqn=stage_fqn,
|
|
480
|
-
)
|
|
481
|
-
return diff
|
|
482
|
-
|
|
483
|
-
def get_existing_app_info(self) -> Optional[dict]:
|
|
484
|
-
"""
|
|
485
|
-
Check for an existing application object by the same name as in project definition, in account.
|
|
486
|
-
It executes a 'show applications like' query and returns the result as single row, if one exists.
|
|
487
|
-
"""
|
|
488
|
-
with self.use_role(self.app_role):
|
|
489
|
-
return self.show_specific_object(
|
|
490
|
-
"applications", self.app_name, name_col=NAME_COL
|
|
491
|
-
)
|
|
492
|
-
|
|
493
|
-
def get_existing_app_pkg_info(self) -> Optional[dict]:
|
|
494
|
-
"""
|
|
495
|
-
Check for an existing application package by the same name as in project definition, in account.
|
|
496
|
-
It executes a 'show application packages like' query and returns the result as single row, if one exists.
|
|
497
|
-
"""
|
|
498
|
-
|
|
499
|
-
with self.use_role(self.package_role):
|
|
500
|
-
return self.show_specific_object(
|
|
501
|
-
"application packages", self.package_name, name_col=NAME_COL
|
|
502
|
-
)
|
|
503
|
-
|
|
504
|
-
def get_objects_owned_by_application(self) -> List[ApplicationOwnedObject]:
|
|
505
|
-
"""
|
|
506
|
-
Returns all application objects owned by this application.
|
|
507
|
-
"""
|
|
508
|
-
with self.use_role(self.app_role):
|
|
509
|
-
results = self._execute_query(
|
|
510
|
-
f"show objects owned by application {self.app_name}"
|
|
511
|
-
).fetchall()
|
|
512
|
-
return [{"name": row[1], "type": row[2]} for row in results]
|
|
513
|
-
|
|
514
|
-
def _application_objects_to_str(
|
|
515
|
-
self, application_objects: list[ApplicationOwnedObject]
|
|
516
|
-
) -> str:
|
|
517
|
-
"""
|
|
518
|
-
Returns a list in an "(Object Type) Object Name" format. Database-level and schema-level object names are fully qualified:
|
|
519
|
-
(COMPUTE_POOL) POOL_NAME
|
|
520
|
-
(DATABASE) DB_NAME
|
|
521
|
-
(SCHEMA) DB_NAME.PUBLIC
|
|
522
|
-
...
|
|
523
|
-
"""
|
|
524
|
-
return "\n".join(
|
|
525
|
-
[self._application_object_to_str(obj) for obj in application_objects]
|
|
526
|
-
)
|
|
527
|
-
|
|
528
|
-
def _application_object_to_str(self, obj: ApplicationOwnedObject) -> str:
|
|
529
|
-
return f"({obj['type']}) {obj['name']}"
|
|
530
|
-
|
|
531
|
-
def get_snowsight_url(self) -> str:
|
|
532
|
-
"""Returns the URL that can be used to visit this app via Snowsight."""
|
|
533
|
-
name = identifier_for_url(self.app_name)
|
|
534
|
-
with self.use_application_warehouse():
|
|
535
|
-
return make_snowsight_url(self._conn, f"/#/apps/application/{name}")
|
|
536
|
-
|
|
537
|
-
def create_app_package(self) -> None:
|
|
538
|
-
"""
|
|
539
|
-
Creates the application package with our up-to-date stage if none exists.
|
|
540
|
-
"""
|
|
541
|
-
|
|
542
|
-
# 1. Check for existing existing application package
|
|
543
|
-
show_obj_row = self.get_existing_app_pkg_info()
|
|
544
|
-
|
|
545
|
-
if show_obj_row:
|
|
546
|
-
# 1. Check for the right owner role
|
|
547
|
-
ensure_correct_owner(
|
|
548
|
-
row=show_obj_row, role=self.package_role, obj_name=self.package_name
|
|
549
|
-
)
|
|
550
|
-
|
|
551
|
-
# 2. Check distribution of the existing application package
|
|
552
|
-
actual_distribution = self.get_app_pkg_distribution_in_snowflake
|
|
553
|
-
if not self.verify_project_distribution(actual_distribution):
|
|
554
|
-
cc.warning(
|
|
555
|
-
f"Continuing to execute `snow app run` on application package {self.package_name} with distribution '{actual_distribution}'."
|
|
556
|
-
)
|
|
557
|
-
|
|
558
|
-
# 3. If actual_distribution is external, skip comment check
|
|
559
|
-
if actual_distribution == INTERNAL_DISTRIBUTION:
|
|
560
|
-
row_comment = show_obj_row[COMMENT_COL]
|
|
561
|
-
|
|
562
|
-
if row_comment not in ALLOWED_SPECIAL_COMMENTS:
|
|
563
|
-
raise ApplicationPackageAlreadyExistsError(self.package_name)
|
|
564
|
-
|
|
565
|
-
return
|
|
566
|
-
|
|
567
|
-
# If no application package pre-exists, create an application package, with the specified distribution in the project definition file.
|
|
568
|
-
with self.use_role(self.package_role):
|
|
569
|
-
cc.step(f"Creating new application package {self.package_name} in account.")
|
|
570
|
-
self._execute_query(
|
|
571
|
-
dedent(
|
|
572
|
-
f"""\
|
|
573
|
-
create application package {self.package_name}
|
|
574
|
-
comment = {SPECIAL_COMMENT}
|
|
575
|
-
distribution = {self.package_distribution}
|
|
576
|
-
"""
|
|
577
|
-
)
|
|
578
|
-
)
|
|
579
|
-
|
|
580
|
-
def _expand_script_templates(
|
|
581
|
-
self,
|
|
582
|
-
env: jinja2.Environment,
|
|
583
|
-
jinja_context: dict[str, Any],
|
|
584
|
-
scripts: List[str],
|
|
585
|
-
) -> List[str]:
|
|
586
|
-
"""
|
|
587
|
-
Input:
|
|
588
|
-
- env: Jinja2 environment
|
|
589
|
-
- jinja_context: a dictionary with the jinja context
|
|
590
|
-
- scripts: list of scripts that need to be expanded with Jinja
|
|
591
|
-
Returns:
|
|
592
|
-
- List of expanded scripts content.
|
|
593
|
-
Size of the return list is the same as the size of the input scripts list.
|
|
594
|
-
"""
|
|
595
|
-
scripts_contents = []
|
|
596
|
-
for script in scripts:
|
|
597
|
-
full_path = SecurePath(self.project_root) / script
|
|
598
|
-
try:
|
|
599
|
-
template_content = full_path.read_text(file_size_limit_mb=UNLIMITED)
|
|
600
|
-
template = env.from_string(template_content)
|
|
601
|
-
result = template.render(**jinja_context)
|
|
602
|
-
scripts_contents.append(result)
|
|
603
|
-
|
|
604
|
-
except FileNotFoundError as e:
|
|
605
|
-
raise MissingScriptError(script) from e
|
|
606
|
-
|
|
607
|
-
except jinja2.TemplateSyntaxError as e:
|
|
608
|
-
raise InvalidScriptError(script, e, e.lineno) from e
|
|
609
|
-
|
|
610
|
-
except jinja2.UndefinedError as e:
|
|
611
|
-
raise InvalidScriptError(script, e) from e
|
|
612
|
-
|
|
613
|
-
return scripts_contents
|
|
614
|
-
|
|
615
|
-
def _apply_package_scripts(self) -> None:
|
|
616
|
-
"""
|
|
617
|
-
Assuming the application package exists and we are using the correct role,
|
|
618
|
-
applies all package scripts in-order to the application package.
|
|
619
|
-
"""
|
|
620
|
-
|
|
621
|
-
if self.package_scripts:
|
|
622
|
-
cc.warning(
|
|
623
|
-
"WARNING: native_app.package.scripts is deprecated. Please migrate to using native_app.package.post_deploy."
|
|
624
|
-
)
|
|
625
|
-
|
|
626
|
-
env = jinja2.Environment(
|
|
627
|
-
loader=jinja2.BaseLoader(),
|
|
628
|
-
keep_trailing_newline=True,
|
|
629
|
-
undefined=jinja2.StrictUndefined,
|
|
630
|
-
)
|
|
631
|
-
|
|
632
|
-
queued_queries = self._expand_script_templates(
|
|
633
|
-
env, dict(package_name=self.package_name), self.package_scripts
|
|
634
|
-
)
|
|
635
|
-
|
|
636
|
-
# once we're sure all the templates expanded correctly, execute all of them
|
|
637
|
-
with self.use_package_warehouse():
|
|
638
|
-
try:
|
|
639
|
-
for i, queries in enumerate(queued_queries):
|
|
640
|
-
cc.step(f"Applying package script: {self.package_scripts[i]}")
|
|
641
|
-
self._execute_queries(queries)
|
|
642
|
-
except ProgrammingError as err:
|
|
643
|
-
generic_sql_error_handler(
|
|
644
|
-
err, role=self.package_role, warehouse=self.package_warehouse
|
|
645
|
-
)
|
|
646
|
-
|
|
647
|
-
def _execute_sql_script(
|
|
648
|
-
self, script_content: str, database_name: Optional[str] = None
|
|
649
|
-
) -> None:
|
|
650
|
-
"""
|
|
651
|
-
Executing the provided SQL script content.
|
|
652
|
-
This assumes that a relevant warehouse is already active.
|
|
653
|
-
If database_name is passed in, it will be used first.
|
|
654
|
-
"""
|
|
655
|
-
try:
|
|
656
|
-
if database_name is not None:
|
|
657
|
-
self._execute_query(f"use database {database_name}")
|
|
658
|
-
|
|
659
|
-
self._execute_queries(script_content)
|
|
660
|
-
except ProgrammingError as err:
|
|
661
|
-
generic_sql_error_handler(err)
|
|
662
|
-
|
|
663
|
-
def _execute_post_deploy_hooks(
|
|
664
|
-
self,
|
|
665
|
-
post_deploy_hooks: Optional[List[PostDeployHook]],
|
|
666
|
-
deployed_object_type: str,
|
|
667
|
-
database_name: str,
|
|
668
|
-
) -> None:
|
|
669
|
-
"""
|
|
670
|
-
Executes post-deploy hooks for the given object type.
|
|
671
|
-
While executing SQL post deploy hooks, it first switches to the database provided in the input.
|
|
672
|
-
All post deploy scripts templates will first be expanded using the global template context.
|
|
673
|
-
"""
|
|
674
|
-
if not post_deploy_hooks:
|
|
675
|
-
return
|
|
676
|
-
|
|
677
|
-
with cc.phase(f"Executing {deployed_object_type} post_deploy actions"):
|
|
678
|
-
sql_scripts_paths = []
|
|
679
|
-
for hook in post_deploy_hooks:
|
|
680
|
-
if hook.sql_script:
|
|
681
|
-
sql_scripts_paths.append(hook.sql_script)
|
|
682
|
-
else:
|
|
683
|
-
raise ValueError(
|
|
684
|
-
f"Unsupported {deployed_object_type} post_deploy hook type: {hook}"
|
|
685
|
-
)
|
|
686
|
-
|
|
687
|
-
env = get_sql_cli_jinja_env()
|
|
688
|
-
scripts_content_list = self._expand_script_templates(
|
|
689
|
-
env, cli_context.template_context, sql_scripts_paths
|
|
690
|
-
)
|
|
691
|
-
|
|
692
|
-
for index, sql_script_path in enumerate(sql_scripts_paths):
|
|
693
|
-
cc.step(f"Executing SQL script: {sql_script_path}")
|
|
694
|
-
self._execute_sql_script(scripts_content_list[index], database_name)
|
|
695
|
-
|
|
696
|
-
def execute_package_post_deploy_hooks(self) -> None:
|
|
697
|
-
self._execute_post_deploy_hooks(
|
|
698
|
-
self.package_post_deploy_hooks, "application package", self.package_name
|
|
699
|
-
)
|
|
700
|
-
|
|
701
|
-
def execute_app_post_deploy_hooks(self) -> None:
|
|
702
|
-
self._execute_post_deploy_hooks(
|
|
703
|
-
self.app_post_deploy_hooks, "application", self.app_name
|
|
704
|
-
)
|
|
705
|
-
|
|
706
|
-
def deploy(
|
|
707
|
-
self,
|
|
708
|
-
bundle_map: BundleMap,
|
|
709
|
-
prune: bool,
|
|
710
|
-
recursive: bool,
|
|
711
|
-
stage_fqn: Optional[str] = None,
|
|
712
|
-
local_paths_to_sync: List[Path] | None = None,
|
|
713
|
-
validate: bool = True,
|
|
714
|
-
print_diff: bool = True,
|
|
715
|
-
) -> DiffResult:
|
|
716
|
-
"""app deploy process"""
|
|
717
|
-
|
|
718
|
-
# 1. Create an empty application package, if none exists
|
|
719
|
-
self.create_app_package()
|
|
720
|
-
|
|
721
|
-
with self.use_role(self.package_role):
|
|
722
|
-
# 2. now that the application package exists, create shared data
|
|
723
|
-
self._apply_package_scripts()
|
|
724
|
-
|
|
725
|
-
# 3. Upload files from deploy root local folder to the above stage
|
|
726
|
-
stage_fqn = stage_fqn or self.stage_fqn
|
|
727
|
-
diff = self.sync_deploy_root_with_stage(
|
|
728
|
-
bundle_map=bundle_map,
|
|
729
|
-
role=self.package_role,
|
|
730
|
-
prune=prune,
|
|
731
|
-
recursive=recursive,
|
|
732
|
-
stage_fqn=stage_fqn,
|
|
733
|
-
local_paths_to_sync=local_paths_to_sync,
|
|
734
|
-
print_diff=print_diff,
|
|
735
|
-
)
|
|
736
|
-
|
|
737
|
-
# 4. Execute post-deploy hooks
|
|
738
|
-
with self.use_package_warehouse():
|
|
739
|
-
self.execute_package_post_deploy_hooks()
|
|
740
|
-
|
|
741
|
-
if validate:
|
|
742
|
-
self.validate(use_scratch_stage=False)
|
|
743
|
-
|
|
744
|
-
return diff
|
|
745
|
-
|
|
746
|
-
def validate(self, use_scratch_stage: bool = False):
|
|
747
|
-
"""Validates Native App setup script SQL."""
|
|
748
|
-
with cc.phase(f"Validating Snowflake Native App setup script."):
|
|
749
|
-
validation_result = self.get_validation_result(use_scratch_stage)
|
|
750
|
-
|
|
751
|
-
# First print warnings, regardless of the outcome of validation
|
|
752
|
-
for warning in validation_result.get("warnings", []):
|
|
753
|
-
cc.warning(_validation_item_to_str(warning))
|
|
754
|
-
|
|
755
|
-
# Then print errors
|
|
756
|
-
for error in validation_result.get("errors", []):
|
|
757
|
-
# Print them as warnings for now since we're going to be
|
|
758
|
-
# revamping CLI output soon
|
|
759
|
-
cc.warning(_validation_item_to_str(error))
|
|
760
|
-
|
|
761
|
-
# Then raise an exception if validation failed
|
|
762
|
-
if validation_result["status"] == "FAIL":
|
|
763
|
-
raise SetupScriptFailedValidation()
|
|
764
|
-
|
|
765
|
-
def get_validation_result(self, use_scratch_stage: bool):
|
|
766
|
-
"""Call system$validate_native_app_setup() to validate deployed Native App setup script."""
|
|
767
|
-
stage_fqn = self.stage_fqn
|
|
768
|
-
if use_scratch_stage:
|
|
769
|
-
stage_fqn = self.scratch_stage_fqn
|
|
770
|
-
bundle_map = self.build_bundle()
|
|
771
|
-
self.deploy(
|
|
772
|
-
bundle_map=bundle_map,
|
|
773
|
-
prune=True,
|
|
774
|
-
recursive=True,
|
|
775
|
-
stage_fqn=stage_fqn,
|
|
776
|
-
validate=False,
|
|
777
|
-
print_diff=False,
|
|
778
|
-
)
|
|
779
|
-
prefixed_stage_fqn = StageManager.get_standard_stage_prefix(stage_fqn)
|
|
780
|
-
try:
|
|
781
|
-
cursor = self._execute_query(
|
|
782
|
-
f"call system$validate_native_app_setup('{prefixed_stage_fqn}')"
|
|
783
|
-
)
|
|
784
|
-
except ProgrammingError as err:
|
|
785
|
-
if err.errno == DOES_NOT_EXIST_OR_NOT_AUTHORIZED:
|
|
786
|
-
raise ApplicationPackageDoesNotExistError(self.package_name)
|
|
787
|
-
generic_sql_error_handler(err)
|
|
788
|
-
else:
|
|
789
|
-
if not cursor.rowcount:
|
|
790
|
-
raise SnowflakeSQLExecutionError()
|
|
791
|
-
return json.loads(cursor.fetchone()[0])
|
|
792
|
-
finally:
|
|
793
|
-
if use_scratch_stage:
|
|
794
|
-
cc.step(f"Dropping stage {self.scratch_stage_fqn}.")
|
|
795
|
-
with self.use_role(self.package_role):
|
|
796
|
-
self._execute_query(
|
|
797
|
-
f"drop stage if exists {self.scratch_stage_fqn}"
|
|
798
|
-
)
|
|
799
|
-
|
|
800
|
-
def get_events(self) -> list[dict]:
|
|
801
|
-
if not self.account_event_table:
|
|
802
|
-
raise NoEventTableForAccount()
|
|
803
|
-
|
|
804
|
-
# resource_attributes:"snow.database.name" uses the unquoted/uppercase app name
|
|
805
|
-
app_name = unquote_identifier(self.app_name)
|
|
806
|
-
query = dedent(
|
|
807
|
-
f"""\
|
|
808
|
-
select timestamp, value::varchar value
|
|
809
|
-
from {self.account_event_table}
|
|
810
|
-
where resource_attributes:"snow.database.name" = '{app_name}'
|
|
811
|
-
order by timestamp asc;"""
|
|
812
|
-
)
|
|
813
|
-
try:
|
|
814
|
-
return self._execute_query(query, cursor_class=DictCursor).fetchall()
|
|
815
|
-
except ProgrammingError as err:
|
|
816
|
-
generic_sql_error_handler(err)
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
def _validation_item_to_str(item: dict[str, str | int]):
|
|
820
|
-
s = item["message"]
|
|
821
|
-
if item["errorCode"]:
|
|
822
|
-
s = f"{s} (error code {item['errorCode']})"
|
|
823
|
-
return s
|