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
@@ -27,11 +27,12 @@ from textwrap import dedent
27
27
  from typing import Dict, List, Optional, Union
28
28
 
29
29
  from click import ClickException
30
- from snowflake.cli.api.commands.flags import (
30
+ from snowflake.cli._plugins.snowpark.package_utils import parse_requirements
31
+ from snowflake.cli.api.commands.common import (
31
32
  OnErrorType,
32
33
  Variable,
33
- parse_key_value_variables,
34
34
  )
35
+ from snowflake.cli.api.commands.utils import parse_key_value_variables
35
36
  from snowflake.cli.api.console import cli_console
36
37
  from snowflake.cli.api.constants import PYTHON_3_12
37
38
  from snowflake.cli.api.identifiers import FQN
@@ -39,7 +40,6 @@ from snowflake.cli.api.project.util import to_string_literal
39
40
  from snowflake.cli.api.secure_path import SecurePath
40
41
  from snowflake.cli.api.sql_execution import SqlExecutionMixin
41
42
  from snowflake.cli.api.utils.path_utils import path_resolver
42
- from snowflake.cli.plugins.snowpark.package_utils import parse_requirements
43
43
  from snowflake.connector import DictCursor, ProgrammingError
44
44
  from snowflake.connector.cursor import SnowflakeCursor
45
45
 
@@ -65,14 +65,21 @@ class StagePathParts:
65
65
  stage_name: str
66
66
  is_directory: bool
67
67
 
68
- @staticmethod
69
- def get_directory(stage_path: str) -> str:
68
+ @classmethod
69
+ def get_directory(cls, stage_path: str) -> str:
70
70
  return "/".join(Path(stage_path).parts[1:])
71
71
 
72
72
  @property
73
73
  def path(self) -> str:
74
74
  raise NotImplementedError
75
75
 
76
+ @property
77
+ def full_path(self) -> str:
78
+ raise NotImplementedError
79
+
80
+ def replace_stage_prefix(self, file_path: str) -> str:
81
+ raise NotImplementedError
82
+
76
83
  def add_stage_prefix(self, file_path: str) -> str:
77
84
  raise NotImplementedError
78
85
 
@@ -112,24 +119,27 @@ class DefaultStagePathParts(StagePathParts):
112
119
  self.directory = self.get_directory(stage_path)
113
120
  self.stage = StageManager.get_stage_from_path(stage_path)
114
121
  stage_name = self.stage.split(".")[-1]
115
- if stage_name.startswith("@"):
116
- stage_name = stage_name[1:]
122
+ stage_name = stage_name[1:] if stage_name.startswith("@") else stage_name
117
123
  self.stage_name = stage_name
118
124
  self.is_directory = True if stage_path.endswith("/") else False
119
125
 
120
126
  @property
121
127
  def path(self) -> str:
122
- return (
123
- f"{self.stage_name}{self.directory}"
124
- if self.stage_name.endswith("/")
125
- else f"{self.stage_name}/{self.directory}"
126
- )
128
+ return f"{self.stage_name.rstrip('/')}/{self.directory}"
127
129
 
128
- def add_stage_prefix(self, file_path: str) -> str:
130
+ @property
131
+ def full_path(self) -> str:
132
+ return f"{self.stage.rstrip('/')}/{self.directory}"
133
+
134
+ def replace_stage_prefix(self, file_path: str) -> str:
129
135
  stage = Path(self.stage).parts[0]
130
136
  file_path_without_prefix = Path(file_path).parts[1:]
131
137
  return f"{stage}/{'/'.join(file_path_without_prefix)}"
132
138
 
139
+ def add_stage_prefix(self, file_path: str) -> str:
140
+ stage = self.stage.rstrip("/")
141
+ return f"{stage}/{file_path.lstrip('/')}"
142
+
133
143
  def get_directory_from_file_path(self, file_path: str) -> List[str]:
134
144
  stage_path_length = len(Path(self.directory).parts)
135
145
  return list(Path(file_path).parts[1 + stage_path_length : -1])
@@ -146,14 +156,29 @@ class UserStagePathParts(StagePathParts):
146
156
 
147
157
  def __init__(self, stage_path: str):
148
158
  self.directory = self.get_directory(stage_path)
149
- self.stage = "@~"
150
- self.stage_name = "@~"
159
+ self.stage = USER_STAGE_PREFIX
160
+ self.stage_name = USER_STAGE_PREFIX
151
161
  self.is_directory = True if stage_path.endswith("/") else False
152
162
 
163
+ @classmethod
164
+ def get_directory(cls, stage_path: str) -> str:
165
+ if Path(stage_path).parts[0] == USER_STAGE_PREFIX:
166
+ return super().get_directory(stage_path)
167
+ return stage_path
168
+
153
169
  @property
154
170
  def path(self) -> str:
155
171
  return f"{self.directory}"
156
172
 
173
+ @property
174
+ def full_path(self) -> str:
175
+ return f"{self.stage}/{self.directory}"
176
+
177
+ def replace_stage_prefix(self, file_path: str) -> str:
178
+ if Path(file_path).parts[0] == self.stage_name:
179
+ return file_path
180
+ return f"{self.stage}/{file_path}"
181
+
157
182
  def add_stage_prefix(self, file_path: str) -> str:
158
183
  return f"{self.stage}/{file_path}"
159
184
 
@@ -168,7 +193,9 @@ class StageManager(SqlExecutionMixin):
168
193
  self._python_exe_procedure = None
169
194
 
170
195
  @staticmethod
171
- def get_standard_stage_prefix(name: str) -> str:
196
+ def get_standard_stage_prefix(name: str | FQN) -> str:
197
+ if isinstance(name, FQN):
198
+ name = name.identifier
172
199
  # Handle embedded stages
173
200
  if name.startswith("snow://") or name.startswith("@"):
174
201
  return name
@@ -239,7 +266,7 @@ class StageManager(SqlExecutionMixin):
239
266
  self._assure_is_existing_directory(dest_directory)
240
267
 
241
268
  result = self._execute_query(
242
- f"get {self.quote_stage_name(stage_path_parts.add_stage_prefix(file_path))} {self._to_uri(f'{dest_directory}/')} parallel={parallel}"
269
+ f"get {self.quote_stage_name(stage_path_parts.replace_stage_prefix(file_path))} {self._to_uri(f'{dest_directory}/')} parallel={parallel}"
243
270
  )
244
271
  results.append(result)
245
272
 
@@ -300,8 +327,8 @@ class StageManager(SqlExecutionMixin):
300
327
  quoted_stage_name = self.quote_stage_name(f"{stage_name}{path}")
301
328
  return self._execute_query(f"remove {quoted_stage_name}")
302
329
 
303
- def create(self, stage_name: str, comment: Optional[str] = None) -> SnowflakeCursor:
304
- query = f"create stage if not exists {stage_name}"
330
+ def create(self, fqn: FQN, comment: Optional[str] = None) -> SnowflakeCursor:
331
+ query = f"create stage if not exists {fqn.sql_identifier}"
305
332
  if comment:
306
333
  query += f" comment='{comment}'"
307
334
  return self._execute_query(query)
@@ -319,8 +346,14 @@ class StageManager(SqlExecutionMixin):
319
346
  stage_path_parts = self._stage_path_part_factory(stage_path)
320
347
  all_files_list = self._get_files_list_from_stage(stage_path_parts)
321
348
 
349
+ all_files_with_stage_name_prefix = [
350
+ stage_path_parts.get_directory(file) for file in all_files_list
351
+ ]
352
+
322
353
  # filter files from stage if match stage_path pattern
323
- filtered_file_list = self._filter_files_list(stage_path_parts, all_files_list)
354
+ filtered_file_list = self._filter_files_list(
355
+ stage_path_parts, all_files_with_stage_name_prefix
356
+ )
324
357
 
325
358
  if not filtered_file_list:
326
359
  raise ClickException(f"No files matched pattern '{stage_path}'")
@@ -376,7 +409,7 @@ class StageManager(SqlExecutionMixin):
376
409
  if not stage_path_parts.directory:
377
410
  return self._filter_supported_files(files_on_stage)
378
411
 
379
- stage_path = stage_path_parts.path.lower()
412
+ stage_path = stage_path_parts.directory
380
413
 
381
414
  # Exact file path was provided if stage_path in file list
382
415
  if stage_path in files_on_stage:
@@ -12,13 +12,13 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ from snowflake.cli._plugins.stage import commands
15
16
  from snowflake.cli.api.plugins.command import (
16
17
  SNOWCLI_ROOT_COMMAND_PATH,
17
18
  CommandSpec,
18
19
  CommandType,
19
20
  plugin_hook_impl,
20
21
  )
21
- from snowflake.cli.plugins.stage import commands
22
22
 
23
23
 
24
24
  @plugin_hook_impl
@@ -0,0 +1,54 @@
1
+ from typing import Optional
2
+
3
+ from snowflake.cli._plugins.nativeapp.artifacts import BundleMap
4
+ from snowflake.cli._plugins.stage.diff import (
5
+ DiffResult,
6
+ _to_diff_line,
7
+ _to_src_dest_pair,
8
+ )
9
+ from snowflake.cli.api.console import cli_console as cc
10
+
11
+
12
+ def print_diff_to_console(
13
+ diff: DiffResult,
14
+ bundle_map: Optional[BundleMap] = None,
15
+ ):
16
+ if not diff.has_changes():
17
+ cc.message("Your stage is up-to-date with your local deploy root.")
18
+ return
19
+
20
+ blank_line_needed = False
21
+ if diff.only_local or diff.different:
22
+ cc.message("Local changes to be deployed:")
23
+ messages_to_output = []
24
+ for p in diff.different:
25
+ src_dest_pair = _to_src_dest_pair(p, bundle_map)
26
+ messages_to_output.append(
27
+ (
28
+ src_dest_pair,
29
+ _to_diff_line("modified", src_dest_pair[0], src_dest_pair[1]),
30
+ )
31
+ )
32
+ for p in diff.only_local:
33
+ src_dest_pair = _to_src_dest_pair(p, bundle_map)
34
+ messages_to_output.append(
35
+ (
36
+ src_dest_pair,
37
+ _to_diff_line("added", src_dest_pair[0], src_dest_pair[1]),
38
+ )
39
+ )
40
+
41
+ with cc.indented():
42
+ for key, message in sorted(messages_to_output, key=lambda pair: pair[0]):
43
+ cc.message(message)
44
+
45
+ blank_line_needed = True
46
+
47
+ if diff.only_on_stage:
48
+ if blank_line_needed:
49
+ cc.message("")
50
+ cc.message(f"Deleted paths to be removed from your stage:")
51
+ with cc.indented():
52
+ for p in sorted(diff.only_on_stage):
53
+ diff_line = _to_diff_line("deleted", src=None, dest=str(p))
54
+ cc.message(diff_line)
@@ -16,32 +16,42 @@ from __future__ import annotations
16
16
 
17
17
  import logging
18
18
  from pathlib import Path
19
+ from typing import Dict
19
20
 
20
21
  import click
21
22
  import typer
22
- from click import ClickException
23
- from snowflake.cli.api.cli_global_context import cli_context
23
+ from click import ClickException, UsageError
24
+ from snowflake.cli._plugins.object.command_aliases import (
25
+ add_object_command_aliases,
26
+ scope_option,
27
+ )
28
+ from snowflake.cli._plugins.streamlit.manager import StreamlitManager
29
+ from snowflake.cli.api.cli_global_context import get_cli_context
24
30
  from snowflake.cli.api.commands.decorators import (
25
31
  with_experimental_behaviour,
26
32
  with_project_definition,
27
33
  )
28
- from snowflake.cli.api.commands.flags import ReplaceOption, like_option
29
- from snowflake.cli.api.commands.project_initialisation import add_init_command
34
+ from snowflake.cli.api.commands.flags import (
35
+ ReplaceOption,
36
+ entity_argument,
37
+ identifier_argument,
38
+ like_option,
39
+ )
30
40
  from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
31
41
  from snowflake.cli.api.constants import ObjectType
42
+ from snowflake.cli.api.exceptions import NoProjectDefinitionError
32
43
  from snowflake.cli.api.identifiers import FQN
33
44
  from snowflake.cli.api.output.types import (
34
45
  CommandResult,
35
46
  MessageResult,
36
47
  SingleQueryResult,
37
48
  )
38
- from snowflake.cli.api.project.project_verification import assert_project_type
39
- from snowflake.cli.api.project.schemas.streamlit.streamlit import Streamlit
40
- from snowflake.cli.plugins.object.command_aliases import (
41
- add_object_command_aliases,
42
- scope_option,
49
+ from snowflake.cli.api.project.definition_conversion import (
50
+ convert_project_definition_to_v2,
51
+ )
52
+ from snowflake.cli.api.project.schemas.entities.streamlit_entity_model import (
53
+ StreamlitEntityModel,
43
54
  )
44
- from snowflake.cli.plugins.streamlit.manager import StreamlitManager
45
55
 
46
56
  app = SnowTyperFactory(
47
57
  name="streamlit",
@@ -49,19 +59,8 @@ app = SnowTyperFactory(
49
59
  )
50
60
  log = logging.getLogger(__name__)
51
61
 
52
-
53
- class IdentifierType(click.ParamType):
54
- name = "TEXT"
55
-
56
- def convert(self, value, param, ctx):
57
- return FQN.from_string(value)
58
-
59
-
60
- StreamlitNameArgument = typer.Argument(
61
- ...,
62
- help="Name of the Streamlit app.",
63
- show_default=False,
64
- click_type=IdentifierType(),
62
+ StreamlitNameArgument = identifier_argument(
63
+ sf_object="Streamlit app", example="my_streamlit"
65
64
  )
66
65
  OpenOption = typer.Option(
67
66
  False,
@@ -70,12 +69,6 @@ OpenOption = typer.Option(
70
69
  is_flag=True,
71
70
  )
72
71
 
73
- add_init_command(
74
- app,
75
- project_type="Streamlit",
76
- template="default_streamlit",
77
- help_message="Name of the Streamlit app project directory you want to create. Defaults to `example_streamlit`.",
78
- )
79
72
 
80
73
  add_object_command_aliases(
81
74
  app=app,
@@ -92,7 +85,9 @@ add_object_command_aliases(
92
85
  def streamlit_share(
93
86
  name: FQN = StreamlitNameArgument,
94
87
  to_role: str = typer.Argument(
95
- ..., help="Role with which to share the Streamlit app."
88
+ ...,
89
+ help="Role with which to share the Streamlit app.",
90
+ show_default=False,
96
91
  ),
97
92
  **options,
98
93
  ) -> CommandResult:
@@ -125,48 +120,50 @@ def streamlit_deploy(
125
120
  replace: bool = ReplaceOption(
126
121
  help="Replace the Streamlit app if it already exists."
127
122
  ),
123
+ entity_id: str = entity_argument("streamlit"),
128
124
  open_: bool = OpenOption,
129
125
  **options,
130
126
  ) -> CommandResult:
131
127
  """
132
128
  Deploys a Streamlit app defined in the project definition file (snowflake.yml). By default, the command uploads
133
129
  environment.yml and any other pages or folders, if present. If you don’t specify a stage name, the `streamlit`
134
- stage is used. If the specified stage does not exist, the command creates it.
130
+ stage is used. If the specified stage does not exist, the command creates it. If multiple Streamlits are defined
131
+ in snowflake.yml and no entity_id is provided then command will raise an error.
135
132
  """
136
133
 
137
- assert_project_type("streamlit")
138
-
139
- streamlit: Streamlit = cli_context.project_definition.streamlit
140
- if not streamlit:
141
- return MessageResult("No streamlit were specified in project definition.")
142
-
143
- environment_file = streamlit.env_file
144
- if environment_file and not Path(environment_file).exists():
145
- raise ClickException(f"Provided file {environment_file} does not exist")
146
- elif environment_file is None:
147
- environment_file = "environment.yml"
148
-
149
- pages_dir = streamlit.pages_dir
150
- if pages_dir and not Path(pages_dir).exists():
151
- raise ClickException(f"Provided file {pages_dir} does not exist")
152
- elif pages_dir is None:
153
- pages_dir = "pages"
154
-
155
- streamlit_id = FQN.from_identifier_model(streamlit).using_context()
156
-
157
- url = StreamlitManager().deploy(
158
- streamlit_id=streamlit_id,
159
- environment_file=Path(environment_file),
160
- pages_dir=Path(pages_dir),
161
- stage_name=streamlit.stage,
162
- main_file=Path(streamlit.main_file),
163
- replace=replace,
164
- query_warehouse=streamlit.query_warehouse,
165
- additional_source_files=streamlit.additional_source_files,
166
- title=streamlit.title,
167
- **options,
134
+ cli_context = get_cli_context()
135
+ pd = cli_context.project_definition
136
+ if not pd.meets_version_requirement("2"):
137
+ if not pd.streamlit:
138
+ raise NoProjectDefinitionError(
139
+ project_type="streamlit", project_root=cli_context.project_root
140
+ )
141
+ pd = convert_project_definition_to_v2(pd)
142
+
143
+ streamlits: Dict[str, StreamlitEntityModel] = pd.get_entities_by_type(
144
+ entity_type="streamlit"
168
145
  )
169
146
 
147
+ if not streamlits:
148
+ raise NoProjectDefinitionError(
149
+ project_type="streamlit", project_root=cli_context.project_root
150
+ )
151
+
152
+ if entity_id and entity_id not in streamlits:
153
+ raise UsageError(f"No '{entity_id}' entity in project definition file.")
154
+
155
+ if len(streamlits.keys()) == 1:
156
+ entity_id = list(streamlits.keys())[0]
157
+
158
+ if entity_id is None:
159
+ raise UsageError(
160
+ "Multiple Streamlit apps found. Please provide entity id for the operation."
161
+ )
162
+
163
+ # Get first streamlit
164
+ streamlit: StreamlitEntityModel = streamlits[entity_id]
165
+ url = StreamlitManager().deploy(streamlit=streamlit, replace=replace)
166
+
170
167
  if open_:
171
168
  typer.launch(url)
172
169
 
@@ -15,22 +15,25 @@
15
15
  from __future__ import annotations
16
16
 
17
17
  import logging
18
- import os
19
18
  from pathlib import Path
20
19
  from typing import List, Optional
21
20
 
21
+ from snowflake.cli._plugins.connection.util import (
22
+ MissingConnectionAccountError,
23
+ MissingConnectionRegionError,
24
+ make_snowsight_url,
25
+ )
26
+ from snowflake.cli._plugins.stage.manager import StageManager
22
27
  from snowflake.cli.api.commands.experimental_behaviour import (
23
28
  experimental_behaviour_enabled,
24
29
  )
30
+ from snowflake.cli.api.console import cli_console
25
31
  from snowflake.cli.api.feature_flags import FeatureFlag
26
32
  from snowflake.cli.api.identifiers import FQN
27
- from snowflake.cli.api.sql_execution import SqlExecutionMixin
28
- from snowflake.cli.plugins.connection.util import (
29
- MissingConnectionAccountError,
30
- MissingConnectionRegionError,
31
- make_snowsight_url,
33
+ from snowflake.cli.api.project.schemas.entities.streamlit_entity_model import (
34
+ StreamlitEntityModel,
32
35
  )
33
- from snowflake.cli.plugins.stage.manager import StageManager
36
+ from snowflake.cli.api.sql_execution import SqlExecutionMixin
34
37
  from snowflake.connector.cursor import SnowflakeCursor
35
38
  from snowflake.connector.errors import ProgrammingError
36
39
 
@@ -46,39 +49,35 @@ class StreamlitManager(SqlExecutionMixin):
46
49
  def _put_streamlit_files(
47
50
  self,
48
51
  root_location: str,
49
- main_file: Path,
50
- environment_file: Optional[Path],
51
- pages_dir: Optional[Path],
52
- additional_source_files: Optional[List[Path]],
52
+ artifacts: Optional[List[Path]] = None,
53
53
  ):
54
+ cli_console.step(f"Deploying files to {root_location}")
55
+ if not artifacts:
56
+ return
54
57
  stage_manager = StageManager()
55
-
56
- stage_manager.put(main_file, root_location, 4, True)
57
-
58
- if environment_file and environment_file.exists():
59
- stage_manager.put(environment_file, root_location, 4, True)
60
-
61
- if pages_dir and pages_dir.exists():
62
- stage_manager.put(pages_dir / "*.py", f"{root_location}/pages", 4, True)
63
-
64
- if additional_source_files:
65
- for file in additional_source_files:
66
- if os.sep in str(file):
67
- destination = f"{root_location}/{str(file.parent)}"
68
- else:
69
- destination = root_location
70
- stage_manager.put(file, destination, 4, True)
58
+ for file in artifacts:
59
+ if file.is_dir():
60
+ if not any(file.iterdir()):
61
+ cli_console.warning(f"Skipping empty directory: {file}")
62
+ continue
63
+
64
+ stage_manager.put(
65
+ f"{file.joinpath('*')}", f"{root_location}/{file}", 4, True
66
+ )
67
+ elif len(file.parts) > 1:
68
+ stage_manager.put(file, f"{root_location}/{file.parent}", 4, True)
69
+ else:
70
+ stage_manager.put(file, root_location, 4, True)
71
71
 
72
72
  def _create_streamlit(
73
73
  self,
74
- streamlit_id: FQN,
75
- main_file: Path,
74
+ streamlit: StreamlitEntityModel,
76
75
  replace: Optional[bool] = None,
77
76
  experimental: Optional[bool] = None,
78
- query_warehouse: Optional[str] = None,
79
77
  from_stage_name: Optional[str] = None,
80
- title: Optional[str] = None,
81
78
  ):
79
+ streamlit_id = streamlit.fqn.using_connection(self._conn)
80
+ cli_console.step(f"Creating {streamlit_id} Streamlit")
82
81
  query = []
83
82
  if replace:
84
83
  query.append(f"CREATE OR REPLACE STREAMLIT {streamlit_id.sql_identifier}")
@@ -96,28 +95,24 @@ class StreamlitManager(SqlExecutionMixin):
96
95
  if from_stage_name:
97
96
  query.append(f"ROOT_LOCATION = '{from_stage_name}'")
98
97
 
99
- query.append(f"MAIN_FILE = '{main_file.name}'")
98
+ query.append(f"MAIN_FILE = '{streamlit.main_file}'")
100
99
 
101
- if query_warehouse:
102
- query.append(f"QUERY_WAREHOUSE = {query_warehouse}")
103
- if title:
104
- query.append(f"TITLE = '{title}'")
100
+ if streamlit.query_warehouse:
101
+ query.append(f"QUERY_WAREHOUSE = {streamlit.query_warehouse}")
102
+ if streamlit.title:
103
+ query.append(f"TITLE = '{streamlit.title}'")
104
+
105
+ if streamlit.external_access_integrations:
106
+ query.append(streamlit.get_external_access_integrations_sql())
107
+
108
+ if streamlit.secrets:
109
+ query.append(streamlit.get_secrets_sql())
105
110
 
106
111
  self._execute_query("\n".join(query))
107
112
 
108
- def deploy(
109
- self,
110
- streamlit_id: FQN,
111
- main_file: Path,
112
- environment_file: Optional[Path] = None,
113
- pages_dir: Optional[Path] = None,
114
- stage_name: Optional[str] = None,
115
- query_warehouse: Optional[str] = None,
116
- replace: Optional[bool] = False,
117
- additional_source_files: Optional[List[Path]] = None,
118
- title: Optional[str] = None,
119
- **options,
120
- ):
113
+ def deploy(self, streamlit: StreamlitEntityModel, replace: bool = False):
114
+ streamlit_id = streamlit.fqn.using_connection(self._conn)
115
+
121
116
  # for backwards compatibility - quoted stage path might be case-sensitive
122
117
  # https://docs.snowflake.com/en/sql-reference/identifiers-syntax#double-quoted-identifiers
123
118
  streamlit_name_for_root_location = streamlit_id.name
@@ -134,12 +129,9 @@ class StreamlitManager(SqlExecutionMixin):
134
129
  # TODO: Support from_stage
135
130
  # from_stage_stmt = f"FROM_STAGE = '{stage_name}'" if stage_name else ""
136
131
  self._create_streamlit(
137
- streamlit_id,
138
- main_file,
132
+ streamlit=streamlit,
139
133
  replace=replace,
140
- query_warehouse=query_warehouse,
141
134
  experimental=True,
142
- title=title,
143
135
  )
144
136
  try:
145
137
  if use_versioned_stage:
@@ -169,10 +161,7 @@ class StreamlitManager(SqlExecutionMixin):
169
161
 
170
162
  self._put_streamlit_files(
171
163
  root_location,
172
- main_file,
173
- environment_file,
174
- pages_dir,
175
- additional_source_files,
164
+ streamlit.artifacts,
176
165
  )
177
166
  else:
178
167
  """
@@ -182,31 +171,23 @@ class StreamlitManager(SqlExecutionMixin):
182
171
  """
183
172
  stage_manager = StageManager()
184
173
 
185
- stage_name = stage_name or "streamlit"
174
+ stage_name = streamlit.stage or "streamlit"
186
175
  stage_name = FQN.from_string(stage_name).using_connection(self._conn)
187
176
 
188
- stage_manager.create(stage_name=stage_name)
177
+ cli_console.step(f"Creating {stage_name} stage")
178
+ stage_manager.create(fqn=stage_name)
189
179
 
190
180
  root_location = stage_manager.get_standard_stage_prefix(
191
181
  f"{stage_name}/{streamlit_name_for_root_location}"
192
182
  )
193
183
 
194
- self._put_streamlit_files(
195
- root_location,
196
- main_file,
197
- environment_file,
198
- pages_dir,
199
- additional_source_files,
200
- )
184
+ self._put_streamlit_files(root_location, streamlit.artifacts)
201
185
 
202
186
  self._create_streamlit(
203
- streamlit_id,
204
- main_file,
187
+ streamlit=streamlit,
205
188
  replace=replace,
206
- query_warehouse=query_warehouse,
207
189
  from_stage_name=root_location,
208
190
  experimental=False,
209
- title=title,
210
191
  )
211
192
 
212
193
  return self.get_url(streamlit_name=streamlit_id)
@@ -0,0 +1,30 @@
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 snowflake.cli._plugins.streamlit import commands
16
+ from snowflake.cli.api.plugins.command import (
17
+ SNOWCLI_ROOT_COMMAND_PATH,
18
+ CommandSpec,
19
+ CommandType,
20
+ plugin_hook_impl,
21
+ )
22
+
23
+
24
+ @plugin_hook_impl
25
+ def command_spec():
26
+ return CommandSpec(
27
+ parent_command_path=SNOWCLI_ROOT_COMMAND_PATH,
28
+ command_type=CommandType.COMMAND_GROUP,
29
+ typer_instance=commands.app.create_instance(),
30
+ )
@@ -0,0 +1,17 @@
1
+ from dataclasses import dataclass
2
+ from pathlib import Path
3
+ from typing import Optional
4
+
5
+ from snowflake.cli.api.console.abc import AbstractConsole
6
+
7
+
8
+ @dataclass
9
+ class ActionContext:
10
+ """
11
+ An object that is passed to each action when called by WorkspaceManager
12
+ """
13
+
14
+ console: AbstractConsole
15
+ project_root: Path
16
+ default_role: str
17
+ default_warehouse: Optional[str]