snowflake-cli-labs 3.0.0rc4__py3-none-any.whl → 3.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. README.md +21 -0
  2. {snowflake_cli_labs-3.0.0rc4.dist-info → snowflake_cli_labs-3.0.1.dist-info}/METADATA +6 -96
  3. snowflake_cli_labs-3.0.1.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 -61
  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 -395
  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.0rc4.dist-info/RECORD +0 -242
  242. snowflake_cli_labs-3.0.0rc4.dist-info/entry_points.txt +0 -2
  243. {snowflake_cli_labs-3.0.0rc4.dist-info → snowflake_cli_labs-3.0.1.dist-info}/WHEEL +0 -0
  244. {snowflake_cli_labs-3.0.0rc4.dist-info → snowflake_cli_labs-3.0.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,178 +0,0 @@
1
- # Copyright (c) 2024 Snowflake Inc.
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
-
15
- from __future__ import annotations
16
-
17
- import json
18
- import logging
19
- from typing import Any, Dict, Optional
20
-
21
- from click import ClickException
22
- from snowflake.cli.api.constants import SF_REST_API_URL_PREFIX
23
- from snowflake.connector.connection import SnowflakeConnection
24
- from snowflake.connector.errors import BadRequest
25
- from snowflake.connector.network import SnowflakeRestful
26
- from snowflake.connector.vendored.requests.exceptions import HTTPError
27
-
28
- log = logging.getLogger(__name__)
29
-
30
-
31
- def _pluralize_object_type(object_type: str) -> str:
32
- """
33
- Pluralize object type without depending on OBJECT_TO_NAMES.
34
- """
35
- if object_type.endswith("y"):
36
- return object_type[:-1].lower() + "ies"
37
- else:
38
- return object_type.lower() + "s"
39
-
40
-
41
- class RestApi:
42
- def __init__(self, connection: SnowflakeConnection):
43
- self.conn = connection
44
- self.rest: SnowflakeRestful = connection.rest
45
-
46
- def get_endpoint_exists(self, url: str) -> bool:
47
- """
48
- Check whether [get] endpoint exists under given URL.
49
- """
50
- try:
51
- self.send_rest_request(url, method="get")
52
- return True
53
- except HTTPError as err:
54
- if err.response.status_code == 404:
55
- return False
56
- raise err
57
-
58
- def _fetch_endpoint_exists(self, url: str) -> bool:
59
- try:
60
- result = self.send_rest_request(url, method="get")
61
- return bool(result)
62
- except BadRequest:
63
- return False
64
- except HTTPError as err:
65
- if err.response.status_code == 404:
66
- return False
67
- raise err
68
-
69
- def send_rest_request(
70
- self, url: str, method: str, data: Optional[Dict[str, Any]] = None
71
- ):
72
- """
73
- Executes rest request via snowflake.connector.network.SnowflakeRestful
74
- """
75
- # SnowflakeRestful.request assumes that API response is always a dict,
76
- # which is not true in case of this API, so we need to do this workaround:
77
- from snowflake.connector.network import (
78
- CONTENT_TYPE_APPLICATION_JSON,
79
- HTTP_HEADER_ACCEPT,
80
- HTTP_HEADER_CONTENT_TYPE,
81
- HTTP_HEADER_USER_AGENT,
82
- PYTHON_CONNECTOR_USER_AGENT,
83
- )
84
-
85
- log.debug("Sending %s request to %s", method, url)
86
- full_url = f"{self.rest.server_url}{url}"
87
- headers = {
88
- HTTP_HEADER_CONTENT_TYPE: CONTENT_TYPE_APPLICATION_JSON,
89
- HTTP_HEADER_ACCEPT: CONTENT_TYPE_APPLICATION_JSON,
90
- HTTP_HEADER_USER_AGENT: PYTHON_CONNECTOR_USER_AGENT,
91
- }
92
- return self.rest.fetch(
93
- method=method,
94
- full_url=full_url,
95
- headers=headers,
96
- token=self.rest.token,
97
- data=json.dumps(data if data else {}),
98
- no_retry=True,
99
- raise_raw_http_failure=True,
100
- )
101
-
102
- def _database_exists(self, db_name: str) -> bool:
103
- url = f"{SF_REST_API_URL_PREFIX}/databases/{db_name}"
104
- return self._fetch_endpoint_exists(url)
105
-
106
- def _schema_exists(self, db_name: str, schema_name: str) -> bool:
107
- url = f"{SF_REST_API_URL_PREFIX}/databases/{db_name}/schemas/{schema_name}"
108
- return self._fetch_endpoint_exists(url)
109
-
110
- def determine_url_for_create_query(self, object_type: str) -> str:
111
- """
112
- Determine an url for creating an object of given type via REST API.
113
- If URL cannot be determined, the function throws CannotDetermineCreateURLException exception.
114
-
115
- URLs we check:
116
- * /api/v2/<type>/
117
- * /api/v2/databases/<database>/<type>/
118
- * /api/v2/databases/<database>/schemas/<schema>/<type>/
119
-
120
- We assume that the URLs for CREATE and LIST are the same for every type of object
121
- (endpoints differ by method: POST vs GET, accordingly).
122
- To check whether an URL exists, we send read-only GET request (LIST endpoint,
123
- which should imply CREATE endpoint).
124
- """
125
- plural_object_type = _pluralize_object_type(object_type)
126
-
127
- if self.get_endpoint_exists(
128
- url := f"{SF_REST_API_URL_PREFIX}/{plural_object_type}/"
129
- ):
130
- return url
131
-
132
- db = self.conn.database
133
- if not db:
134
- raise DatabaseNotDefinedException(
135
- "Database not defined in connection. Please try again with `--database` flag."
136
- )
137
- if not self._database_exists(db):
138
- raise DatabaseNotExistsException(f"Database '{db}' does not exist.")
139
- if self.get_endpoint_exists(
140
- url := f"{SF_REST_API_URL_PREFIX}/databases/{db}/{plural_object_type}/"
141
- ):
142
- return url
143
-
144
- schema = self.conn.schema
145
- if not schema:
146
- raise SchemaNotDefinedException(
147
- "Schema not defined in connection. Please try again with `--schema` flag."
148
- )
149
- if not self._schema_exists(db_name=db, schema_name=schema):
150
- raise SchemaNotExistsException(f"Schema '{schema}' does not exist.")
151
- if self.get_endpoint_exists(
152
- url := f"{SF_REST_API_URL_PREFIX}/databases/{self.conn.database}/schemas/{self.conn.schema}/{plural_object_type}/"
153
- ):
154
- return url
155
-
156
- raise CannotDetermineCreateURLException(
157
- f"Create operation for type {object_type} is not supported. Try using `sql -q 'CREATE ...'` command."
158
- )
159
-
160
-
161
- class DatabaseNotDefinedException(ClickException):
162
- pass
163
-
164
-
165
- class SchemaNotDefinedException(ClickException):
166
- pass
167
-
168
-
169
- class DatabaseNotExistsException(ClickException):
170
- pass
171
-
172
-
173
- class SchemaNotExistsException(ClickException):
174
- pass
175
-
176
-
177
- class CannotDetermineCreateURLException(ClickException):
178
- pass
@@ -1,43 +0,0 @@
1
- # Copyright (c) 2024 Snowflake Inc.
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
-
15
- from __future__ import annotations
16
-
17
- import re
18
-
19
- # 7-bit C1 ANSI sequences
20
- _ANSI_ESCAPE = re.compile(
21
- r"""
22
- \x1B # ESC
23
- (?: # 7-bit C1 Fe (except CSI)
24
- [@-Z\\-_]
25
- | # or [ for CSI, followed by a control sequence
26
- \[
27
- [0-?]* # Parameter bytes
28
- [ -/]* # Intermediate bytes
29
- [@-~] # Final byte
30
- )
31
- """,
32
- re.VERBOSE,
33
- )
34
-
35
-
36
- def sanitize_for_terminal(text: str) -> str | None:
37
- """
38
- Escape ASCII escape codes in string. This should be always used
39
- when printing output to terminal.
40
- """
41
- if text is None:
42
- return None
43
- return _ANSI_ESCAPE.sub("", text)
@@ -1,360 +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 errno
18
- import logging
19
- import os
20
- import shutil
21
- import tempfile
22
- from contextlib import contextmanager
23
- from pathlib import Path
24
- from typing import Optional, Union
25
-
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
- )
33
-
34
- log = logging.getLogger(__name__)
35
-
36
- UNLIMITED = -1
37
-
38
-
39
- class SecurePath:
40
- def __init__(self, path: Union[Path, str]):
41
- self._path = Path(path)
42
-
43
- def __repr__(self):
44
- return f'SecurePath("{self._path}")'
45
-
46
- def __truediv__(self, key):
47
- return SecurePath(self._path / key)
48
-
49
- @property
50
- def path(self) -> Path:
51
- """
52
- Returns itself in pathlib.Path format
53
- """
54
- return self._path
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
-
62
- @property
63
- def parent(self):
64
- """
65
- The logical parent of the path. For details, check pathlib.Path.parent
66
- """
67
- return SecurePath(self._path.parent)
68
-
69
- def absolute(self):
70
- """
71
- Make the path absolute, without normalization or resolving symlinks.
72
- """
73
- return SecurePath(self._path.absolute())
74
-
75
- def iterdir(self):
76
- """
77
- When the path points to a directory, yield path objects of the directory contents.
78
- Otherwise, NotADirectoryError is raised.
79
- If the location does not exist, FileNotFoundError is raised.
80
-
81
- For details, check pathlib.Path.iterdir()
82
- """
83
- self.assert_exists()
84
- self.assert_is_directory()
85
- return (SecurePath(p) for p in self._path.iterdir())
86
-
87
- def exists(self) -> bool:
88
- """
89
- Return True if the path points to an existing file or directory.
90
- """
91
- return self._path.exists()
92
-
93
- def is_dir(self) -> bool:
94
- """
95
- Return True if the path points to a directory (or a symbolic link pointing to a directory),
96
- False if it points to another kind of file.
97
- """
98
- return self._path.is_dir()
99
-
100
- def is_file(self) -> bool:
101
- """
102
- Return True if the path points to a regular file (or a symbolic link pointing to a regular file),
103
- False if it points to another kind of file.
104
- """
105
- return self._path.is_file()
106
-
107
- @property
108
- def name(self) -> str:
109
- """A string representing the final path component."""
110
- return self._path.name
111
-
112
- def restrict_permissions(self) -> None:
113
- """
114
- Restrict file/directory permissions to owner-only.
115
- """
116
- restrict_file_permissions(self._path)
117
-
118
- def touch(self, permissions_mask: int = 0o600, exist_ok: bool = True) -> None:
119
- """
120
- Create a file at this given path. For details, check pathlib.Path.touch()
121
- """
122
- if not self.exists():
123
- log.info("Creating file %s", str(self._path))
124
- self._path.touch(mode=permissions_mask, exist_ok=exist_ok)
125
-
126
- def mkdir(
127
- self,
128
- permissions_mask: int = 0o700,
129
- parents: bool = False,
130
- exist_ok: bool = False,
131
- ) -> None:
132
- """
133
- Create a directory at this given path. For details, check pathlib.Path.mkdir()
134
- """
135
- if parents and not self.parent.exists():
136
- self.parent.mkdir(
137
- permissions_mask=permissions_mask, exist_ok=exist_ok, parents=True
138
- )
139
- if not self.exists():
140
- log.info("Creating directory %s", str(self._path))
141
- self._path.mkdir(mode=permissions_mask, exist_ok=exist_ok)
142
-
143
- def read_text(self, file_size_limit_mb: int, *args, **kwargs) -> str:
144
- """
145
- Return the decoded contents of the file as a string.
146
- Raises an error of the file exceeds the specified size limit.
147
- For details, check pathlib.Path.read_text()
148
- """
149
- self._assert_exists_and_is_file()
150
- self._assert_file_size_limit(file_size_limit_mb)
151
- log.info("Reading file %s", self._path)
152
- return self._path.read_text(*args, **kwargs)
153
-
154
- def write_text(self, *args, **kwargs):
155
- """
156
- Open the file pointed to in text mode, write data to it, and close the file.
157
- """
158
- if not self.exists():
159
- self.touch()
160
- log.info("Writing to file %s", self._path)
161
- self.path.write_text(*args, **kwargs)
162
-
163
- @contextmanager
164
- def open( # noqa: A003
165
- self,
166
- mode="r",
167
- read_file_limit_mb: Optional[int] = None,
168
- **open_kwargs,
169
- ):
170
- """
171
- Open the file pointed by this path and return a file object, as
172
- the built-in open() function does.
173
- If the file is opened for reading, [read_file_limit_kb] parameter must be provided.
174
- Raises error if the read file exceeds the specified size limit.
175
- """
176
- opened_for_reading = "r" in mode
177
- if opened_for_reading:
178
- assert (
179
- read_file_limit_mb is not None
180
- ), "For reading mode ('r') read_file_limit_mb must be provided"
181
- self._assert_exists_and_is_file()
182
- self._assert_file_size_limit(read_file_limit_mb)
183
-
184
- if self.exists():
185
- self.assert_is_file()
186
- else:
187
- self.touch() # makes sure permissions of freshly-created file are strict
188
-
189
- log.info("Opening file %s in mode '%s'", self._path, mode)
190
- with self._path.open(mode=mode, **open_kwargs) as fd:
191
- yield fd
192
- log.info("Closing file %s", self._path)
193
-
194
- def move(self, destination: Union[Path, str]) -> "SecurePath":
195
- """Recursively move a file or directory (src) to another location and return the destination.
196
-
197
- If dst is an existing directory or a symlink to a directory, then src is moved inside that directory.
198
- The destination path in that directory must not already exist.
199
- """
200
- destination = Path(destination)
201
- if destination.is_dir():
202
- destination = destination / self._path.name
203
- if destination.exists():
204
- _raise_file_exists_error(destination)
205
- log.info("Moving %s to %s", str(self._path), destination.resolve())
206
- return SecurePath(shutil.move(str(self._path), destination))
207
-
208
- def copy(
209
- self, destination: Union[Path, str], dirs_exist_ok: bool = False
210
- ) -> "SecurePath":
211
- """
212
- Copy the file/directory into the destination.
213
- If source is a directory, its whole content is copied recursively.
214
- Permissions of the copy are limited only to the owner.
215
-
216
- If destination is an existing directory, the copy will be created inside it,
217
- unless dirs_exist_ok is true and the destination has the same name as this path.
218
-
219
- Otherwise, the copied file/base directory will be renamed to match destination.
220
- If dirs_exist_ok is false (the default) and dst already exists,
221
- a FileExistsError is raised. If dirs_exist_ok is true,
222
- the copying operation will continue if it encounters existing directories,
223
- and files within the destination tree will be overwritten by corresponding
224
- files from the src tree.
225
- """
226
- self.assert_exists()
227
-
228
- destination = Path(destination)
229
- if destination.exists():
230
- if destination.is_dir() and (
231
- destination.name != self._path.name or self.path.is_file()
232
- ):
233
- destination = destination / self._path.name
234
-
235
- if destination.exists():
236
- if not all([destination.is_dir(), self._path.is_dir(), dirs_exist_ok]):
237
- raise FileExistsError(
238
- errno.EEXIST, os.strerror(errno.EEXIST), self._path.resolve()
239
- )
240
-
241
- def _recursive_check_for_conflicts(src: Path, dst: Path):
242
- if dst.exists() and not dirs_exist_ok:
243
- _raise_file_exists_error(dst)
244
- if dst.is_file() and not src.is_file():
245
- _raise_not_a_directory_error(dst)
246
- if dst.is_dir() and not src.is_dir():
247
- _raise_is_a_directory_error(dst)
248
- if src.is_dir():
249
- for child in src.iterdir():
250
- _recursive_check_for_conflicts(child, dst / child.name)
251
-
252
- def _recursive_copy(src: SecurePath, dst: SecurePath):
253
- if src.is_file():
254
- log.info("Copying file %s into %s", src.path, dst.path)
255
- if dst.exists():
256
- dst.unlink()
257
- shutil.copyfile(src.path, dst.path)
258
- dst.restrict_permissions()
259
- if src.is_dir():
260
- dst.mkdir(exist_ok=True)
261
- for child in src.iterdir():
262
- _recursive_copy(child, dst / child.name)
263
-
264
- _recursive_check_for_conflicts(self._path, destination)
265
- _recursive_copy(self, self.__class__(destination))
266
-
267
- return SecurePath(destination)
268
-
269
- def unlink(self, missing_ok=False):
270
- """
271
- Remove this file or symbolic link.
272
- If the path points to a directory, use SecurePath.rmdir() instead.
273
-
274
- Check pathlib.Path.unlink() for details.
275
- """
276
- if not self.exists():
277
- if not missing_ok:
278
- self.assert_exists()
279
- return
280
-
281
- self.assert_is_file()
282
- log.info("Removing file %s", self._path)
283
- self._path.unlink()
284
-
285
- def rmdir(self, recursive=False, missing_ok=False):
286
- """
287
- Remove this directory.
288
- If the path points to a file, use SecurePath.unlink() instead.
289
-
290
- If path points to a file, NotADirectoryError will be raised.
291
- If directory does not exist, FileNotFoundError will be raised unless [missing_ok] is True.
292
- If the directory is not empty, DirectoryNotEmpty will be raised unless [recursive] is True.
293
- """
294
- if not self.exists():
295
- if not missing_ok:
296
- self.assert_exists()
297
- return
298
-
299
- self.assert_is_directory()
300
-
301
- if not recursive and any(self._path.iterdir()):
302
- raise DirectoryIsNotEmptyError(self._path.resolve())
303
-
304
- log.info("Removing directory %s", self._path)
305
- shutil.rmtree(str(self._path))
306
-
307
- @classmethod
308
- @contextmanager
309
- def temporary_directory(cls):
310
- """
311
- Creates a temporary directory in the most secure manner possible.
312
- The directory is readable, writable, and searchable only by the creating user ID.
313
- Yields SecurePath pointing to the absolute location of created directory.
314
-
315
- Works similarly to tempfile.TemporaryDirectory
316
- """
317
- with tempfile.TemporaryDirectory(prefix="snowflake-cli") as tmpdir:
318
- log.info("Created temporary directory %s", tmpdir)
319
- yield SecurePath(tmpdir)
320
- log.info("Removing temporary directory %s", tmpdir)
321
-
322
- def _assert_exists_and_is_file(self) -> None:
323
- self.assert_exists()
324
- self.assert_is_file()
325
-
326
- def assert_exists(self) -> None:
327
- if not self.exists():
328
- raise FileNotFoundError(
329
- errno.ENOENT, os.strerror(errno.ENOENT), self._path.resolve()
330
- )
331
-
332
- def assert_is_file(self) -> None:
333
- if not self._path.is_file():
334
- _raise_is_a_directory_error(self._path.resolve())
335
-
336
- def assert_is_directory(self) -> None:
337
- if not self._path.is_dir():
338
- _raise_not_a_directory_error(self._path.resolve())
339
-
340
- def _assert_file_size_limit(self, size_limit_in_mb):
341
- if (
342
- size_limit_in_mb != UNLIMITED
343
- and self._path.stat().st_size > size_limit_in_mb * 1024 * 1024
344
- ):
345
- raise FileTooLargeError(self._path.resolve(), size_limit_in_mb)
346
-
347
- def rename(self, new_name: Union[str | Path]):
348
- self._path.rename(new_name)
349
-
350
-
351
- def _raise_file_exists_error(path: Path):
352
- raise FileExistsError(errno.EEXIST, os.strerror(errno.EEXIST), path)
353
-
354
-
355
- def _raise_is_a_directory_error(path: Path):
356
- raise IsADirectoryError(errno.EISDIR, os.strerror(errno.EISDIR), path)
357
-
358
-
359
- def _raise_not_a_directory_error(path: Path):
360
- raise NotADirectoryError(errno.ENOTDIR, os.strerror(errno.ENOTDIR), path)
@@ -1,118 +0,0 @@
1
- # Copyright (c) 2024 Snowflake Inc.
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
-
15
- import logging
16
- import stat
17
- from pathlib import Path
18
- from typing import List
19
-
20
- from snowflake.connector.compat import IS_WINDOWS
21
-
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:
73
- accessible_by_others = (
74
- # https://docs.python.org/3/library/stat.html
75
- stat.S_IRGRP # readable by group
76
- | stat.S_IROTH # readable by others
77
- | stat.S_IWGRP # writeable by group
78
- | stat.S_IWOTH # writeable by others
79
- | stat.S_IXGRP # executable by group
80
- | stat.S_IXOTH # executable by others
81
- )
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)