snowflake-cli 3.3.0__py3-none-any.whl → 3.4.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 (77) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/_app/__main__.py +2 -2
  3. snowflake/cli/_app/cli_app.py +224 -192
  4. snowflake/cli/_app/commands_registration/commands_registration_with_callbacks.py +1 -27
  5. snowflake/cli/_plugins/cortex/commands.py +2 -4
  6. snowflake/cli/_plugins/git/manager.py +1 -1
  7. snowflake/cli/_plugins/nativeapp/artifacts.py +6 -624
  8. snowflake/cli/_plugins/nativeapp/bundle_context.py +1 -1
  9. snowflake/cli/_plugins/nativeapp/codegen/artifact_processor.py +1 -1
  10. snowflake/cli/_plugins/nativeapp/codegen/compiler.py +1 -3
  11. snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +2 -2
  12. snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +2 -2
  13. snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +2 -2
  14. snowflake/cli/_plugins/nativeapp/commands.py +21 -19
  15. snowflake/cli/_plugins/nativeapp/entities/application.py +16 -19
  16. snowflake/cli/_plugins/nativeapp/entities/application_package.py +142 -55
  17. snowflake/cli/_plugins/nativeapp/release_channel/commands.py +37 -3
  18. snowflake/cli/_plugins/nativeapp/release_directive/commands.py +80 -2
  19. snowflake/cli/_plugins/nativeapp/sf_sql_facade.py +224 -44
  20. snowflake/cli/_plugins/nativeapp/v2_conversions/compat.py +2 -2
  21. snowflake/cli/_plugins/nativeapp/version/commands.py +1 -1
  22. snowflake/cli/_plugins/notebook/commands.py +55 -2
  23. snowflake/cli/_plugins/notebook/exceptions.py +1 -1
  24. snowflake/cli/_plugins/notebook/manager.py +3 -3
  25. snowflake/cli/_plugins/notebook/notebook_entity.py +120 -0
  26. snowflake/cli/_plugins/notebook/notebook_entity_model.py +42 -0
  27. snowflake/cli/_plugins/notebook/notebook_project_paths.py +15 -0
  28. snowflake/cli/_plugins/notebook/types.py +3 -0
  29. snowflake/cli/_plugins/snowpark/commands.py +48 -30
  30. snowflake/cli/_plugins/snowpark/common.py +47 -2
  31. snowflake/cli/_plugins/snowpark/snowpark_entity.py +38 -25
  32. snowflake/cli/_plugins/snowpark/snowpark_entity_model.py +18 -30
  33. snowflake/cli/_plugins/snowpark/snowpark_project_paths.py +156 -23
  34. snowflake/cli/_plugins/snowpark/zipper.py +33 -1
  35. snowflake/cli/_plugins/spcs/services/commands.py +0 -3
  36. snowflake/cli/_plugins/stage/commands.py +2 -1
  37. snowflake/cli/_plugins/stage/diff.py +60 -39
  38. snowflake/cli/_plugins/stage/manager.py +24 -11
  39. snowflake/cli/_plugins/stage/utils.py +1 -1
  40. snowflake/cli/_plugins/streamlit/commands.py +10 -1
  41. snowflake/cli/_plugins/streamlit/manager.py +62 -21
  42. snowflake/cli/_plugins/streamlit/streamlit_entity.py +20 -41
  43. snowflake/cli/_plugins/streamlit/streamlit_entity_model.py +14 -24
  44. snowflake/cli/_plugins/streamlit/streamlit_project_paths.py +30 -0
  45. snowflake/cli/_plugins/workspace/commands.py +3 -3
  46. snowflake/cli/_plugins/workspace/manager.py +1 -1
  47. snowflake/cli/api/artifacts/__init__.py +13 -0
  48. snowflake/cli/api/artifacts/bundle_map.py +500 -0
  49. snowflake/cli/api/artifacts/common.py +78 -0
  50. snowflake/cli/api/artifacts/utils.py +82 -0
  51. snowflake/cli/api/cli_global_context.py +14 -1
  52. snowflake/cli/api/commands/flags.py +10 -4
  53. snowflake/cli/api/commands/utils.py +28 -2
  54. snowflake/cli/api/constants.py +1 -0
  55. snowflake/cli/api/entities/common.py +14 -32
  56. snowflake/cli/api/entities/resolver.py +160 -0
  57. snowflake/cli/api/entities/utils.py +56 -15
  58. snowflake/cli/api/errno.py +3 -0
  59. snowflake/cli/api/feature_flags.py +1 -2
  60. snowflake/cli/api/project/definition_conversion.py +3 -2
  61. snowflake/cli/api/project/project_paths.py +28 -0
  62. snowflake/cli/api/project/schemas/entities/common.py +130 -1
  63. snowflake/cli/api/project/schemas/entities/entities.py +4 -0
  64. snowflake/cli/api/project/schemas/project_definition.py +27 -0
  65. snowflake/cli/api/project/schemas/updatable_model.py +2 -2
  66. snowflake/cli/api/project/schemas/v1/native_app/native_app.py +5 -7
  67. snowflake/cli/api/secure_path.py +6 -0
  68. snowflake/cli/api/sql_execution.py +5 -1
  69. snowflake/cli/api/stage_path.py +7 -2
  70. snowflake/cli/api/utils/graph.py +3 -0
  71. snowflake/cli/api/utils/path_utils.py +24 -0
  72. {snowflake_cli-3.3.0.dist-info → snowflake_cli-3.4.1.dist-info}/METADATA +8 -9
  73. {snowflake_cli-3.3.0.dist-info → snowflake_cli-3.4.1.dist-info}/RECORD +76 -67
  74. snowflake/cli/api/project/schemas/v1/native_app/path_mapping.py +0 -65
  75. {snowflake_cli-3.3.0.dist-info → snowflake_cli-3.4.1.dist-info}/WHEEL +0 -0
  76. {snowflake_cli-3.3.0.dist-info → snowflake_cli-3.4.1.dist-info}/entry_points.txt +0 -0
  77. {snowflake_cli-3.3.0.dist-info → snowflake_cli-3.4.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,42 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Literal, Optional
5
+
6
+ from pydantic import Field, model_validator
7
+ from snowflake.cli._plugins.notebook.exceptions import NotebookFilePathError
8
+ from snowflake.cli.api.project.schemas.entities.common import (
9
+ EntityModelBaseWithArtifacts,
10
+ )
11
+ from snowflake.cli.api.project.schemas.updatable_model import (
12
+ DiscriminatorField,
13
+ )
14
+
15
+
16
+ class NotebookEntityModel(EntityModelBaseWithArtifacts):
17
+ type: Literal["notebook"] = DiscriminatorField() # noqa: A003
18
+ stage_path: Optional[str] = Field(
19
+ title="Stage directory in which the notebook file will be stored", default=None
20
+ )
21
+ notebook_file: Path = Field(title="Notebook file")
22
+ query_warehouse: str = Field(title="Snowflake warehouse to execute the notebook")
23
+ compute_pool: Optional[str] = Field(
24
+ title="Compute pool to run the notebook in", default=None
25
+ )
26
+ runtime_name: Optional[str] = Field(title="Container Runtime for ML", default=None)
27
+
28
+ @model_validator(mode="after")
29
+ def validate_notebook_file(self):
30
+ if not self.notebook_file.exists():
31
+ raise ValueError(f"Notebook file {self.notebook_file} does not exist")
32
+ if self.notebook_file.suffix.lower() != ".ipynb":
33
+ raise NotebookFilePathError(str(self.notebook_file))
34
+ return self
35
+
36
+ @model_validator(mode="after")
37
+ def validate_container_setup(self):
38
+ if self.compute_pool and not self.runtime_name:
39
+ raise ValueError("compute_pool is specified without runtime_name")
40
+ if self.runtime_name and not self.compute_pool and not self:
41
+ raise ValueError("runtime_name is specified without compute_pool")
42
+ return self
@@ -0,0 +1,15 @@
1
+ from dataclasses import dataclass
2
+ from pathlib import Path
3
+
4
+ from snowflake.cli.api.project.project_paths import ProjectPaths, bundle_root
5
+
6
+
7
+ @dataclass
8
+ class NotebookProjectPaths(ProjectPaths):
9
+ """
10
+ This class allows you to manage files paths related to given project.
11
+ """
12
+
13
+ @property
14
+ def bundle_root(self) -> Path:
15
+ return bundle_root(self.project_root, "notebook")
@@ -12,4 +12,7 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ from pathlib import Path
16
+
15
17
  NotebookStagePath = str
18
+ NotebookLocalPath = Path
@@ -39,7 +39,9 @@ from snowflake.cli._plugins.snowpark.common import (
39
39
  SnowparkEntities,
40
40
  SnowparkObject,
41
41
  SnowparkObjectManager,
42
- StageToArtefactMapping,
42
+ StageToArtifactMapping,
43
+ map_path_mapping_to_artifact,
44
+ zip_and_copy_artifacts_to_deploy,
43
45
  )
44
46
  from snowflake.cli._plugins.snowpark.package.anaconda_packages import (
45
47
  AnacondaPackages,
@@ -68,6 +70,7 @@ from snowflake.cli.api.commands.decorators import (
68
70
  with_project_definition,
69
71
  )
70
72
  from snowflake.cli.api.commands.flags import (
73
+ ForceReplaceOption,
71
74
  ReplaceOption,
72
75
  execution_identifier_argument,
73
76
  identifier_argument,
@@ -81,6 +84,7 @@ from snowflake.cli.api.constants import (
81
84
  from snowflake.cli.api.exceptions import (
82
85
  SecretsWithoutExternalAccessIntegrationError,
83
86
  )
87
+ from snowflake.cli.api.feature_flags import FeatureFlag
84
88
  from snowflake.cli.api.identifiers import FQN
85
89
  from snowflake.cli.api.output.types import (
86
90
  CollectionResult,
@@ -125,8 +129,9 @@ LikeOption = like_option(
125
129
  @with_project_definition()
126
130
  def deploy(
127
131
  replace: bool = ReplaceOption(
128
- help="Replaces procedure or function, even if no detected changes to metadata"
132
+ help="Replaces procedure or function if there were changes in the definition."
129
133
  ),
134
+ force_replace: bool = ForceReplaceOption(),
130
135
  **options,
131
136
  ) -> CommandResult:
132
137
  """
@@ -156,7 +161,11 @@ def deploy(
156
161
  with cli_console.phase("Checking remote state"):
157
162
  om = ObjectManager()
158
163
  _check_if_all_defined_integrations_exists(om, snowpark_entities)
159
- existing_objects = check_for_existing_objects(om, replace, snowpark_entities)
164
+ existing_objects = (
165
+ {}
166
+ if force_replace
167
+ else check_for_existing_objects(om, replace, snowpark_entities)
168
+ )
160
169
 
161
170
  with cli_console.phase("Preparing required stages and artifacts"):
162
171
  entities_to_imports_map, stages_to_artifact_map = build_artifacts_mappings(
@@ -189,11 +198,11 @@ def validate_all_artifacts_exists(
189
198
  project_paths: SnowparkProjectPaths, snowpark_entities: SnowparkEntities
190
199
  ):
191
200
  for key, entity in snowpark_entities.items():
192
- for artefact in entity.artifacts:
193
- path = project_paths.get_artefact_dto(artefact).post_build_path
201
+ for artifact in entity.artifacts:
202
+ path = project_paths.get_artifact_dto(artifact).post_build_path
194
203
  if not path.exists():
195
204
  raise UsageError(
196
- f"Artefact {path} required for {entity.type} {key} does not exist."
205
+ f"Artifact {path} required for {entity.type} {key} does not exist."
197
206
  )
198
207
 
199
208
 
@@ -213,38 +222,39 @@ def check_for_existing_objects(
213
222
 
214
223
  def build_artifacts_mappings(
215
224
  project_paths: SnowparkProjectPaths, snowpark_entities: SnowparkEntities
216
- ) -> Tuple[EntityToImportPathsMapping, StageToArtefactMapping]:
217
- stages_to_artifact_map: StageToArtefactMapping = defaultdict(set)
225
+ ) -> Tuple[EntityToImportPathsMapping, StageToArtifactMapping]:
226
+ stages_to_artifact_map: StageToArtifactMapping = defaultdict(set)
218
227
  entities_to_imports_map: EntityToImportPathsMapping = defaultdict(set)
219
- for entity_id, entity in snowpark_entities.items():
228
+ for name, entity in snowpark_entities.items():
220
229
  stage = entity.stage
221
230
  required_artifacts = set()
222
- for artefact in entity.artifacts:
223
- artefact_dto = project_paths.get_artefact_dto(artefact)
224
- required_artifacts.add(artefact_dto)
225
- entities_to_imports_map[entity_id].add(artefact_dto.import_path(stage))
231
+ for artifact in entity.artifacts:
232
+ artifact_dto = project_paths.get_artifact_dto(artifact)
233
+ required_artifacts.add(artifact_dto)
234
+ entities_to_imports_map[name].add(artifact_dto.import_path(stage))
226
235
  stages_to_artifact_map[stage].update(required_artifacts)
227
236
 
228
- if project_paths.dependencies.exists():
229
- deps_artefact = project_paths.get_dependencies_artefact()
230
- stages_to_artifact_map[stage].add(deps_artefact)
231
- entities_to_imports_map[entity_id].add(deps_artefact.import_path(stage))
237
+ deps_artifact = project_paths.get_dependencies_artifact()
238
+ if deps_artifact.post_build_path.exists():
239
+ stages_to_artifact_map[stage].add(deps_artifact)
240
+ entities_to_imports_map[name].add(deps_artifact.import_path(stage))
232
241
  return entities_to_imports_map, stages_to_artifact_map
233
242
 
234
243
 
235
- def create_stages_and_upload_artifacts(stages_to_artifact_map: StageToArtefactMapping):
244
+ def create_stages_and_upload_artifacts(stages_to_artifact_map: StageToArtifactMapping):
236
245
  stage_manager = StageManager()
237
246
  for stage, artifacts in stages_to_artifact_map.items():
238
247
  cli_console.step(f"Creating (if not exists) stage: {stage}")
239
248
  stage = FQN.from_stage(stage).using_context()
240
249
  stage_manager.create(fqn=stage, comment="deployments managed by Snowflake CLI")
241
- for artefact in artifacts:
250
+ for artifact in artifacts:
251
+ post_build_path = artifact.post_build_path
242
252
  cli_console.step(
243
- f"Uploading {artefact.post_build_path.name} to {artefact.upload_path(stage)}"
253
+ f"Uploading {post_build_path.name} to {artifact.upload_path(stage)}"
244
254
  )
245
255
  stage_manager.put(
246
- local_path=artefact.post_build_path,
247
- stage_path=artefact.upload_path(stage),
256
+ local_path=post_build_path,
257
+ stage_path=artifact.upload_path(stage),
248
258
  overwrite=True,
249
259
  )
250
260
 
@@ -324,6 +334,9 @@ def build(
324
334
 
325
335
  anaconda_packages_manager = AnacondaPackagesManager()
326
336
 
337
+ # Clean up bundle root
338
+ project_paths.remove_up_bundle_root()
339
+
327
340
  # Resolve dependencies
328
341
  if project_paths.requirements.exists():
329
342
  with (
@@ -362,22 +375,27 @@ def build(
362
375
  )
363
376
 
364
377
  if any(temp_deps_dir.path.iterdir()):
365
- cli_console.step(f"Creating {project_paths.dependencies.name}")
378
+ dep_artifact = project_paths.get_dependencies_artifact()
379
+ cli_console.step(f"Creating {dep_artifact.path.name}")
366
380
  zip_dir(
367
381
  source=temp_deps_dir.path,
368
- dest_zip=project_paths.dependencies,
382
+ dest_zip=dep_artifact.post_build_path,
369
383
  )
370
384
  else:
371
385
  cli_console.step(f"No external dependencies.")
372
386
 
373
387
  artifacts = set()
374
- for entity in get_snowpark_entities(pd).values():
375
- artifacts.update(entity.artifacts)
376
-
377
388
  with cli_console.phase("Preparing artifacts for source code"):
378
- for artefact in artifacts:
379
- artefact_dto = project_paths.get_artefact_dto(artefact)
380
- artefact_dto.build()
389
+ for entity in get_snowpark_entities(pd).values():
390
+ artifacts.update(
391
+ map_path_mapping_to_artifact(project_paths, entity.artifacts)
392
+ )
393
+
394
+ if FeatureFlag.ENABLE_SNOWPARK_GLOB_SUPPORT.is_enabled():
395
+ zip_and_copy_artifacts_to_deploy(artifacts, project_paths.bundle_root)
396
+ else:
397
+ for artifact in artifacts:
398
+ artifact.build()
381
399
 
382
400
  return MessageResult(f"Build done.")
383
401
 
@@ -17,6 +17,7 @@ from __future__ import annotations
17
17
  import logging
18
18
  import re
19
19
  from enum import Enum
20
+ from pathlib import Path
20
21
  from typing import Dict, List, Set
21
22
 
22
23
  from click import UsageError
@@ -25,7 +26,13 @@ from snowflake.cli._plugins.snowpark.snowpark_entity_model import (
25
26
  ProcedureEntityModel,
26
27
  SnowparkEntityModel,
27
28
  )
28
- from snowflake.cli._plugins.snowpark.snowpark_project_paths import Artefact
29
+ from snowflake.cli._plugins.snowpark.snowpark_project_paths import (
30
+ Artifact,
31
+ SnowparkProjectPaths,
32
+ )
33
+ from snowflake.cli._plugins.snowpark.zipper import zip_dir_using_bundle_map
34
+ from snowflake.cli.api.artifacts.bundle_map import BundleMap
35
+ from snowflake.cli.api.artifacts.utils import symlink_or_copy
29
36
  from snowflake.cli.api.console import cli_console
30
37
  from snowflake.cli.api.constants import (
31
38
  INIT_TEMPLATE_VARIABLE_CLOSING,
@@ -34,13 +41,14 @@ from snowflake.cli.api.constants import (
34
41
  PROJECT_TEMPLATE_VARIABLE_OPENING,
35
42
  ObjectType,
36
43
  )
44
+ from snowflake.cli.api.project.schemas.entities.common import PathMapping
37
45
  from snowflake.cli.api.sql_execution import SqlExecutionMixin
38
46
  from snowflake.connector.cursor import SnowflakeCursor
39
47
 
40
48
  log = logging.getLogger(__name__)
41
49
 
42
50
  SnowparkEntities = Dict[str, SnowparkEntityModel]
43
- StageToArtefactMapping = Dict[str, set[Artefact]]
51
+ StageToArtifactMapping = Dict[str, set[Artifact]]
44
52
  EntityToImportPathsMapping = Dict[str, set[str]]
45
53
 
46
54
  DEFAULT_RUNTIME = "3.10"
@@ -214,6 +222,43 @@ def _snowflake_dependencies_differ(
214
222
  return _standardize(old_dependencies) != _standardize(new_dependencies)
215
223
 
216
224
 
225
+ def map_path_mapping_to_artifact(
226
+ project_paths: SnowparkProjectPaths, artifacts: List[PathMapping]
227
+ ) -> List[Artifact]:
228
+ return [project_paths.get_artifact_dto(artifact) for artifact in artifacts]
229
+
230
+
231
+ def zip_and_copy_artifacts_to_deploy(
232
+ artifacts: Set[Artifact] | List[Artifact], bundle_root: Path
233
+ ) -> List[Path]:
234
+ copied_files = []
235
+ for artifact in artifacts:
236
+ bundle_map = BundleMap(
237
+ project_root=artifact.project_root,
238
+ deploy_root=bundle_root,
239
+ )
240
+ bundle_map.add(PathMapping(src=str(artifact.path), dest=artifact.dest))
241
+
242
+ if artifact.path.is_file():
243
+ for (absolute_src, absolute_dest) in bundle_map.all_mappings(
244
+ absolute=True, expand_directories=False
245
+ ):
246
+ symlink_or_copy(
247
+ absolute_src,
248
+ absolute_dest,
249
+ deploy_root=bundle_map.deploy_root(),
250
+ )
251
+ copied_files.append(absolute_dest)
252
+ else:
253
+ post_build_path = artifact.post_build_path
254
+ zip_dir_using_bundle_map(
255
+ bundle_map=bundle_map,
256
+ dest_zip=post_build_path,
257
+ )
258
+ copied_files.append(post_build_path)
259
+ return copied_files
260
+
261
+
217
262
  def same_type(sf_type: str, local_type: str) -> bool:
218
263
  sf_type, local_type = sf_type.upper(), local_type.upper()
219
264
 
@@ -5,7 +5,11 @@ from typing import Generic, List, Optional, TypeVar
5
5
  from click import ClickException
6
6
  from snowflake.cli._plugins.nativeapp.feature_flags import FeatureFlag
7
7
  from snowflake.cli._plugins.snowpark import package_utils
8
- from snowflake.cli._plugins.snowpark.common import DEFAULT_RUNTIME
8
+ from snowflake.cli._plugins.snowpark.common import (
9
+ DEFAULT_RUNTIME,
10
+ map_path_mapping_to_artifact,
11
+ zip_and_copy_artifacts_to_deploy,
12
+ )
9
13
  from snowflake.cli._plugins.snowpark.package.anaconda_packages import (
10
14
  AnacondaPackages,
11
15
  AnacondaPackagesManager,
@@ -17,6 +21,7 @@ from snowflake.cli._plugins.snowpark.snowpark_entity_model import (
17
21
  FunctionEntityModel,
18
22
  ProcedureEntityModel,
19
23
  )
24
+ from snowflake.cli._plugins.snowpark.snowpark_project_paths import SnowparkProjectPaths
20
25
  from snowflake.cli._plugins.snowpark.zipper import zip_dir
21
26
  from snowflake.cli._plugins.workspace.context import ActionContext
22
27
  from snowflake.cli.api.entities.common import EntityBase
@@ -44,27 +49,31 @@ class SnowparkEntity(EntityBase[Generic[T]]):
44
49
  def action_bundle(
45
50
  self,
46
51
  action_ctx: ActionContext,
47
- output_dir: Path | None,
48
52
  ignore_anaconda: bool,
49
53
  skip_version_check: bool,
54
+ output_dir: Path | None = None,
50
55
  index_url: str | None = None,
51
56
  allow_shared_libraries: bool = False,
52
57
  *args,
53
58
  **kwargs,
54
59
  ) -> List[Path]:
55
60
  return self.bundle(
56
- output_dir,
57
61
  ignore_anaconda,
58
62
  skip_version_check,
63
+ output_dir,
59
64
  index_url,
60
65
  allow_shared_libraries,
61
66
  )
62
67
 
63
68
  def action_deploy(
64
- self, action_ctx: ActionContext, mode: CreateMode, *args, **kwargs
69
+ self,
70
+ action_ctx: ActionContext,
71
+ mode: CreateMode = CreateMode.create,
72
+ *args,
73
+ **kwargs,
65
74
  ):
66
75
  # TODO: After introducing bundle map, we should introduce file copying part here
67
- return self._execute_query(self.get_deploy_sql(mode))
76
+ return self.deploy(mode, *args, **kwargs)
68
77
 
69
78
  def action_drop(self, action_ctx: ActionContext, *args, **kwargs):
70
79
  return self._execute_query(self.get_drop_sql())
@@ -83,9 +92,9 @@ class SnowparkEntity(EntityBase[Generic[T]]):
83
92
 
84
93
  def bundle(
85
94
  self,
86
- output_dir: Path | None,
87
95
  ignore_anaconda: bool,
88
96
  skip_version_check: bool,
97
+ output_dir: Path | None = None,
89
98
  index_url: str | None = None,
90
99
  allow_shared_libraries: bool = False,
91
100
  ) -> List[Path]:
@@ -93,18 +102,20 @@ class SnowparkEntity(EntityBase[Generic[T]]):
93
102
  Bundles the entity artifacts and dependencies into a directory.
94
103
  Parameters:
95
104
  output_dir: The directory to output the bundled artifacts to. Defaults to output dir in project root
96
- ignore_anaconda: If True, ignores anaconda chceck and tries to download all packages using pip
105
+ ignore_anaconda: If True, ignores anaconda check and tries to download all packages using pip
97
106
  skip_version_check: If True, skips version check when downloading packages
98
107
  index_url: The index URL to use when downloading packages, if none set - default pip index is used (in most cases- Pypi)
99
108
  allow_shared_libraries: If not set to True, using dependency with .so/.dll files will raise an exception
100
109
  Returns:
101
110
  """
102
111
  # 0 Create a directory for the entity
112
+ project_paths = SnowparkProjectPaths(
113
+ project_root=self.root.absolute(),
114
+ )
103
115
  if not output_dir:
104
- output_dir = self.root / "output" / "bundle" / "snowpark"
105
- output_dir.mkdir(parents=True, exist_ok=True) # type: ignore
106
-
107
- output_files = []
116
+ output_dir = project_paths.bundle_root
117
+ if not output_dir.exists(): # type: ignore[union-attr]
118
+ SecurePath(output_dir).mkdir(parents=True)
108
119
 
109
120
  # 1 Check if requirements exits
110
121
  if (self.root / "requirements.txt").exists():
@@ -118,21 +129,20 @@ class SnowparkEntity(EntityBase[Generic[T]]):
118
129
  allow_shared_libraries=allow_shared_libraries,
119
130
  )
120
131
 
121
- # 3 get the artifacts list
122
- artifacts = self.model.artifacts
123
-
124
- for artifact in artifacts:
125
- output_file = output_dir / artifact.dest / artifact.src.name
126
-
127
- if artifact.src.is_file():
128
- output_file.mkdir(parents=True, exist_ok=True)
129
- SecurePath(artifact.src).copy(output_file)
130
- elif artifact.is_dir():
131
- output_file.mkdir(parents=True, exist_ok=True)
132
+ # 2 get the artifacts list
133
+ artifacts = map_path_mapping_to_artifact(project_paths, self.model.artifacts)
134
+ from snowflake.cli.api.feature_flags import FeatureFlag
132
135
 
133
- output_files.append(output_file)
134
-
135
- return output_files
136
+ if FeatureFlag.ENABLE_SNOWPARK_GLOB_SUPPORT.is_enabled():
137
+ return zip_and_copy_artifacts_to_deploy(
138
+ artifacts, project_paths.bundle_root
139
+ )
140
+ else:
141
+ copied_files = []
142
+ for artifact in artifacts:
143
+ artifact.build()
144
+ copied_files.append(artifact.post_build_path)
145
+ return copied_files
136
146
 
137
147
  def check_if_exists(
138
148
  self, action_ctx: ActionContext
@@ -143,6 +153,9 @@ class SnowparkEntity(EntityBase[Generic[T]]):
143
153
  except ProgrammingError:
144
154
  return False
145
155
 
156
+ def deploy(self, mode: CreateMode = CreateMode.create, *args, **kwargs):
157
+ return self._execute_query(self.get_deploy_sql(mode))
158
+
146
159
  def get_deploy_sql(self, mode: CreateMode):
147
160
  query = [
148
161
  f"{mode.value} {self.model.type.upper()} {self.identifier}",
@@ -14,37 +14,27 @@
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
- from pathlib import Path
17
+ import glob
18
18
  from typing import List, Literal, Optional, Union
19
19
 
20
20
  from pydantic import Field, field_validator
21
+ from snowflake.cli.api.feature_flags import FeatureFlag
21
22
  from snowflake.cli.api.identifiers import FQN
22
23
  from snowflake.cli.api.project.schemas.entities.common import (
23
- EntityModelBase,
24
+ EntityModelBaseWithArtifacts,
24
25
  ExternalAccessBaseModel,
25
26
  ImportsBaseModel,
27
+ PathMapping,
26
28
  )
27
29
  from snowflake.cli.api.project.schemas.updatable_model import (
28
30
  DiscriminatorField,
29
- UpdatableModel,
30
31
  )
31
32
  from snowflake.cli.api.project.schemas.v1.snowpark.argument import Argument
32
33
 
33
34
 
34
- class PathMapping(UpdatableModel):
35
- class Config:
36
- frozen = True
37
-
38
- src: Path = Field(title="Source path (relative to project root)", default=None)
39
-
40
- dest: Optional[str] = Field(
41
- title="Destination path on stage",
42
- description="Paths are relative to stage root; paths ending with a slash indicate that the destination is a directory which source files should be copied into.",
43
- default=None,
44
- )
45
-
46
-
47
- class SnowparkEntityModel(EntityModelBase, ExternalAccessBaseModel, ImportsBaseModel):
35
+ class SnowparkEntityModel(
36
+ EntityModelBaseWithArtifacts, ExternalAccessBaseModel, ImportsBaseModel
37
+ ):
48
38
  handler: str = Field(
49
39
  title="Function’s or procedure’s implementation of the object inside source module",
50
40
  examples=["functions.hello_function"],
@@ -59,17 +49,23 @@ class SnowparkEntityModel(EntityModelBase, ExternalAccessBaseModel, ImportsBaseM
59
49
  title="Python version to use when executing ", default=None
60
50
  )
61
51
  stage: str = Field(title="Stage in which artifacts will be stored")
62
- artifacts: List[Union[PathMapping, str]] = Field(title="List of required sources")
63
52
 
64
53
  @field_validator("artifacts")
65
54
  @classmethod
66
55
  def _convert_artifacts(cls, artifacts: Union[dict, str]):
67
56
  _artifacts = []
68
- for artefact in artifacts:
69
- if isinstance(artefact, PathMapping):
70
- _artifacts.append(artefact)
57
+ for artifact in artifacts:
58
+ if (
59
+ (isinstance(artifact, str) and glob.has_magic(artifact))
60
+ or (isinstance(artifact, PathMapping) and glob.has_magic(artifact.src))
61
+ ) and FeatureFlag.ENABLE_SNOWPARK_GLOB_SUPPORT.is_disabled():
62
+ raise ValueError(
63
+ "If you want to use glob patterns in artifacts, you need to enable the Snowpark new build feature flag (enable_snowpark_glob_support=true)"
64
+ )
65
+ if isinstance(artifact, PathMapping):
66
+ _artifacts.append(artifact)
71
67
  else:
72
- _artifacts.append(PathMapping(src=artefact))
68
+ _artifacts.append(PathMapping(src=artifact))
73
69
  return _artifacts
74
70
 
75
71
  @field_validator("runtime")
@@ -79,14 +75,6 @@ class SnowparkEntityModel(EntityModelBase, ExternalAccessBaseModel, ImportsBaseM
79
75
  return str(runtime_input)
80
76
  return runtime_input
81
77
 
82
- @field_validator("artifacts")
83
- @classmethod
84
- def validate_artifacts(cls, artifacts: List[Path]) -> List[Path]:
85
- for artefact in artifacts:
86
- if "*" in str(artefact):
87
- raise ValueError("Glob patterns not supported for Snowpark artifacts.")
88
- return artifacts
89
-
90
78
  @property
91
79
  def udf_sproc_identifier(self) -> UdfSprocIdentifier:
92
80
  return UdfSprocIdentifier.from_definition(self)