snowflake-cli-labs 3.0.0rc5__py3-none-any.whl → 3.0.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 (244) hide show
  1. README.md +21 -0
  2. {snowflake_cli_labs-3.0.0rc5.dist-info → snowflake_cli_labs-3.0.2.dist-info}/METADATA +6 -96
  3. snowflake_cli_labs-3.0.2.dist-info/RECORD +5 -0
  4. snowflake/cli/__about__.py +0 -17
  5. snowflake/cli/__init__.py +0 -13
  6. snowflake/cli/_app/__init__.py +0 -22
  7. snowflake/cli/_app/__main__.py +0 -31
  8. snowflake/cli/_app/api_impl/__init__.py +0 -13
  9. snowflake/cli/_app/api_impl/plugin/__init__.py +0 -13
  10. snowflake/cli/_app/api_impl/plugin/plugin_config_provider_impl.py +0 -66
  11. snowflake/cli/_app/cli_app.py +0 -252
  12. snowflake/cli/_app/commands_registration/__init__.py +0 -33
  13. snowflake/cli/_app/commands_registration/builtin_plugins.py +0 -50
  14. snowflake/cli/_app/commands_registration/command_plugins_loader.py +0 -169
  15. snowflake/cli/_app/commands_registration/commands_registration_with_callbacks.py +0 -105
  16. snowflake/cli/_app/commands_registration/exception_logging.py +0 -26
  17. snowflake/cli/_app/commands_registration/threadsafe.py +0 -48
  18. snowflake/cli/_app/commands_registration/typer_registration.py +0 -153
  19. snowflake/cli/_app/constants.py +0 -19
  20. snowflake/cli/_app/dev/__init__.py +0 -13
  21. snowflake/cli/_app/dev/commands_structure.py +0 -48
  22. snowflake/cli/_app/dev/docs/__init__.py +0 -13
  23. snowflake/cli/_app/dev/docs/commands_docs_generator.py +0 -118
  24. snowflake/cli/_app/dev/docs/generator.py +0 -35
  25. snowflake/cli/_app/dev/docs/project_definition_docs_generator.py +0 -58
  26. snowflake/cli/_app/dev/docs/project_definition_generate_json_schema.py +0 -227
  27. snowflake/cli/_app/dev/docs/template_utils.py +0 -23
  28. snowflake/cli/_app/dev/docs/templates/definition_description.rst.jinja2 +0 -38
  29. snowflake/cli/_app/dev/docs/templates/overview.rst.jinja2 +0 -9
  30. snowflake/cli/_app/dev/docs/templates/usage.rst.jinja2 +0 -67
  31. snowflake/cli/_app/dev/pycharm_remote_debug.py +0 -46
  32. snowflake/cli/_app/loggers.py +0 -199
  33. snowflake/cli/_app/main_typer.py +0 -62
  34. snowflake/cli/_app/printing.py +0 -181
  35. snowflake/cli/_app/secret.py +0 -9
  36. snowflake/cli/_app/snow_connector.py +0 -309
  37. snowflake/cli/_app/telemetry.py +0 -220
  38. snowflake/cli/_app/version_check.py +0 -74
  39. snowflake/cli/_plugins/__init__.py +0 -13
  40. snowflake/cli/_plugins/connection/__init__.py +0 -13
  41. snowflake/cli/_plugins/connection/commands.py +0 -353
  42. snowflake/cli/_plugins/connection/plugin_spec.py +0 -30
  43. snowflake/cli/_plugins/connection/util.py +0 -195
  44. snowflake/cli/_plugins/cortex/__init__.py +0 -13
  45. snowflake/cli/_plugins/cortex/commands.py +0 -332
  46. snowflake/cli/_plugins/cortex/constants.py +0 -17
  47. snowflake/cli/_plugins/cortex/manager.py +0 -189
  48. snowflake/cli/_plugins/cortex/plugin_spec.py +0 -30
  49. snowflake/cli/_plugins/cortex/types.py +0 -22
  50. snowflake/cli/_plugins/git/__init__.py +0 -13
  51. snowflake/cli/_plugins/git/commands.py +0 -358
  52. snowflake/cli/_plugins/git/manager.py +0 -151
  53. snowflake/cli/_plugins/git/plugin_spec.py +0 -30
  54. snowflake/cli/_plugins/helpers/__init__.py +0 -13
  55. snowflake/cli/_plugins/helpers/commands.py +0 -90
  56. snowflake/cli/_plugins/helpers/plugin_spec.py +0 -30
  57. snowflake/cli/_plugins/init/__init__.py +0 -13
  58. snowflake/cli/_plugins/init/commands.py +0 -248
  59. snowflake/cli/_plugins/init/plugin_spec.py +0 -30
  60. snowflake/cli/_plugins/nativeapp/__init__.py +0 -13
  61. snowflake/cli/_plugins/nativeapp/artifacts.py +0 -757
  62. snowflake/cli/_plugins/nativeapp/bundle_context.py +0 -31
  63. snowflake/cli/_plugins/nativeapp/codegen/__init__.py +0 -13
  64. snowflake/cli/_plugins/nativeapp/codegen/artifact_processor.py +0 -91
  65. snowflake/cli/_plugins/nativeapp/codegen/compiler.py +0 -149
  66. snowflake/cli/_plugins/nativeapp/codegen/sandbox.py +0 -306
  67. snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +0 -249
  68. snowflake/cli/_plugins/nativeapp/codegen/setup/setup_driver.py.source +0 -59
  69. snowflake/cli/_plugins/nativeapp/codegen/snowpark/callback_source.py.jinja +0 -181
  70. snowflake/cli/_plugins/nativeapp/codegen/snowpark/extension_function_utils.py +0 -217
  71. snowflake/cli/_plugins/nativeapp/codegen/snowpark/models.py +0 -61
  72. snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +0 -523
  73. snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +0 -114
  74. snowflake/cli/_plugins/nativeapp/commands.py +0 -559
  75. snowflake/cli/_plugins/nativeapp/common_flags.py +0 -44
  76. snowflake/cli/_plugins/nativeapp/constants.py +0 -27
  77. snowflake/cli/_plugins/nativeapp/entities/__init__.py +0 -0
  78. snowflake/cli/_plugins/nativeapp/entities/application.py +0 -878
  79. snowflake/cli/_plugins/nativeapp/entities/application_package.py +0 -1392
  80. snowflake/cli/_plugins/nativeapp/exceptions.py +0 -113
  81. snowflake/cli/_plugins/nativeapp/feature_flags.py +0 -24
  82. snowflake/cli/_plugins/nativeapp/manager.py +0 -415
  83. snowflake/cli/_plugins/nativeapp/plugin_spec.py +0 -30
  84. snowflake/cli/_plugins/nativeapp/policy.py +0 -53
  85. snowflake/cli/_plugins/nativeapp/project_model.py +0 -211
  86. snowflake/cli/_plugins/nativeapp/run_processor.py +0 -184
  87. snowflake/cli/_plugins/nativeapp/same_account_install_method.py +0 -70
  88. snowflake/cli/_plugins/nativeapp/teardown_processor.py +0 -70
  89. snowflake/cli/_plugins/nativeapp/utils.py +0 -98
  90. snowflake/cli/_plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +0 -262
  91. snowflake/cli/_plugins/nativeapp/version/__init__.py +0 -13
  92. snowflake/cli/_plugins/nativeapp/version/commands.py +0 -141
  93. snowflake/cli/_plugins/nativeapp/version/version_processor.py +0 -98
  94. snowflake/cli/_plugins/notebook/__init__.py +0 -13
  95. snowflake/cli/_plugins/notebook/commands.py +0 -86
  96. snowflake/cli/_plugins/notebook/exceptions.py +0 -20
  97. snowflake/cli/_plugins/notebook/manager.py +0 -71
  98. snowflake/cli/_plugins/notebook/plugin_spec.py +0 -30
  99. snowflake/cli/_plugins/notebook/types.py +0 -15
  100. snowflake/cli/_plugins/object/__init__.py +0 -13
  101. snowflake/cli/_plugins/object/command_aliases.py +0 -95
  102. snowflake/cli/_plugins/object/commands.py +0 -180
  103. snowflake/cli/_plugins/object/common.py +0 -85
  104. snowflake/cli/_plugins/object/manager.py +0 -118
  105. snowflake/cli/_plugins/object/plugin_spec.py +0 -30
  106. snowflake/cli/_plugins/snowpark/__init__.py +0 -13
  107. snowflake/cli/_plugins/snowpark/commands.py +0 -450
  108. snowflake/cli/_plugins/snowpark/common.py +0 -268
  109. snowflake/cli/_plugins/snowpark/models.py +0 -150
  110. snowflake/cli/_plugins/snowpark/package/__init__.py +0 -13
  111. snowflake/cli/_plugins/snowpark/package/anaconda_packages.py +0 -199
  112. snowflake/cli/_plugins/snowpark/package/commands.py +0 -195
  113. snowflake/cli/_plugins/snowpark/package/manager.py +0 -44
  114. snowflake/cli/_plugins/snowpark/package/utils.py +0 -26
  115. snowflake/cli/_plugins/snowpark/package_utils.py +0 -354
  116. snowflake/cli/_plugins/snowpark/plugin_spec.py +0 -30
  117. snowflake/cli/_plugins/snowpark/snowpark_entity.py +0 -29
  118. snowflake/cli/_plugins/snowpark/snowpark_entity_model.py +0 -173
  119. snowflake/cli/_plugins/snowpark/snowpark_project_paths.py +0 -109
  120. snowflake/cli/_plugins/snowpark/snowpark_shared.py +0 -59
  121. snowflake/cli/_plugins/snowpark/zipper.py +0 -89
  122. snowflake/cli/_plugins/spcs/__init__.py +0 -33
  123. snowflake/cli/_plugins/spcs/common.py +0 -99
  124. snowflake/cli/_plugins/spcs/compute_pool/__init__.py +0 -13
  125. snowflake/cli/_plugins/spcs/compute_pool/commands.py +0 -241
  126. snowflake/cli/_plugins/spcs/compute_pool/manager.py +0 -121
  127. snowflake/cli/_plugins/spcs/image_registry/__init__.py +0 -13
  128. snowflake/cli/_plugins/spcs/image_registry/commands.py +0 -65
  129. snowflake/cli/_plugins/spcs/image_registry/manager.py +0 -105
  130. snowflake/cli/_plugins/spcs/image_repository/__init__.py +0 -13
  131. snowflake/cli/_plugins/spcs/image_repository/commands.py +0 -202
  132. snowflake/cli/_plugins/spcs/image_repository/manager.py +0 -84
  133. snowflake/cli/_plugins/spcs/plugin_spec.py +0 -30
  134. snowflake/cli/_plugins/spcs/services/__init__.py +0 -13
  135. snowflake/cli/_plugins/spcs/services/commands.py +0 -345
  136. snowflake/cli/_plugins/spcs/services/manager.py +0 -208
  137. snowflake/cli/_plugins/sql/__init__.py +0 -13
  138. snowflake/cli/_plugins/sql/commands.py +0 -86
  139. snowflake/cli/_plugins/sql/manager.py +0 -92
  140. snowflake/cli/_plugins/sql/plugin_spec.py +0 -30
  141. snowflake/cli/_plugins/sql/snowsql_templating.py +0 -28
  142. snowflake/cli/_plugins/stage/__init__.py +0 -13
  143. snowflake/cli/_plugins/stage/commands.py +0 -264
  144. snowflake/cli/_plugins/stage/diff.py +0 -280
  145. snowflake/cli/_plugins/stage/manager.py +0 -582
  146. snowflake/cli/_plugins/stage/md5.py +0 -160
  147. snowflake/cli/_plugins/stage/plugin_spec.py +0 -30
  148. snowflake/cli/_plugins/stage/utils.py +0 -54
  149. snowflake/cli/_plugins/streamlit/__init__.py +0 -13
  150. snowflake/cli/_plugins/streamlit/commands.py +0 -195
  151. snowflake/cli/_plugins/streamlit/manager.py +0 -220
  152. snowflake/cli/_plugins/streamlit/plugin_spec.py +0 -30
  153. snowflake/cli/_plugins/streamlit/streamlit_entity.py +0 -12
  154. snowflake/cli/_plugins/streamlit/streamlit_entity_model.py +0 -66
  155. snowflake/cli/_plugins/workspace/__init__.py +0 -13
  156. snowflake/cli/_plugins/workspace/action_context.py +0 -18
  157. snowflake/cli/_plugins/workspace/commands.py +0 -306
  158. snowflake/cli/_plugins/workspace/manager.py +0 -74
  159. snowflake/cli/_plugins/workspace/plugin_spec.py +0 -30
  160. snowflake/cli/api/__init__.py +0 -48
  161. snowflake/cli/api/cli_global_context.py +0 -247
  162. snowflake/cli/api/commands/__init__.py +0 -13
  163. snowflake/cli/api/commands/alias.py +0 -23
  164. snowflake/cli/api/commands/common.py +0 -25
  165. snowflake/cli/api/commands/decorators.py +0 -369
  166. snowflake/cli/api/commands/execution_metadata.py +0 -40
  167. snowflake/cli/api/commands/experimental_behaviour.py +0 -18
  168. snowflake/cli/api/commands/flags.py +0 -561
  169. snowflake/cli/api/commands/overrideable_parameter.py +0 -143
  170. snowflake/cli/api/commands/snow_typer.py +0 -247
  171. snowflake/cli/api/commands/utils.py +0 -18
  172. snowflake/cli/api/config.py +0 -380
  173. snowflake/cli/api/connections.py +0 -216
  174. snowflake/cli/api/console/__init__.py +0 -17
  175. snowflake/cli/api/console/abc.py +0 -94
  176. snowflake/cli/api/console/console.py +0 -134
  177. snowflake/cli/api/console/enum.py +0 -17
  178. snowflake/cli/api/constants.py +0 -90
  179. snowflake/cli/api/entities/common.py +0 -56
  180. snowflake/cli/api/entities/utils.py +0 -370
  181. snowflake/cli/api/errno.py +0 -28
  182. snowflake/cli/api/exceptions.py +0 -190
  183. snowflake/cli/api/feature_flags.py +0 -54
  184. snowflake/cli/api/identifiers.py +0 -190
  185. snowflake/cli/api/metrics.py +0 -92
  186. snowflake/cli/api/output/__init__.py +0 -13
  187. snowflake/cli/api/output/formats.py +0 -20
  188. snowflake/cli/api/output/types.py +0 -118
  189. snowflake/cli/api/plugins/__init__.py +0 -13
  190. snowflake/cli/api/plugins/command/__init__.py +0 -72
  191. snowflake/cli/api/plugins/command/plugin_hook_specs.py +0 -21
  192. snowflake/cli/api/plugins/plugin_config.py +0 -32
  193. snowflake/cli/api/project/__init__.py +0 -13
  194. snowflake/cli/api/project/definition.py +0 -126
  195. snowflake/cli/api/project/definition_conversion.py +0 -400
  196. snowflake/cli/api/project/definition_manager.py +0 -145
  197. snowflake/cli/api/project/errors.py +0 -56
  198. snowflake/cli/api/project/project_verification.py +0 -23
  199. snowflake/cli/api/project/schemas/__init__.py +0 -13
  200. snowflake/cli/api/project/schemas/entities/__init__.py +0 -13
  201. snowflake/cli/api/project/schemas/entities/common.py +0 -153
  202. snowflake/cli/api/project/schemas/entities/entities.py +0 -61
  203. snowflake/cli/api/project/schemas/project_definition.py +0 -330
  204. snowflake/cli/api/project/schemas/template.py +0 -77
  205. snowflake/cli/api/project/schemas/updatable_model.py +0 -202
  206. snowflake/cli/api/project/schemas/v1/__init__.py +0 -0
  207. snowflake/cli/api/project/schemas/v1/identifier_model.py +0 -51
  208. snowflake/cli/api/project/schemas/v1/native_app/__init__.py +0 -0
  209. snowflake/cli/api/project/schemas/v1/native_app/application.py +0 -61
  210. snowflake/cli/api/project/schemas/v1/native_app/native_app.py +0 -93
  211. snowflake/cli/api/project/schemas/v1/native_app/package.py +0 -84
  212. snowflake/cli/api/project/schemas/v1/native_app/path_mapping.py +0 -65
  213. snowflake/cli/api/project/schemas/v1/snowpark/__init__.py +0 -0
  214. snowflake/cli/api/project/schemas/v1/snowpark/argument.py +0 -28
  215. snowflake/cli/api/project/schemas/v1/snowpark/callable.py +0 -69
  216. snowflake/cli/api/project/schemas/v1/snowpark/snowpark.py +0 -36
  217. snowflake/cli/api/project/schemas/v1/streamlit/__init__.py +0 -0
  218. snowflake/cli/api/project/schemas/v1/streamlit/streamlit.py +0 -47
  219. snowflake/cli/api/project/util.py +0 -278
  220. snowflake/cli/api/rendering/__init__.py +0 -13
  221. snowflake/cli/api/rendering/jinja.py +0 -118
  222. snowflake/cli/api/rendering/project_definition_templates.py +0 -43
  223. snowflake/cli/api/rendering/project_templates.py +0 -98
  224. snowflake/cli/api/rendering/sql_templates.py +0 -105
  225. snowflake/cli/api/rest_api.py +0 -178
  226. snowflake/cli/api/sanitizers.py +0 -43
  227. snowflake/cli/api/secure_path.py +0 -360
  228. snowflake/cli/api/secure_utils.py +0 -118
  229. snowflake/cli/api/sql_execution.py +0 -280
  230. snowflake/cli/api/utils/__init__.py +0 -13
  231. snowflake/cli/api/utils/cursor.py +0 -34
  232. snowflake/cli/api/utils/definition_rendering.py +0 -415
  233. snowflake/cli/api/utils/dict_utils.py +0 -73
  234. snowflake/cli/api/utils/error_handling.py +0 -23
  235. snowflake/cli/api/utils/graph.py +0 -97
  236. snowflake/cli/api/utils/models.py +0 -63
  237. snowflake/cli/api/utils/naming_utils.py +0 -13
  238. snowflake/cli/api/utils/path_utils.py +0 -36
  239. snowflake/cli/api/utils/templating_functions.py +0 -144
  240. snowflake/cli/api/utils/types.py +0 -35
  241. snowflake_cli_labs-3.0.0rc5.dist-info/RECORD +0 -242
  242. snowflake_cli_labs-3.0.0rc5.dist-info/entry_points.txt +0 -2
  243. {snowflake_cli_labs-3.0.0rc5.dist-info → snowflake_cli_labs-3.0.2.dist-info}/WHEEL +0 -0
  244. {snowflake_cli_labs-3.0.0rc5.dist-info → snowflake_cli_labs-3.0.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,582 +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 fnmatch
18
- import glob
19
- import logging
20
- import re
21
- import sys
22
- from contextlib import nullcontext
23
- from dataclasses import dataclass
24
- from os import path
25
- from pathlib import Path
26
- from textwrap import dedent
27
- from typing import Dict, List, Optional, Union
28
-
29
- from click import ClickException
30
- from snowflake.cli._plugins.snowpark.package_utils import parse_requirements
31
- from snowflake.cli.api.commands.common import (
32
- OnErrorType,
33
- Variable,
34
- )
35
- from snowflake.cli.api.commands.utils import parse_key_value_variables
36
- from snowflake.cli.api.console import cli_console
37
- from snowflake.cli.api.constants import PYTHON_3_12
38
- from snowflake.cli.api.identifiers import FQN
39
- from snowflake.cli.api.project.util import to_string_literal
40
- from snowflake.cli.api.secure_path import SecurePath
41
- from snowflake.cli.api.sql_execution import SqlExecutionMixin
42
- from snowflake.cli.api.utils.path_utils import path_resolver
43
- from snowflake.connector import DictCursor, ProgrammingError
44
- from snowflake.connector.cursor import SnowflakeCursor
45
-
46
- if sys.version_info < PYTHON_3_12:
47
- # Because Snowpark works only below 3.12 and to use @sproc Session must be imported here.
48
- from snowflake.snowpark import Session
49
-
50
- log = logging.getLogger(__name__)
51
-
52
-
53
- UNQUOTED_FILE_URI_REGEX = r"[\w/*?\-.=&{}$#[\]\"\\!@%^+:]+"
54
- USER_STAGE_PREFIX = "@~"
55
- EXECUTE_SUPPORTED_FILES_FORMATS = (
56
- ".sql",
57
- ".py",
58
- ) # tuple to preserve order but it's a set
59
-
60
- # Replace magic numbers with constants
61
- OMIT_FIRST = slice(1, None)
62
-
63
-
64
- @dataclass
65
- class StagePathParts:
66
- directory: str
67
- stage: str
68
- stage_name: str
69
- is_directory: bool
70
-
71
- @classmethod
72
- def get_directory(cls, stage_path: str) -> str:
73
- return "/".join(Path(stage_path).parts[OMIT_FIRST])
74
-
75
- @property
76
- def path(self) -> str:
77
- raise NotImplementedError
78
-
79
- @property
80
- def full_path(self) -> str:
81
- raise NotImplementedError
82
-
83
- def replace_stage_prefix(self, file_path: str) -> str:
84
- raise NotImplementedError
85
-
86
- def add_stage_prefix(self, file_path: str) -> str:
87
- raise NotImplementedError
88
-
89
- def get_directory_from_file_path(self, file_path: str) -> List[str]:
90
- raise NotImplementedError
91
-
92
- def get_full_stage_path(self, path: str):
93
- if prefix := FQN.from_stage(self.stage).prefix:
94
- return prefix + "." + path
95
- return path
96
-
97
- def get_standard_stage_path(self) -> str:
98
- path = self.path
99
- return f"@{path}{'/'if self.is_directory and not path.endswith('/') else ''}"
100
-
101
- def get_standard_stage_directory_path(self) -> str:
102
- path = self.get_standard_stage_path()
103
- if not path.endswith("/"):
104
- return path + "/"
105
- return path
106
-
107
-
108
- @dataclass
109
- class DefaultStagePathParts(StagePathParts):
110
- """
111
- For path like @db.schema.stage/dir the values will be:
112
- directory = dir
113
- stage = @db.schema.stage
114
- stage_name = stage
115
- For `@stage/dir` to
116
- stage -> @stage
117
- stage_name -> stage
118
- directory -> dir
119
- """
120
-
121
- def __init__(self, stage_path: str):
122
- self.directory = self.get_directory(stage_path)
123
- self.stage = StageManager.get_stage_from_path(stage_path)
124
- stage_name = self.stage.split(".")[-1]
125
- stage_name = (
126
- stage_name[OMIT_FIRST] if stage_name.startswith("@") else stage_name
127
- )
128
- self.stage_name = stage_name
129
- self.is_directory = True if stage_path.endswith("/") else False
130
-
131
- @property
132
- def path(self) -> str:
133
- return f"{self.stage_name.rstrip('/')}/{self.directory}"
134
-
135
- @property
136
- def full_path(self) -> str:
137
- return f"{self.stage.rstrip('/')}/{self.directory}"
138
-
139
- def replace_stage_prefix(self, file_path: str) -> str:
140
- stage = Path(self.stage).parts[0]
141
- file_path_without_prefix = Path(file_path).parts[OMIT_FIRST]
142
- return f"{stage}/{'/'.join(file_path_without_prefix)}"
143
-
144
- def add_stage_prefix(self, file_path: str) -> str:
145
- stage = self.stage.rstrip("/")
146
- return f"{stage}/{file_path.lstrip('/')}"
147
-
148
- def get_directory_from_file_path(self, file_path: str) -> List[str]:
149
- stage_path_length = len(Path(self.directory).parts)
150
- return list(Path(file_path).parts[1 + stage_path_length : -1])
151
-
152
-
153
- @dataclass
154
- class UserStagePathParts(StagePathParts):
155
- """
156
- For path like @db.schema.stage/dir the values will be:
157
- directory = dir
158
- stage = @~
159
- stage_name = @~
160
- """
161
-
162
- def __init__(self, stage_path: str):
163
- self.directory = self.get_directory(stage_path)
164
- self.stage = USER_STAGE_PREFIX
165
- self.stage_name = USER_STAGE_PREFIX
166
- self.is_directory = True if stage_path.endswith("/") else False
167
-
168
- @classmethod
169
- def get_directory(cls, stage_path: str) -> str:
170
- if Path(stage_path).parts[0] == USER_STAGE_PREFIX:
171
- return super().get_directory(stage_path)
172
- return stage_path
173
-
174
- @property
175
- def path(self) -> str:
176
- return f"{self.directory}"
177
-
178
- @property
179
- def full_path(self) -> str:
180
- return f"{self.stage}/{self.directory}"
181
-
182
- def replace_stage_prefix(self, file_path: str) -> str:
183
- if Path(file_path).parts[0] == self.stage_name:
184
- return file_path
185
- return f"{self.stage}/{file_path}"
186
-
187
- def add_stage_prefix(self, file_path: str) -> str:
188
- return f"{self.stage}/{file_path}"
189
-
190
- def get_directory_from_file_path(self, file_path: str) -> List[str]:
191
- stage_path_length = len(Path(self.directory).parts)
192
- return list(Path(file_path).parts[stage_path_length:-1])
193
-
194
-
195
- class StageManager(SqlExecutionMixin):
196
- def __init__(self):
197
- super().__init__()
198
- self._python_exe_procedure = None
199
-
200
- @staticmethod
201
- def get_standard_stage_prefix(name: str | FQN) -> str:
202
- if isinstance(name, FQN):
203
- name = name.identifier
204
- # Handle embedded stages
205
- if name.startswith("snow://") or name.startswith("@"):
206
- return name
207
-
208
- return f"@{name}"
209
-
210
- @staticmethod
211
- def get_stage_from_path(path: str):
212
- """
213
- Returns stage name from potential path on stage. For example
214
- db.schema.stage/foo/bar -> db.schema.stage
215
- """
216
- return Path(path).parts[0]
217
-
218
- @staticmethod
219
- def quote_stage_name(name: str) -> str:
220
- if name.startswith("'") and name.endswith("'"):
221
- return name # already quoted
222
-
223
- standard_name = StageManager.get_standard_stage_prefix(name)
224
- if standard_name.startswith("@") and not re.fullmatch(
225
- r"@([\w./$])+", standard_name
226
- ):
227
- return to_string_literal(standard_name)
228
-
229
- return standard_name
230
-
231
- def _to_uri(self, local_path: str):
232
- uri = f"file://{local_path}"
233
- if re.fullmatch(UNQUOTED_FILE_URI_REGEX, uri):
234
- return uri
235
- return to_string_literal(uri)
236
-
237
- def list_files(self, stage_name: str, pattern: str | None = None) -> DictCursor:
238
- stage_name = self.get_standard_stage_prefix(stage_name)
239
- query = f"ls {self.quote_stage_name(stage_name)}"
240
- if pattern is not None:
241
- query += f" pattern = '{pattern}'"
242
- return self._execute_query(query, cursor_class=DictCursor)
243
-
244
- @staticmethod
245
- def _assure_is_existing_directory(path: Path) -> None:
246
- spath = SecurePath(path)
247
- if not spath.exists():
248
- spath.mkdir(parents=True)
249
- spath.assert_is_directory()
250
-
251
- def get(
252
- self, stage_path: str, dest_path: Path, parallel: int = 4
253
- ) -> SnowflakeCursor:
254
- stage_path = self.get_standard_stage_prefix(stage_path)
255
- self._assure_is_existing_directory(dest_path)
256
- dest_directory = f"{dest_path}/"
257
- return self._execute_query(
258
- f"get {self.quote_stage_name(stage_path)} {self._to_uri(dest_directory)} parallel={parallel}"
259
- )
260
-
261
- def get_recursive(
262
- self, stage_path: str, dest_path: Path, parallel: int = 4
263
- ) -> List[SnowflakeCursor]:
264
- stage_path_parts = self._stage_path_part_factory(stage_path)
265
-
266
- results = []
267
- for file_path in self.iter_stage(stage_path):
268
- dest_directory = dest_path
269
- for path_part in stage_path_parts.get_directory_from_file_path(file_path):
270
- dest_directory = dest_directory / path_part
271
- self._assure_is_existing_directory(dest_directory)
272
-
273
- result = self._execute_query(
274
- f"get {self.quote_stage_name(stage_path_parts.replace_stage_prefix(file_path))} {self._to_uri(f'{dest_directory}/')} parallel={parallel}"
275
- )
276
- results.append(result)
277
-
278
- return results
279
-
280
- def put(
281
- self,
282
- local_path: Union[str, Path],
283
- stage_path: str,
284
- parallel: int = 4,
285
- overwrite: bool = False,
286
- role: Optional[str] = None,
287
- auto_compress: bool = False,
288
- ) -> SnowflakeCursor:
289
- """
290
- This method will take a file path from the user's system and put it into a Snowflake stage,
291
- which includes its fully qualified name as well as the path within the stage.
292
- If provided with a role, then temporarily use this role to perform the operation above,
293
- and switch back to the original role for the next commands to run.
294
- """
295
- with self.use_role(role) if role else nullcontext():
296
- stage_path = self.get_standard_stage_prefix(stage_path)
297
- local_resolved_path = path_resolver(str(local_path))
298
- log.info("Uploading %s to %s", local_resolved_path, stage_path)
299
- cursor = self._execute_query(
300
- f"put {self._to_uri(local_resolved_path)} {self.quote_stage_name(stage_path)} "
301
- f"auto_compress={str(auto_compress).lower()} parallel={parallel} overwrite={overwrite}"
302
- )
303
- return cursor
304
-
305
- def copy_files(self, source_path: str, destination_path: str) -> SnowflakeCursor:
306
- source_path_parts = self._stage_path_part_factory(source_path)
307
- destination_path_parts = self._stage_path_part_factory(destination_path)
308
-
309
- if isinstance(destination_path_parts, UserStagePathParts):
310
- raise ClickException(
311
- "Destination path cannot be a user stage. Please provide a named stage."
312
- )
313
-
314
- source = source_path_parts.get_standard_stage_path()
315
- destination = destination_path_parts.get_standard_stage_directory_path()
316
- log.info("Copying files from %s to %s", source, destination)
317
- query = f"copy files into {destination} from {source}"
318
- return self._execute_query(query)
319
-
320
- def remove(
321
- self, stage_name: str, path: str, role: Optional[str] = None
322
- ) -> SnowflakeCursor:
323
- """
324
- This method will take a file path that exists on a Snowflake stage,
325
- and remove it from the stage.
326
- If provided with a role, then temporarily use this role to perform the operation above,
327
- and switch back to the original role for the next commands to run.
328
- """
329
- with self.use_role(role) if role else nullcontext():
330
- stage_name = self.get_standard_stage_prefix(stage_name)
331
- path = path if path.startswith("/") else "/" + path
332
- quoted_stage_name = self.quote_stage_name(f"{stage_name}{path}")
333
- return self._execute_query(f"remove {quoted_stage_name}")
334
-
335
- def create(self, fqn: FQN, comment: Optional[str] = None) -> SnowflakeCursor:
336
- query = f"create stage if not exists {fqn.sql_identifier}"
337
- if comment:
338
- query += f" comment='{comment}'"
339
- return self._execute_query(query)
340
-
341
- def iter_stage(self, stage_path: str):
342
- for file in self.list_files(stage_path).fetchall():
343
- yield file["name"]
344
-
345
- def execute(
346
- self,
347
- stage_path: str,
348
- on_error: OnErrorType,
349
- variables: Optional[List[str]] = None,
350
- ):
351
- stage_path_parts = self._stage_path_part_factory(stage_path)
352
- all_files_list = self._get_files_list_from_stage(stage_path_parts)
353
-
354
- all_files_with_stage_name_prefix = [
355
- stage_path_parts.get_directory(file) for file in all_files_list
356
- ]
357
-
358
- # filter files from stage if match stage_path pattern
359
- filtered_file_list = self._filter_files_list(
360
- stage_path_parts, all_files_with_stage_name_prefix
361
- )
362
-
363
- if not filtered_file_list:
364
- raise ClickException(f"No files matched pattern '{stage_path}'")
365
-
366
- # sort filtered files in alphabetical order with directories at the end
367
- sorted_file_path_list = sorted(
368
- filtered_file_list, key=lambda f: (path.dirname(f), path.basename(f))
369
- )
370
-
371
- parsed_variables = parse_key_value_variables(variables)
372
- sql_variables = self._parse_execute_variables(parsed_variables)
373
- python_variables = {str(v.key): v.value for v in parsed_variables}
374
- results = []
375
-
376
- if any(file.endswith(".py") for file in sorted_file_path_list):
377
- self._python_exe_procedure = self._bootstrap_snowpark_execution_environment(
378
- stage_path_parts
379
- )
380
-
381
- for file_path in sorted_file_path_list:
382
- file_stage_path = stage_path_parts.add_stage_prefix(file_path)
383
- if file_path.endswith(".py"):
384
- result = self._execute_python(
385
- file_stage_path=file_stage_path,
386
- on_error=on_error,
387
- variables=python_variables,
388
- )
389
- else:
390
- result = self._call_execute_immediate(
391
- file_stage_path=file_stage_path,
392
- variables=sql_variables,
393
- on_error=on_error,
394
- )
395
- results.append(result)
396
-
397
- return results
398
-
399
- def _get_files_list_from_stage(
400
- self, stage_path_parts: StagePathParts, pattern: str | None = None
401
- ) -> List[str]:
402
- files_list_result = self.list_files(
403
- stage_path_parts.stage, pattern=pattern
404
- ).fetchall()
405
-
406
- if not files_list_result:
407
- raise ClickException(f"No files found on stage '{stage_path_parts.stage}'")
408
-
409
- return [f["name"] for f in files_list_result]
410
-
411
- def _filter_files_list(
412
- self, stage_path_parts: StagePathParts, files_on_stage: List[str]
413
- ) -> List[str]:
414
- if not stage_path_parts.directory:
415
- return self._filter_supported_files(files_on_stage)
416
-
417
- stage_path = stage_path_parts.directory
418
-
419
- # Exact file path was provided if stage_path in file list
420
- if stage_path in files_on_stage:
421
- filtered_files = self._filter_supported_files([stage_path])
422
- if filtered_files:
423
- return filtered_files
424
- else:
425
- raise ClickException(
426
- f"Invalid file extension, only {', '.join(EXECUTE_SUPPORTED_FILES_FORMATS)} files are allowed."
427
- )
428
- # Filter with fnmatch if contains `*` or `?`
429
- if glob.has_magic(stage_path):
430
- filtered_files = fnmatch.filter(files_on_stage, stage_path)
431
- else:
432
- # Path to directory was provided
433
- filtered_files = fnmatch.filter(files_on_stage, f"{stage_path}*")
434
- return self._filter_supported_files(filtered_files)
435
-
436
- @staticmethod
437
- def _filter_supported_files(files: List[str]) -> List[str]:
438
- return [f for f in files if Path(f).suffix in EXECUTE_SUPPORTED_FILES_FORMATS]
439
-
440
- @staticmethod
441
- def _parse_execute_variables(variables: List[Variable]) -> Optional[str]:
442
- if not variables:
443
- return None
444
- query_parameters = [f"{v.key}=>{v.value}" for v in variables]
445
- return f" using ({', '.join(query_parameters)})"
446
-
447
- @staticmethod
448
- def _success_result(file: str):
449
- cli_console.warning(f"SUCCESS - {file}")
450
- return {"File": file, "Status": "SUCCESS", "Error": None}
451
-
452
- @staticmethod
453
- def _error_result(file: str, msg: str):
454
- cli_console.warning(f"FAILURE - {file}")
455
- return {"File": file, "Status": "FAILURE", "Error": msg}
456
-
457
- @staticmethod
458
- def _handle_execution_exception(on_error: OnErrorType, exception: Exception):
459
- if on_error == OnErrorType.BREAK:
460
- raise exception
461
-
462
- def _call_execute_immediate(
463
- self,
464
- file_stage_path: str,
465
- variables: Optional[str],
466
- on_error: OnErrorType,
467
- ) -> Dict:
468
- try:
469
- query = f"execute immediate from {self.quote_stage_name(file_stage_path)}"
470
- if variables:
471
- query += variables
472
- self._execute_query(query)
473
- return StageManager._success_result(file=file_stage_path)
474
- except ProgrammingError as e:
475
- StageManager._handle_execution_exception(on_error=on_error, exception=e)
476
- return StageManager._error_result(file=file_stage_path, msg=e.msg)
477
-
478
- @staticmethod
479
- def _stage_path_part_factory(stage_path: str) -> StagePathParts:
480
- stage_path = StageManager.get_standard_stage_prefix(stage_path)
481
- if stage_path.startswith(USER_STAGE_PREFIX):
482
- return UserStagePathParts(stage_path)
483
- return DefaultStagePathParts(stage_path)
484
-
485
- def _check_for_requirements_file(
486
- self, stage_path_parts: StagePathParts
487
- ) -> List[str]:
488
- """Looks for requirements.txt file on stage."""
489
- req_files_on_stage = self._get_files_list_from_stage(
490
- stage_path_parts, pattern=r".*requirements\.txt$"
491
- )
492
- if not req_files_on_stage:
493
- return []
494
-
495
- # Construct all possible path for requirements file for this context
496
- # We don't use os.path or pathlib to preserve compatibility on Windows
497
- req_file_name = "requirements.txt"
498
- path_parts = stage_path_parts.path.split("/")
499
- possible_req_files = []
500
-
501
- while path_parts:
502
- current_file = "/".join([*path_parts, req_file_name])
503
- possible_req_files.append(str(current_file))
504
- path_parts = path_parts[:-1]
505
-
506
- # Now for every possible path check if the file exists on stage,
507
- # if yes break, we use the first possible file
508
- requirements_file = None
509
- for req_file in possible_req_files:
510
- if req_file in req_files_on_stage:
511
- requirements_file = req_file
512
- break
513
-
514
- # If we haven't found any matching requirements
515
- if requirements_file is None:
516
- return []
517
-
518
- # req_file at this moment is the first found requirements file
519
- with SecurePath.temporary_directory() as tmp_dir:
520
- self.get(
521
- stage_path_parts.get_full_stage_path(requirements_file), tmp_dir.path
522
- )
523
- requirements = parse_requirements(
524
- requirements_file=tmp_dir / "requirements.txt"
525
- )
526
-
527
- return [req.package_name for req in requirements]
528
-
529
- def _bootstrap_snowpark_execution_environment(
530
- self, stage_path_parts: StagePathParts
531
- ):
532
- """Prepares Snowpark session for executing Python code remotely."""
533
- if sys.version_info >= PYTHON_3_12:
534
- raise ClickException(
535
- f"Executing python files is not supported in Python >= 3.12. Current version: {sys.version}"
536
- )
537
-
538
- from snowflake.snowpark.functions import sproc
539
-
540
- self.snowpark_session.add_packages("snowflake-snowpark-python")
541
- self.snowpark_session.add_packages("snowflake.core")
542
- requirements = self._check_for_requirements_file(stage_path_parts)
543
- self.snowpark_session.add_packages(*requirements)
544
-
545
- @sproc(is_permanent=False)
546
- def _python_execution_procedure(
547
- _: Session, file_path: str, variables: Dict | None = None
548
- ) -> None:
549
- """Snowpark session-scoped stored procedure to execute content of provided python file."""
550
- import json
551
-
552
- from snowflake.snowpark.files import SnowflakeFile
553
-
554
- with SnowflakeFile.open(file_path, require_scoped_url=False) as f:
555
- file_content: str = f.read() # type: ignore
556
-
557
- wrapper = dedent(
558
- f"""\
559
- import os
560
- os.environ.update({json.dumps(variables)})
561
- """
562
- )
563
-
564
- exec(wrapper + file_content)
565
-
566
- return _python_execution_procedure
567
-
568
- def _execute_python(
569
- self, file_stage_path: str, on_error: OnErrorType, variables: Dict
570
- ):
571
- """
572
- Executes Python file from stage using a Snowpark temporary procedure.
573
- Currently, there's no option to pass input to the execution.
574
- """
575
- from snowflake.snowpark.exceptions import SnowparkSQLException
576
-
577
- try:
578
- self._python_exe_procedure(self.get_standard_stage_prefix(file_stage_path), variables) # type: ignore
579
- return StageManager._success_result(file=file_stage_path)
580
- except SnowparkSQLException as e:
581
- StageManager._handle_execution_exception(on_error=on_error, exception=e)
582
- return StageManager._error_result(file=file_stage_path, msg=e.message)