snowflake-cli 2.8.2__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.
Files changed (240) hide show
  1. snowflake/cli/__about__.py +17 -0
  2. snowflake/cli/__init__.py +13 -0
  3. snowflake/cli/api/__init__.py +48 -0
  4. snowflake/cli/api/cli_global_context.py +390 -0
  5. snowflake/cli/api/commands/__init__.py +13 -0
  6. snowflake/cli/api/commands/alias.py +23 -0
  7. snowflake/cli/api/commands/decorators.py +354 -0
  8. snowflake/cli/api/commands/execution_metadata.py +40 -0
  9. snowflake/cli/api/commands/experimental_behaviour.py +19 -0
  10. snowflake/cli/api/commands/flags.py +662 -0
  11. snowflake/cli/api/commands/project_initialisation.py +65 -0
  12. snowflake/cli/api/commands/snow_typer.py +237 -0
  13. snowflake/cli/api/commands/typer_pre_execute.py +26 -0
  14. snowflake/cli/api/config.py +348 -0
  15. snowflake/cli/api/console/__init__.py +17 -0
  16. snowflake/cli/api/console/abc.py +89 -0
  17. snowflake/cli/api/console/console.py +134 -0
  18. snowflake/cli/api/console/enum.py +17 -0
  19. snowflake/cli/api/constants.py +79 -0
  20. snowflake/cli/api/errno.py +27 -0
  21. snowflake/cli/api/exceptions.py +164 -0
  22. snowflake/cli/api/feature_flags.py +55 -0
  23. snowflake/cli/api/identifiers.py +167 -0
  24. snowflake/cli/api/output/__init__.py +13 -0
  25. snowflake/cli/api/output/formats.py +20 -0
  26. snowflake/cli/api/output/types.py +118 -0
  27. snowflake/cli/api/plugins/__init__.py +13 -0
  28. snowflake/cli/api/plugins/command/__init__.py +72 -0
  29. snowflake/cli/api/plugins/command/plugin_hook_specs.py +21 -0
  30. snowflake/cli/api/plugins/plugin_config.py +32 -0
  31. snowflake/cli/api/project/__init__.py +13 -0
  32. snowflake/cli/api/project/definition.py +84 -0
  33. snowflake/cli/api/project/definition_manager.py +134 -0
  34. snowflake/cli/api/project/errors.py +56 -0
  35. snowflake/cli/api/project/project_verification.py +23 -0
  36. snowflake/cli/api/project/schemas/__init__.py +13 -0
  37. snowflake/cli/api/project/schemas/entities/application_entity.py +44 -0
  38. snowflake/cli/api/project/schemas/entities/application_package_entity.py +66 -0
  39. snowflake/cli/api/project/schemas/entities/common.py +78 -0
  40. snowflake/cli/api/project/schemas/entities/entities.py +30 -0
  41. snowflake/cli/api/project/schemas/identifier_model.py +49 -0
  42. snowflake/cli/api/project/schemas/native_app/__init__.py +13 -0
  43. snowflake/cli/api/project/schemas/native_app/application.py +62 -0
  44. snowflake/cli/api/project/schemas/native_app/native_app.py +93 -0
  45. snowflake/cli/api/project/schemas/native_app/package.py +78 -0
  46. snowflake/cli/api/project/schemas/native_app/path_mapping.py +65 -0
  47. snowflake/cli/api/project/schemas/project_definition.py +199 -0
  48. snowflake/cli/api/project/schemas/snowpark/__init__.py +13 -0
  49. snowflake/cli/api/project/schemas/snowpark/argument.py +28 -0
  50. snowflake/cli/api/project/schemas/snowpark/callable.py +69 -0
  51. snowflake/cli/api/project/schemas/snowpark/snowpark.py +36 -0
  52. snowflake/cli/api/project/schemas/streamlit/__init__.py +13 -0
  53. snowflake/cli/api/project/schemas/streamlit/streamlit.py +46 -0
  54. snowflake/cli/api/project/schemas/template.py +77 -0
  55. snowflake/cli/api/project/schemas/updatable_model.py +194 -0
  56. snowflake/cli/api/project/util.py +261 -0
  57. snowflake/cli/api/rendering/__init__.py +13 -0
  58. snowflake/cli/api/rendering/jinja.py +112 -0
  59. snowflake/cli/api/rendering/project_definition_templates.py +39 -0
  60. snowflake/cli/api/rendering/project_templates.py +98 -0
  61. snowflake/cli/api/rendering/sql_templates.py +60 -0
  62. snowflake/cli/api/rest_api.py +172 -0
  63. snowflake/cli/api/sanitizers.py +43 -0
  64. snowflake/cli/api/secure_path.py +362 -0
  65. snowflake/cli/api/secure_utils.py +29 -0
  66. snowflake/cli/api/sql_execution.py +260 -0
  67. snowflake/cli/api/utils/__init__.py +13 -0
  68. snowflake/cli/api/utils/cursor.py +34 -0
  69. snowflake/cli/api/utils/definition_rendering.py +383 -0
  70. snowflake/cli/api/utils/dict_utils.py +73 -0
  71. snowflake/cli/api/utils/error_handling.py +23 -0
  72. snowflake/cli/api/utils/graph.py +97 -0
  73. snowflake/cli/api/utils/models.py +63 -0
  74. snowflake/cli/api/utils/naming_utils.py +13 -0
  75. snowflake/cli/api/utils/path_utils.py +36 -0
  76. snowflake/cli/api/utils/templating_functions.py +144 -0
  77. snowflake/cli/api/utils/types.py +35 -0
  78. snowflake/cli/app/__init__.py +22 -0
  79. snowflake/cli/app/__main__.py +31 -0
  80. snowflake/cli/app/api_impl/__init__.py +13 -0
  81. snowflake/cli/app/api_impl/plugin/__init__.py +13 -0
  82. snowflake/cli/app/api_impl/plugin/plugin_config_provider_impl.py +66 -0
  83. snowflake/cli/app/build_and_push.sh +8 -0
  84. snowflake/cli/app/cli_app.py +243 -0
  85. snowflake/cli/app/commands_registration/__init__.py +33 -0
  86. snowflake/cli/app/commands_registration/builtin_plugins.py +54 -0
  87. snowflake/cli/app/commands_registration/command_plugins_loader.py +169 -0
  88. snowflake/cli/app/commands_registration/commands_registration_with_callbacks.py +105 -0
  89. snowflake/cli/app/commands_registration/exception_logging.py +26 -0
  90. snowflake/cli/app/commands_registration/threadsafe.py +48 -0
  91. snowflake/cli/app/commands_registration/typer_registration.py +153 -0
  92. snowflake/cli/app/constants.py +19 -0
  93. snowflake/cli/app/dev/__init__.py +13 -0
  94. snowflake/cli/app/dev/commands_structure.py +48 -0
  95. snowflake/cli/app/dev/docs/__init__.py +13 -0
  96. snowflake/cli/app/dev/docs/commands_docs_generator.py +100 -0
  97. snowflake/cli/app/dev/docs/generator.py +35 -0
  98. snowflake/cli/app/dev/docs/project_definition_docs_generator.py +58 -0
  99. snowflake/cli/app/dev/docs/project_definition_generate_json_schema.py +227 -0
  100. snowflake/cli/app/dev/docs/template_utils.py +23 -0
  101. snowflake/cli/app/dev/docs/templates/definition_description.rst.jinja2 +38 -0
  102. snowflake/cli/app/dev/docs/templates/overview.rst.jinja2 +9 -0
  103. snowflake/cli/app/dev/docs/templates/usage.rst.jinja2 +57 -0
  104. snowflake/cli/app/dev/pycharm_remote_debug.py +46 -0
  105. snowflake/cli/app/loggers.py +199 -0
  106. snowflake/cli/app/main_typer.py +62 -0
  107. snowflake/cli/app/printing.py +181 -0
  108. snowflake/cli/app/snow_connector.py +243 -0
  109. snowflake/cli/app/telemetry.py +189 -0
  110. snowflake/cli/plugins/__init__.py +13 -0
  111. snowflake/cli/plugins/connection/__init__.py +13 -0
  112. snowflake/cli/plugins/connection/commands.py +330 -0
  113. snowflake/cli/plugins/connection/plugin_spec.py +30 -0
  114. snowflake/cli/plugins/connection/util.py +179 -0
  115. snowflake/cli/plugins/cortex/__init__.py +13 -0
  116. snowflake/cli/plugins/cortex/commands.py +327 -0
  117. snowflake/cli/plugins/cortex/constants.py +17 -0
  118. snowflake/cli/plugins/cortex/manager.py +189 -0
  119. snowflake/cli/plugins/cortex/plugin_spec.py +30 -0
  120. snowflake/cli/plugins/cortex/types.py +22 -0
  121. snowflake/cli/plugins/git/__init__.py +13 -0
  122. snowflake/cli/plugins/git/commands.py +354 -0
  123. snowflake/cli/plugins/git/manager.py +105 -0
  124. snowflake/cli/plugins/git/plugin_spec.py +30 -0
  125. snowflake/cli/plugins/init/__init__.py +13 -0
  126. snowflake/cli/plugins/init/commands.py +248 -0
  127. snowflake/cli/plugins/init/plugin_spec.py +30 -0
  128. snowflake/cli/plugins/nativeapp/__init__.py +13 -0
  129. snowflake/cli/plugins/nativeapp/artifacts.py +742 -0
  130. snowflake/cli/plugins/nativeapp/codegen/__init__.py +13 -0
  131. snowflake/cli/plugins/nativeapp/codegen/artifact_processor.py +91 -0
  132. snowflake/cli/plugins/nativeapp/codegen/compiler.py +130 -0
  133. snowflake/cli/plugins/nativeapp/codegen/sandbox.py +306 -0
  134. snowflake/cli/plugins/nativeapp/codegen/setup/native_app_setup_processor.py +172 -0
  135. snowflake/cli/plugins/nativeapp/codegen/setup/setup_driver.py.source +56 -0
  136. snowflake/cli/plugins/nativeapp/codegen/snowpark/callback_source.py.jinja +181 -0
  137. snowflake/cli/plugins/nativeapp/codegen/snowpark/extension_function_utils.py +217 -0
  138. snowflake/cli/plugins/nativeapp/codegen/snowpark/models.py +61 -0
  139. snowflake/cli/plugins/nativeapp/codegen/snowpark/python_processor.py +528 -0
  140. snowflake/cli/plugins/nativeapp/commands.py +439 -0
  141. snowflake/cli/plugins/nativeapp/common_flags.py +44 -0
  142. snowflake/cli/plugins/nativeapp/constants.py +27 -0
  143. snowflake/cli/plugins/nativeapp/exceptions.py +122 -0
  144. snowflake/cli/plugins/nativeapp/feature_flags.py +24 -0
  145. snowflake/cli/plugins/nativeapp/init.py +345 -0
  146. snowflake/cli/plugins/nativeapp/manager.py +823 -0
  147. snowflake/cli/plugins/nativeapp/plugin_spec.py +30 -0
  148. snowflake/cli/plugins/nativeapp/policy.py +50 -0
  149. snowflake/cli/plugins/nativeapp/project_model.py +195 -0
  150. snowflake/cli/plugins/nativeapp/run_processor.py +389 -0
  151. snowflake/cli/plugins/nativeapp/teardown_processor.py +301 -0
  152. snowflake/cli/plugins/nativeapp/utils.py +98 -0
  153. snowflake/cli/plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +135 -0
  154. snowflake/cli/plugins/nativeapp/version/__init__.py +13 -0
  155. snowflake/cli/plugins/nativeapp/version/commands.py +170 -0
  156. snowflake/cli/plugins/nativeapp/version/version_processor.py +362 -0
  157. snowflake/cli/plugins/notebook/__init__.py +13 -0
  158. snowflake/cli/plugins/notebook/commands.py +85 -0
  159. snowflake/cli/plugins/notebook/exceptions.py +20 -0
  160. snowflake/cli/plugins/notebook/manager.py +71 -0
  161. snowflake/cli/plugins/notebook/plugin_spec.py +30 -0
  162. snowflake/cli/plugins/notebook/types.py +15 -0
  163. snowflake/cli/plugins/object/__init__.py +13 -0
  164. snowflake/cli/plugins/object/command_aliases.py +95 -0
  165. snowflake/cli/plugins/object/commands.py +181 -0
  166. snowflake/cli/plugins/object/common.py +85 -0
  167. snowflake/cli/plugins/object/manager.py +97 -0
  168. snowflake/cli/plugins/object/plugin_spec.py +30 -0
  169. snowflake/cli/plugins/object_stage_deprecated/__init__.py +15 -0
  170. snowflake/cli/plugins/object_stage_deprecated/commands.py +122 -0
  171. snowflake/cli/plugins/object_stage_deprecated/plugin_spec.py +32 -0
  172. snowflake/cli/plugins/snowpark/__init__.py +13 -0
  173. snowflake/cli/plugins/snowpark/commands.py +546 -0
  174. snowflake/cli/plugins/snowpark/common.py +307 -0
  175. snowflake/cli/plugins/snowpark/manager.py +109 -0
  176. snowflake/cli/plugins/snowpark/models.py +157 -0
  177. snowflake/cli/plugins/snowpark/package/__init__.py +13 -0
  178. snowflake/cli/plugins/snowpark/package/anaconda_packages.py +233 -0
  179. snowflake/cli/plugins/snowpark/package/commands.py +256 -0
  180. snowflake/cli/plugins/snowpark/package/manager.py +44 -0
  181. snowflake/cli/plugins/snowpark/package/utils.py +26 -0
  182. snowflake/cli/plugins/snowpark/package_utils.py +354 -0
  183. snowflake/cli/plugins/snowpark/plugin_spec.py +30 -0
  184. snowflake/cli/plugins/snowpark/snowpark_package_paths.py +65 -0
  185. snowflake/cli/plugins/snowpark/snowpark_shared.py +95 -0
  186. snowflake/cli/plugins/snowpark/zipper.py +81 -0
  187. snowflake/cli/plugins/spcs/__init__.py +35 -0
  188. snowflake/cli/plugins/spcs/common.py +99 -0
  189. snowflake/cli/plugins/spcs/compute_pool/__init__.py +13 -0
  190. snowflake/cli/plugins/spcs/compute_pool/commands.py +241 -0
  191. snowflake/cli/plugins/spcs/compute_pool/manager.py +121 -0
  192. snowflake/cli/plugins/spcs/image_registry/__init__.py +13 -0
  193. snowflake/cli/plugins/spcs/image_registry/commands.py +65 -0
  194. snowflake/cli/plugins/spcs/image_registry/manager.py +105 -0
  195. snowflake/cli/plugins/spcs/image_repository/__init__.py +13 -0
  196. snowflake/cli/plugins/spcs/image_repository/commands.py +202 -0
  197. snowflake/cli/plugins/spcs/image_repository/manager.py +84 -0
  198. snowflake/cli/plugins/spcs/jobs/__init__.py +13 -0
  199. snowflake/cli/plugins/spcs/jobs/commands.py +78 -0
  200. snowflake/cli/plugins/spcs/jobs/manager.py +53 -0
  201. snowflake/cli/plugins/spcs/plugin_spec.py +30 -0
  202. snowflake/cli/plugins/spcs/services/__init__.py +13 -0
  203. snowflake/cli/plugins/spcs/services/commands.py +312 -0
  204. snowflake/cli/plugins/spcs/services/manager.py +170 -0
  205. snowflake/cli/plugins/sql/__init__.py +13 -0
  206. snowflake/cli/plugins/sql/commands.py +83 -0
  207. snowflake/cli/plugins/sql/manager.py +92 -0
  208. snowflake/cli/plugins/sql/plugin_spec.py +30 -0
  209. snowflake/cli/plugins/sql/snowsql_templating.py +28 -0
  210. snowflake/cli/plugins/stage/__init__.py +13 -0
  211. snowflake/cli/plugins/stage/commands.py +263 -0
  212. snowflake/cli/plugins/stage/diff.py +326 -0
  213. snowflake/cli/plugins/stage/manager.py +577 -0
  214. snowflake/cli/plugins/stage/md5.py +160 -0
  215. snowflake/cli/plugins/stage/plugin_spec.py +30 -0
  216. snowflake/cli/plugins/streamlit/__init__.py +13 -0
  217. snowflake/cli/plugins/streamlit/commands.py +179 -0
  218. snowflake/cli/plugins/streamlit/manager.py +222 -0
  219. snowflake/cli/plugins/streamlit/plugin_spec.py +30 -0
  220. snowflake/cli/plugins/workspace/__init__.py +13 -0
  221. snowflake/cli/plugins/workspace/commands.py +35 -0
  222. snowflake/cli/plugins/workspace/plugin_spec.py +30 -0
  223. snowflake/cli/templates/default_snowpark/.gitignore +4 -0
  224. snowflake/cli/templates/default_snowpark/app/__init__.py +0 -0
  225. snowflake/cli/templates/default_snowpark/app/common.py +2 -0
  226. snowflake/cli/templates/default_snowpark/app/functions.py +15 -0
  227. snowflake/cli/templates/default_snowpark/app/procedures.py +22 -0
  228. snowflake/cli/templates/default_snowpark/requirements.txt +1 -0
  229. snowflake/cli/templates/default_snowpark/snowflake.yml +23 -0
  230. snowflake/cli/templates/default_streamlit/.gitignore +4 -0
  231. snowflake/cli/templates/default_streamlit/common/hello.py +2 -0
  232. snowflake/cli/templates/default_streamlit/environment.yml +6 -0
  233. snowflake/cli/templates/default_streamlit/pages/my_page.py +3 -0
  234. snowflake/cli/templates/default_streamlit/snowflake.yml +10 -0
  235. snowflake/cli/templates/default_streamlit/streamlit_app.py +4 -0
  236. snowflake_cli-2.8.2.dist-info/METADATA +325 -0
  237. snowflake_cli-2.8.2.dist-info/RECORD +240 -0
  238. snowflake_cli-2.8.2.dist-info/WHEEL +4 -0
  239. snowflake_cli-2.8.2.dist-info/entry_points.txt +2 -0
  240. snowflake_cli-2.8.2.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,260 @@
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 logging
18
+ from contextlib import contextmanager
19
+ from functools import cached_property
20
+ from io import StringIO
21
+ from textwrap import dedent
22
+ from typing import Iterable, Optional, Tuple
23
+
24
+ from snowflake.cli.api.cli_global_context import cli_context
25
+ from snowflake.cli.api.console import cli_console
26
+ from snowflake.cli.api.constants import ObjectType
27
+ from snowflake.cli.api.exceptions import (
28
+ DatabaseNotProvidedError,
29
+ SchemaNotProvidedError,
30
+ SnowflakeSQLExecutionError,
31
+ )
32
+ from snowflake.cli.api.identifiers import FQN
33
+ from snowflake.cli.api.project.util import (
34
+ identifier_to_show_like_pattern,
35
+ unquote_identifier,
36
+ )
37
+ from snowflake.cli.api.utils.cursor import find_first_row
38
+ from snowflake.connector.cursor import DictCursor, SnowflakeCursor
39
+ from snowflake.connector.errors import ProgrammingError
40
+
41
+
42
+ class SqlExecutionMixin:
43
+ def __init__(self):
44
+ self._snowpark_session = None
45
+
46
+ @property
47
+ def _conn(self):
48
+ return cli_context.connection
49
+
50
+ @property
51
+ def snowpark_session(self):
52
+ if not self._snowpark_session:
53
+ from snowflake.snowpark.session import Session
54
+
55
+ self._snowpark_session = Session.builder.configs(
56
+ {"connection": self._conn}
57
+ ).create()
58
+ return self._snowpark_session
59
+
60
+ @cached_property
61
+ def _log(self):
62
+ return logging.getLogger(__name__)
63
+
64
+ def _execute_string(
65
+ self,
66
+ sql_text: str,
67
+ remove_comments: bool = False,
68
+ return_cursors: bool = True,
69
+ cursor_class: SnowflakeCursor = SnowflakeCursor,
70
+ **kwargs,
71
+ ) -> Iterable[SnowflakeCursor]:
72
+ """
73
+ This is a custom implementation of SnowflakeConnection.execute_string that returns generator
74
+ instead of list. In case of executing multiple queries are executed one by one. This mean we can
75
+ access result of previous queries while evaluating next one. For example, we can print the results.
76
+ """
77
+ self._log.debug("Executing %s", sql_text)
78
+ stream = StringIO(sql_text)
79
+ stream_generator = self._conn.execute_stream(
80
+ stream, remove_comments=remove_comments, cursor_class=cursor_class, **kwargs
81
+ )
82
+ return stream_generator if return_cursors else list()
83
+
84
+ def _execute_query(self, query: str, **kwargs):
85
+ *_, last_result = self._execute_queries(query, **kwargs)
86
+ return last_result
87
+
88
+ def _execute_queries(self, queries: str, **kwargs):
89
+ return list(self._execute_string(dedent(queries), **kwargs))
90
+
91
+ def use(self, object_type: ObjectType, name: str):
92
+ try:
93
+ self._execute_query(f"use {object_type.value.sf_name} {name}")
94
+ except ProgrammingError:
95
+ # Rewrite the error to make the message more useful.
96
+ raise ProgrammingError(
97
+ f"Could not use {object_type} {name}. Object does not exist, or operation cannot be performed."
98
+ )
99
+
100
+ @contextmanager
101
+ def use_role(self, new_role: str):
102
+ """
103
+ Switches to a different role for a while, then switches back.
104
+ This is a no-op if the requested role is already active.
105
+ """
106
+ role_result = self._execute_query(
107
+ f"select current_role()", cursor_class=DictCursor
108
+ ).fetchone()
109
+ prev_role = role_result["CURRENT_ROLE()"]
110
+ is_different_role = new_role.lower() != prev_role.lower()
111
+ if is_different_role:
112
+ self._log.debug("Assuming different role: %s", new_role)
113
+ self._execute_query(f"use role {new_role}")
114
+ try:
115
+ yield
116
+ finally:
117
+ if is_different_role:
118
+ self._execute_query(f"use role {prev_role}")
119
+
120
+ @contextmanager
121
+ def use_warehouse(self, new_wh: str):
122
+ """
123
+ Switches to a different warehouse for a while, then switches back.
124
+ This is a no-op if the requested warehouse is already active.
125
+ If there is no default warehouse in the account, it will throw an error.
126
+ """
127
+
128
+ wh_result = self._execute_query(
129
+ f"select current_warehouse()", cursor_class=DictCursor
130
+ ).fetchone()
131
+ # If user has an assigned default warehouse, prev_wh will contain a value even if the warehouse is suspended.
132
+ try:
133
+ prev_wh = wh_result["CURRENT_WAREHOUSE()"]
134
+ except:
135
+ prev_wh = None
136
+
137
+ # new_wh is not None, and should already be a valid identifier, no additional check is performed here.
138
+ is_different_wh = new_wh != prev_wh
139
+ try:
140
+ if is_different_wh:
141
+ self._log.debug("Using warehouse: %s", new_wh)
142
+ self.use(object_type=ObjectType.WAREHOUSE, name=new_wh)
143
+ yield
144
+ finally:
145
+ if prev_wh and is_different_wh:
146
+ self._log.debug("Switching back to warehouse: %s", prev_wh)
147
+ self.use(object_type=ObjectType.WAREHOUSE, name=prev_wh)
148
+
149
+ def create_password_secret(
150
+ self, name: FQN, username: str, password: str
151
+ ) -> SnowflakeCursor:
152
+ return self._execute_query(
153
+ f"""
154
+ create secret {name.sql_identifier}
155
+ type = password
156
+ username = '{username}'
157
+ password = '{password}'
158
+ """
159
+ )
160
+
161
+ def create_api_integration(
162
+ self, name: FQN, api_provider: str, allowed_prefix: str, secret: Optional[str]
163
+ ) -> SnowflakeCursor:
164
+ return self._execute_query(
165
+ f"""
166
+ create api integration {name.sql_identifier}
167
+ api_provider = {api_provider}
168
+ api_allowed_prefixes = ('{allowed_prefix}')
169
+ allowed_authentication_secrets = ({secret if secret else ''})
170
+ enabled = true
171
+ """
172
+ )
173
+
174
+ def _execute_schema_query(self, query: str, name: Optional[str] = None, **kwargs):
175
+ """
176
+ Check that a database and schema are provided before executing the query. Useful for operating on schema level objects.
177
+ """
178
+ self.check_database_and_schema_provided(name)
179
+ return self._execute_query(query, **kwargs)
180
+
181
+ def check_database_and_schema_provided(self, name: Optional[str] = None) -> None:
182
+ """
183
+ Checks if a database and schema are provided, either through the connection context or a qualified name.
184
+ """
185
+ fqn = FQN.from_string(name).using_connection(self._conn)
186
+ if not fqn.database:
187
+ raise DatabaseNotProvidedError()
188
+ if not fqn.schema:
189
+ raise SchemaNotProvidedError()
190
+
191
+ @staticmethod
192
+ def _qualified_name_to_in_clause(identifier: FQN) -> Tuple[str, Optional[str]]:
193
+ if identifier.database:
194
+ schema = identifier.schema or "PUBLIC"
195
+ in_clause = f"in schema {identifier.database}.{schema}"
196
+ elif identifier.schema:
197
+ in_clause = f"in schema {identifier.schema}"
198
+ else:
199
+ in_clause = None
200
+ return identifier.name, in_clause
201
+
202
+ class InClauseWithQualifiedNameError(ValueError):
203
+ def __init__(self):
204
+ super().__init__("non-empty 'in_clause' passed with qualified 'name'")
205
+
206
+ def show_specific_object(
207
+ self,
208
+ object_type_plural: str,
209
+ name: str,
210
+ name_col: str = "name",
211
+ in_clause: str = "",
212
+ check_schema: bool = False,
213
+ ) -> Optional[dict]:
214
+ """
215
+ Executes a "show <objects> like" query for a particular entity with a
216
+ given (optionally qualified) name. This command is useful when the corresponding
217
+ "describe <object>" query does not provide the information you seek.
218
+
219
+ Note that this command is analogous to describe and should only return a single row.
220
+ If the target object type is a schema level object, then check_schema should be set to True
221
+ so that the function will verify that a database and schema are provided, either through
222
+ the connection or a qualified name, before executing the query.
223
+ """
224
+
225
+ unqualified_name, name_in_clause = self._qualified_name_to_in_clause(
226
+ FQN.from_string(name)
227
+ )
228
+ if in_clause and name_in_clause:
229
+ raise self.InClauseWithQualifiedNameError()
230
+ elif name_in_clause:
231
+ in_clause = name_in_clause
232
+ show_obj_query = f"show {object_type_plural} like {identifier_to_show_like_pattern(unqualified_name)} {in_clause}".strip()
233
+
234
+ if check_schema:
235
+ show_obj_cursor = self._execute_schema_query( # type: ignore
236
+ show_obj_query, name=name, cursor_class=DictCursor
237
+ )
238
+ else:
239
+ show_obj_cursor = self._execute_query( # type: ignore
240
+ show_obj_query, cursor_class=DictCursor
241
+ )
242
+
243
+ if show_obj_cursor.rowcount is None:
244
+ raise SnowflakeSQLExecutionError(show_obj_query)
245
+ elif show_obj_cursor.rowcount > 1:
246
+ raise ProgrammingError(
247
+ f"Received multiple rows from result of SQL statement: {show_obj_query}. Usage of 'show_specific_object' may not be properly scoped."
248
+ )
249
+
250
+ show_obj_row = find_first_row(
251
+ show_obj_cursor,
252
+ lambda row: row[name_col] == unquote_identifier(unqualified_name),
253
+ )
254
+ return show_obj_row
255
+
256
+
257
+ class VerboseCursor(SnowflakeCursor):
258
+ def execute(self, command: str, *args, **kwargs):
259
+ cli_console.message(command)
260
+ super().execute(command, *args, **kwargs)
@@ -0,0 +1,13 @@
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.
@@ -0,0 +1,34 @@
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 Callable, List, Optional
18
+
19
+ from snowflake.connector.cursor import DictCursor
20
+
21
+
22
+ def _rows_generator(cursor: DictCursor, predicate: Callable[[dict], bool]):
23
+ return (row for row in cursor.fetchall() if predicate(row))
24
+
25
+
26
+ def find_all_rows(cursor: DictCursor, predicate: Callable[[dict], bool]) -> List[dict]:
27
+ return list(_rows_generator(cursor, predicate))
28
+
29
+
30
+ def find_first_row(
31
+ cursor: DictCursor, predicate: Callable[[dict], bool]
32
+ ) -> Optional[dict]:
33
+ """Returns the first row that matches the predicate, or None."""
34
+ return next(_rows_generator(cursor, predicate), None)
@@ -0,0 +1,383 @@
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 copy
18
+ from typing import Any, Optional
19
+
20
+ from jinja2 import Environment, TemplateSyntaxError, nodes
21
+ from packaging.version import Version
22
+ from snowflake.cli.api.console import cli_console as cc
23
+ from snowflake.cli.api.exceptions import CycleDetectedError, InvalidTemplate
24
+ from snowflake.cli.api.project.schemas.project_definition import (
25
+ ProjectProperties,
26
+ build_project_definition,
27
+ )
28
+ from snowflake.cli.api.project.schemas.updatable_model import context
29
+ from snowflake.cli.api.rendering.jinja import CONTEXT_KEY
30
+ from snowflake.cli.api.rendering.project_definition_templates import (
31
+ get_project_definition_cli_jinja_env,
32
+ )
33
+ from snowflake.cli.api.utils.dict_utils import deep_merge_dicts, traverse
34
+ from snowflake.cli.api.utils.graph import Graph, Node
35
+ from snowflake.cli.api.utils.models import ProjectEnvironment
36
+ from snowflake.cli.api.utils.templating_functions import get_templating_functions
37
+ from snowflake.cli.api.utils.types import Context, Definition
38
+
39
+
40
+ class TemplatedEnvironment:
41
+ """
42
+ This class is a utility class
43
+ that encapsulates some of the Jinja Templating functionality.
44
+ """
45
+
46
+ def __init__(self, env: Environment):
47
+ self._jinja_env: Environment = env
48
+
49
+ def render(self, template_value: Any, context: Context) -> Any:
50
+ if not self.get_referenced_vars(template_value):
51
+ return template_value
52
+
53
+ jinja_template = self._jinja_env.from_string(str(template_value))
54
+ return jinja_template.render(context)
55
+
56
+ def get_referenced_vars(self, template_value: Any) -> set[TemplateVar]:
57
+ template_str = str(template_value)
58
+ try:
59
+ ast = self._jinja_env.parse(template_str)
60
+ except TemplateSyntaxError as e:
61
+ raise InvalidTemplate(
62
+ f"Error parsing template from project definition file. Value: '{template_str}'. Error: {e}"
63
+ ) from e
64
+
65
+ return self._get_referenced_vars(ast, template_str)
66
+
67
+ def _get_referenced_vars(
68
+ self,
69
+ ast_node: nodes.Template,
70
+ template_value: str,
71
+ current_attr_chain: Optional[list[str]] = None,
72
+ ) -> set[TemplateVar]:
73
+ """
74
+ Traverse Jinja AST to find the variable chain referenced by the template.
75
+ A variable like ctx.env.test is internally represented in the AST tree as
76
+ Getattr Node (attr='test') -> Getattr Node (attr='env') -> Name Node (name='ctx')
77
+ """
78
+ all_referenced_vars: set[TemplateVar] = set()
79
+ if isinstance(ast_node, nodes.Getattr):
80
+ current_attr_chain = [ast_node.attr] + (current_attr_chain or []) # type: ignore[attr-defined]
81
+ elif isinstance(ast_node, nodes.Name):
82
+ current_attr_chain = [ast_node.name] + (current_attr_chain or []) # type: ignore[attr-defined]
83
+ all_referenced_vars.add(TemplateVar(current_attr_chain))
84
+ current_attr_chain = None
85
+ elif (
86
+ not isinstance(
87
+ ast_node,
88
+ (
89
+ nodes.Template,
90
+ nodes.TemplateData,
91
+ nodes.Output,
92
+ nodes.Call,
93
+ nodes.Const,
94
+ nodes.Filter,
95
+ ),
96
+ )
97
+ or current_attr_chain is not None
98
+ ):
99
+ raise InvalidTemplate(f"Unexpected templating syntax in {template_value}")
100
+
101
+ for child_node in ast_node.iter_child_nodes():
102
+ all_referenced_vars.update(
103
+ self._get_referenced_vars(
104
+ child_node, template_value, current_attr_chain
105
+ )
106
+ )
107
+
108
+ return all_referenced_vars
109
+
110
+
111
+ class TemplateVar:
112
+ """
113
+ This class tracks template variable information.
114
+ For a variable like ctx.env.var, this class will track
115
+ the chain of keys referenced by this variable (ctx, env, var),
116
+ as well as the value of this variable. (e.g. ctx.env.var = "hello_<% ctx.definition_version %>")
117
+
118
+ The value of this variable is divided into 2 parts.
119
+ The templated value (e.g. "hello_<% ctx.definition %>"),
120
+ as well as the rendered_value (e.g. "hello_1.1")
121
+ """
122
+
123
+ def __init__(self, vars_chain):
124
+ self._vars_chain: list[str] = list(vars_chain)
125
+ self.templated_value: Optional[Any] = None
126
+ self.rendered_value: Optional[Any] = None
127
+
128
+ @property
129
+ def key(self) -> str:
130
+ return ".".join(self._vars_chain)
131
+
132
+ @property
133
+ def is_env_var(self) -> bool:
134
+ return (
135
+ len(self._vars_chain) == 3
136
+ and self._vars_chain[0] == CONTEXT_KEY
137
+ and self._vars_chain[1] == "env"
138
+ )
139
+
140
+ def get_env_var_name(self) -> str:
141
+ if not self.is_env_var:
142
+ raise KeyError(
143
+ f"Referenced variable {self.key} is not an environment variable"
144
+ )
145
+ return self._vars_chain[2]
146
+
147
+ def add_to_context(self, context: Context) -> None:
148
+ """
149
+ Takes a multi-level context dict as input. Modifies the context dict with the rendered value of this variable.
150
+
151
+ If the variable has multi-levels (e.g. ctx.env), recursively traverse the dictionary
152
+ to set the inner level's key to the rendered value of this variable.
153
+
154
+ Example: vars chain contains ['ctx', 'env', 'x'], and context is {}, and rendered_value is 'val'.
155
+ At the end of this call, context content will be: {'ctx': {'env': {'x': 'val'}}}
156
+ """
157
+ current_dict_level = context
158
+ last_element_index = len(self._vars_chain) - 1
159
+ for index, var in enumerate(self._vars_chain):
160
+ if index == last_element_index:
161
+ current_dict_level[var] = self.rendered_value
162
+ else:
163
+ current_dict_level.setdefault(var, {})
164
+ current_dict_level = current_dict_level[var]
165
+
166
+ def read_from_context(self, context: Context) -> Any:
167
+ """
168
+ Takes a multi-level context dict as input.
169
+
170
+ If the variable has multi-levels (e.g. ctx.env), recursively traverse the dictionary
171
+ to find the key that the variable points to.
172
+
173
+ Returns the value in that location.
174
+
175
+ Raise InvalidTemplate if the variable is None or not found.
176
+ """
177
+ current_dict_level = context
178
+ for key in self._vars_chain:
179
+ if (
180
+ not isinstance(current_dict_level, dict)
181
+ or key not in current_dict_level
182
+ ):
183
+ raise InvalidTemplate(f"Could not find template variable {self.key}")
184
+ current_dict_level = current_dict_level[key]
185
+
186
+ value = current_dict_level
187
+
188
+ if value is None:
189
+ raise InvalidTemplate(f"Template variable {self.key} does not have a value")
190
+
191
+ if isinstance(value, (dict, list)):
192
+ raise InvalidTemplate(
193
+ f"Template variable {self.key} does not have a scalar value"
194
+ )
195
+
196
+ return value
197
+
198
+ def __hash__(self):
199
+ return hash(self.key)
200
+
201
+ def __eq__(self, other):
202
+ return self.key == other.key
203
+
204
+
205
+ def _build_dependency_graph(
206
+ env: TemplatedEnvironment,
207
+ all_vars: set[TemplateVar],
208
+ context: Context,
209
+ environment_overrides: ProjectEnvironment,
210
+ ) -> Graph[TemplateVar]:
211
+ dependencies_graph = Graph[TemplateVar]()
212
+ for variable in all_vars:
213
+ dependencies_graph.add(Node[TemplateVar](key=variable.key, data=variable))
214
+ for variable in all_vars:
215
+ # If variable is found in os.environ or from cli override, then use the value as is
216
+ # skip rendering by pre-setting the rendered_value attribute
217
+ if variable.is_env_var and variable.get_env_var_name() in environment_overrides:
218
+ env_value = environment_overrides.get(variable.get_env_var_name())
219
+ variable.rendered_value = env_value
220
+ variable.templated_value = env_value
221
+ else:
222
+ variable.templated_value = variable.read_from_context(context)
223
+ dependencies_vars = env.get_referenced_vars(variable.templated_value)
224
+
225
+ for referenced_var in dependencies_vars:
226
+ dependencies_graph.add_directed_edge(variable.key, referenced_var.key)
227
+
228
+ return dependencies_graph
229
+
230
+
231
+ def _render_graph_node(env: TemplatedEnvironment, node: Node[TemplateVar]) -> None:
232
+ if node.data.rendered_value is not None:
233
+ # Do not re-evaluate resolved nodes like env variable nodes
234
+ # which might contain template-like values, or non-string nodes
235
+ return
236
+
237
+ current_context: Context = {}
238
+ for dep_node in node.neighbors:
239
+ dep_node.data.add_to_context(current_context)
240
+
241
+ node.data.rendered_value = env.render(node.data.templated_value, current_context)
242
+
243
+
244
+ def _validate_env_section(env_section: dict):
245
+ if not isinstance(env_section, dict):
246
+ raise InvalidTemplate(
247
+ "env section in project definition file should be a mapping"
248
+ )
249
+ for variable, value in env_section.items():
250
+ if value is None or isinstance(value, (dict, list)):
251
+ raise InvalidTemplate(
252
+ f"Variable {variable} in env section of project definition file should be a scalar"
253
+ )
254
+
255
+
256
+ def _get_referenced_vars_in_definition(
257
+ template_env: TemplatedEnvironment, definition: Definition
258
+ ):
259
+ referenced_vars = set()
260
+
261
+ def find_any_template_vars(element):
262
+ referenced_vars.update(template_env.get_referenced_vars(element))
263
+
264
+ traverse(definition, visit_action=find_any_template_vars)
265
+
266
+ return referenced_vars
267
+
268
+
269
+ def _template_version_warning():
270
+ cc.warning(
271
+ "Ignoring template pattern in project definition file. "
272
+ "Update 'definition_version' to 1.1 or later in snowflake.yml to enable template expansion."
273
+ )
274
+
275
+
276
+ def _add_defaults_to_definition(original_definition: Definition) -> Definition:
277
+ with context({"skip_validation_on_templates": True}):
278
+ # pass a flag to Pydantic to skip validation for templated scalars
279
+ # populate the defaults
280
+ project_definition = build_project_definition(**original_definition)
281
+
282
+ definition_with_defaults = project_definition.model_dump(
283
+ exclude_none=True, warnings=False, by_alias=True
284
+ )
285
+ # The main purpose of the above operation was to populate defaults from Pydantic.
286
+ # By merging the original definition back in, we ensure that any transformations
287
+ # that Pydantic would have performed are undone.
288
+ deep_merge_dicts(definition_with_defaults, original_definition)
289
+ return definition_with_defaults
290
+
291
+
292
+ def render_definition_template(
293
+ original_definition: Optional[Definition], context_overrides: Context
294
+ ) -> ProjectProperties:
295
+ """
296
+ Takes a definition file as input. An arbitrary structure containing dict|list|scalars,
297
+ with the top level being a dictionary.
298
+ Requires item 'definition_version' to be set to a version of 1.1 and higher.
299
+
300
+ Searches for any templating in all of the scalar fields, and attempts to resolve them
301
+ from the definition structure itself or from the environment variable.
302
+
303
+ Environment variables take precedence during the rendering process.
304
+ """
305
+
306
+ # copy input to protect it from update
307
+ definition = copy.deepcopy(original_definition)
308
+
309
+ # collect all the override --env variables passed through CLI input
310
+ override_env = context_overrides.get(CONTEXT_KEY, {}).get("env", {})
311
+
312
+ # set up Project Environment with empty default_env because
313
+ # default env section from project definition file is still templated at this time
314
+ environment_overrides = ProjectEnvironment(
315
+ default_env={}, override_env=override_env
316
+ )
317
+
318
+ if definition is None:
319
+ return ProjectProperties(None, {CONTEXT_KEY: {"env": environment_overrides}})
320
+
321
+ template_env = TemplatedEnvironment(get_project_definition_cli_jinja_env())
322
+
323
+ if "definition_version" not in definition or Version(
324
+ definition["definition_version"]
325
+ ) < Version("1.1"):
326
+ try:
327
+ referenced_vars = _get_referenced_vars_in_definition(
328
+ template_env, definition
329
+ )
330
+ if referenced_vars:
331
+ _template_version_warning()
332
+ except Exception:
333
+ # also warn on Exception, as it means the user is incorrectly attempting to use templating
334
+ _template_version_warning()
335
+
336
+ project_definition = build_project_definition(**definition)
337
+ project_context = {CONTEXT_KEY: definition}
338
+ project_context[CONTEXT_KEY]["env"] = environment_overrides
339
+ return ProjectProperties(project_definition, project_context)
340
+
341
+ definition = _add_defaults_to_definition(definition)
342
+ project_context = {CONTEXT_KEY: definition}
343
+
344
+ _validate_env_section(definition.get("env", {}))
345
+
346
+ # add available templating functions
347
+ project_context["fn"] = get_templating_functions()
348
+
349
+ referenced_vars = _get_referenced_vars_in_definition(template_env, definition)
350
+
351
+ dependencies_graph = _build_dependency_graph(
352
+ template_env, referenced_vars, project_context, environment_overrides
353
+ )
354
+
355
+ def on_cycle_action(node: Node[TemplateVar]):
356
+ raise CycleDetectedError(
357
+ f"Cycle detected in templating variable {node.data.key}"
358
+ )
359
+
360
+ dependencies_graph.dfs(
361
+ visit_action=lambda node: _render_graph_node(template_env, node),
362
+ on_cycle_action=on_cycle_action,
363
+ )
364
+
365
+ # now that we determined the values of all templated vars,
366
+ # use these resolved values as a fresh context to resolve definition
367
+ final_context: Context = {}
368
+ for node in dependencies_graph.get_all_nodes():
369
+ node.data.add_to_context(final_context)
370
+
371
+ traverse(
372
+ definition,
373
+ update_action=lambda val: template_env.render(val, final_context),
374
+ )
375
+
376
+ project_definition = build_project_definition(**definition)
377
+ project_context[CONTEXT_KEY] = definition
378
+ # Use `ProjectEnvironment` in project context in order to
379
+ # handle env variables overrides from OS env and from CLI arguments.
380
+ project_context[CONTEXT_KEY]["env"] = ProjectEnvironment(
381
+ default_env=project_context[CONTEXT_KEY].get("env"), override_env=override_env
382
+ )
383
+ return ProjectProperties(project_definition, project_context)