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
@@ -95,7 +95,7 @@ TemporaryConnectionOption = typer.Option(
95
95
  False,
96
96
  "--temporary-connection",
97
97
  "-x",
98
- help="Uses connection defined with command line parameters, instead of one defined in config",
98
+ help="Uses a connection defined with command line parameters, instead of one defined in config",
99
99
  callback=_connection_callback("temporary_connection"),
100
100
  is_flag=True,
101
101
  rich_help_panel=_CONNECTION_SECTION,
@@ -383,6 +383,12 @@ ReplaceOption = OverrideableOption(
383
383
  mutually_exclusive=CREATE_MODE_OPTION_NAMES,
384
384
  )
385
385
 
386
+ ForceReplaceOption = OverrideableOption(
387
+ False,
388
+ "--force-replace",
389
+ help="Replace this object, even if the state didn't change",
390
+ )
391
+
386
392
  OnErrorOption = typer.Option(
387
393
  OnErrorType.BREAK.value,
388
394
  "--on-error",
@@ -531,8 +537,8 @@ def project_definition_option(is_optional: bool):
531
537
  None,
532
538
  "-p",
533
539
  "--project",
534
- help=f"Path where Snowflake project resides. "
535
- f"Defaults to current working directory.",
540
+ help=f"Path where the Snowflake project is stored. "
541
+ f"Defaults to the current working directory.",
536
542
  callback=project_path_callback,
537
543
  show_default=False,
538
544
  )
@@ -551,7 +557,7 @@ def project_env_overrides_option():
551
557
  return typer.Option(
552
558
  [],
553
559
  "--env",
554
- help="String in format of key=value. Overrides variables from env section used for templates.",
560
+ help="String in the format key=value. Overrides variables from the env section used for templates.",
555
561
  callback=project_env_overrides_callback,
556
562
  show_default=False,
557
563
  )
@@ -1,7 +1,9 @@
1
- from typing import List, Optional
1
+ from typing import Dict, List, Optional
2
2
 
3
- from click import ClickException
3
+ from click import ClickException, UsageError
4
4
  from snowflake.cli.api.commands.common import Variable
5
+ from snowflake.cli.api.exceptions import NoProjectDefinitionError
6
+ from snowflake.cli.api.project.schemas.entities.common import EntityModelBase
5
7
 
6
8
 
7
9
  def parse_key_value_variables(variables: Optional[List[str]]) -> List[Variable]:
@@ -16,3 +18,27 @@ def parse_key_value_variables(variables: Optional[List[str]]) -> List[Variable]:
16
18
  key, value = p.split("=", 1)
17
19
  result.append(Variable(key.strip(), value.strip()))
18
20
  return result
21
+
22
+
23
+ def get_entity_for_operation(
24
+ cli_context,
25
+ entity_id: str | None,
26
+ project_definition,
27
+ entity_type: str,
28
+ ):
29
+ entities: Dict[str, EntityModelBase] = project_definition.get_entities_by_type(
30
+ entity_type=entity_type
31
+ )
32
+ if not entities:
33
+ raise NoProjectDefinitionError(
34
+ project_type=entity_type, project_root=cli_context.project_root
35
+ )
36
+ if entity_id and entity_id not in entities:
37
+ raise UsageError(f"No '{entity_id}' entity in project definition file.")
38
+ if len(entities.keys()) == 1:
39
+ entity_id = list(entities.keys())[0]
40
+ if entity_id is None:
41
+ raise UsageError(
42
+ f"Multiple entities of type {entity_type} found. Please provide entity id for the operation."
43
+ )
44
+ return entities[entity_id]
@@ -45,6 +45,7 @@ class ObjectType(Enum):
45
45
  )
46
46
  # JOB = ObjectNames("job", "job", "jobs")
47
47
  NETWORK_RULE = ObjectNames("network-rule", "network rule", "network rules")
48
+ NOTEBOOK = ObjectNames("notebook", "notebook", "notebooks")
48
49
  PROCEDURE = ObjectNames("procedure", "procedure", "procedures")
49
50
  ROLE = ObjectNames("role", "role", "roles")
50
51
  SCHEMA = ObjectNames("schema", "schema", "schemas")
@@ -1,40 +1,16 @@
1
1
  import functools
2
- from enum import Enum
3
2
  from pathlib import Path
4
3
  from typing import Generic, Type, TypeVar, get_args
5
4
 
6
5
  from snowflake.cli._plugins.workspace.context import ActionContext, WorkspaceContext
7
- from snowflake.cli.api.cli_global_context import span
6
+ from snowflake.cli.api.cli_global_context import get_cli_context, span
7
+ from snowflake.cli.api.entities.resolver import DependencyResolver
8
+ from snowflake.cli.api.entities.utils import EntityActions, get_sql_executor
8
9
  from snowflake.cli.api.identifiers import FQN
9
10
  from snowflake.cli.api.sql_execution import SqlExecutor
10
11
  from snowflake.connector import SnowflakeConnection
11
12
  from snowflake.connector.cursor import SnowflakeCursor
12
13
 
13
-
14
- class EntityActions(str, Enum):
15
- BUNDLE = "action_bundle"
16
- DEPLOY = "action_deploy"
17
- DROP = "action_drop"
18
- VALIDATE = "action_validate"
19
- EVENTS = "action_events"
20
-
21
- VERSION_LIST = "action_version_list"
22
- VERSION_CREATE = "action_version_create"
23
- VERSION_DROP = "action_version_drop"
24
-
25
- RELEASE_DIRECTIVE_UNSET = "action_release_directive_unset"
26
- RELEASE_DIRECTIVE_SET = "action_release_directive_set"
27
- RELEASE_DIRECTIVE_LIST = "action_release_directive_list"
28
-
29
- RELEASE_CHANNEL_LIST = "action_release_channel_list"
30
- RELEASE_CHANNEL_ADD_ACCOUNTS = "action_release_channel_add_accounts"
31
- RELEASE_CHANNEL_REMOVE_ACCOUNTS = "action_release_channel_remove_accounts"
32
- RELEASE_CHANNEL_ADD_VERSION = "action_release_channel_add_version"
33
- RELEASE_CHANNEL_REMOVE_VERSION = "action_release_channel_remove_version"
34
-
35
- PUBLISH = "action_publish"
36
-
37
-
38
14
  T = TypeVar("T")
39
15
 
40
16
 
@@ -71,6 +47,7 @@ class EntityBase(Generic[T]):
71
47
  def __init__(self, entity_model: T, workspace_ctx: WorkspaceContext):
72
48
  self._entity_model = entity_model
73
49
  self._workspace_ctx = workspace_ctx
50
+ self.dependency_resolver = DependencyResolver(entity_model)
74
51
 
75
52
  @property
76
53
  def entity_id(self) -> str:
@@ -96,7 +73,10 @@ class EntityBase(Generic[T]):
96
73
  ):
97
74
  """
98
75
  Performs the requested action.
76
+ This is a preferred way to perform actions on entities, over calling actions directly,
77
+ as it will also call the dependencies in the correct order.
99
78
  """
79
+ self.dependency_resolver.perform_for_dep(action, action_ctx, *args, **kwargs)
100
80
  return getattr(self, action)(action_ctx, *args, **kwargs)
101
81
 
102
82
  @property
@@ -124,10 +104,17 @@ class EntityBase(Generic[T]):
124
104
  def _conn(self) -> SnowflakeConnection:
125
105
  return self._sql_executor._conn # noqa
126
106
 
107
+ @property
108
+ def snow_api_root(self):
109
+ return get_cli_context().snow_api_root
110
+
127
111
  @property
128
112
  def model(self):
129
113
  return self._entity_model
130
114
 
115
+ def dependent_entities(self, action_ctx: ActionContext):
116
+ return self.dependency_resolver.depends_on(action_ctx)
117
+
131
118
  def get_usage_grant_sql(self, app_role: str) -> str:
132
119
  return f"GRANT USAGE ON {self.model.type.upper()} {self.identifier} TO ROLE {app_role};"
133
120
 
@@ -136,8 +123,3 @@ class EntityBase(Generic[T]):
136
123
 
137
124
  def get_drop_sql(self) -> str:
138
125
  return f"DROP {self.model.type.upper()} {self.identifier};"
139
-
140
-
141
- def get_sql_executor() -> SqlExecutor:
142
- """Returns an SQL Executor that uses the connection from the current CLI context"""
143
- return SqlExecutor()
@@ -0,0 +1,160 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any, Dict, List, Tuple
3
+
4
+ from snowflake.cli._plugins.workspace.context import ActionContext
5
+ from snowflake.cli.api.entities.utils import EntityActions
6
+ from snowflake.cli.api.exceptions import CycleDetectedError
7
+ from snowflake.cli.api.project.schemas.entities.common import EntityModelBase
8
+ from snowflake.cli.api.utils.graph import Graph, Node
9
+
10
+
11
+ @dataclass
12
+ class Dependency:
13
+ entity_id: str
14
+ call_arguments: Dict[str, Any]
15
+
16
+ def __eq__(self, other):
17
+ return self.entity_id == other.entity_id
18
+
19
+ def __hash__(self):
20
+ return hash(self.entity_id)
21
+
22
+
23
+ class DependencyResolver:
24
+ """
25
+ Base class for resolving dependencies logic.
26
+ Any logic for resolving dependencies, calling their actions or validating them should be implemented here.
27
+ If an entity uses it's specific logic, it should implement its own resolver, inheriting from this one
28
+ """
29
+
30
+ def __init__(self, model: EntityModelBase):
31
+ self.entity_model = model
32
+ self.dependencies: List[Dependency] = []
33
+
34
+ def depends_on(self, action_ctx: ActionContext) -> List[Dependency]:
35
+ """
36
+ Returns a list of entities that this entity depends on.
37
+ The list is sorted in order they should be called- last one depends on all the previous.
38
+ """
39
+ if not self.dependencies:
40
+ graph = self._create_dependency_graph(action_ctx)
41
+ self.dependencies = self._check_and_sort_dependencies(graph)
42
+
43
+ return self.dependencies
44
+
45
+ def perform_for_dep(
46
+ self, action: EntityActions, action_ctx: ActionContext, *args, **kwargs
47
+ ):
48
+ """
49
+ Method used to perform selected
50
+ """
51
+ for dependency in self.depends_on(action_ctx):
52
+ entity = action_ctx.get_entity(dependency.entity_id)
53
+ if entity.supports(action):
54
+ arguments = dependency.call_arguments.get(action.get_action_name, {})
55
+ getattr(entity, action)(action_ctx, **arguments)
56
+
57
+ def _create_dependency_graph(self, action_ctx: ActionContext) -> Graph[Dependency]:
58
+ """
59
+ Creates a graph for dependencies. We need the graph, instead of a simple list, because we need to check if
60
+ calling dependencies actions in selected order is possible.
61
+ """
62
+ graph = Graph()
63
+ depends_on = self.entity_model.meta.depends_on if self.entity_model.meta else [] # type: ignore
64
+ self_dependency = Dependency(entity_id=self.entity_model.entity_id, call_arguments={}) # type: ignore
65
+ resolved_nodes = set()
66
+
67
+ graph.add(Node(key=self_dependency.entity_id, data=self_dependency))
68
+
69
+ def _resolve_dependencies(parent_id: str, dependency_id: str) -> None:
70
+
71
+ (
72
+ child_dependencies,
73
+ call_arguments,
74
+ ) = self._get_child_dependencies_and_call_arguments(
75
+ dependency_id=dependency_id, action_ctx=action_ctx
76
+ )
77
+
78
+ if not graph.contains_node(dependency_id):
79
+ dependency_node = Node(
80
+ key=dependency_id,
81
+ data=Dependency(
82
+ entity_id=dependency_id, call_arguments=call_arguments
83
+ ),
84
+ )
85
+ graph.add(dependency_node)
86
+
87
+ graph.add_directed_edge(parent_id, dependency_id)
88
+
89
+ resolved_nodes.add(dependency_node.key)
90
+
91
+ for child_dependency in child_dependencies:
92
+ if child_dependency not in resolved_nodes:
93
+ _resolve_dependencies(dependency_node.key, child_dependency)
94
+ else:
95
+ graph.add_directed_edge(dependency_node.key, child_dependency)
96
+
97
+ for dependency in depends_on:
98
+ _resolve_dependencies(self_dependency.entity_id, dependency)
99
+
100
+ return graph
101
+
102
+ @staticmethod
103
+ def _check_and_sort_dependencies(
104
+ graph: Graph[Dependency],
105
+ ) -> List[Dependency]:
106
+ """
107
+ This function is used to check and organize the dependency list.
108
+ The check has two stages:
109
+ * Cycle detection in dependency
110
+ * Clearing duplicate
111
+
112
+ In the first stage, if cycle is detected, it raises CycleDetectedError with node causing it specified.
113
+ The result list, shows entities this one depends on, in order they should be called.
114
+ Duplicates are removed in a way, that preserves earliest possible call.
115
+ Last item is removed from the result list, as it is this entity itself.
116
+ """
117
+ result = []
118
+
119
+ def _on_cycle(node: Node[Dependency]) -> None:
120
+ raise CycleDetectedError(
121
+ f"Cycle detected in entity dependencies: {node.key}"
122
+ )
123
+
124
+ def _on_visit(node: Node[Dependency]) -> None:
125
+ result.append(node.data)
126
+
127
+ graph.dfs(on_cycle_action=_on_cycle, visit_action=_on_visit)
128
+
129
+ return clear_duplicates_from_list(result)[:-1]
130
+
131
+ @staticmethod
132
+ def _get_child_dependencies_and_call_arguments(
133
+ dependency_id: str, action_ctx: ActionContext
134
+ ) -> Tuple[List[str], Dict[str, Any]]:
135
+ child_dependency = action_ctx.get_entity(dependency_id)
136
+
137
+ if not child_dependency:
138
+ raise ValueError(f"Entity with id {dependency_id} not found in project")
139
+
140
+ if child_dependency.model.meta:
141
+ return (
142
+ child_dependency.model.meta.depends_on,
143
+ child_dependency.model.meta.action_arguments,
144
+ )
145
+
146
+ else:
147
+ return [], {}
148
+
149
+ @staticmethod
150
+ def get_action_name(action: EntityActions) -> str:
151
+
152
+ return action.value.split("_")[1]
153
+
154
+
155
+ def clear_duplicates_from_list(input_list: list[Any]) -> list[Any]:
156
+ """
157
+ Removes duplicates from the input list, preserving the first occurrence.
158
+ """
159
+ seen = set()
160
+ return [x for x in input_list if not (x in seen or seen.add(x))] # type: ignore
@@ -1,13 +1,10 @@
1
1
  import os
2
+ from enum import Enum
2
3
  from pathlib import Path
3
4
  from typing import Any, List, NoReturn, Optional
4
5
 
5
6
  import jinja2
6
7
  from click import ClickException
7
- from snowflake.cli._plugins.nativeapp.artifacts import (
8
- BundleMap,
9
- resolve_without_follow,
10
- )
11
8
  from snowflake.cli._plugins.nativeapp.exceptions import (
12
9
  InvalidTemplateInFileError,
13
10
  MissingScriptError,
@@ -22,10 +19,11 @@ from snowflake.cli._plugins.stage.diff import (
22
19
  sync_local_diff_with_stage,
23
20
  to_stage_path,
24
21
  )
22
+ from snowflake.cli._plugins.stage.manager import DefaultStagePathParts
25
23
  from snowflake.cli._plugins.stage.utils import print_diff_to_console
24
+ from snowflake.cli.api.artifacts.bundle_map import BundleMap
26
25
  from snowflake.cli.api.cli_global_context import get_cli_context, span
27
26
  from snowflake.cli.api.console.abc import AbstractConsole
28
- from snowflake.cli.api.entities.common import get_sql_executor
29
27
  from snowflake.cli.api.errno import (
30
28
  DOES_NOT_EXIST_OR_CANNOT_BE_PERFORMED,
31
29
  NO_WAREHOUSE_SELECTED_IN_SESSION,
@@ -41,6 +39,8 @@ from snowflake.cli.api.rendering.sql_templates import (
41
39
  choose_sql_jinja_env_based_on_template_syntax,
42
40
  )
43
41
  from snowflake.cli.api.secure_path import UNLIMITED, SecurePath
42
+ from snowflake.cli.api.sql_execution import SqlExecutor
43
+ from snowflake.cli.api.utils.path_utils import resolve_without_follow
44
44
  from snowflake.connector import ProgrammingError
45
45
 
46
46
 
@@ -80,12 +80,11 @@ def sync_deploy_root_with_stage(
80
80
  console: AbstractConsole,
81
81
  deploy_root: Path,
82
82
  package_name: str,
83
- stage_schema: str,
84
83
  bundle_map: BundleMap,
85
84
  role: str,
86
85
  prune: bool,
87
86
  recursive: bool,
88
- stage_fqn: str,
87
+ stage_path: DefaultStagePathParts,
89
88
  local_paths_to_sync: List[Path] | None = None,
90
89
  print_diff: bool = True,
91
90
  ) -> DiffResult:
@@ -98,32 +97,37 @@ def sync_deploy_root_with_stage(
98
97
  role (str): The name of the role to use for queries and commands.
99
98
  prune (bool): Whether to prune artifacts from the stage that don't exist locally.
100
99
  recursive (bool): Whether to traverse directories recursively.
101
- stage_fqn (str): The name of the stage to diff against and upload to.
100
+ stage_path (DefaultStagePathParts): stage path object.
101
+
102
102
  local_paths_to_sync (List[Path], optional): List of local paths to sync. Defaults to None to sync all
103
- local paths. Note that providing an empty list here is equivalent to None.
103
+ local paths. Note that providing an empty list here is equivalent to None.
104
104
  print_diff (bool): Whether to print the diff between the local files and the remote stage. Defaults to True
105
105
 
106
106
  Returns:
107
107
  A `DiffResult` instance describing the changes that were performed.
108
108
  """
109
-
110
109
  sql_facade = get_snowflake_facade()
110
+ schema = stage_path.schema
111
+ stage_fqn = stage_path.stage
111
112
  # Does a stage already exist within the application package, or we need to create one?
112
113
  # Using "if not exists" should take care of either case.
113
114
  console.step(
114
115
  f"Checking if stage {stage_fqn} exists, or creating a new one if none exists."
115
116
  )
116
117
  if not sql_facade.stage_exists(stage_fqn):
117
- sql_facade.create_schema(stage_schema, database=package_name)
118
+ sql_facade.create_schema(schema, database=package_name)
118
119
  sql_facade.create_stage(stage_fqn)
119
120
 
120
121
  # Perform a diff operation and display results to the user for informational purposes
121
122
  if print_diff:
122
123
  console.step(
123
- "Performing a diff between the Snowflake stage and your local deploy_root ('%s') directory."
124
- % deploy_root.resolve()
124
+ f"Performing a diff between the Snowflake stage: {stage_path.path} and your local deploy_root: {deploy_root.resolve()}."
125
125
  )
126
- diff: DiffResult = compute_stage_diff(deploy_root, stage_fqn)
126
+
127
+ diff: DiffResult = compute_stage_diff(
128
+ local_root=deploy_root,
129
+ stage_path=stage_path,
130
+ )
127
131
 
128
132
  if local_paths_to_sync:
129
133
  # Deploying specific files/directories
@@ -184,7 +188,7 @@ def sync_deploy_root_with_stage(
184
188
  role=role,
185
189
  deploy_root_path=deploy_root,
186
190
  diff_result=diff,
187
- stage_fqn=stage_fqn,
191
+ stage_full_path=stage_path.full_path,
188
192
  )
189
193
  return diff
190
194
 
@@ -336,3 +340,40 @@ def print_messages(console: AbstractConsole, cursor_results: list[tuple[str]]):
336
340
  for message in messages:
337
341
  console.warning(message)
338
342
  console.message("")
343
+
344
+
345
+ def get_sql_executor() -> SqlExecutor:
346
+ """Returns an SQL Executor that uses the connection from the current CLI context"""
347
+ return SqlExecutor()
348
+
349
+
350
+ class EntityActions(str, Enum):
351
+ BUNDLE = "action_bundle"
352
+ DEPLOY = "action_deploy"
353
+ DROP = "action_drop"
354
+ VALIDATE = "action_validate"
355
+ EVENTS = "action_events"
356
+ DIFF = "action_diff"
357
+
358
+ VERSION_LIST = "action_version_list"
359
+ VERSION_CREATE = "action_version_create"
360
+ VERSION_DROP = "action_version_drop"
361
+
362
+ RELEASE_DIRECTIVE_UNSET = "action_release_directive_unset"
363
+ RELEASE_DIRECTIVE_SET = "action_release_directive_set"
364
+ RELEASE_DIRECTIVE_LIST = "action_release_directive_list"
365
+ RELEASE_DIRECTIVE_ADD_ACCOUNTS = "action_release_directive_add_accounts"
366
+ RELEASE_DIRECTIVE_REMOVE_ACCOUNTS = "action_release_directive_remove_accounts"
367
+
368
+ RELEASE_CHANNEL_LIST = "action_release_channel_list"
369
+ RELEASE_CHANNEL_ADD_ACCOUNTS = "action_release_channel_add_accounts"
370
+ RELEASE_CHANNEL_REMOVE_ACCOUNTS = "action_release_channel_remove_accounts"
371
+ RELEASE_CHANNEL_ADD_VERSION = "action_release_channel_add_version"
372
+ RELEASE_CHANNEL_REMOVE_VERSION = "action_release_channel_remove_version"
373
+ RELEASE_CHANNEL_SET_ACCOUNTS = "action_release_channel_set_accounts"
374
+
375
+ PUBLISH = "action_publish"
376
+
377
+ @property
378
+ def get_action_name(self):
379
+ return self.value.replace("action_", "")
@@ -54,6 +54,7 @@ APPLICATION_PACKAGE_MANIFEST_CONTAINER_IMAGE_URL_BAD_VALUE = 93148
54
54
  CANNOT_GRANT_NON_MANIFEST_PRIVILEGE = 93118
55
55
  APPLICATION_OWNS_EXTERNAL_OBJECTS = 93128
56
56
  APPLICATION_PACKAGE_PATCH_ALREADY_EXISTS = 93168
57
+ CANNOT_ADD_PATCH_WITH_NON_INCREASING_PATCH_NUMBER = 93167
57
58
  APPLICATION_PACKAGE_CANNOT_SET_EXTERNAL_DISTRIBUTION_WITH_SPCS = 93197
58
59
  NATIVE_APPLICATION_MANIFEST_UNRECOGNIZED_FIELD = 93301
59
60
  NATIVE_APPLICATION_MANIFEST_UNEXPECTED_VALUE_FOR_PROPERTY = 93302
@@ -64,8 +65,10 @@ CANNOT_DISABLE_MANDATORY_TELEMETRY = 93329
64
65
  VERSION_NOT_ADDED_TO_RELEASE_CHANNEL = 512008
65
66
  CANNOT_DISABLE_RELEASE_CHANNELS = 512001
66
67
  RELEASE_DIRECTIVES_VERSION_PATCH_NOT_FOUND = 93036
68
+ RELEASE_DIRECTIVE_UNAPPROVED_VERSION_OR_PATCH = 93074
67
69
  RELEASE_DIRECTIVE_DOES_NOT_EXIST = 93090
68
70
  VERSION_DOES_NOT_EXIST = 93031
71
+ CANNOT_CREATE_VERSION_WITH_NON_ZERO_PATCH = 93170
69
72
  VERSION_NOT_IN_RELEASE_CHANNEL = 512010
70
73
  ACCOUNT_DOES_NOT_EXIST = 1999
71
74
  ACCOUNT_HAS_TOO_MANY_QUALIFIERS = 906
@@ -66,5 +66,4 @@ class FeatureFlag(FeatureFlagMixin):
66
66
  ENABLE_SEPARATE_AUTHENTICATION_POLICY_ID = BooleanFlag(
67
67
  "ENABLE_SEPARATE_AUTHENTICATION_POLICY_ID", False
68
68
  )
69
- ENABLE_SPCS_SERVICE_EVENTS = BooleanFlag("ENABLE_SPCS_SERVICE_EVENTS", False)
70
- ENABLE_SPCS_SERVICE_METRICS = BooleanFlag("ENABLE_SPCS_SERVICE_METRICS", False)
69
+ ENABLE_SNOWPARK_GLOB_SUPPORT = BooleanFlag("ENABLE_SNOWPARK_GLOB_SUPPORT", False)
@@ -222,10 +222,11 @@ def convert_streamlit_to_v2_data(streamlit: Streamlit) -> Dict[str, Any]:
222
222
  environment_file,
223
223
  pages_dir,
224
224
  ]
225
- artifacts = [a for a in artifacts if a is not None]
225
+ artifacts = [str(a) for a in artifacts if a is not None]
226
226
 
227
227
  if streamlit.additional_source_files:
228
- artifacts.extend(streamlit.additional_source_files)
228
+ for additional_file in streamlit.additional_source_files:
229
+ artifacts.append(str(additional_file))
229
230
 
230
231
  identifier = {"name": streamlit.name}
231
232
  if streamlit.schema_name:
@@ -0,0 +1,28 @@
1
+ from dataclasses import dataclass
2
+ from pathlib import Path
3
+
4
+ from snowflake.cli.api.secure_path import SecurePath
5
+
6
+
7
+ @dataclass
8
+ class ProjectPaths:
9
+ """
10
+ This class allows you to manage files paths related to given project.
11
+ Class provides bundle root path and allows to remove it.
12
+ """
13
+
14
+ project_root: Path
15
+
16
+ @property
17
+ def bundle_root(self) -> Path:
18
+ return bundle_root(self.project_root)
19
+
20
+ def remove_up_bundle_root(self) -> None:
21
+ if self.bundle_root.exists():
22
+ SecurePath(self.bundle_root).rmdir(recursive=True)
23
+
24
+
25
+ def bundle_root(root: Path, app_type: str | None = None) -> Path:
26
+ if app_type:
27
+ return root / "output" / "bundle" / app_type
28
+ return root / "output" / "bundle"