snowflake-cli-labs 3.0.0rc4__py3-none-any.whl → 3.0.1__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.
- README.md +21 -0
- {snowflake_cli_labs-3.0.0rc4.dist-info → snowflake_cli_labs-3.0.1.dist-info}/METADATA +6 -96
- snowflake_cli_labs-3.0.1.dist-info/RECORD +5 -0
- snowflake/cli/__about__.py +0 -17
- snowflake/cli/__init__.py +0 -13
- snowflake/cli/_app/__init__.py +0 -22
- snowflake/cli/_app/__main__.py +0 -31
- snowflake/cli/_app/api_impl/__init__.py +0 -13
- snowflake/cli/_app/api_impl/plugin/__init__.py +0 -13
- snowflake/cli/_app/api_impl/plugin/plugin_config_provider_impl.py +0 -66
- snowflake/cli/_app/cli_app.py +0 -252
- snowflake/cli/_app/commands_registration/__init__.py +0 -33
- snowflake/cli/_app/commands_registration/builtin_plugins.py +0 -50
- snowflake/cli/_app/commands_registration/command_plugins_loader.py +0 -169
- snowflake/cli/_app/commands_registration/commands_registration_with_callbacks.py +0 -105
- snowflake/cli/_app/commands_registration/exception_logging.py +0 -26
- snowflake/cli/_app/commands_registration/threadsafe.py +0 -48
- snowflake/cli/_app/commands_registration/typer_registration.py +0 -153
- snowflake/cli/_app/constants.py +0 -19
- snowflake/cli/_app/dev/__init__.py +0 -13
- snowflake/cli/_app/dev/commands_structure.py +0 -48
- snowflake/cli/_app/dev/docs/__init__.py +0 -13
- snowflake/cli/_app/dev/docs/commands_docs_generator.py +0 -118
- snowflake/cli/_app/dev/docs/generator.py +0 -35
- snowflake/cli/_app/dev/docs/project_definition_docs_generator.py +0 -58
- snowflake/cli/_app/dev/docs/project_definition_generate_json_schema.py +0 -227
- snowflake/cli/_app/dev/docs/template_utils.py +0 -23
- snowflake/cli/_app/dev/docs/templates/definition_description.rst.jinja2 +0 -38
- snowflake/cli/_app/dev/docs/templates/overview.rst.jinja2 +0 -9
- snowflake/cli/_app/dev/docs/templates/usage.rst.jinja2 +0 -67
- snowflake/cli/_app/dev/pycharm_remote_debug.py +0 -46
- snowflake/cli/_app/loggers.py +0 -199
- snowflake/cli/_app/main_typer.py +0 -62
- snowflake/cli/_app/printing.py +0 -181
- snowflake/cli/_app/secret.py +0 -9
- snowflake/cli/_app/snow_connector.py +0 -309
- snowflake/cli/_app/telemetry.py +0 -220
- snowflake/cli/_app/version_check.py +0 -74
- snowflake/cli/_plugins/__init__.py +0 -13
- snowflake/cli/_plugins/connection/__init__.py +0 -13
- snowflake/cli/_plugins/connection/commands.py +0 -353
- snowflake/cli/_plugins/connection/plugin_spec.py +0 -30
- snowflake/cli/_plugins/connection/util.py +0 -195
- snowflake/cli/_plugins/cortex/__init__.py +0 -13
- snowflake/cli/_plugins/cortex/commands.py +0 -332
- snowflake/cli/_plugins/cortex/constants.py +0 -17
- snowflake/cli/_plugins/cortex/manager.py +0 -189
- snowflake/cli/_plugins/cortex/plugin_spec.py +0 -30
- snowflake/cli/_plugins/cortex/types.py +0 -22
- snowflake/cli/_plugins/git/__init__.py +0 -13
- snowflake/cli/_plugins/git/commands.py +0 -358
- snowflake/cli/_plugins/git/manager.py +0 -151
- snowflake/cli/_plugins/git/plugin_spec.py +0 -30
- snowflake/cli/_plugins/helpers/__init__.py +0 -13
- snowflake/cli/_plugins/helpers/commands.py +0 -61
- snowflake/cli/_plugins/helpers/plugin_spec.py +0 -30
- snowflake/cli/_plugins/init/__init__.py +0 -13
- snowflake/cli/_plugins/init/commands.py +0 -248
- snowflake/cli/_plugins/init/plugin_spec.py +0 -30
- snowflake/cli/_plugins/nativeapp/__init__.py +0 -13
- snowflake/cli/_plugins/nativeapp/artifacts.py +0 -757
- snowflake/cli/_plugins/nativeapp/bundle_context.py +0 -31
- snowflake/cli/_plugins/nativeapp/codegen/__init__.py +0 -13
- snowflake/cli/_plugins/nativeapp/codegen/artifact_processor.py +0 -91
- snowflake/cli/_plugins/nativeapp/codegen/compiler.py +0 -149
- snowflake/cli/_plugins/nativeapp/codegen/sandbox.py +0 -306
- snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +0 -249
- snowflake/cli/_plugins/nativeapp/codegen/setup/setup_driver.py.source +0 -59
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/callback_source.py.jinja +0 -181
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/extension_function_utils.py +0 -217
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/models.py +0 -61
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +0 -523
- snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +0 -114
- snowflake/cli/_plugins/nativeapp/commands.py +0 -559
- snowflake/cli/_plugins/nativeapp/common_flags.py +0 -44
- snowflake/cli/_plugins/nativeapp/constants.py +0 -27
- snowflake/cli/_plugins/nativeapp/entities/__init__.py +0 -0
- snowflake/cli/_plugins/nativeapp/entities/application.py +0 -878
- snowflake/cli/_plugins/nativeapp/entities/application_package.py +0 -1392
- snowflake/cli/_plugins/nativeapp/exceptions.py +0 -113
- snowflake/cli/_plugins/nativeapp/feature_flags.py +0 -24
- snowflake/cli/_plugins/nativeapp/manager.py +0 -415
- snowflake/cli/_plugins/nativeapp/plugin_spec.py +0 -30
- snowflake/cli/_plugins/nativeapp/policy.py +0 -53
- snowflake/cli/_plugins/nativeapp/project_model.py +0 -211
- snowflake/cli/_plugins/nativeapp/run_processor.py +0 -184
- snowflake/cli/_plugins/nativeapp/same_account_install_method.py +0 -70
- snowflake/cli/_plugins/nativeapp/teardown_processor.py +0 -70
- snowflake/cli/_plugins/nativeapp/utils.py +0 -98
- snowflake/cli/_plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +0 -262
- snowflake/cli/_plugins/nativeapp/version/__init__.py +0 -13
- snowflake/cli/_plugins/nativeapp/version/commands.py +0 -141
- snowflake/cli/_plugins/nativeapp/version/version_processor.py +0 -98
- snowflake/cli/_plugins/notebook/__init__.py +0 -13
- snowflake/cli/_plugins/notebook/commands.py +0 -86
- snowflake/cli/_plugins/notebook/exceptions.py +0 -20
- snowflake/cli/_plugins/notebook/manager.py +0 -71
- snowflake/cli/_plugins/notebook/plugin_spec.py +0 -30
- snowflake/cli/_plugins/notebook/types.py +0 -15
- snowflake/cli/_plugins/object/__init__.py +0 -13
- snowflake/cli/_plugins/object/command_aliases.py +0 -95
- snowflake/cli/_plugins/object/commands.py +0 -180
- snowflake/cli/_plugins/object/common.py +0 -85
- snowflake/cli/_plugins/object/manager.py +0 -118
- snowflake/cli/_plugins/object/plugin_spec.py +0 -30
- snowflake/cli/_plugins/snowpark/__init__.py +0 -13
- snowflake/cli/_plugins/snowpark/commands.py +0 -450
- snowflake/cli/_plugins/snowpark/common.py +0 -268
- snowflake/cli/_plugins/snowpark/models.py +0 -150
- snowflake/cli/_plugins/snowpark/package/__init__.py +0 -13
- snowflake/cli/_plugins/snowpark/package/anaconda_packages.py +0 -199
- snowflake/cli/_plugins/snowpark/package/commands.py +0 -195
- snowflake/cli/_plugins/snowpark/package/manager.py +0 -44
- snowflake/cli/_plugins/snowpark/package/utils.py +0 -26
- snowflake/cli/_plugins/snowpark/package_utils.py +0 -354
- snowflake/cli/_plugins/snowpark/plugin_spec.py +0 -30
- snowflake/cli/_plugins/snowpark/snowpark_entity.py +0 -29
- snowflake/cli/_plugins/snowpark/snowpark_entity_model.py +0 -173
- snowflake/cli/_plugins/snowpark/snowpark_project_paths.py +0 -109
- snowflake/cli/_plugins/snowpark/snowpark_shared.py +0 -59
- snowflake/cli/_plugins/snowpark/zipper.py +0 -89
- snowflake/cli/_plugins/spcs/__init__.py +0 -33
- snowflake/cli/_plugins/spcs/common.py +0 -99
- snowflake/cli/_plugins/spcs/compute_pool/__init__.py +0 -13
- snowflake/cli/_plugins/spcs/compute_pool/commands.py +0 -241
- snowflake/cli/_plugins/spcs/compute_pool/manager.py +0 -121
- snowflake/cli/_plugins/spcs/image_registry/__init__.py +0 -13
- snowflake/cli/_plugins/spcs/image_registry/commands.py +0 -65
- snowflake/cli/_plugins/spcs/image_registry/manager.py +0 -105
- snowflake/cli/_plugins/spcs/image_repository/__init__.py +0 -13
- snowflake/cli/_plugins/spcs/image_repository/commands.py +0 -202
- snowflake/cli/_plugins/spcs/image_repository/manager.py +0 -84
- snowflake/cli/_plugins/spcs/plugin_spec.py +0 -30
- snowflake/cli/_plugins/spcs/services/__init__.py +0 -13
- snowflake/cli/_plugins/spcs/services/commands.py +0 -345
- snowflake/cli/_plugins/spcs/services/manager.py +0 -208
- snowflake/cli/_plugins/sql/__init__.py +0 -13
- snowflake/cli/_plugins/sql/commands.py +0 -86
- snowflake/cli/_plugins/sql/manager.py +0 -92
- snowflake/cli/_plugins/sql/plugin_spec.py +0 -30
- snowflake/cli/_plugins/sql/snowsql_templating.py +0 -28
- snowflake/cli/_plugins/stage/__init__.py +0 -13
- snowflake/cli/_plugins/stage/commands.py +0 -264
- snowflake/cli/_plugins/stage/diff.py +0 -280
- snowflake/cli/_plugins/stage/manager.py +0 -582
- snowflake/cli/_plugins/stage/md5.py +0 -160
- snowflake/cli/_plugins/stage/plugin_spec.py +0 -30
- snowflake/cli/_plugins/stage/utils.py +0 -54
- snowflake/cli/_plugins/streamlit/__init__.py +0 -13
- snowflake/cli/_plugins/streamlit/commands.py +0 -195
- snowflake/cli/_plugins/streamlit/manager.py +0 -220
- snowflake/cli/_plugins/streamlit/plugin_spec.py +0 -30
- snowflake/cli/_plugins/streamlit/streamlit_entity.py +0 -12
- snowflake/cli/_plugins/streamlit/streamlit_entity_model.py +0 -66
- snowflake/cli/_plugins/workspace/__init__.py +0 -13
- snowflake/cli/_plugins/workspace/action_context.py +0 -18
- snowflake/cli/_plugins/workspace/commands.py +0 -306
- snowflake/cli/_plugins/workspace/manager.py +0 -74
- snowflake/cli/_plugins/workspace/plugin_spec.py +0 -30
- snowflake/cli/api/__init__.py +0 -48
- snowflake/cli/api/cli_global_context.py +0 -247
- snowflake/cli/api/commands/__init__.py +0 -13
- snowflake/cli/api/commands/alias.py +0 -23
- snowflake/cli/api/commands/common.py +0 -25
- snowflake/cli/api/commands/decorators.py +0 -369
- snowflake/cli/api/commands/execution_metadata.py +0 -40
- snowflake/cli/api/commands/experimental_behaviour.py +0 -18
- snowflake/cli/api/commands/flags.py +0 -561
- snowflake/cli/api/commands/overrideable_parameter.py +0 -143
- snowflake/cli/api/commands/snow_typer.py +0 -247
- snowflake/cli/api/commands/utils.py +0 -18
- snowflake/cli/api/config.py +0 -380
- snowflake/cli/api/connections.py +0 -216
- snowflake/cli/api/console/__init__.py +0 -17
- snowflake/cli/api/console/abc.py +0 -94
- snowflake/cli/api/console/console.py +0 -134
- snowflake/cli/api/console/enum.py +0 -17
- snowflake/cli/api/constants.py +0 -90
- snowflake/cli/api/entities/common.py +0 -56
- snowflake/cli/api/entities/utils.py +0 -370
- snowflake/cli/api/errno.py +0 -28
- snowflake/cli/api/exceptions.py +0 -190
- snowflake/cli/api/feature_flags.py +0 -54
- snowflake/cli/api/identifiers.py +0 -190
- snowflake/cli/api/metrics.py +0 -92
- snowflake/cli/api/output/__init__.py +0 -13
- snowflake/cli/api/output/formats.py +0 -20
- snowflake/cli/api/output/types.py +0 -118
- snowflake/cli/api/plugins/__init__.py +0 -13
- snowflake/cli/api/plugins/command/__init__.py +0 -72
- snowflake/cli/api/plugins/command/plugin_hook_specs.py +0 -21
- snowflake/cli/api/plugins/plugin_config.py +0 -32
- snowflake/cli/api/project/__init__.py +0 -13
- snowflake/cli/api/project/definition.py +0 -126
- snowflake/cli/api/project/definition_conversion.py +0 -395
- snowflake/cli/api/project/definition_manager.py +0 -145
- snowflake/cli/api/project/errors.py +0 -56
- snowflake/cli/api/project/project_verification.py +0 -23
- snowflake/cli/api/project/schemas/__init__.py +0 -13
- snowflake/cli/api/project/schemas/entities/__init__.py +0 -13
- snowflake/cli/api/project/schemas/entities/common.py +0 -153
- snowflake/cli/api/project/schemas/entities/entities.py +0 -61
- snowflake/cli/api/project/schemas/project_definition.py +0 -330
- snowflake/cli/api/project/schemas/template.py +0 -77
- snowflake/cli/api/project/schemas/updatable_model.py +0 -202
- snowflake/cli/api/project/schemas/v1/__init__.py +0 -0
- snowflake/cli/api/project/schemas/v1/identifier_model.py +0 -51
- snowflake/cli/api/project/schemas/v1/native_app/__init__.py +0 -0
- snowflake/cli/api/project/schemas/v1/native_app/application.py +0 -61
- snowflake/cli/api/project/schemas/v1/native_app/native_app.py +0 -93
- snowflake/cli/api/project/schemas/v1/native_app/package.py +0 -84
- snowflake/cli/api/project/schemas/v1/native_app/path_mapping.py +0 -65
- snowflake/cli/api/project/schemas/v1/snowpark/__init__.py +0 -0
- snowflake/cli/api/project/schemas/v1/snowpark/argument.py +0 -28
- snowflake/cli/api/project/schemas/v1/snowpark/callable.py +0 -69
- snowflake/cli/api/project/schemas/v1/snowpark/snowpark.py +0 -36
- snowflake/cli/api/project/schemas/v1/streamlit/__init__.py +0 -0
- snowflake/cli/api/project/schemas/v1/streamlit/streamlit.py +0 -47
- snowflake/cli/api/project/util.py +0 -278
- snowflake/cli/api/rendering/__init__.py +0 -13
- snowflake/cli/api/rendering/jinja.py +0 -118
- snowflake/cli/api/rendering/project_definition_templates.py +0 -43
- snowflake/cli/api/rendering/project_templates.py +0 -98
- snowflake/cli/api/rendering/sql_templates.py +0 -105
- snowflake/cli/api/rest_api.py +0 -178
- snowflake/cli/api/sanitizers.py +0 -43
- snowflake/cli/api/secure_path.py +0 -360
- snowflake/cli/api/secure_utils.py +0 -118
- snowflake/cli/api/sql_execution.py +0 -280
- snowflake/cli/api/utils/__init__.py +0 -13
- snowflake/cli/api/utils/cursor.py +0 -34
- snowflake/cli/api/utils/definition_rendering.py +0 -415
- snowflake/cli/api/utils/dict_utils.py +0 -73
- snowflake/cli/api/utils/error_handling.py +0 -23
- snowflake/cli/api/utils/graph.py +0 -97
- snowflake/cli/api/utils/models.py +0 -63
- snowflake/cli/api/utils/naming_utils.py +0 -13
- snowflake/cli/api/utils/path_utils.py +0 -36
- snowflake/cli/api/utils/templating_functions.py +0 -144
- snowflake/cli/api/utils/types.py +0 -35
- snowflake_cli_labs-3.0.0rc4.dist-info/RECORD +0 -242
- snowflake_cli_labs-3.0.0rc4.dist-info/entry_points.txt +0 -2
- {snowflake_cli_labs-3.0.0rc4.dist-info → snowflake_cli_labs-3.0.1.dist-info}/WHEEL +0 -0
- {snowflake_cli_labs-3.0.0rc4.dist-info → snowflake_cli_labs-3.0.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,278 +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 codecs
|
|
18
|
-
import os
|
|
19
|
-
import re
|
|
20
|
-
from typing import List, Optional
|
|
21
|
-
from urllib.parse import quote
|
|
22
|
-
|
|
23
|
-
IDENTIFIER = r'((?:"[^"]*(?:""[^"]*)*")|(?:[A-Za-z_][\w$]{0,254}))'
|
|
24
|
-
IDENTIFIER_NO_LENGTH = r'((?:"[^"]*(?:""[^"]*)*")|(?:[A-Za-z_][\w$]*))'
|
|
25
|
-
DB_SCHEMA_AND_NAME = f"{IDENTIFIER}[.]{IDENTIFIER}[.]{IDENTIFIER}"
|
|
26
|
-
SCHEMA_AND_NAME = f"{IDENTIFIER}[.]{IDENTIFIER}"
|
|
27
|
-
GLOB_REGEX = r"^[a-zA-Z0-9_\-./*?**\p{L}\p{N}]+$"
|
|
28
|
-
RELATIVE_PATH = r"^[^/][\p{L}\p{N}_\-.][^/]*$"
|
|
29
|
-
|
|
30
|
-
SINGLE_QUOTED_STRING_LITERAL_REGEX = r"'((?:\\.|''|[^'\n])+?)'"
|
|
31
|
-
|
|
32
|
-
# See https://docs.snowflake.com/en/sql-reference/identifiers-syntax for identifier syntax
|
|
33
|
-
UNQUOTED_IDENTIFIER_REGEX = r"([a-zA-Z_])([a-zA-Z0-9_$]{0,254})"
|
|
34
|
-
QUOTED_IDENTIFIER_REGEX = r'"((""|[^"]){0,255})"'
|
|
35
|
-
VALID_IDENTIFIER_REGEX = f"(?:{UNQUOTED_IDENTIFIER_REGEX}|{QUOTED_IDENTIFIER_REGEX})"
|
|
36
|
-
|
|
37
|
-
# An env var that is used to suffix the names of some account-level resources
|
|
38
|
-
TEST_RESOURCE_SUFFIX_VAR = "SNOWFLAKE_CLI_TEST_RESOURCE_SUFFIX"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def encode_uri_component(s: str) -> str:
|
|
42
|
-
"""
|
|
43
|
-
Implementation of JavaScript's encodeURIComponent.
|
|
44
|
-
"""
|
|
45
|
-
return quote(s, safe="!~*'()")
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def sanitize_identifier(input_: str):
|
|
49
|
-
"""
|
|
50
|
-
Removes characters that cannot be used in an unquoted identifier.
|
|
51
|
-
If the identifier does not start with a letter or underscore, prefix it with an underscore.
|
|
52
|
-
Limits the identifier to 255 characters.
|
|
53
|
-
"""
|
|
54
|
-
value = re.sub(r"[^a-zA-Z0-9_$]", "", f"{input_}")
|
|
55
|
-
|
|
56
|
-
# if it does not start with a letter or underscore, prefix it with an underscore
|
|
57
|
-
if not value or not re.match(r"[a-zA-Z_]", value[0]):
|
|
58
|
-
value = f"_{value}"
|
|
59
|
-
|
|
60
|
-
# limit it to 255 characters
|
|
61
|
-
return value[:255]
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def is_valid_unquoted_identifier(identifier: str) -> bool:
|
|
65
|
-
"""
|
|
66
|
-
Determines whether the provided identifier is a valid Snowflake unquoted identifier.
|
|
67
|
-
"""
|
|
68
|
-
return re.fullmatch(UNQUOTED_IDENTIFIER_REGEX, identifier) is not None
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def is_valid_quoted_identifier(identifier: str) -> bool:
|
|
72
|
-
"""
|
|
73
|
-
Determines whether the provided identifier is a valid Snowflake quoted identifier.
|
|
74
|
-
"""
|
|
75
|
-
return re.fullmatch(QUOTED_IDENTIFIER_REGEX, identifier) is not None
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def is_valid_identifier(identifier: str) -> bool:
|
|
79
|
-
"""
|
|
80
|
-
Determines whether the provided identifier is a valid Snowflake quoted or unquoted identifier.
|
|
81
|
-
"""
|
|
82
|
-
return is_valid_unquoted_identifier(identifier) or is_valid_quoted_identifier(
|
|
83
|
-
identifier
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
def is_valid_object_name(name: str, max_depth=2, allow_quoted=True) -> bool:
|
|
88
|
-
"""
|
|
89
|
-
Determines whether the given identifier is a valid object name in the form <name>, <schema>.<name>, or <database>.<schema>.<name>.
|
|
90
|
-
Max_depth determines how many valid identifiers are allowed. For example, account level objects would have a max depth of 0
|
|
91
|
-
because they cannot be qualified by a database or schema, just the single identifier.
|
|
92
|
-
"""
|
|
93
|
-
if max_depth < 0:
|
|
94
|
-
raise ValueError("max_depth must be non-negative")
|
|
95
|
-
identifier_pattern = (
|
|
96
|
-
VALID_IDENTIFIER_REGEX if allow_quoted else UNQUOTED_IDENTIFIER_REGEX
|
|
97
|
-
)
|
|
98
|
-
pattern = rf"{identifier_pattern}(?:\.{identifier_pattern}){{0,{max_depth}}}"
|
|
99
|
-
return re.fullmatch(pattern, name) is not None
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def to_quoted_identifier(input_value: str) -> str:
|
|
103
|
-
"""
|
|
104
|
-
Turn the input into a valid quoted identifier.
|
|
105
|
-
If it is already a valid quoted identifier,
|
|
106
|
-
return it as is.
|
|
107
|
-
"""
|
|
108
|
-
if is_valid_quoted_identifier(input_value):
|
|
109
|
-
return input_value
|
|
110
|
-
|
|
111
|
-
return '"' + input_value.replace('"', '""') + '"'
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def to_identifier(name: str) -> str:
|
|
115
|
-
"""
|
|
116
|
-
Converts a name to a valid Snowflake identifier. If the name is already a valid
|
|
117
|
-
Snowflake identifier, then it is returned unmodified.
|
|
118
|
-
"""
|
|
119
|
-
if is_valid_identifier(name):
|
|
120
|
-
return name
|
|
121
|
-
|
|
122
|
-
return to_quoted_identifier(name)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
def identifier_to_str(identifier: str) -> str:
|
|
126
|
-
if is_valid_quoted_identifier(identifier):
|
|
127
|
-
unquoted_id = identifier[1:-1]
|
|
128
|
-
return unquoted_id.replace('""', '"')
|
|
129
|
-
|
|
130
|
-
return identifier
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
def append_to_identifier(identifier: str, suffix: str) -> str:
|
|
134
|
-
"""
|
|
135
|
-
Appends a suffix to a valid identifier.
|
|
136
|
-
"""
|
|
137
|
-
if is_valid_unquoted_identifier(identifier):
|
|
138
|
-
return to_identifier(f"{identifier}{suffix}")
|
|
139
|
-
else:
|
|
140
|
-
# the identifier is quoted, so insert the suffix within the quotes
|
|
141
|
-
unquoted = identifier[1:-1]
|
|
142
|
-
return f'"{unquoted}{suffix}"'
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
def unquote_identifier(identifier: str) -> str:
|
|
146
|
-
"""
|
|
147
|
-
Returns a version of this identifier that can be used inside of a
|
|
148
|
-
string for a LIKE clause, or to match an identifier passed back as
|
|
149
|
-
a value from a SQL statement.
|
|
150
|
-
"""
|
|
151
|
-
if match := re.fullmatch(QUOTED_IDENTIFIER_REGEX, identifier):
|
|
152
|
-
return match.group(1).replace('""', '"')
|
|
153
|
-
# unquoted identifiers are internally represented as uppercase
|
|
154
|
-
return identifier.upper()
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def identifier_for_url(identifier: str) -> str:
|
|
158
|
-
"""
|
|
159
|
-
Returns a version of this identifier that can be used as part of a URL.
|
|
160
|
-
"""
|
|
161
|
-
return encode_uri_component(unquote_identifier(identifier))
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
def is_valid_string_literal(literal: str) -> bool:
|
|
165
|
-
"""
|
|
166
|
-
Determines if a literal is a valid single quoted string literal
|
|
167
|
-
"""
|
|
168
|
-
return re.fullmatch(SINGLE_QUOTED_STRING_LITERAL_REGEX, literal) is not None
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
def to_string_literal(raw_value: str) -> str:
|
|
172
|
-
"""
|
|
173
|
-
Converts the raw string value to a correctly escaped, single-quoted string literal.
|
|
174
|
-
"""
|
|
175
|
-
# encode escape sequences
|
|
176
|
-
escaped = str(codecs.encode(raw_value, "unicode-escape"), "utf-8")
|
|
177
|
-
|
|
178
|
-
# escape single quotes
|
|
179
|
-
escaped = re.sub(r"^'|(?<!')'", r"\'", escaped)
|
|
180
|
-
|
|
181
|
-
return f"'{escaped}'"
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
def extract_schema(qualified_name: str):
|
|
185
|
-
"""
|
|
186
|
-
Extracts the schema from either a two-part or three-part qualified name
|
|
187
|
-
(i.e. schema.object or database.schema.object). If qualified_name is not
|
|
188
|
-
qualified with a schema, returns None.
|
|
189
|
-
"""
|
|
190
|
-
if match := re.fullmatch(DB_SCHEMA_AND_NAME, qualified_name):
|
|
191
|
-
return match.group(2)
|
|
192
|
-
elif match := re.fullmatch(SCHEMA_AND_NAME, qualified_name):
|
|
193
|
-
return match.group(1)
|
|
194
|
-
return None
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
def first_set_env(*keys: str):
|
|
198
|
-
for k in keys:
|
|
199
|
-
v = os.getenv(k)
|
|
200
|
-
if v:
|
|
201
|
-
return v
|
|
202
|
-
|
|
203
|
-
return None
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
def get_env_username() -> Optional[str]:
|
|
207
|
-
return first_set_env("USER", "USERNAME", "LOGNAME")
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
def concat_identifiers(identifiers: list[str]) -> str:
|
|
211
|
-
"""
|
|
212
|
-
Concatenate multiple identifiers.
|
|
213
|
-
If any of them is quoted identifier or contains unsafe characters, quote the result.
|
|
214
|
-
Otherwise, the resulting identifier will be unquoted.
|
|
215
|
-
"""
|
|
216
|
-
quotes_found = False
|
|
217
|
-
stringified_identifiers: List[str] = []
|
|
218
|
-
|
|
219
|
-
for identifier in identifiers:
|
|
220
|
-
if is_valid_quoted_identifier(identifier):
|
|
221
|
-
quotes_found = True
|
|
222
|
-
stringified_identifiers.append(identifier_to_str(identifier))
|
|
223
|
-
|
|
224
|
-
concatenated_ids_str = "".join(stringified_identifiers)
|
|
225
|
-
if quotes_found:
|
|
226
|
-
return to_quoted_identifier(concatenated_ids_str)
|
|
227
|
-
|
|
228
|
-
return to_identifier(concatenated_ids_str)
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
SUPPORTED_VERSIONS = [1]
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
def validate_version(version: str):
|
|
235
|
-
if version in SUPPORTED_VERSIONS:
|
|
236
|
-
raise ValueError(
|
|
237
|
-
f"Project definition version {version} is not supported by this version of Snowflake CLI. Supported versions: {SUPPORTED_VERSIONS}"
|
|
238
|
-
)
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
def escape_like_pattern(pattern: str, escape_sequence: str = r"\\") -> str:
|
|
242
|
-
"""
|
|
243
|
-
When used with LIKE in Snowflake, '%' and '_' are wildcard characters and must be escaped to be used literally.
|
|
244
|
-
The escape character is '\\' when used in SHOW LIKE and must be specified when used with string matching using the
|
|
245
|
-
following syntax: <subject> LIKE <pattern> [ ESCAPE <escape> ].
|
|
246
|
-
"""
|
|
247
|
-
pattern = pattern.replace("%", rf"{escape_sequence}%").replace(
|
|
248
|
-
"_", rf"{escape_sequence}_"
|
|
249
|
-
)
|
|
250
|
-
return pattern
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
def identifier_to_show_like_pattern(identifier: str) -> str:
|
|
254
|
-
"""
|
|
255
|
-
Takes an identifier and converts it into a pattern to be used with SHOW ... LIKE ... to get all rows with name
|
|
256
|
-
matching this identifier
|
|
257
|
-
"""
|
|
258
|
-
return f"'{escape_like_pattern(unquote_identifier(identifier))}'"
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
def append_test_resource_suffix(identifier: str) -> str:
|
|
262
|
-
"""
|
|
263
|
-
Append a suffix that should be added to specified account-level resources.
|
|
264
|
-
|
|
265
|
-
This is an internal concern that is currently only used in tests
|
|
266
|
-
to isolate concurrent runs and to add the test name to resources.
|
|
267
|
-
"""
|
|
268
|
-
suffix = os.environ.get(TEST_RESOURCE_SUFFIX_VAR, "")
|
|
269
|
-
if identifier_to_str(identifier).endswith(identifier_to_str(suffix)):
|
|
270
|
-
# If the suffix has already been added, don't add it again
|
|
271
|
-
return identifier
|
|
272
|
-
if is_valid_quoted_identifier(identifier) or is_valid_quoted_identifier(suffix):
|
|
273
|
-
# If either identifier is already quoted, use concat_identifier
|
|
274
|
-
# to add the suffix inside the quotes
|
|
275
|
-
return concat_identifiers([identifier, suffix])
|
|
276
|
-
# Otherwise just append the string, don't add quotes
|
|
277
|
-
# in case the user doesn't want them
|
|
278
|
-
return f"{identifier}{suffix}"
|
|
@@ -1,13 +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.
|
|
@@ -1,118 +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
|
-
|
|
16
|
-
from __future__ import annotations
|
|
17
|
-
|
|
18
|
-
from pathlib import Path
|
|
19
|
-
from textwrap import dedent
|
|
20
|
-
from typing import Any, Dict, Optional
|
|
21
|
-
|
|
22
|
-
import jinja2
|
|
23
|
-
from jinja2 import Environment, StrictUndefined, loaders
|
|
24
|
-
from snowflake.cli.api.secure_path import UNLIMITED, SecurePath
|
|
25
|
-
|
|
26
|
-
CONTEXT_KEY = "ctx"
|
|
27
|
-
FUNCTION_KEY = "fn"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def read_file_content(file_name: str):
|
|
31
|
-
return SecurePath(file_name).read_text(file_size_limit_mb=UNLIMITED)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
@jinja2.pass_environment # type: ignore
|
|
35
|
-
def procedure_from_js_file(env: jinja2.Environment, file_name: str):
|
|
36
|
-
template = env.from_string(
|
|
37
|
-
dedent(
|
|
38
|
-
"""\
|
|
39
|
-
var module = {};
|
|
40
|
-
var exports = {};
|
|
41
|
-
module.exports = exports;
|
|
42
|
-
(function() {
|
|
43
|
-
{{ code }}
|
|
44
|
-
})()
|
|
45
|
-
return module.exports.apply(this, arguments);
|
|
46
|
-
"""
|
|
47
|
-
)
|
|
48
|
-
)
|
|
49
|
-
return template.render(
|
|
50
|
-
code=SecurePath(file_name).read_text(file_size_limit_mb=UNLIMITED)
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
_CUSTOM_FILTERS = [read_file_content, procedure_from_js_file]
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def env_bootstrap(env: Environment) -> Environment:
|
|
58
|
-
for custom_filter in _CUSTOM_FILTERS:
|
|
59
|
-
env.filters[custom_filter.__name__] = custom_filter
|
|
60
|
-
|
|
61
|
-
return env
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
class IgnoreAttrEnvironment(Environment):
|
|
65
|
-
"""
|
|
66
|
-
extend Environment class and ignore attributes during rendering.
|
|
67
|
-
This ensures that attributes of classes
|
|
68
|
-
do not get used during rendering (e.g. __class__, get, etc).
|
|
69
|
-
Only dict items can be used for rendering.
|
|
70
|
-
"""
|
|
71
|
-
|
|
72
|
-
def getattr(self, obj, attribute): # noqa: A003
|
|
73
|
-
try:
|
|
74
|
-
return obj[attribute]
|
|
75
|
-
except (TypeError, LookupError, AttributeError):
|
|
76
|
-
return self.undefined(obj=obj, name=attribute)
|
|
77
|
-
|
|
78
|
-
def getitem(self, obj, argument):
|
|
79
|
-
try:
|
|
80
|
-
return obj[argument]
|
|
81
|
-
except (AttributeError, TypeError, LookupError):
|
|
82
|
-
return self.undefined(obj=obj, name=argument)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def get_basic_jinja_env(loader: Optional[loaders.BaseLoader] = None) -> Environment:
|
|
86
|
-
return env_bootstrap(
|
|
87
|
-
IgnoreAttrEnvironment(
|
|
88
|
-
loader=loader or loaders.BaseLoader(),
|
|
89
|
-
keep_trailing_newline=True,
|
|
90
|
-
undefined=StrictUndefined,
|
|
91
|
-
)
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def jinja_render_from_file(
|
|
96
|
-
template_path: Path, data: Dict[str, Any], output_file_path: Optional[Path] = None
|
|
97
|
-
) -> Optional[str]:
|
|
98
|
-
"""
|
|
99
|
-
Renders a jinja template and outputs either the rendered contents as string or writes to a file.
|
|
100
|
-
|
|
101
|
-
Args:
|
|
102
|
-
template_path (Path): Path to the template
|
|
103
|
-
data (dict): A dictionary of jinja variables and their actual values
|
|
104
|
-
output_file_path (Optional[Path]): If provided then rendered template will be written to this file
|
|
105
|
-
|
|
106
|
-
Returns:
|
|
107
|
-
None if file path is provided, else returns the rendered string.
|
|
108
|
-
"""
|
|
109
|
-
env = get_basic_jinja_env(
|
|
110
|
-
loader=loaders.FileSystemLoader(template_path.parent.as_posix())
|
|
111
|
-
)
|
|
112
|
-
loaded_template = env.get_template(template_path.name)
|
|
113
|
-
rendered_result = loaded_template.render(**data)
|
|
114
|
-
if output_file_path:
|
|
115
|
-
SecurePath(output_file_path).write_text(rendered_result)
|
|
116
|
-
return None
|
|
117
|
-
else:
|
|
118
|
-
return rendered_result
|
|
@@ -1,43 +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
|
-
from jinja2 import Environment, StrictUndefined, loaders
|
|
18
|
-
from snowflake.cli.api.rendering.jinja import (
|
|
19
|
-
IgnoreAttrEnvironment,
|
|
20
|
-
env_bootstrap,
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
_YML_TEMPLATE_START = "<%"
|
|
24
|
-
_YML_TEMPLATE_END = "%>"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def has_client_side_templates(template_content: str) -> bool:
|
|
28
|
-
return _YML_TEMPLATE_START in template_content
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def get_client_side_jinja_env() -> Environment:
|
|
32
|
-
_random_block = "___very___unique___block___to___disable___logic___blocks___"
|
|
33
|
-
return env_bootstrap(
|
|
34
|
-
IgnoreAttrEnvironment(
|
|
35
|
-
loader=loaders.BaseLoader(),
|
|
36
|
-
keep_trailing_newline=True,
|
|
37
|
-
variable_start_string=_YML_TEMPLATE_START,
|
|
38
|
-
variable_end_string=_YML_TEMPLATE_END,
|
|
39
|
-
block_start_string=_random_block,
|
|
40
|
-
block_end_string=_random_block,
|
|
41
|
-
undefined=StrictUndefined,
|
|
42
|
-
)
|
|
43
|
-
)
|
|
@@ -1,98 +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
|
-
from typing import Any, Dict, List, Optional
|
|
18
|
-
|
|
19
|
-
from click import ClickException
|
|
20
|
-
from jinja2 import (
|
|
21
|
-
Environment,
|
|
22
|
-
StrictUndefined,
|
|
23
|
-
TemplateSyntaxError,
|
|
24
|
-
UndefinedError,
|
|
25
|
-
loaders,
|
|
26
|
-
)
|
|
27
|
-
from snowflake.cli.api.exceptions import InvalidTemplate
|
|
28
|
-
from snowflake.cli.api.rendering.jinja import IgnoreAttrEnvironment, env_bootstrap
|
|
29
|
-
from snowflake.cli.api.secure_path import SecurePath
|
|
30
|
-
|
|
31
|
-
_VARIABLE_TEMPLATE_START = "<!"
|
|
32
|
-
_VARIABLE_TEMPLATE_END = "!>"
|
|
33
|
-
_BLOCK_TEMPLATE_START = "<!!"
|
|
34
|
-
_BLOCK_TEMPLATE_END = "!!>"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def to_snowflake_identifier(value: Optional[str]) -> Optional[str]:
|
|
38
|
-
if not value:
|
|
39
|
-
# passing "None" through filter to allow jinja to handle "undefined value" exception
|
|
40
|
-
return value
|
|
41
|
-
|
|
42
|
-
import re
|
|
43
|
-
|
|
44
|
-
# TODO: remove code duplication when joining "init" with "snow app init"
|
|
45
|
-
# See https://docs.snowflake.com/en/sql-reference/identifiers-syntax for identifier syntax
|
|
46
|
-
unquoted_identifier_regex = r"([a-zA-Z_])([a-zA-Z0-9_$]{0,254})"
|
|
47
|
-
quoted_identifier_regex = r'"((""|[^"]){0,255})"'
|
|
48
|
-
|
|
49
|
-
if re.fullmatch(quoted_identifier_regex, value):
|
|
50
|
-
return value
|
|
51
|
-
|
|
52
|
-
result = re.sub(r"[. -]+", "_", value)
|
|
53
|
-
if not re.fullmatch(unquoted_identifier_regex, result):
|
|
54
|
-
raise ClickException(
|
|
55
|
-
f"Value '{value}' cannot be converted to valid Snowflake identifier."
|
|
56
|
-
' Consider enclosing it in double quotes: ""'
|
|
57
|
-
)
|
|
58
|
-
return result
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
PROJECT_TEMPLATE_FILTERS = [to_snowflake_identifier]
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def get_template_cli_jinja_env(template_root: SecurePath) -> Environment:
|
|
65
|
-
env = env_bootstrap(
|
|
66
|
-
IgnoreAttrEnvironment(
|
|
67
|
-
loader=loaders.FileSystemLoader(searchpath=template_root.path),
|
|
68
|
-
keep_trailing_newline=True,
|
|
69
|
-
variable_start_string=_VARIABLE_TEMPLATE_START,
|
|
70
|
-
variable_end_string=_VARIABLE_TEMPLATE_END,
|
|
71
|
-
block_start_string=_BLOCK_TEMPLATE_START,
|
|
72
|
-
block_end_string=_BLOCK_TEMPLATE_END,
|
|
73
|
-
undefined=StrictUndefined,
|
|
74
|
-
)
|
|
75
|
-
)
|
|
76
|
-
env.filters["to_snowflake_identifier"] = to_snowflake_identifier
|
|
77
|
-
|
|
78
|
-
return env
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def render_template_files(
|
|
82
|
-
template_root: SecurePath, files_to_render: List[str], data: Dict[str, Any]
|
|
83
|
-
) -> None:
|
|
84
|
-
"""Override all listed files with their rendered version."""
|
|
85
|
-
jinja_env = get_template_cli_jinja_env(template_root)
|
|
86
|
-
for path in files_to_render:
|
|
87
|
-
try:
|
|
88
|
-
jinja_template = jinja_env.get_template(path)
|
|
89
|
-
rendered_result = jinja_template.render(**data)
|
|
90
|
-
full_path = template_root / path
|
|
91
|
-
full_path.write_text(rendered_result)
|
|
92
|
-
except TemplateSyntaxError as err:
|
|
93
|
-
raise InvalidTemplate(
|
|
94
|
-
f"Invalid template syntax in line {err.lineno} of file {path}:\n"
|
|
95
|
-
f"{err.message}"
|
|
96
|
-
)
|
|
97
|
-
except UndefinedError as err:
|
|
98
|
-
raise InvalidTemplate(err.message)
|
|
@@ -1,105 +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
|
-
from typing import Dict, Optional
|
|
18
|
-
|
|
19
|
-
from click import ClickException
|
|
20
|
-
from jinja2 import Environment, StrictUndefined, loaders, meta
|
|
21
|
-
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
22
|
-
from snowflake.cli.api.console.console import cli_console
|
|
23
|
-
from snowflake.cli.api.exceptions import InvalidTemplate
|
|
24
|
-
from snowflake.cli.api.metrics import CLICounterField
|
|
25
|
-
from snowflake.cli.api.rendering.jinja import (
|
|
26
|
-
CONTEXT_KEY,
|
|
27
|
-
FUNCTION_KEY,
|
|
28
|
-
IgnoreAttrEnvironment,
|
|
29
|
-
env_bootstrap,
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
_SQL_TEMPLATE_START = "<%"
|
|
33
|
-
_SQL_TEMPLATE_END = "%>"
|
|
34
|
-
_OLD_SQL_TEMPLATE_START = "&{"
|
|
35
|
-
_OLD_SQL_TEMPLATE_END = "}"
|
|
36
|
-
RESERVED_KEYS = [CONTEXT_KEY, FUNCTION_KEY]
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def _get_sql_jinja_env(template_start: str, template_end: str) -> Environment:
|
|
40
|
-
_random_block = "___very___unique___block___to___disable___logic___blocks___"
|
|
41
|
-
return env_bootstrap(
|
|
42
|
-
IgnoreAttrEnvironment(
|
|
43
|
-
variable_start_string=template_start,
|
|
44
|
-
variable_end_string=template_end,
|
|
45
|
-
loader=loaders.BaseLoader(),
|
|
46
|
-
block_start_string=_random_block,
|
|
47
|
-
block_end_string=_random_block,
|
|
48
|
-
keep_trailing_newline=True,
|
|
49
|
-
undefined=StrictUndefined,
|
|
50
|
-
)
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def _does_template_have_env_syntax(env: Environment, template_content: str) -> bool:
|
|
55
|
-
template = env.parse(template_content)
|
|
56
|
-
return bool(meta.find_undeclared_variables(template))
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def has_sql_templates(template_content: str) -> bool:
|
|
60
|
-
return (
|
|
61
|
-
_OLD_SQL_TEMPLATE_START in template_content
|
|
62
|
-
or _SQL_TEMPLATE_START in template_content
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def choose_sql_jinja_env_based_on_template_syntax(
|
|
67
|
-
template_content: str, reference_name: Optional[str] = None
|
|
68
|
-
) -> Environment:
|
|
69
|
-
old_syntax_env = _get_sql_jinja_env(_OLD_SQL_TEMPLATE_START, _OLD_SQL_TEMPLATE_END)
|
|
70
|
-
new_syntax_env = _get_sql_jinja_env(_SQL_TEMPLATE_START, _SQL_TEMPLATE_END)
|
|
71
|
-
has_old_syntax = _does_template_have_env_syntax(old_syntax_env, template_content)
|
|
72
|
-
has_new_syntax = _does_template_have_env_syntax(new_syntax_env, template_content)
|
|
73
|
-
reference_name_str = f" in {reference_name}" if reference_name else ""
|
|
74
|
-
if has_old_syntax and has_new_syntax:
|
|
75
|
-
raise InvalidTemplate(
|
|
76
|
-
f"The SQL query{reference_name_str} mixes {_OLD_SQL_TEMPLATE_START} ... {_OLD_SQL_TEMPLATE_END} syntax"
|
|
77
|
-
f" and {_SQL_TEMPLATE_START} ... {_SQL_TEMPLATE_END} syntax."
|
|
78
|
-
)
|
|
79
|
-
if has_old_syntax:
|
|
80
|
-
cli_console.warning(
|
|
81
|
-
f"Warning: {_OLD_SQL_TEMPLATE_START} ... {_OLD_SQL_TEMPLATE_END} syntax{reference_name_str} is deprecated."
|
|
82
|
-
f" Use {_SQL_TEMPLATE_START} ... {_SQL_TEMPLATE_END} syntax instead."
|
|
83
|
-
)
|
|
84
|
-
return old_syntax_env
|
|
85
|
-
return new_syntax_env
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def snowflake_sql_jinja_render(content: str, data: Dict | None = None) -> str:
|
|
89
|
-
data = data or {}
|
|
90
|
-
|
|
91
|
-
for reserved_key in RESERVED_KEYS:
|
|
92
|
-
if reserved_key in data:
|
|
93
|
-
raise ClickException(
|
|
94
|
-
f"{reserved_key} in user defined data. The `{reserved_key}` variable is reserved for CLI usage."
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
context_data = get_cli_context().template_context
|
|
98
|
-
context_data.update(data)
|
|
99
|
-
env = choose_sql_jinja_env_based_on_template_syntax(content)
|
|
100
|
-
|
|
101
|
-
get_cli_context().metrics.set_counter(
|
|
102
|
-
CLICounterField.SQL_TEMPLATES, int(has_sql_templates(content))
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
return env.from_string(content).render(context_data)
|