snowflake-cli-labs 2.8.0rc1__py3-none-any.whl → 3.0.0rc1__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 (224) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/{app → _app}/__main__.py +1 -1
  3. snowflake/cli/{app → _app}/cli_app.py +12 -12
  4. snowflake/cli/{app → _app}/commands_registration/builtin_plugins.py +13 -19
  5. snowflake/cli/{app → _app}/commands_registration/command_plugins_loader.py +9 -9
  6. snowflake/cli/{app → _app}/commands_registration/commands_registration_with_callbacks.py +4 -4
  7. snowflake/cli/{app → _app}/commands_registration/exception_logging.py +2 -2
  8. snowflake/cli/{app → _app}/commands_registration/typer_registration.py +2 -2
  9. snowflake/cli/{app → _app}/dev/docs/commands_docs_generator.py +30 -12
  10. snowflake/cli/{app → _app}/dev/docs/generator.py +3 -3
  11. snowflake/cli/{app → _app}/dev/docs/project_definition_docs_generator.py +4 -4
  12. snowflake/cli/{app → _app}/dev/docs/templates/usage.rst.jinja2 +14 -4
  13. snowflake/cli/{app → _app}/main_typer.py +2 -2
  14. snowflake/cli/{app → _app}/printing.py +2 -2
  15. snowflake/cli/{app → _app}/snow_connector.py +24 -17
  16. snowflake/cli/{app → _app}/telemetry.py +4 -5
  17. snowflake/cli/{plugins → _plugins}/connection/commands.py +25 -7
  18. snowflake/cli/_plugins/connection/plugin_spec.py +30 -0
  19. snowflake/cli/{plugins → _plugins}/connection/util.py +16 -0
  20. snowflake/cli/{plugins → _plugins}/cortex/commands.py +54 -49
  21. snowflake/cli/{plugins → _plugins}/cortex/constants.py +1 -1
  22. snowflake/cli/{plugins → _plugins}/cortex/manager.py +5 -5
  23. snowflake/cli/{plugins → _plugins}/cortex/plugin_spec.py +1 -1
  24. snowflake/cli/{plugins → _plugins}/git/commands.py +32 -20
  25. snowflake/cli/{plugins → _plugins}/git/manager.py +20 -11
  26. snowflake/cli/{plugins → _plugins}/git/plugin_spec.py +1 -1
  27. snowflake/cli/{plugins → _plugins}/init/commands.py +10 -6
  28. snowflake/cli/{plugins → _plugins}/init/plugin_spec.py +1 -1
  29. snowflake/cli/{plugins → _plugins}/nativeapp/artifacts.py +14 -0
  30. snowflake/cli/_plugins/nativeapp/bundle_context.py +31 -0
  31. snowflake/cli/{plugins → _plugins}/nativeapp/codegen/artifact_processor.py +3 -3
  32. snowflake/cli/{plugins → _plugins}/nativeapp/codegen/compiler.py +32 -18
  33. snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +249 -0
  34. snowflake/cli/{plugins → _plugins}/nativeapp/codegen/setup/setup_driver.py.source +5 -2
  35. snowflake/cli/{plugins → _plugins}/nativeapp/codegen/snowpark/extension_function_utils.py +4 -4
  36. snowflake/cli/{plugins → _plugins}/nativeapp/codegen/snowpark/python_processor.py +23 -29
  37. snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +93 -0
  38. snowflake/cli/{plugins → _plugins}/nativeapp/commands.py +171 -42
  39. snowflake/cli/{plugins → _plugins}/nativeapp/common_flags.py +1 -1
  40. snowflake/cli/{plugins → _plugins}/nativeapp/exceptions.py +3 -3
  41. snowflake/cli/{plugins → _plugins}/nativeapp/init.py +1 -1
  42. snowflake/cli/_plugins/nativeapp/manager.py +572 -0
  43. snowflake/cli/{plugins/connection → _plugins/nativeapp}/plugin_spec.py +1 -1
  44. snowflake/cli/{plugins → _plugins}/nativeapp/project_model.py +35 -19
  45. snowflake/cli/{plugins → _plugins}/nativeapp/run_processor.py +25 -23
  46. snowflake/cli/{plugins → _plugins}/nativeapp/teardown_processor.py +24 -110
  47. snowflake/cli/{plugins → _plugins}/nativeapp/v2_conversions/v2_to_v1_decorator.py +47 -28
  48. snowflake/cli/{plugins → _plugins}/nativeapp/version/commands.py +15 -12
  49. snowflake/cli/{plugins → _plugins}/nativeapp/version/version_processor.py +22 -20
  50. snowflake/cli/{plugins → _plugins}/notebook/commands.py +8 -6
  51. snowflake/cli/{plugins → _plugins}/notebook/manager.py +14 -14
  52. snowflake/cli/{plugins → _plugins}/notebook/plugin_spec.py +1 -1
  53. snowflake/cli/{plugins → _plugins}/notebook/types.py +0 -1
  54. snowflake/cli/{plugins → _plugins}/object/command_aliases.py +6 -5
  55. snowflake/cli/{plugins → _plugins}/object/commands.py +16 -10
  56. snowflake/cli/{plugins → _plugins}/object/manager.py +7 -6
  57. snowflake/cli/{plugins → _plugins}/object/plugin_spec.py +1 -1
  58. snowflake/cli/_plugins/snowpark/commands.py +450 -0
  59. snowflake/cli/_plugins/snowpark/common.py +268 -0
  60. snowflake/cli/{plugins → _plugins}/snowpark/models.py +0 -7
  61. snowflake/cli/{plugins → _plugins}/snowpark/package/anaconda_packages.py +2 -36
  62. snowflake/cli/{plugins → _plugins}/snowpark/package/commands.py +13 -74
  63. snowflake/cli/{plugins → _plugins}/snowpark/package/manager.py +4 -3
  64. snowflake/cli/{plugins → _plugins}/snowpark/package_utils.py +5 -5
  65. snowflake/cli/{plugins/nativeapp → _plugins/snowpark}/plugin_spec.py +1 -1
  66. snowflake/cli/_plugins/snowpark/snowpark_project_paths.py +109 -0
  67. snowflake/cli/{plugins → _plugins}/snowpark/snowpark_shared.py +0 -36
  68. snowflake/cli/{plugins → _plugins}/snowpark/zipper.py +16 -8
  69. snowflake/cli/{plugins → _plugins}/spcs/__init__.py +5 -7
  70. snowflake/cli/{plugins → _plugins}/spcs/compute_pool/commands.py +29 -28
  71. snowflake/cli/{plugins → _plugins}/spcs/compute_pool/manager.py +3 -3
  72. snowflake/cli/{plugins → _plugins}/spcs/image_registry/commands.py +3 -3
  73. snowflake/cli/{plugins → _plugins}/spcs/image_repository/commands.py +25 -19
  74. snowflake/cli/{plugins → _plugins}/spcs/image_repository/manager.py +1 -1
  75. snowflake/cli/{plugins → _plugins}/spcs/plugin_spec.py +1 -1
  76. snowflake/cli/{plugins → _plugins}/spcs/services/commands.py +66 -32
  77. snowflake/cli/{plugins → _plugins}/spcs/services/manager.py +43 -5
  78. snowflake/cli/{plugins → _plugins}/sql/commands.py +20 -17
  79. snowflake/cli/{plugins → _plugins}/sql/manager.py +1 -1
  80. snowflake/cli/{plugins → _plugins}/sql/plugin_spec.py +1 -1
  81. snowflake/cli/{plugins → _plugins}/stage/commands.py +20 -17
  82. snowflake/cli/{plugins → _plugins}/stage/diff.py +1 -47
  83. snowflake/cli/{plugins → _plugins}/stage/manager.py +54 -21
  84. snowflake/cli/{plugins → _plugins}/stage/plugin_spec.py +1 -1
  85. snowflake/cli/_plugins/stage/utils.py +54 -0
  86. snowflake/cli/{plugins → _plugins}/streamlit/commands.py +59 -62
  87. snowflake/cli/{plugins → _plugins}/streamlit/manager.py +51 -70
  88. snowflake/cli/_plugins/streamlit/plugin_spec.py +30 -0
  89. snowflake/cli/_plugins/workspace/action_context.py +17 -0
  90. snowflake/cli/_plugins/workspace/commands.py +194 -0
  91. snowflake/cli/_plugins/workspace/manager.py +73 -0
  92. snowflake/cli/{plugins → _plugins}/workspace/plugin_spec.py +1 -1
  93. snowflake/cli/api/cli_global_context.py +40 -13
  94. snowflake/cli/api/commands/common.py +25 -0
  95. snowflake/cli/api/commands/decorators.py +5 -4
  96. snowflake/cli/api/commands/experimental_behaviour.py +2 -3
  97. snowflake/cli/api/commands/flags.py +97 -179
  98. snowflake/cli/api/commands/overrideable_parameter.py +143 -0
  99. snowflake/cli/api/commands/snow_typer.py +14 -6
  100. snowflake/cli/api/commands/typer_pre_execute.py +3 -3
  101. snowflake/cli/api/commands/utils.py +18 -0
  102. snowflake/cli/api/config.py +18 -5
  103. snowflake/cli/api/console/abc.py +5 -2
  104. snowflake/cli/api/constants.py +11 -0
  105. snowflake/cli/api/entities/application_entity.py +12 -0
  106. snowflake/cli/api/entities/application_package_entity.py +553 -0
  107. snowflake/cli/api/entities/common.py +51 -0
  108. snowflake/cli/api/entities/snowpark_entity.py +29 -0
  109. snowflake/cli/api/entities/streamlit_entity.py +12 -0
  110. snowflake/cli/api/entities/utils.py +357 -0
  111. snowflake/cli/api/exceptions.py +31 -5
  112. snowflake/cli/api/feature_flags.py +0 -1
  113. snowflake/cli/api/identifiers.py +41 -9
  114. snowflake/cli/api/project/definition.py +37 -6
  115. snowflake/cli/api/project/definition_conversion.py +194 -0
  116. snowflake/cli/api/project/definition_manager.py +12 -1
  117. snowflake/cli/api/project/project_verification.py +3 -3
  118. snowflake/cli/api/project/schemas/entities/{application_entity.py → application_entity_model.py} +21 -9
  119. snowflake/cli/api/project/schemas/entities/{application_package_entity.py → application_package_entity_model.py} +43 -15
  120. snowflake/cli/api/project/schemas/entities/common.py +80 -6
  121. snowflake/cli/api/project/schemas/entities/entities.py +38 -8
  122. snowflake/cli/api/project/schemas/entities/snowpark_entity.py +176 -0
  123. snowflake/cli/api/project/schemas/entities/streamlit_entity_model.py +73 -0
  124. snowflake/cli/api/project/schemas/identifier_model.py +10 -1
  125. snowflake/cli/api/project/schemas/native_app/application.py +8 -9
  126. snowflake/cli/api/project/schemas/native_app/package.py +7 -1
  127. snowflake/cli/api/project/schemas/project_definition.py +98 -27
  128. snowflake/cli/api/project/schemas/updatable_model.py +11 -3
  129. snowflake/cli/api/project/util.py +23 -6
  130. snowflake/cli/api/rendering/jinja.py +14 -8
  131. snowflake/cli/api/rendering/project_definition_templates.py +1 -1
  132. snowflake/cli/api/rendering/sql_templates.py +43 -11
  133. snowflake/cli/api/secure_path.py +16 -18
  134. snowflake/cli/api/secure_utils.py +90 -1
  135. snowflake/cli/api/sql_execution.py +48 -19
  136. snowflake/cli/api/utils/definition_rendering.py +18 -8
  137. {snowflake_cli_labs-2.8.0rc1.dist-info → snowflake_cli_labs-3.0.0rc1.dist-info}/METADATA +13 -13
  138. snowflake_cli_labs-3.0.0rc1.dist-info/RECORD +236 -0
  139. snowflake_cli_labs-3.0.0rc1.dist-info/entry_points.txt +2 -0
  140. snowflake/cli/api/commands/project_initialisation.py +0 -65
  141. snowflake/cli/app/build_and_push.sh +0 -8
  142. snowflake/cli/plugins/nativeapp/codegen/setup/native_app_setup_processor.py +0 -172
  143. snowflake/cli/plugins/nativeapp/manager.py +0 -823
  144. snowflake/cli/plugins/object_stage_deprecated/__init__.py +0 -15
  145. snowflake/cli/plugins/object_stage_deprecated/commands.py +0 -122
  146. snowflake/cli/plugins/object_stage_deprecated/plugin_spec.py +0 -32
  147. snowflake/cli/plugins/snowpark/commands.py +0 -548
  148. snowflake/cli/plugins/snowpark/common.py +0 -307
  149. snowflake/cli/plugins/snowpark/manager.py +0 -109
  150. snowflake/cli/plugins/snowpark/plugin_spec.py +0 -30
  151. snowflake/cli/plugins/snowpark/snowpark_package_paths.py +0 -65
  152. snowflake/cli/plugins/spcs/jobs/commands.py +0 -78
  153. snowflake/cli/plugins/spcs/jobs/manager.py +0 -53
  154. snowflake/cli/plugins/streamlit/plugin_spec.py +0 -30
  155. snowflake/cli/plugins/workspace/commands.py +0 -35
  156. snowflake/cli/templates/default_snowpark/.gitignore +0 -4
  157. snowflake/cli/templates/default_snowpark/app/__init__.py +0 -0
  158. snowflake/cli/templates/default_snowpark/app/common.py +0 -2
  159. snowflake/cli/templates/default_snowpark/app/functions.py +0 -15
  160. snowflake/cli/templates/default_snowpark/app/procedures.py +0 -22
  161. snowflake/cli/templates/default_snowpark/requirements.txt +0 -1
  162. snowflake/cli/templates/default_snowpark/snowflake.yml +0 -23
  163. snowflake/cli/templates/default_streamlit/.gitignore +0 -4
  164. snowflake/cli/templates/default_streamlit/common/hello.py +0 -2
  165. snowflake/cli/templates/default_streamlit/environment.yml +0 -6
  166. snowflake/cli/templates/default_streamlit/pages/my_page.py +0 -3
  167. snowflake/cli/templates/default_streamlit/snowflake.yml +0 -10
  168. snowflake/cli/templates/default_streamlit/streamlit_app.py +0 -4
  169. snowflake_cli_labs-2.8.0rc1.dist-info/RECORD +0 -240
  170. snowflake_cli_labs-2.8.0rc1.dist-info/entry_points.txt +0 -2
  171. /snowflake/cli/{app → _app}/__init__.py +0 -0
  172. /snowflake/cli/{app → _app}/api_impl/__init__.py +0 -0
  173. /snowflake/cli/{app → _app}/api_impl/plugin/__init__.py +0 -0
  174. /snowflake/cli/{app → _app}/api_impl/plugin/plugin_config_provider_impl.py +0 -0
  175. /snowflake/cli/{app → _app}/commands_registration/__init__.py +0 -0
  176. /snowflake/cli/{app → _app}/commands_registration/threadsafe.py +0 -0
  177. /snowflake/cli/{app → _app}/constants.py +0 -0
  178. /snowflake/cli/{app → _app}/dev/__init__.py +0 -0
  179. /snowflake/cli/{app → _app}/dev/commands_structure.py +0 -0
  180. /snowflake/cli/{app → _app}/dev/docs/__init__.py +0 -0
  181. /snowflake/cli/{app → _app}/dev/docs/project_definition_generate_json_schema.py +0 -0
  182. /snowflake/cli/{app → _app}/dev/docs/template_utils.py +0 -0
  183. /snowflake/cli/{app → _app}/dev/docs/templates/definition_description.rst.jinja2 +0 -0
  184. /snowflake/cli/{app → _app}/dev/docs/templates/overview.rst.jinja2 +0 -0
  185. /snowflake/cli/{app → _app}/dev/pycharm_remote_debug.py +0 -0
  186. /snowflake/cli/{app → _app}/loggers.py +0 -0
  187. /snowflake/cli/{plugins → _plugins}/__init__.py +0 -0
  188. /snowflake/cli/{plugins → _plugins}/connection/__init__.py +0 -0
  189. /snowflake/cli/{plugins → _plugins}/cortex/__init__.py +0 -0
  190. /snowflake/cli/{plugins → _plugins}/cortex/types.py +0 -0
  191. /snowflake/cli/{plugins → _plugins}/git/__init__.py +0 -0
  192. /snowflake/cli/{plugins → _plugins}/init/__init__.py +0 -0
  193. /snowflake/cli/{plugins → _plugins}/nativeapp/__init__.py +0 -0
  194. /snowflake/cli/{plugins → _plugins}/nativeapp/codegen/__init__.py +0 -0
  195. /snowflake/cli/{plugins → _plugins}/nativeapp/codegen/sandbox.py +0 -0
  196. /snowflake/cli/{plugins → _plugins}/nativeapp/codegen/snowpark/callback_source.py.jinja +0 -0
  197. /snowflake/cli/{plugins → _plugins}/nativeapp/codegen/snowpark/models.py +0 -0
  198. /snowflake/cli/{plugins → _plugins}/nativeapp/constants.py +0 -0
  199. /snowflake/cli/{plugins → _plugins}/nativeapp/feature_flags.py +0 -0
  200. /snowflake/cli/{plugins → _plugins}/nativeapp/policy.py +0 -0
  201. /snowflake/cli/{plugins → _plugins}/nativeapp/utils.py +0 -0
  202. /snowflake/cli/{plugins → _plugins}/nativeapp/version/__init__.py +0 -0
  203. /snowflake/cli/{plugins → _plugins}/notebook/__init__.py +0 -0
  204. /snowflake/cli/{plugins → _plugins}/notebook/exceptions.py +0 -0
  205. /snowflake/cli/{plugins → _plugins}/object/__init__.py +0 -0
  206. /snowflake/cli/{plugins → _plugins}/object/common.py +0 -0
  207. /snowflake/cli/{plugins → _plugins}/snowpark/__init__.py +0 -0
  208. /snowflake/cli/{plugins → _plugins}/snowpark/package/__init__.py +0 -0
  209. /snowflake/cli/{plugins → _plugins}/snowpark/package/utils.py +0 -0
  210. /snowflake/cli/{plugins → _plugins}/spcs/common.py +0 -0
  211. /snowflake/cli/{plugins → _plugins}/spcs/compute_pool/__init__.py +0 -0
  212. /snowflake/cli/{plugins → _plugins}/spcs/image_registry/__init__.py +0 -0
  213. /snowflake/cli/{plugins → _plugins}/spcs/image_registry/manager.py +0 -0
  214. /snowflake/cli/{plugins → _plugins}/spcs/image_repository/__init__.py +0 -0
  215. /snowflake/cli/{plugins/spcs/jobs → _plugins/spcs/services}/__init__.py +0 -0
  216. /snowflake/cli/{plugins/spcs/services → _plugins/sql}/__init__.py +0 -0
  217. /snowflake/cli/{plugins → _plugins}/sql/snowsql_templating.py +0 -0
  218. /snowflake/cli/{plugins/sql → _plugins/stage}/__init__.py +0 -0
  219. /snowflake/cli/{plugins → _plugins}/stage/md5.py +0 -0
  220. /snowflake/cli/{plugins/stage → _plugins/streamlit}/__init__.py +0 -0
  221. /snowflake/cli/{plugins/streamlit → _plugins/workspace}/__init__.py +0 -0
  222. /snowflake/cli/{plugins/workspace → api/project/schemas/entities}/__init__.py +0 -0
  223. {snowflake_cli_labs-2.8.0rc1.dist-info → snowflake_cli_labs-3.0.0rc1.dist-info}/WHEEL +0 -0
  224. {snowflake_cli_labs-2.8.0rc1.dist-info → snowflake_cli_labs-3.0.0rc1.dist-info}/licenses/LICENSE +0 -0
@@ -17,8 +17,10 @@ from __future__ import annotations
17
17
  from typing import Dict, Optional
18
18
 
19
19
  from click import ClickException
20
- from jinja2 import StrictUndefined, loaders
21
- from snowflake.cli.api.cli_global_context import cli_context
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
22
24
  from snowflake.cli.api.rendering.jinja import (
23
25
  CONTEXT_KEY,
24
26
  FUNCTION_KEY,
@@ -26,26 +28,55 @@ from snowflake.cli.api.rendering.jinja import (
26
28
  env_bootstrap,
27
29
  )
28
30
 
29
- _SQL_TEMPLATE_START = "&{"
30
- _SQL_TEMPLATE_END = "}"
31
+ _SQL_TEMPLATE_START = "<%"
32
+ _SQL_TEMPLATE_END = "%>"
33
+ _OLD_SQL_TEMPLATE_START = "&{"
34
+ _OLD_SQL_TEMPLATE_END = "}"
31
35
  RESERVED_KEYS = [CONTEXT_KEY, FUNCTION_KEY]
32
36
 
33
37
 
34
- def get_sql_cli_jinja_env(*, loader: Optional[loaders.BaseLoader] = None):
38
+ def _get_sql_jinja_env(template_start: str, template_end: str) -> Environment:
35
39
  _random_block = "___very___unique___block___to___disable___logic___blocks___"
36
40
  return env_bootstrap(
37
41
  IgnoreAttrEnvironment(
38
- loader=loader or loaders.BaseLoader(),
39
- keep_trailing_newline=True,
40
- variable_start_string=_SQL_TEMPLATE_START,
41
- variable_end_string=_SQL_TEMPLATE_END,
42
+ variable_start_string=template_start,
43
+ variable_end_string=template_end,
44
+ loader=loaders.BaseLoader(),
42
45
  block_start_string=_random_block,
43
46
  block_end_string=_random_block,
47
+ keep_trailing_newline=True,
44
48
  undefined=StrictUndefined,
45
49
  )
46
50
  )
47
51
 
48
52
 
53
+ def _does_template_have_env_syntax(env: Environment, template_content: str) -> bool:
54
+ template = env.parse(template_content)
55
+ return bool(meta.find_undeclared_variables(template))
56
+
57
+
58
+ def choose_sql_jinja_env_based_on_template_syntax(
59
+ template_content: str, reference_name: Optional[str] = None
60
+ ) -> Environment:
61
+ old_syntax_env = _get_sql_jinja_env(_OLD_SQL_TEMPLATE_START, _OLD_SQL_TEMPLATE_END)
62
+ new_syntax_env = _get_sql_jinja_env(_SQL_TEMPLATE_START, _SQL_TEMPLATE_END)
63
+ has_old_syntax = _does_template_have_env_syntax(old_syntax_env, template_content)
64
+ has_new_syntax = _does_template_have_env_syntax(new_syntax_env, template_content)
65
+ reference_name_str = f" in {reference_name}" if reference_name else ""
66
+ if has_old_syntax and has_new_syntax:
67
+ raise InvalidTemplate(
68
+ f"The SQL query{reference_name_str} mixes {_OLD_SQL_TEMPLATE_START} ... {_OLD_SQL_TEMPLATE_END} syntax"
69
+ f" and {_SQL_TEMPLATE_START} ... {_SQL_TEMPLATE_END} syntax."
70
+ )
71
+ if has_old_syntax:
72
+ cli_console.warning(
73
+ f"Warning: {_OLD_SQL_TEMPLATE_START} ... {_OLD_SQL_TEMPLATE_END} syntax{reference_name_str} is deprecated."
74
+ f" Use {_SQL_TEMPLATE_START} ... {_SQL_TEMPLATE_END} syntax instead."
75
+ )
76
+ return old_syntax_env
77
+ return new_syntax_env
78
+
79
+
49
80
  def snowflake_sql_jinja_render(content: str, data: Dict | None = None) -> str:
50
81
  data = data or {}
51
82
 
@@ -55,6 +86,7 @@ def snowflake_sql_jinja_render(content: str, data: Dict | None = None) -> str:
55
86
  f"{reserved_key} in user defined data. The `{reserved_key}` variable is reserved for CLI usage."
56
87
  )
57
88
 
58
- context_data = cli_context.template_context
89
+ context_data = get_cli_context().template_context
59
90
  context_data.update(data)
60
- return get_sql_cli_jinja_env().from_string(content).render(**context_data)
91
+ env = choose_sql_jinja_env_based_on_template_syntax(content)
92
+ return env.from_string(content).render(context_data)
@@ -24,6 +24,12 @@ from pathlib import Path
24
24
  from typing import Optional, Union
25
25
 
26
26
  from snowflake.cli.api.exceptions import DirectoryIsNotEmptyError, FileTooLargeError
27
+ from snowflake.cli.api.secure_utils import (
28
+ chmod as secure_chmod,
29
+ )
30
+ from snowflake.cli.api.secure_utils import (
31
+ restrict_file_permissions,
32
+ )
27
33
 
28
34
  log = logging.getLogger(__name__)
29
35
 
@@ -47,6 +53,12 @@ class SecurePath:
47
53
  """
48
54
  return self._path
49
55
 
56
+ def chmod(self, permissions_mask: int) -> None:
57
+ """
58
+ Change the file mode and permissions, like os.chmod().
59
+ """
60
+ secure_chmod(self._path, permissions_mask)
61
+
50
62
  @property
51
63
  def parent(self):
52
64
  """
@@ -97,28 +109,11 @@ class SecurePath:
97
109
  """A string representing the final path component."""
98
110
  return self._path.name
99
111
 
100
- def chmod(self, permissions_mask: int) -> None:
101
- """
102
- Change the file mode and permissions, like os.chmod().
103
- """
104
- log.info(
105
- "Update permissions of file %s to %s", self._path, oct(permissions_mask)
106
- )
107
- self._path.chmod(permissions_mask)
108
-
109
112
  def restrict_permissions(self) -> None:
110
113
  """
111
114
  Restrict file/directory permissions to owner-only.
112
115
  """
113
- import stat
114
-
115
- owner_permissions = (
116
- # https://docs.python.org/3/library/stat.html
117
- stat.S_IRUSR # readable by owner
118
- | stat.S_IWUSR # writeable by owner
119
- | stat.S_IXUSR # executable by owner
120
- )
121
- self.chmod(self._path.stat().st_mode & owner_permissions)
116
+ restrict_file_permissions(self._path)
122
117
 
123
118
  def touch(self, permissions_mask: int = 0o600, exist_ok: bool = True) -> None:
124
119
  """
@@ -349,6 +344,9 @@ class SecurePath:
349
344
  ):
350
345
  raise FileTooLargeError(self._path.resolve(), size_limit_in_mb)
351
346
 
347
+ def rename(self, new_name: Union[str | Path]):
348
+ self._path.rename(new_name)
349
+
352
350
 
353
351
  def _raise_file_exists_error(path: Path):
354
352
  raise FileExistsError(errno.EEXIST, os.strerror(errno.EEXIST), path)
@@ -12,11 +12,64 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ import logging
15
16
  import stat
16
17
  from pathlib import Path
18
+ from typing import List
17
19
 
20
+ from snowflake.connector.compat import IS_WINDOWS
18
21
 
19
- def file_permissions_are_strict(file_path: Path) -> bool:
22
+ log = logging.getLogger(__name__)
23
+
24
+
25
+ def _get_windows_whitelisted_users():
26
+ # whitelisted users list obtained in consultation with prodsec: CASEC-9627
27
+ import os
28
+
29
+ return [
30
+ "SYSTEM",
31
+ "Administrators",
32
+ "Network",
33
+ "Domain Admins",
34
+ "Domain Users",
35
+ os.getlogin(),
36
+ ]
37
+
38
+
39
+ def _run_icacls(file_path: Path) -> str:
40
+ import subprocess
41
+
42
+ return subprocess.check_output(["icacls", str(file_path)], text=True)
43
+
44
+
45
+ def _windows_permissions_are_denied(permission_codes: str) -> bool:
46
+ # according to https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/icacls
47
+ return "(DENY)" in permission_codes or "(N)" in permission_codes
48
+
49
+
50
+ def windows_get_not_whitelisted_users_with_access(file_path: Path) -> List[str]:
51
+ import re
52
+
53
+ # according to https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/icacls
54
+ icacls_output_regex = (
55
+ rf"({re.escape(str(file_path))})?.*\\(?P<user>.*):(?P<permissions>[(A-Z),]+)"
56
+ )
57
+ whitelisted_users = _get_windows_whitelisted_users()
58
+
59
+ users_with_access = []
60
+ for permission in re.finditer(icacls_output_regex, _run_icacls(file_path)):
61
+ if (permission.group("user") not in whitelisted_users) and (
62
+ not _windows_permissions_are_denied(permission.group("permissions"))
63
+ ):
64
+ users_with_access.append(permission.group("user"))
65
+ return list(set(users_with_access))
66
+
67
+
68
+ def _windows_file_permissions_are_strict(file_path: Path) -> bool:
69
+ return windows_get_not_whitelisted_users_with_access(file_path) == []
70
+
71
+
72
+ def _unix_file_permissions_are_strict(file_path: Path) -> bool:
20
73
  accessible_by_others = (
21
74
  # https://docs.python.org/3/library/stat.html
22
75
  stat.S_IRGRP # readable by group
@@ -27,3 +80,39 @@ def file_permissions_are_strict(file_path: Path) -> bool:
27
80
  | stat.S_IXOTH # executable by others
28
81
  )
29
82
  return (file_path.stat().st_mode & accessible_by_others) == 0
83
+
84
+
85
+ def file_permissions_are_strict(file_path: Path) -> bool:
86
+ if IS_WINDOWS:
87
+ return _windows_file_permissions_are_strict(file_path)
88
+ return _unix_file_permissions_are_strict(file_path)
89
+
90
+
91
+ def chmod(path: Path, permissions_mask: int) -> None:
92
+ log.info("Update permissions of file %s to %s", path, oct(permissions_mask))
93
+ path.chmod(permissions_mask)
94
+
95
+
96
+ def _unix_restrict_file_permissions(path: Path) -> None:
97
+ owner_permissions = (
98
+ # https://docs.python.org/3/library/stat.html
99
+ stat.S_IRUSR # readable by owner
100
+ | stat.S_IWUSR # writeable by owner
101
+ | stat.S_IXUSR # executable by owner
102
+ )
103
+ chmod(path, path.stat().st_mode & owner_permissions)
104
+
105
+
106
+ def _windows_restrict_file_permissions(path: Path) -> None:
107
+ import subprocess
108
+
109
+ for user in windows_get_not_whitelisted_users_with_access(path):
110
+ log.info("Removing permissions of user %s from file %s", user, path)
111
+ subprocess.run(["icacls", str(path), "/DENY", f"{user}:F"])
112
+
113
+
114
+ def restrict_file_permissions(file_path: Path) -> None:
115
+ if IS_WINDOWS:
116
+ _windows_restrict_file_permissions(file_path)
117
+ else:
118
+ _unix_restrict_file_permissions(file_path)
@@ -21,7 +21,7 @@ from io import StringIO
21
21
  from textwrap import dedent
22
22
  from typing import Iterable, Optional, Tuple
23
23
 
24
- from snowflake.cli.api.cli_global_context import cli_context
24
+ from snowflake.cli.api.cli_global_context import get_cli_context
25
25
  from snowflake.cli.api.console import cli_console
26
26
  from snowflake.cli.api.constants import ObjectType
27
27
  from snowflake.cli.api.exceptions import (
@@ -35,27 +35,21 @@ from snowflake.cli.api.project.util import (
35
35
  unquote_identifier,
36
36
  )
37
37
  from snowflake.cli.api.utils.cursor import find_first_row
38
+ from snowflake.connector import SnowflakeConnection
38
39
  from snowflake.connector.cursor import DictCursor, SnowflakeCursor
39
40
  from snowflake.connector.errors import ProgrammingError
40
41
 
41
42
 
42
- class SqlExecutionMixin:
43
- def __init__(self):
43
+ class SqlExecutor:
44
+ def __init__(self, connection: SnowflakeConnection | None = None):
44
45
  self._snowpark_session = None
46
+ self._connection = connection
45
47
 
46
48
  @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
49
+ def _conn(self) -> SnowflakeConnection:
50
+ if self._connection:
51
+ return self._connection
52
+ return get_cli_context().connection
59
53
 
60
54
  @cached_property
61
55
  def _log(self):
@@ -88,6 +82,12 @@ class SqlExecutionMixin:
88
82
  def _execute_queries(self, queries: str, **kwargs):
89
83
  return list(self._execute_string(dedent(queries), **kwargs))
90
84
 
85
+ def execute_query(self, query: str, **kwargs):
86
+ return self._execute_query(query, **kwargs)
87
+
88
+ def execute_queries(self, queries: str, **kwargs):
89
+ return self._execute_queries(queries, **kwargs)
90
+
91
91
  def use(self, object_type: ObjectType, name: str):
92
92
  try:
93
93
  self._execute_query(f"use {object_type.value.sf_name} {name}")
@@ -97,6 +97,13 @@ class SqlExecutionMixin:
97
97
  f"Could not use {object_type} {name}. Object does not exist, or operation cannot be performed."
98
98
  )
99
99
 
100
+ def current_role(self) -> str:
101
+ *_, cursor = self._execute_string(
102
+ "select current_role()", cursor_class=DictCursor
103
+ )
104
+ role_result = cursor.fetchone()
105
+ return role_result["CURRENT_ROLE()"]
106
+
100
107
  @contextmanager
101
108
  def use_role(self, new_role: str):
102
109
  """
@@ -117,6 +124,12 @@ class SqlExecutionMixin:
117
124
  if is_different_role:
118
125
  self._execute_query(f"use role {prev_role}")
119
126
 
127
+ def session_has_warehouse(self) -> bool:
128
+ result = self._execute_query(
129
+ "select current_warehouse() is not null as result", cursor_class=DictCursor
130
+ ).fetchone()
131
+ return bool(result.get("RESULT"))
132
+
120
133
  @contextmanager
121
134
  def use_warehouse(self, new_wh: str):
122
135
  """
@@ -147,11 +160,11 @@ class SqlExecutionMixin:
147
160
  self.use(object_type=ObjectType.WAREHOUSE, name=prev_wh)
148
161
 
149
162
  def create_password_secret(
150
- self, name: str, username: str, password: str
163
+ self, name: FQN, username: str, password: str
151
164
  ) -> SnowflakeCursor:
152
165
  return self._execute_query(
153
166
  f"""
154
- create secret {name}
167
+ create secret {name.sql_identifier}
155
168
  type = password
156
169
  username = '{username}'
157
170
  password = '{password}'
@@ -159,11 +172,11 @@ class SqlExecutionMixin:
159
172
  )
160
173
 
161
174
  def create_api_integration(
162
- self, name: str, api_provider: str, allowed_prefix: str, secret: Optional[str]
175
+ self, name: FQN, api_provider: str, allowed_prefix: str, secret: Optional[str]
163
176
  ) -> SnowflakeCursor:
164
177
  return self._execute_query(
165
178
  f"""
166
- create api integration {name}
179
+ create api integration {name.sql_identifier}
167
180
  api_provider = {api_provider}
168
181
  api_allowed_prefixes = ('{allowed_prefix}')
169
182
  allowed_authentication_secrets = ({secret if secret else ''})
@@ -254,6 +267,22 @@ class SqlExecutionMixin:
254
267
  return show_obj_row
255
268
 
256
269
 
270
+ class SqlExecutionMixin(SqlExecutor):
271
+ def __init__(self, *args, **kwargs):
272
+ super().__init__(*args, **kwargs)
273
+ self._snowpark_session = None
274
+
275
+ @property
276
+ def snowpark_session(self):
277
+ if not self._snowpark_session:
278
+ from snowflake.snowpark.session import Session
279
+
280
+ self._snowpark_session = Session.builder.configs(
281
+ {"connection": self._conn}
282
+ ).create()
283
+ return self._snowpark_session
284
+
285
+
257
286
  class VerboseCursor(SnowflakeCursor):
258
287
  def execute(self, command: str, *args, **kwargs):
259
288
  cli_console.message(command)
@@ -26,9 +26,9 @@ from snowflake.cli.api.project.schemas.project_definition import (
26
26
  build_project_definition,
27
27
  )
28
28
  from snowflake.cli.api.project.schemas.updatable_model import context
29
- from snowflake.cli.api.rendering.jinja import CONTEXT_KEY
29
+ from snowflake.cli.api.rendering.jinja import CONTEXT_KEY, FUNCTION_KEY
30
30
  from snowflake.cli.api.rendering.project_definition_templates import (
31
- get_project_definition_cli_jinja_env,
31
+ get_client_side_jinja_env,
32
32
  )
33
33
  from snowflake.cli.api.utils.dict_utils import deep_merge_dicts, traverse
34
34
  from snowflake.cli.api.utils.graph import Graph, Node
@@ -96,7 +96,7 @@ class TemplatedEnvironment:
96
96
  )
97
97
  or current_attr_chain is not None
98
98
  ):
99
- raise InvalidTemplate(f"Unexpected templating syntax in {template_value}")
99
+ raise InvalidTemplate(f"Unexpected template syntax in {template_value}")
100
100
 
101
101
  for child_node in ast_node.iter_child_nodes():
102
102
  all_referenced_vars.update(
@@ -318,7 +318,7 @@ def render_definition_template(
318
318
  if definition is None:
319
319
  return ProjectProperties(None, {CONTEXT_KEY: {"env": environment_overrides}})
320
320
 
321
- template_env = TemplatedEnvironment(get_project_definition_cli_jinja_env())
321
+ template_env = TemplatedEnvironment(get_client_side_jinja_env())
322
322
 
323
323
  if "definition_version" not in definition or Version(
324
324
  definition["definition_version"]
@@ -344,7 +344,7 @@ def render_definition_template(
344
344
  _validate_env_section(definition.get("env", {}))
345
345
 
346
346
  # add available templating functions
347
- project_context["fn"] = get_templating_functions()
347
+ project_context[FUNCTION_KEY] = get_templating_functions()
348
348
 
349
349
  referenced_vars = _get_referenced_vars_in_definition(template_env, definition)
350
350
 
@@ -353,9 +353,7 @@ def render_definition_template(
353
353
  )
354
354
 
355
355
  def on_cycle_action(node: Node[TemplateVar]):
356
- raise CycleDetectedError(
357
- f"Cycle detected in templating variable {node.data.key}"
358
- )
356
+ raise CycleDetectedError(f"Cycle detected in template variable {node.data.key}")
359
357
 
360
358
  dependencies_graph.dfs(
361
359
  visit_action=lambda node: _render_graph_node(template_env, node),
@@ -374,10 +372,22 @@ def render_definition_template(
374
372
  )
375
373
 
376
374
  project_definition = build_project_definition(**definition)
375
+
376
+ # Use the values originally provided by the user as the template context
377
+ # This intentionally doesn't reflect any field changes made by
378
+ # validators, to minimize user surprise when templating values
377
379
  project_context[CONTEXT_KEY] = definition
380
+
378
381
  # Use `ProjectEnvironment` in project context in order to
379
382
  # handle env variables overrides from OS env and from CLI arguments.
380
383
  project_context[CONTEXT_KEY]["env"] = ProjectEnvironment(
381
384
  default_env=project_context[CONTEXT_KEY].get("env"), override_env=override_env
382
385
  )
383
386
  return ProjectProperties(project_definition, project_context)
387
+
388
+
389
+ def raw_project_properties(definition: Definition) -> ProjectProperties:
390
+ """
391
+ Returns the raw project definition data without any templating.
392
+ """
393
+ return ProjectProperties(build_project_definition(**definition), {})
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: snowflake-cli-labs
3
- Version: 2.8.0rc1
3
+ Version: 3.0.0rc1
4
4
  Summary: Snowflake CLI
5
5
  Project-URL: Source code, https://github.com/snowflakedb/snowflake-cli
6
6
  Project-URL: Bug Tracker, https://github.com/snowflakedb/snowflake-cli/issues
@@ -216,7 +216,7 @@ Classifier: License :: OSI Approved :: Apache Software License
216
216
  Classifier: Programming Language :: Python :: 3 :: Only
217
217
  Classifier: Programming Language :: SQL
218
218
  Classifier: Topic :: Database
219
- Requires-Python: >=3.8
219
+ Requires-Python: >=3.10
220
220
  Requires-Dist: gitpython==3.1.43
221
221
  Requires-Dist: jinja2==3.1.4
222
222
  Requires-Dist: packaging
@@ -225,20 +225,20 @@ Requires-Dist: pluggy==1.5.0
225
225
  Requires-Dist: pydantic==2.8.2
226
226
  Requires-Dist: pyyaml==6.0.1
227
227
  Requires-Dist: requests==2.32.3
228
- Requires-Dist: requirements-parser==0.9.0
228
+ Requires-Dist: requirements-parser==0.11.0
229
229
  Requires-Dist: rich==13.7.1
230
- Requires-Dist: setuptools==70.3.0
231
- Requires-Dist: snowflake-connector-python[secure-local-storage]==3.11.0
230
+ Requires-Dist: setuptools==74.1.0
231
+ Requires-Dist: snowflake-connector-python[secure-local-storage]==3.12.1
232
232
  Requires-Dist: snowflake-core==0.8.0; python_version < '3.12'
233
233
  Requires-Dist: snowflake-snowpark-python>=1.15.0; python_version < '3.12'
234
- Requires-Dist: tomlkit==0.13.0
235
- Requires-Dist: typer==0.12.3
234
+ Requires-Dist: tomlkit==0.13.2
235
+ Requires-Dist: typer==0.12.5
236
236
  Requires-Dist: urllib3<2.3,>=1.24.3
237
237
  Provides-Extra: development
238
- Requires-Dist: coverage==7.6.0; extra == 'development'
238
+ Requires-Dist: coverage==7.6.1; extra == 'development'
239
239
  Requires-Dist: pre-commit>=3.5.0; extra == 'development'
240
240
  Requires-Dist: pytest-randomly==3.15.0; extra == 'development'
241
- Requires-Dist: pytest==8.3.1; extra == 'development'
241
+ Requires-Dist: pytest==8.3.2; extra == 'development'
242
242
  Requires-Dist: syrupy==4.6.1; extra == 'development'
243
243
  Description-Content-Type: text/markdown
244
244
 
@@ -282,12 +282,12 @@ Cheatsheet: https://github.com/Snowflake-Labs/sf-cheatsheets/blob/main/snowflake
282
282
 
283
283
  ## Install Snowflake CLI
284
284
 
285
- ### Install with pip (PyPi)
285
+ ### Install with pipx (PyPi)
286
286
 
287
- Requires Python >= 3.8
287
+ We recommend installing Snowflake CLI in isolated environment using [pipx](https://pipx.pypa.io/stable/). Requires Python >= 3.10
288
288
 
289
289
  ```bash
290
- pip install snowflake-cli-labs
290
+ pipx install snowflake-cli-labs
291
291
  snow --help
292
292
  ```
293
293
 
@@ -303,7 +303,7 @@ snow --help
303
303
 
304
304
  ### Install from source
305
305
 
306
- Requires Python >= 3.8 and git
306
+ Requires Python >= 3.10 and git
307
307
 
308
308
  ```bash
309
309
  git clone https://github.com/snowflakedb/snowflake-cli