snowflake-cli-labs 3.0.0rc1__py3-none-any.whl → 3.0.0rc3__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 (92) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/_app/cli_app.py +10 -1
  3. snowflake/cli/_app/commands_registration/builtin_plugins.py +2 -0
  4. snowflake/cli/_app/secret.py +9 -0
  5. snowflake/cli/_app/snow_connector.py +110 -51
  6. snowflake/cli/_app/telemetry.py +8 -4
  7. snowflake/cli/_app/version_check.py +74 -0
  8. snowflake/cli/_plugins/git/commands.py +55 -14
  9. snowflake/cli/_plugins/git/manager.py +53 -7
  10. snowflake/cli/_plugins/helpers/commands.py +57 -0
  11. snowflake/cli/{api/commands/typer_pre_execute.py → _plugins/helpers/plugin_spec.py} +14 -10
  12. snowflake/cli/_plugins/nativeapp/application_entity.py +651 -0
  13. snowflake/cli/{api/project/schemas/entities → _plugins/nativeapp}/application_entity_model.py +2 -2
  14. snowflake/cli/_plugins/nativeapp/application_package_entity.py +1107 -0
  15. snowflake/cli/{api/project/schemas/entities → _plugins/nativeapp}/application_package_entity_model.py +3 -3
  16. snowflake/cli/_plugins/nativeapp/artifacts.py +10 -9
  17. snowflake/cli/_plugins/nativeapp/bundle_context.py +1 -1
  18. snowflake/cli/_plugins/nativeapp/codegen/artifact_processor.py +1 -1
  19. snowflake/cli/_plugins/nativeapp/codegen/compiler.py +1 -1
  20. snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +1 -1
  21. snowflake/cli/_plugins/nativeapp/codegen/snowpark/extension_function_utils.py +1 -1
  22. snowflake/cli/_plugins/nativeapp/codegen/snowpark/models.py +1 -1
  23. snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +3 -6
  24. snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +50 -32
  25. snowflake/cli/_plugins/nativeapp/commands.py +84 -16
  26. snowflake/cli/_plugins/nativeapp/exceptions.py +0 -9
  27. snowflake/cli/_plugins/nativeapp/manager.py +56 -92
  28. snowflake/cli/_plugins/nativeapp/policy.py +3 -0
  29. snowflake/cli/_plugins/nativeapp/project_model.py +2 -2
  30. snowflake/cli/_plugins/nativeapp/run_processor.py +65 -272
  31. snowflake/cli/_plugins/nativeapp/same_account_install_method.py +70 -0
  32. snowflake/cli/_plugins/nativeapp/teardown_processor.py +11 -154
  33. snowflake/cli/_plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +150 -40
  34. snowflake/cli/_plugins/nativeapp/version/commands.py +6 -24
  35. snowflake/cli/_plugins/nativeapp/version/version_processor.py +35 -235
  36. snowflake/cli/_plugins/snowpark/commands.py +5 -5
  37. snowflake/cli/_plugins/snowpark/common.py +4 -4
  38. snowflake/cli/_plugins/snowpark/models.py +2 -1
  39. snowflake/cli/{api/entities → _plugins/snowpark}/snowpark_entity.py +2 -2
  40. snowflake/cli/{api/project/schemas/entities/snowpark_entity.py → _plugins/snowpark/snowpark_entity_model.py} +3 -6
  41. snowflake/cli/_plugins/snowpark/snowpark_project_paths.py +1 -1
  42. snowflake/cli/_plugins/stage/manager.py +9 -4
  43. snowflake/cli/_plugins/streamlit/commands.py +4 -4
  44. snowflake/cli/_plugins/streamlit/manager.py +17 -4
  45. snowflake/cli/{api/entities → _plugins/streamlit}/streamlit_entity.py +2 -2
  46. snowflake/cli/{api/project/schemas/entities → _plugins/streamlit}/streamlit_entity_model.py +5 -12
  47. snowflake/cli/_plugins/workspace/action_context.py +2 -1
  48. snowflake/cli/_plugins/workspace/commands.py +127 -48
  49. snowflake/cli/_plugins/workspace/manager.py +1 -0
  50. snowflake/cli/_plugins/workspace/plugin_spec.py +1 -1
  51. snowflake/cli/api/cli_global_context.py +136 -313
  52. snowflake/cli/api/commands/flags.py +76 -91
  53. snowflake/cli/api/commands/snow_typer.py +7 -5
  54. snowflake/cli/api/config.py +1 -1
  55. snowflake/cli/api/connections.py +214 -0
  56. snowflake/cli/api/console/abc.py +4 -2
  57. snowflake/cli/api/entities/common.py +4 -0
  58. snowflake/cli/api/entities/utils.py +41 -31
  59. snowflake/cli/api/errno.py +1 -0
  60. snowflake/cli/api/identifiers.py +7 -3
  61. snowflake/cli/api/project/definition.py +11 -0
  62. snowflake/cli/api/project/definition_conversion.py +175 -16
  63. snowflake/cli/api/project/schemas/entities/common.py +15 -14
  64. snowflake/cli/api/project/schemas/entities/entities.py +13 -10
  65. snowflake/cli/api/project/schemas/project_definition.py +107 -45
  66. snowflake/cli/api/project/schemas/v1/__init__.py +0 -0
  67. snowflake/cli/api/project/schemas/{identifier_model.py → v1/identifier_model.py} +0 -7
  68. snowflake/cli/api/project/schemas/v1/native_app/__init__.py +0 -0
  69. snowflake/cli/api/project/schemas/{native_app → v1/native_app}/native_app.py +4 -4
  70. snowflake/cli/api/project/schemas/v1/snowpark/__init__.py +0 -0
  71. snowflake/cli/api/project/schemas/{snowpark → v1/snowpark}/callable.py +2 -2
  72. snowflake/cli/api/project/schemas/{snowpark → v1/snowpark}/snowpark.py +2 -2
  73. snowflake/cli/api/project/schemas/v1/streamlit/__init__.py +0 -0
  74. snowflake/cli/api/project/schemas/{streamlit → v1/streamlit}/streamlit.py +2 -1
  75. snowflake/cli/api/rendering/project_definition_templates.py +4 -0
  76. snowflake/cli/api/rendering/sql_templates.py +7 -0
  77. snowflake/cli/api/sql_execution.py +6 -15
  78. snowflake/cli/api/utils/definition_rendering.py +3 -1
  79. {snowflake_cli_labs-3.0.0rc1.dist-info → snowflake_cli_labs-3.0.0rc3.dist-info}/METADATA +9 -9
  80. {snowflake_cli_labs-3.0.0rc1.dist-info → snowflake_cli_labs-3.0.0rc3.dist-info}/RECORD +88 -81
  81. snowflake/cli/api/entities/application_entity.py +0 -12
  82. snowflake/cli/api/entities/application_package_entity.py +0 -553
  83. snowflake/cli/api/project/schemas/snowpark/__init__.py +0 -13
  84. snowflake/cli/api/project/schemas/streamlit/__init__.py +0 -13
  85. /snowflake/cli/{api/project/schemas/native_app → _plugins/helpers}/__init__.py +0 -0
  86. /snowflake/cli/api/project/schemas/{native_app → v1/native_app}/application.py +0 -0
  87. /snowflake/cli/api/project/schemas/{native_app → v1/native_app}/package.py +0 -0
  88. /snowflake/cli/api/project/schemas/{native_app → v1/native_app}/path_mapping.py +0 -0
  89. /snowflake/cli/api/project/schemas/{snowpark → v1/snowpark}/argument.py +0 -0
  90. {snowflake_cli_labs-3.0.0rc1.dist-info → snowflake_cli_labs-3.0.0rc3.dist-info}/WHEEL +0 -0
  91. {snowflake_cli_labs-3.0.0rc1.dist-info → snowflake_cli_labs-3.0.0rc3.dist-info}/entry_points.txt +0 -0
  92. {snowflake_cli_labs-3.0.0rc1.dist-info → snowflake_cli_labs-3.0.0rc3.dist-info}/licenses/LICENSE +0 -0
@@ -16,17 +16,17 @@ from __future__ import annotations
16
16
 
17
17
  from pathlib import Path
18
18
  from textwrap import dedent
19
- from typing import Dict, List, Optional
19
+ from typing import Dict, Optional
20
20
 
21
21
  import typer
22
- from click import BadOptionUsage, ClickException
22
+ from click import ClickException
23
+ from snowflake.cli._plugins.nativeapp.application_package_entity import (
24
+ ApplicationPackageEntity,
25
+ )
23
26
  from snowflake.cli._plugins.nativeapp.artifacts import (
24
- BundleMap,
25
27
  find_version_info_in_manifest_file,
26
28
  )
27
- from snowflake.cli._plugins.nativeapp.constants import VERSION_COL
28
29
  from snowflake.cli._plugins.nativeapp.exceptions import (
29
- ApplicationPackageAlreadyExistsError,
30
30
  ApplicationPackageDoesNotExistError,
31
31
  )
32
32
  from snowflake.cli._plugins.nativeapp.manager import (
@@ -36,245 +36,50 @@ from snowflake.cli._plugins.nativeapp.manager import (
36
36
  from snowflake.cli._plugins.nativeapp.policy import PolicyBase
37
37
  from snowflake.cli._plugins.nativeapp.run_processor import NativeAppRunProcessor
38
38
  from snowflake.cli.api.console import cli_console as cc
39
- from snowflake.cli.api.entities.utils import ensure_correct_owner
40
- from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
41
- from snowflake.cli.api.project.schemas.native_app.native_app import NativeApp
42
- from snowflake.cli.api.project.util import to_identifier, unquote_identifier
43
- from snowflake.cli.api.utils.cursor import (
44
- find_all_rows,
45
- )
39
+ from snowflake.cli.api.project.schemas.v1.native_app.native_app import NativeApp
40
+ from snowflake.cli.api.project.util import to_identifier
46
41
  from snowflake.connector import ProgrammingError
47
- from snowflake.connector.cursor import DictCursor
48
-
49
-
50
- def check_index_changes_in_git_repo(
51
- project_root: Path, policy: PolicyBase, is_interactive: bool
52
- ) -> None:
53
- """
54
- Checks if the project root, i.e. the native apps project is a git repository. If it is a git repository,
55
- it also checks if there any local changes to the directory that may not be on the application package stage.
56
- """
57
- from git import Repo
58
- from git.exc import InvalidGitRepositoryError
59
-
60
- try:
61
- repo = Repo(project_root, search_parent_directories=True)
62
- assert repo.git_dir is not None
63
-
64
- # Check if the repo has any changes, including untracked files
65
- if repo.is_dirty(untracked_files=True):
66
- cc.warning(
67
- "Changes detected in the git repository. "
68
- "(Rerun your command with --skip-git-check flag to ignore this check)"
69
- )
70
- repo.git.execute(["git", "status"])
71
-
72
- user_prompt = (
73
- "You have local changes in this repository that are not part of a previous commit. "
74
- "Do you still want to continue?"
75
- )
76
- if not policy.should_proceed(user_prompt):
77
- if is_interactive:
78
- cc.message("Not creating a new version.")
79
- raise typer.Exit(0)
80
- else:
81
- cc.message(
82
- "Cannot create a new version non-interactively without --force."
83
- )
84
- raise typer.Exit(1)
85
-
86
- except InvalidGitRepositoryError:
87
- pass # not a git repository, which is acceptable
88
42
 
89
43
 
90
44
  class NativeAppVersionCreateProcessor(NativeAppRunProcessor):
91
45
  def __init__(self, project_definition: Dict, project_root: Path):
92
46
  super().__init__(project_definition, project_root)
93
47
 
94
- def get_existing_release_directive_info_for_version(
95
- self, version: str
96
- ) -> List[dict]:
97
- """
98
- Get all existing release directives, if present, set on the version defined in an application package.
99
- It executes a 'show release directives in application package' query and returns the filtered results, if they exist.
100
- """
101
- with self.use_role(self.package_role):
102
- show_obj_query = (
103
- f"show release directives in application package {self.package_name}"
104
- )
105
- show_obj_cursor = self._execute_query(
106
- show_obj_query, cursor_class=DictCursor
107
- )
108
-
109
- if show_obj_cursor.rowcount is None:
110
- raise SnowflakeSQLExecutionError(show_obj_query)
111
-
112
- show_obj_rows = find_all_rows(
113
- show_obj_cursor,
114
- lambda row: row[VERSION_COL] == unquote_identifier(version),
115
- )
116
-
117
- return show_obj_rows
118
-
119
- def add_new_version(self, version: str) -> None:
120
- """
121
- Defines a new version in an existing application package.
122
- """
123
- # Make the version a valid identifier, adding quotes if necessary
124
- version = to_identifier(version)
125
- with self.use_role(self.package_role):
126
- cc.step(
127
- f"Defining a new version {version} in application package {self.package_name}"
128
- )
129
- add_version_query = dedent(
130
- f"""\
131
- alter application package {self.package_name}
132
- add version {version}
133
- using @{self.stage_fqn}
134
- """
135
- )
136
- self._execute_query(add_version_query, cursor_class=DictCursor)
137
- cc.message(
138
- f"Version {version} created for application package {self.package_name}."
139
- )
140
-
141
- def add_new_patch_to_version(self, version: str, patch: Optional[int] = None):
142
- """
143
- Add a new patch, optionally a custom one, to an existing version in an application package.
144
- """
145
- # Make the version a valid identifier, adding quotes if necessary
146
- version = to_identifier(version)
147
- with self.use_role(self.package_role):
148
- cc.step(
149
- f"Adding new patch to version {version} defined in application package {self.package_name}"
150
- )
151
- add_version_query = dedent(
152
- f"""\
153
- alter application package {self.package_name}
154
- add patch {patch if patch else ""} for version {version}
155
- using @{self.stage_fqn}
156
- """
157
- )
158
- result_cursor = self._execute_query(
159
- add_version_query, cursor_class=DictCursor
160
- )
161
-
162
- show_row = result_cursor.fetchall()[0]
163
- new_patch = show_row["patch"]
164
- cc.message(
165
- f"Patch {new_patch} created for version {version} defined in application package {self.package_name}."
166
- )
167
-
168
48
  def process(
169
49
  self,
170
- bundle_map: BundleMap,
171
50
  version: Optional[str],
172
51
  patch: Optional[int],
173
- policy: PolicyBase,
174
- git_policy: PolicyBase,
175
- is_interactive: bool,
52
+ force: bool,
53
+ interactive: bool,
54
+ skip_git_check: bool,
176
55
  *args,
177
56
  **kwargs,
178
57
  ):
179
- """
180
- Perform bundle, application package creation, stage upload, version and/or patch to an application package.
181
- """
182
-
183
- # Make sure version is not None before proceeding any further.
184
- # This will raise an exception if version information is not found. Patch can be None.
185
- if not version:
186
- cc.message(
187
- "Version was not provided through the Snowflake CLI. Checking version in the manifest.yml instead."
188
- )
189
-
190
- version, patch = find_version_info_in_manifest_file(self.deploy_root)
191
- if not version:
192
- raise ClickException(
193
- "Manifest.yml file does not contain a value for the version field."
194
- )
195
-
196
- # Check if --patch needs to throw a bad option error, either if application package does not exist or if version does not exist
197
- if patch:
198
- try:
199
- if not self.get_existing_version_info(version):
200
- raise BadOptionUsage(
201
- option_name="patch",
202
- message=f"Cannot create a custom patch when version {version} is not defined in the application package {self.package_name}. Try again without using --patch.",
203
- )
204
- except ApplicationPackageDoesNotExistError as app_err:
205
- raise BadOptionUsage(
206
- option_name="patch",
207
- message=f"Cannot create a custom patch when application package {self.package_name} does not exist. Try again without using --patch.",
208
- )
209
-
210
- if git_policy.should_proceed():
211
- check_index_changes_in_git_repo(
212
- project_root=self.project_root,
213
- policy=policy,
214
- is_interactive=is_interactive,
215
- )
216
-
217
- # TODO: consider using self.deploy() instead
218
-
219
- try:
220
- self.create_app_package()
221
- except ApplicationPackageAlreadyExistsError as e:
222
- cc.warning(e.message)
223
- if not policy.should_proceed("Proceed with using this package?"):
224
- raise typer.Abort() from e
225
-
226
- with self.use_role(self.package_role):
227
- # Now that the application package exists, create shared data
228
- self._apply_package_scripts()
229
-
230
- # Upload files from deploy root local folder to the above stage
231
- self.sync_deploy_root_with_stage(
232
- bundle_map=bundle_map,
233
- role=self.package_role,
234
- prune=True,
235
- recursive=True,
236
- stage_fqn=self.stage_fqn,
237
- )
238
- with self.use_package_warehouse():
239
- self.execute_package_post_deploy_hooks()
240
-
241
- # Warn if the version exists in a release directive(s)
242
- existing_release_directives = (
243
- self.get_existing_release_directive_info_for_version(version)
58
+ return ApplicationPackageEntity.version_create(
59
+ console=cc,
60
+ project_root=self.project_root,
61
+ deploy_root=self.deploy_root,
62
+ bundle_root=self.bundle_root,
63
+ generated_root=self.generated_root,
64
+ artifacts=self.artifacts,
65
+ package_name=self.package_name,
66
+ package_role=self.package_role,
67
+ package_distribution=self.package_distribution,
68
+ prune=True,
69
+ recursive=True,
70
+ paths=None,
71
+ print_diff=True,
72
+ validate=True,
73
+ stage_fqn=self.stage_fqn,
74
+ package_warehouse=self.package_warehouse,
75
+ post_deploy_hooks=self.package_post_deploy_hooks,
76
+ package_scripts=self.package_scripts,
77
+ version=version,
78
+ patch=patch,
79
+ force=force,
80
+ interactive=interactive,
81
+ skip_git_check=skip_git_check,
244
82
  )
245
- if existing_release_directives:
246
- release_directive_names = ", ".join(
247
- row["name"] for row in existing_release_directives
248
- )
249
- cc.warning(
250
- dedent(
251
- f"""\
252
- Version {version} already defined in application package {self.package_name} and in release directive(s): {release_directive_names}.
253
- """
254
- )
255
- )
256
-
257
- user_prompt = (
258
- f"Are you sure you want to create a new patch for version {version} in application "
259
- f"package {self.package_name}? Once added, this operation cannot be undone."
260
- )
261
- if not policy.should_proceed(user_prompt):
262
- if is_interactive:
263
- cc.message("Not creating a new patch.")
264
- raise typer.Exit(0)
265
- else:
266
- cc.message(
267
- "Cannot create a new patch non-interactively without --force."
268
- )
269
- raise typer.Exit(1)
270
-
271
- # Define a new version in the application package
272
- if not self.get_existing_version_info(version):
273
- self.add_new_version(version=version)
274
- return # A new version created automatically has patch 0, we do not need to further increment the patch.
275
-
276
- # Add a new patch to an existing (old) version
277
- self.add_new_patch_to_version(version=version, patch=patch)
278
83
 
279
84
 
280
85
  class NativeAppVersionDropProcessor(NativeAppManager, NativeAppCommandProcessor):
@@ -295,12 +100,7 @@ class NativeAppVersionDropProcessor(NativeAppManager, NativeAppCommandProcessor)
295
100
 
296
101
  # 1. Check for existing an existing application package
297
102
  show_obj_row = self.get_existing_app_pkg_info()
298
- if show_obj_row:
299
- # Check for the right owner role
300
- ensure_correct_owner(
301
- row=show_obj_row, role=self.package_role, obj_name=self.package_name
302
- )
303
- else:
103
+ if not show_obj_row:
304
104
  raise ApplicationPackageDoesNotExistError(self.package_name)
305
105
 
306
106
  # 2. Check distribution of the existing application package
@@ -46,6 +46,10 @@ from snowflake.cli._plugins.snowpark.package.anaconda_packages import (
46
46
  AnacondaPackagesManager,
47
47
  )
48
48
  from snowflake.cli._plugins.snowpark.package.commands import app as package_app
49
+ from snowflake.cli._plugins.snowpark.snowpark_entity_model import (
50
+ FunctionEntityModel,
51
+ ProcedureEntityModel,
52
+ )
49
53
  from snowflake.cli._plugins.snowpark.snowpark_project_paths import (
50
54
  SnowparkProjectPaths,
51
55
  )
@@ -87,10 +91,6 @@ from snowflake.cli.api.output.types import (
87
91
  from snowflake.cli.api.project.definition_conversion import (
88
92
  convert_project_definition_to_v2,
89
93
  )
90
- from snowflake.cli.api.project.schemas.entities.snowpark_entity import (
91
- FunctionEntityModel,
92
- ProcedureEntityModel,
93
- )
94
94
  from snowflake.cli.api.project.schemas.project_definition import (
95
95
  ProjectDefinition,
96
96
  ProjectDefinitionV2,
@@ -446,5 +446,5 @@ def describe(
446
446
  def _get_v2_project_definition(cli_context) -> ProjectDefinitionV2:
447
447
  pd = cli_context.project_definition
448
448
  if not pd.meets_version_requirement("2"):
449
- pd = convert_project_definition_to_v2(pd)
449
+ pd = convert_project_definition_to_v2(cli_context.project_root, pd)
450
450
  return pd
@@ -21,6 +21,10 @@ from typing import Dict, List, Set
21
21
 
22
22
  from click import UsageError
23
23
  from snowflake.cli._plugins.snowpark.models import Requirement
24
+ from snowflake.cli._plugins.snowpark.snowpark_entity_model import (
25
+ ProcedureEntityModel,
26
+ SnowparkEntityModel,
27
+ )
24
28
  from snowflake.cli._plugins.snowpark.snowpark_project_paths import Artefact
25
29
  from snowflake.cli.api.console import cli_console
26
30
  from snowflake.cli.api.constants import (
@@ -30,10 +34,6 @@ from snowflake.cli.api.constants import (
30
34
  PROJECT_TEMPLATE_VARIABLE_OPENING,
31
35
  ObjectType,
32
36
  )
33
- from snowflake.cli.api.project.schemas.entities.snowpark_entity import (
34
- ProcedureEntityModel,
35
- SnowparkEntityModel,
36
- )
37
37
  from snowflake.cli.api.sql_execution import SqlExecutionMixin
38
38
  from snowflake.connector.cursor import SnowflakeCursor
39
39
 
@@ -121,13 +121,14 @@ class WheelMetadata:
121
121
  if line.startswith(dep_keyword)
122
122
  ]
123
123
  name = cls._get_name_from_wheel_filename(wheel_path.name)
124
+
124
125
  return cls(name=name, wheel_path=wheel_path, dependencies=dependencies)
125
126
 
126
127
  @staticmethod
127
128
  def _get_name_from_wheel_filename(wheel_filename: str) -> str:
128
129
  # wheel filename is in format {name}-{version}[-{extra info}]
129
130
  # https://peps.python.org/pep-0491/#file-name-convention
130
- return wheel_filename.split("-")[0]
131
+ return wheel_filename.split("-")[0].lower()
131
132
 
132
133
  @staticmethod
133
134
  def to_wheel_name_format(package_name: str) -> str:
@@ -1,10 +1,10 @@
1
1
  from typing import Generic, TypeVar
2
2
 
3
- from snowflake.cli.api.entities.common import EntityBase
4
- from snowflake.cli.api.project.schemas.entities.snowpark_entity import (
3
+ from snowflake.cli._plugins.snowpark.snowpark_entity_model import (
5
4
  FunctionEntityModel,
6
5
  ProcedureEntityModel,
7
6
  )
7
+ from snowflake.cli.api.entities.common import EntityBase
8
8
 
9
9
  T = TypeVar("T")
10
10
 
@@ -22,12 +22,13 @@ from snowflake.cli.api.identifiers import FQN
22
22
  from snowflake.cli.api.project.schemas.entities.common import (
23
23
  EntityModelBase,
24
24
  ExternalAccessBaseModel,
25
+ ImportsBaseModel,
25
26
  )
26
- from snowflake.cli.api.project.schemas.snowpark.argument import Argument
27
27
  from snowflake.cli.api.project.schemas.updatable_model import (
28
28
  DiscriminatorField,
29
29
  UpdatableModel,
30
30
  )
31
+ from snowflake.cli.api.project.schemas.v1.snowpark.argument import Argument
31
32
 
32
33
 
33
34
  class PathMapping(UpdatableModel):
@@ -43,7 +44,7 @@ class PathMapping(UpdatableModel):
43
44
  )
44
45
 
45
46
 
46
- class SnowparkEntityModel(EntityModelBase, ExternalAccessBaseModel):
47
+ class SnowparkEntityModel(EntityModelBase, ExternalAccessBaseModel, ImportsBaseModel):
47
48
  handler: str = Field(
48
49
  title="Function’s or procedure’s implementation of the object inside source module",
49
50
  examples=["functions.hello_function"],
@@ -57,10 +58,6 @@ class SnowparkEntityModel(EntityModelBase, ExternalAccessBaseModel):
57
58
  runtime: Optional[Union[str, float]] = Field(
58
59
  title="Python version to use when executing ", default=None
59
60
  )
60
- imports: Optional[List[str]] = Field(
61
- title="Stage and path to previously uploaded files you want to import",
62
- default=[],
63
- )
64
61
  stage: str = Field(title="Stage in which artifacts will be stored")
65
62
  artifacts: List[Union[PathMapping, str]] = Field(title="List of required sources")
66
63
 
@@ -16,11 +16,11 @@ from __future__ import annotations
16
16
  from dataclasses import dataclass
17
17
  from pathlib import Path, PurePosixPath
18
18
 
19
+ from snowflake.cli._plugins.snowpark.snowpark_entity_model import PathMapping
19
20
  from snowflake.cli._plugins.snowpark.zipper import zip_dir
20
21
  from snowflake.cli.api.console import cli_console
21
22
  from snowflake.cli.api.constants import DEPLOYMENT_STAGE
22
23
  from snowflake.cli.api.identifiers import FQN
23
- from snowflake.cli.api.project.schemas.entities.snowpark_entity import PathMapping
24
24
  from snowflake.cli.api.secure_path import SecurePath
25
25
 
26
26
 
@@ -57,6 +57,9 @@ EXECUTE_SUPPORTED_FILES_FORMATS = (
57
57
  ".py",
58
58
  ) # tuple to preserve order but it's a set
59
59
 
60
+ # Replace magic numbers with constants
61
+ OMIT_FIRST = slice(1, None)
62
+
60
63
 
61
64
  @dataclass
62
65
  class StagePathParts:
@@ -67,7 +70,7 @@ class StagePathParts:
67
70
 
68
71
  @classmethod
69
72
  def get_directory(cls, stage_path: str) -> str:
70
- return "/".join(Path(stage_path).parts[1:])
73
+ return "/".join(Path(stage_path).parts[OMIT_FIRST])
71
74
 
72
75
  @property
73
76
  def path(self) -> str:
@@ -119,7 +122,9 @@ class DefaultStagePathParts(StagePathParts):
119
122
  self.directory = self.get_directory(stage_path)
120
123
  self.stage = StageManager.get_stage_from_path(stage_path)
121
124
  stage_name = self.stage.split(".")[-1]
122
- stage_name = stage_name[1:] if stage_name.startswith("@") else stage_name
125
+ stage_name = (
126
+ stage_name[OMIT_FIRST] if stage_name.startswith("@") else stage_name
127
+ )
123
128
  self.stage_name = stage_name
124
129
  self.is_directory = True if stage_path.endswith("/") else False
125
130
 
@@ -133,7 +138,7 @@ class DefaultStagePathParts(StagePathParts):
133
138
 
134
139
  def replace_stage_prefix(self, file_path: str) -> str:
135
140
  stage = Path(self.stage).parts[0]
136
- file_path_without_prefix = Path(file_path).parts[1:]
141
+ file_path_without_prefix = Path(file_path).parts[OMIT_FIRST]
137
142
  return f"{stage}/{'/'.join(file_path_without_prefix)}"
138
143
 
139
144
  def add_stage_prefix(self, file_path: str) -> str:
@@ -461,7 +466,7 @@ class StageManager(SqlExecutionMixin):
461
466
  on_error: OnErrorType,
462
467
  ) -> Dict:
463
468
  try:
464
- query = f"execute immediate from {file_stage_path}"
469
+ query = f"execute immediate from {self.quote_stage_name(file_stage_path)}"
465
470
  if variables:
466
471
  query += variables
467
472
  self._execute_query(query)
@@ -26,6 +26,9 @@ from snowflake.cli._plugins.object.command_aliases import (
26
26
  scope_option,
27
27
  )
28
28
  from snowflake.cli._plugins.streamlit.manager import StreamlitManager
29
+ from snowflake.cli._plugins.streamlit.streamlit_entity_model import (
30
+ StreamlitEntityModel,
31
+ )
29
32
  from snowflake.cli.api.cli_global_context import get_cli_context
30
33
  from snowflake.cli.api.commands.decorators import (
31
34
  with_experimental_behaviour,
@@ -49,9 +52,6 @@ from snowflake.cli.api.output.types import (
49
52
  from snowflake.cli.api.project.definition_conversion import (
50
53
  convert_project_definition_to_v2,
51
54
  )
52
- from snowflake.cli.api.project.schemas.entities.streamlit_entity_model import (
53
- StreamlitEntityModel,
54
- )
55
55
 
56
56
  app = SnowTyperFactory(
57
57
  name="streamlit",
@@ -138,7 +138,7 @@ def streamlit_deploy(
138
138
  raise NoProjectDefinitionError(
139
139
  project_type="streamlit", project_root=cli_context.project_root
140
140
  )
141
- pd = convert_project_definition_to_v2(pd)
141
+ pd = convert_project_definition_to_v2(cli_context.project_root, pd)
142
142
 
143
143
  streamlits: Dict[str, StreamlitEntityModel] = pd.get_entities_by_type(
144
144
  entity_type="streamlit"
@@ -18,21 +18,23 @@ import logging
18
18
  from pathlib import Path
19
19
  from typing import List, Optional
20
20
 
21
+ from click import ClickException
21
22
  from snowflake.cli._plugins.connection.util import (
22
23
  MissingConnectionAccountError,
23
24
  MissingConnectionRegionError,
24
25
  make_snowsight_url,
25
26
  )
27
+ from snowflake.cli._plugins.object.manager import ObjectManager
26
28
  from snowflake.cli._plugins.stage.manager import StageManager
29
+ from snowflake.cli._plugins.streamlit.streamlit_entity_model import (
30
+ StreamlitEntityModel,
31
+ )
27
32
  from snowflake.cli.api.commands.experimental_behaviour import (
28
33
  experimental_behaviour_enabled,
29
34
  )
30
35
  from snowflake.cli.api.console import cli_console
31
36
  from snowflake.cli.api.feature_flags import FeatureFlag
32
37
  from snowflake.cli.api.identifiers import FQN
33
- from snowflake.cli.api.project.schemas.entities.streamlit_entity_model import (
34
- StreamlitEntityModel,
35
- )
36
38
  from snowflake.cli.api.sql_execution import SqlExecutionMixin
37
39
  from snowflake.connector.cursor import SnowflakeCursor
38
40
  from snowflake.connector.errors import ProgrammingError
@@ -96,12 +98,16 @@ class StreamlitManager(SqlExecutionMixin):
96
98
  query.append(f"ROOT_LOCATION = '{from_stage_name}'")
97
99
 
98
100
  query.append(f"MAIN_FILE = '{streamlit.main_file}'")
99
-
101
+ if streamlit.imports:
102
+ query.append(streamlit.get_imports_sql())
100
103
  if streamlit.query_warehouse:
101
104
  query.append(f"QUERY_WAREHOUSE = {streamlit.query_warehouse}")
102
105
  if streamlit.title:
103
106
  query.append(f"TITLE = '{streamlit.title}'")
104
107
 
108
+ if streamlit.comment:
109
+ query.append(f"COMMENT = '{streamlit.comment}'")
110
+
105
111
  if streamlit.external_access_integrations:
106
112
  query.append(streamlit.get_external_access_integrations_sql())
107
113
 
@@ -112,6 +118,13 @@ class StreamlitManager(SqlExecutionMixin):
112
118
 
113
119
  def deploy(self, streamlit: StreamlitEntityModel, replace: bool = False):
114
120
  streamlit_id = streamlit.fqn.using_connection(self._conn)
121
+ if (
122
+ ObjectManager().object_exists(object_type="streamlit", fqn=streamlit_id)
123
+ and not replace
124
+ ):
125
+ raise ClickException(
126
+ f"Streamlit {streamlit.fqn} already exist. If you want to replace it use --replace flag."
127
+ )
115
128
 
116
129
  # for backwards compatibility - quoted stage path might be case-sensitive
117
130
  # https://docs.snowflake.com/en/sql-reference/identifiers-syntax#double-quoted-identifiers
@@ -1,7 +1,7 @@
1
- from snowflake.cli.api.entities.common import EntityBase
2
- from snowflake.cli.api.project.schemas.entities.streamlit_entity_model import (
1
+ from snowflake.cli._plugins.streamlit.streamlit_entity_model import (
3
2
  StreamlitEntityModel,
4
3
  )
4
+ from snowflake.cli.api.entities.common import EntityBase
5
5
 
6
6
 
7
7
  class StreamlitEntity(EntityBase[StreamlitEntityModel]):
@@ -20,17 +20,19 @@ from pydantic import Field, model_validator
20
20
  from snowflake.cli.api.project.schemas.entities.common import (
21
21
  EntityModelBase,
22
22
  ExternalAccessBaseModel,
23
+ ImportsBaseModel,
23
24
  )
24
25
  from snowflake.cli.api.project.schemas.updatable_model import (
25
26
  DiscriminatorField,
26
27
  )
27
28
 
28
29
 
29
- class StreamlitEntityModel(EntityModelBase, ExternalAccessBaseModel):
30
+ class StreamlitEntityModel(EntityModelBase, ExternalAccessBaseModel, ImportsBaseModel):
30
31
  type: Literal["streamlit"] = DiscriminatorField() # noqa: A003
31
32
  title: Optional[str] = Field(
32
33
  title="Human-readable title for the Streamlit dashboard", default=None
33
34
  )
35
+ comment: Optional[str] = Field(title="Comment for the Streamlit app", default=None)
34
36
  query_warehouse: str = Field(
35
37
  title="Snowflake warehouse to host the app", default=None
36
38
  )
@@ -48,23 +50,14 @@ class StreamlitEntityModel(EntityModelBase, ExternalAccessBaseModel):
48
50
  default=None,
49
51
  )
50
52
 
51
- @model_validator(mode="after")
52
- def main_file_must_be_in_artifacts(self):
53
- if not self.artifacts:
54
- return self
55
-
56
- if Path(self.main_file) not in self.artifacts:
57
- raise ValueError(
58
- f"Specified main file {self.main_file} is not included in artifacts."
59
- )
60
- return self
61
-
62
53
  @model_validator(mode="after")
63
54
  def artifacts_must_exists(self):
64
55
  if not self.artifacts:
65
56
  return self
66
57
 
67
58
  for artifact in self.artifacts:
59
+ if "*" in artifact.name:
60
+ continue
68
61
  if not artifact.exists():
69
62
  raise ValueError(
70
63
  f"Specified artifact {artifact} does not exist locally."
@@ -1,6 +1,6 @@
1
1
  from dataclasses import dataclass
2
2
  from pathlib import Path
3
- from typing import Optional
3
+ from typing import Callable, Optional
4
4
 
5
5
  from snowflake.cli.api.console.abc import AbstractConsole
6
6
 
@@ -15,3 +15,4 @@ class ActionContext:
15
15
  project_root: Path
16
16
  default_role: str
17
17
  default_warehouse: Optional[str]
18
+ get_entity: Callable