snowflake-cli 3.5.0__py3-none-any.whl → 3.7.0__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 (64) hide show
  1. snowflake/cli/__about__.py +13 -1
  2. snowflake/cli/_app/commands_registration/builtin_plugins.py +4 -0
  3. snowflake/cli/_app/loggers.py +2 -2
  4. snowflake/cli/_app/snow_connector.py +7 -6
  5. snowflake/cli/_app/telemetry.py +3 -15
  6. snowflake/cli/_app/version_check.py +4 -4
  7. snowflake/cli/_plugins/auth/__init__.py +11 -0
  8. snowflake/cli/_plugins/auth/keypair/__init__.py +0 -0
  9. snowflake/cli/_plugins/auth/keypair/commands.py +151 -0
  10. snowflake/cli/_plugins/auth/keypair/manager.py +331 -0
  11. snowflake/cli/_plugins/auth/keypair/plugin_spec.py +30 -0
  12. snowflake/cli/_plugins/connection/commands.py +78 -1
  13. snowflake/cli/_plugins/helpers/commands.py +25 -1
  14. snowflake/cli/_plugins/helpers/snowsl_vars_reader.py +133 -0
  15. snowflake/cli/_plugins/init/commands.py +9 -6
  16. snowflake/cli/_plugins/logs/__init__.py +0 -0
  17. snowflake/cli/_plugins/logs/commands.py +105 -0
  18. snowflake/cli/_plugins/logs/manager.py +107 -0
  19. snowflake/cli/_plugins/logs/plugin_spec.py +16 -0
  20. snowflake/cli/_plugins/logs/utils.py +60 -0
  21. snowflake/cli/_plugins/nativeapp/entities/application.py +4 -1
  22. snowflake/cli/_plugins/nativeapp/sf_sql_facade.py +33 -6
  23. snowflake/cli/_plugins/notebook/commands.py +3 -0
  24. snowflake/cli/_plugins/notebook/notebook_entity.py +16 -27
  25. snowflake/cli/_plugins/object/command_aliases.py +3 -1
  26. snowflake/cli/_plugins/object/manager.py +4 -2
  27. snowflake/cli/_plugins/project/commands.py +89 -48
  28. snowflake/cli/_plugins/project/manager.py +57 -23
  29. snowflake/cli/_plugins/project/project_entity_model.py +22 -3
  30. snowflake/cli/_plugins/snowpark/commands.py +15 -2
  31. snowflake/cli/_plugins/spcs/compute_pool/commands.py +17 -5
  32. snowflake/cli/_plugins/sql/manager.py +43 -52
  33. snowflake/cli/_plugins/sql/source_reader.py +230 -0
  34. snowflake/cli/_plugins/stage/manager.py +25 -12
  35. snowflake/cli/_plugins/streamlit/commands.py +3 -0
  36. snowflake/cli/_plugins/streamlit/manager.py +19 -15
  37. snowflake/cli/api/artifacts/upload.py +30 -34
  38. snowflake/cli/api/artifacts/utils.py +8 -6
  39. snowflake/cli/api/cli_global_context.py +7 -2
  40. snowflake/cli/api/commands/decorators.py +11 -2
  41. snowflake/cli/api/commands/flags.py +35 -4
  42. snowflake/cli/api/commands/snow_typer.py +20 -2
  43. snowflake/cli/api/config.py +5 -3
  44. snowflake/cli/api/constants.py +2 -0
  45. snowflake/cli/api/entities/utils.py +29 -16
  46. snowflake/cli/api/errno.py +1 -0
  47. snowflake/cli/api/exceptions.py +75 -27
  48. snowflake/cli/api/feature_flags.py +1 -0
  49. snowflake/cli/api/identifiers.py +2 -0
  50. snowflake/cli/api/plugins/plugin_config.py +2 -2
  51. snowflake/cli/api/project/schemas/template.py +3 -3
  52. snowflake/cli/api/rendering/project_templates.py +3 -3
  53. snowflake/cli/api/rendering/sql_templates.py +2 -2
  54. snowflake/cli/api/rest_api.py +2 -3
  55. snowflake/cli/{_app → api}/secret.py +4 -1
  56. snowflake/cli/api/secure_path.py +16 -4
  57. snowflake/cli/api/sql_execution.py +8 -4
  58. snowflake/cli/api/utils/definition_rendering.py +14 -8
  59. snowflake/cli/api/utils/templating_functions.py +4 -4
  60. {snowflake_cli-3.5.0.dist-info → snowflake_cli-3.7.0.dist-info}/METADATA +11 -11
  61. {snowflake_cli-3.5.0.dist-info → snowflake_cli-3.7.0.dist-info}/RECORD +64 -52
  62. {snowflake_cli-3.5.0.dist-info → snowflake_cli-3.7.0.dist-info}/WHEEL +0 -0
  63. {snowflake_cli-3.5.0.dist-info → snowflake_cli-3.7.0.dist-info}/entry_points.txt +0 -0
  64. {snowflake_cli-3.5.0.dist-info → snowflake_cli-3.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -15,26 +15,31 @@
15
15
  from typing import List, Optional
16
16
 
17
17
  import typer
18
+ from click import ClickException
19
+ from snowflake.cli._plugins.object.command_aliases import add_object_command_aliases
20
+ from snowflake.cli._plugins.object.commands import scope_option
21
+ from snowflake.cli._plugins.object.manager import ObjectManager
18
22
  from snowflake.cli._plugins.project.feature_flags import FeatureFlag
19
23
  from snowflake.cli._plugins.project.manager import ProjectManager
20
24
  from snowflake.cli._plugins.project.project_entity_model import (
21
25
  ProjectEntityModel,
22
26
  )
23
- from snowflake.cli._plugins.stage.manager import StageManager
24
- from snowflake.cli.api.artifacts.upload import put_files
25
27
  from snowflake.cli.api.cli_global_context import get_cli_context
26
28
  from snowflake.cli.api.commands.decorators import with_project_definition
27
29
  from snowflake.cli.api.commands.flags import (
30
+ OverrideableOption,
31
+ PruneOption,
28
32
  entity_argument,
29
33
  identifier_argument,
34
+ like_option,
30
35
  variables_option,
31
36
  )
32
37
  from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
33
38
  from snowflake.cli.api.commands.utils import get_entity_for_operation
34
39
  from snowflake.cli.api.console.console import cli_console
40
+ from snowflake.cli.api.constants import ObjectType
35
41
  from snowflake.cli.api.identifiers import FQN
36
- from snowflake.cli.api.output.types import MessageResult, SingleQueryResult
37
- from snowflake.cli.api.project.project_paths import ProjectPaths
42
+ from snowflake.cli.api.output.types import MessageResult, QueryResult, SingleQueryResult
38
43
 
39
44
  app = SnowTyperFactory(
40
45
  name="project",
@@ -44,17 +49,38 @@ app = SnowTyperFactory(
44
49
 
45
50
  project_identifier = identifier_argument(sf_object="project", example="MY_PROJECT")
46
51
  version_flag = typer.Option(
47
- ..., "--version", help="Version of the project to use.", show_default=False
52
+ None,
53
+ "--version",
54
+ help="Version of the project to use. If not specified default version is used",
55
+ show_default=False,
48
56
  )
49
57
  variables_flag = variables_option(
50
58
  'Variables for the execution context; for example: `-D "<key>=<value>"`.'
51
59
  )
60
+ from_option = OverrideableOption(
61
+ None,
62
+ "--from",
63
+ help="Create a new version using given stage instead of uploading local files.",
64
+ show_default=False,
65
+ )
66
+
67
+
68
+ add_object_command_aliases(
69
+ app=app,
70
+ object_type=ObjectType.PROJECT,
71
+ name_argument=project_identifier,
72
+ like_option=like_option(
73
+ help_example='`list --like "my%"` lists all projects that begin with “my”'
74
+ ),
75
+ scope_option=scope_option(help_example="`list --in database my_db`"),
76
+ ommit_commands=["drop", "create", "describe"],
77
+ )
52
78
 
53
79
 
54
80
  @app.command(requires_connection=True)
55
81
  def execute(
56
82
  identifier: FQN = project_identifier,
57
- version: str = version_flag,
83
+ version: Optional[str] = version_flag,
58
84
  variables: Optional[List[str]] = variables_flag,
59
85
  **options,
60
86
  ):
@@ -70,7 +96,7 @@ def execute(
70
96
  @app.command(requires_connection=True)
71
97
  def dry_run(
72
98
  identifier: FQN = project_identifier,
73
- version: str = version_flag,
99
+ version: Optional[str] = version_flag,
74
100
  variables: Optional[List[str]] = variables_flag,
75
101
  **options,
76
102
  ):
@@ -85,12 +111,18 @@ def dry_run(
85
111
 
86
112
  @app.command(requires_connection=True)
87
113
  @with_project_definition()
88
- def create_version(
114
+ def create(
89
115
  entity_id: str = entity_argument("project"),
116
+ no_version: bool = typer.Option(
117
+ False,
118
+ "--no-version",
119
+ help="Do not initialize project with a new version, only create the snowflake object.",
120
+ ),
90
121
  **options,
91
122
  ):
92
123
  """
93
- Upload local files and create a new version of a project using those files. If the stage does not exist, it will be created.
124
+ Creates a project in Snowflake.
125
+ By default, the project is initialized with a new version created from local files.
94
126
  """
95
127
  cli_context = get_cli_context()
96
128
  project: ProjectEntityModel = get_entity_for_operation(
@@ -99,59 +131,68 @@ def create_version(
99
131
  project_definition=cli_context.project_definition,
100
132
  entity_type="project",
101
133
  )
134
+ om = ObjectManager()
135
+ if om.object_exists(object_type="project", fqn=project.fqn):
136
+ raise ClickException(f"Project '{project.fqn}' already exists.")
137
+ if not no_version and om.object_exists(
138
+ object_type="stage", fqn=FQN.from_stage(project.stage)
139
+ ):
140
+ raise ClickException(f"Stage '{project.stage}' already exists.")
102
141
 
103
- # Sync state
104
- with cli_console.phase("Syncing project state"):
105
- stage_name = FQN.from_stage(project.stage)
106
- sm = StageManager()
107
-
108
- cli_console.step(f"Creating stage {stage_name}")
109
- sm.create(fqn=stage_name)
110
-
111
- put_files(
112
- project_paths=ProjectPaths(project_root=cli_context.project_root),
113
- stage_root=project.stage,
114
- artifacts=project.artifacts,
115
- )
116
-
117
- # Create project and version
118
- with cli_console.phase("Creating project and version"):
119
- pm = ProjectManager()
120
- cli_console.step(f"Creating project {project.fqn}")
121
- pm.create(project_name=project.fqn)
142
+ pm = ProjectManager()
143
+ with cli_console.phase(f"Creating project '{project.fqn}'"):
144
+ pm.create(project=project, initialize_version_from_local_files=not no_version)
122
145
 
123
- cli_console.step(f"Creating version from stage {stage_name}")
124
- pm.create_version(project_name=project.fqn, stage_name=stage_name)
125
- return MessageResult(f"Project {project.fqn} deployed.")
146
+ if no_version:
147
+ return MessageResult(f"Project '{project.fqn}' successfully created.")
148
+ return MessageResult(
149
+ f"Project '{project.fqn}' successfully created and initial version is added."
150
+ )
126
151
 
127
152
 
128
153
  @app.command(requires_connection=True)
129
154
  @with_project_definition()
130
155
  def add_version(
131
156
  entity_id: str = entity_argument("project"),
132
- _from: str = typer.Option(
133
- ...,
134
- "--from",
135
- help="Source stage to create the version from.",
136
- show_default=False,
137
- ),
138
- alias: str
139
- | None = typer.Option(
157
+ _from: Optional[str] = from_option(mutually_exclusive=["prune"]),
158
+ _alias: Optional[str] = typer.Option(
140
159
  None, "--alias", help="Alias for the version.", show_default=False
141
160
  ),
142
- comment: str
143
- | None = typer.Option(
161
+ comment: Optional[str] = typer.Option(
144
162
  None, "--comment", help="Version comment.", show_default=False
145
163
  ),
164
+ prune: bool = PruneOption(mutually_exclusive=["_from"]),
146
165
  **options,
147
166
  ):
148
- """Adds a new version to a project using existing sources from provided stage path."""
149
-
150
- pm = ProjectManager()
151
- pm.add_version(
152
- project_name=entity_id,
167
+ """Uploads local files to Snowflake and cerates a new project version."""
168
+ cli_context = get_cli_context()
169
+ project: ProjectEntityModel = get_entity_for_operation(
170
+ cli_context=cli_context,
171
+ entity_id=entity_id,
172
+ project_definition=cli_context.project_definition,
173
+ entity_type="project",
174
+ )
175
+ ProjectManager().add_version(
176
+ project=project,
177
+ prune=prune,
153
178
  from_stage=_from,
154
- alias=alias,
179
+ alias=_alias,
155
180
  comment=comment,
156
181
  )
157
- return MessageResult("Version added.")
182
+ alias_str = "" if _alias is None else f"'{_alias}' "
183
+ return MessageResult(
184
+ f"New project version {alias_str}added to project '{project.fqn}'"
185
+ )
186
+
187
+
188
+ @app.command(requires_connection=True)
189
+ def list_versions(
190
+ identifier: FQN = project_identifier,
191
+ **options,
192
+ ):
193
+ """
194
+ Lists versions of given project.
195
+ """
196
+ pm = ProjectManager()
197
+ results = pm.list_versions(project_name=identifier)
198
+ return QueryResult(results)
@@ -12,13 +12,19 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  from textwrap import dedent
15
- from typing import List
15
+ from typing import List, Optional
16
16
 
17
+ from snowflake.cli._plugins.project.project_entity_model import ProjectEntityModel
17
18
  from snowflake.cli._plugins.stage.manager import StageManager
19
+ from snowflake.cli.api.artifacts.upload import sync_artifacts_with_stage
20
+ from snowflake.cli.api.cli_global_context import get_cli_context
18
21
  from snowflake.cli.api.commands.utils import parse_key_value_variables
22
+ from snowflake.cli.api.console.console import cli_console
19
23
  from snowflake.cli.api.identifiers import FQN
24
+ from snowflake.cli.api.project.project_paths import ProjectPaths
20
25
  from snowflake.cli.api.sql_execution import SqlExecutionMixin
21
26
  from snowflake.cli.api.stage_path import StagePath
27
+ from snowflake.connector.cursor import SnowflakeCursor
22
28
 
23
29
 
24
30
  class ProjectManager(SqlExecutionMixin):
@@ -40,37 +46,65 @@ class ProjectManager(SqlExecutionMixin):
40
46
  query += " DRY_RUN=TRUE"
41
47
  return self.execute_query(query=query)
42
48
 
43
- def create(
44
- self,
45
- project_name: FQN,
46
- ) -> str:
47
- queries = dedent(f"CREATE PROJECT IF NOT EXISTS {project_name.sql_identifier}")
48
- return self.execute_query(query=queries)
49
+ def _create_object(self, project_name: FQN) -> SnowflakeCursor:
50
+ query = dedent(f"CREATE PROJECT {project_name.sql_identifier}")
51
+ return self.execute_query(query)
49
52
 
50
- def create_version(self, project_name: FQN, stage_name: FQN):
51
- return self.add_version(
52
- project_name=project_name,
53
- from_stage=StagePath.from_stage_str(stage_name).absolute_path(
54
- at_prefix=True
55
- ),
56
- )
53
+ def create(
54
+ self, project: ProjectEntityModel, initialize_version_from_local_files: bool
55
+ ) -> None:
56
+ self._create_object(project.fqn)
57
+ if initialize_version_from_local_files:
58
+ self.add_version(project=project)
57
59
 
58
- def add_version(
60
+ def _create_version(
59
61
  self,
60
- project_name: str | FQN,
62
+ project_name: FQN,
61
63
  from_stage: str,
62
64
  alias: str | None = None,
63
65
  comment: str | None = None,
64
66
  ):
65
- project_name = (
66
- project_name
67
- if isinstance(project_name, FQN)
68
- else FQN.from_string(project_name)
69
- )
67
+ stage_path = StagePath.from_stage_str(from_stage)
70
68
  query = f"ALTER PROJECT {project_name.identifier} ADD VERSION"
71
69
  if alias:
72
- query += f" IF NOT EXIST {alias}"
73
- query += f" FROM {from_stage}"
70
+ query += f' IF NOT EXISTS "{alias}"'
71
+ query += f" FROM {stage_path.absolute_path(at_prefix=True)}"
74
72
  if comment:
75
73
  query += f" COMMENT = '{comment}'"
76
74
  return self.execute_query(query=query)
75
+
76
+ def add_version(
77
+ self,
78
+ project: ProjectEntityModel,
79
+ prune: bool = False,
80
+ from_stage: Optional[str] = None,
81
+ alias: Optional[str] = None,
82
+ comment: Optional[str] = None,
83
+ ):
84
+ """
85
+ Adds a version to project. If [from_stage] is not defined,
86
+ uploads local files to the stage defined in project definition.
87
+ """
88
+
89
+ if not from_stage:
90
+ cli_context = get_cli_context()
91
+ from_stage = project.stage
92
+ with cli_console.phase("Uploading artifacts"):
93
+ sync_artifacts_with_stage(
94
+ project_paths=ProjectPaths(project_root=cli_context.project_root),
95
+ stage_root=from_stage,
96
+ artifacts=project.artifacts,
97
+ prune=prune,
98
+ )
99
+
100
+ with cli_console.phase(f"Creating project version from stage {from_stage}"):
101
+ return self._create_version(
102
+ project_name=project.fqn,
103
+ from_stage=from_stage, # type:ignore
104
+ alias=alias,
105
+ comment=comment,
106
+ )
107
+
108
+ def list_versions(self, project_name: FQN):
109
+ query = f"SHOW VERSIONS IN PROJECT {project_name.identifier}"
110
+ return self.execute_query(query=query)
@@ -13,26 +13,45 @@
13
13
  # limitations under the License.
14
14
  from __future__ import annotations
15
15
 
16
- from typing import Literal, Optional, TypeVar
16
+ from typing import List, Literal, Optional, TypeVar
17
17
 
18
- from pydantic import Field
18
+ from pydantic import Field, field_validator
19
+ from snowflake.cli.api.cli_global_context import get_cli_context
19
20
  from snowflake.cli.api.entities.common import EntityBase, attach_spans_to_entity_actions
21
+ from snowflake.cli.api.exceptions import CliError
20
22
  from snowflake.cli.api.project.schemas.entities.common import (
23
+ Artifacts,
21
24
  EntityModelBaseWithArtifacts,
25
+ PathMapping,
22
26
  )
23
27
  from snowflake.cli.api.project.schemas.updatable_model import (
24
28
  DiscriminatorField,
25
29
  )
30
+ from snowflake.cli.api.secure_path import SecurePath
26
31
 
27
32
  T = TypeVar("T")
28
33
 
29
34
 
35
+ MANIFEST_FILE_NAME = "manifest.yml"
36
+
37
+
30
38
  class ProjectEntityModel(EntityModelBaseWithArtifacts):
31
39
  type: Literal["project"] = DiscriminatorField() # noqa: A003
32
40
  stage: Optional[str] = Field(
33
41
  title="Stage in which the project artifacts will be stored", default=None
34
42
  )
35
- main_file: Optional[str] = Field(title="Path to the main file of the project")
43
+
44
+ @field_validator("artifacts")
45
+ @classmethod
46
+ def transform_artifacts(cls, orig_artifacts: Artifacts) -> List[PathMapping]:
47
+ if not (
48
+ SecurePath(get_cli_context().project_root) / MANIFEST_FILE_NAME
49
+ ).exists():
50
+ raise CliError(
51
+ f"{MANIFEST_FILE_NAME} was not found in project root directory"
52
+ )
53
+ orig_artifacts.append(PathMapping(src=MANIFEST_FILE_NAME))
54
+ return super().transform_artifacts(orig_artifacts)
36
55
 
37
56
 
38
57
  @attach_spans_to_entity_actions(entity_name="project")
@@ -71,6 +71,7 @@ from snowflake.cli.api.commands.decorators import (
71
71
  )
72
72
  from snowflake.cli.api.commands.flags import (
73
73
  ForceReplaceOption,
74
+ PruneOption,
74
75
  ReplaceOption,
75
76
  execution_identifier_argument,
76
77
  identifier_argument,
@@ -133,6 +134,9 @@ def deploy(
133
134
  "overwrites existing files, but does not remove any files already on the stage."
134
135
  ),
135
136
  force_replace: bool = ForceReplaceOption(),
137
+ prune: bool = PruneOption(
138
+ help="Remove contents of the stage before uploading artifacts."
139
+ ),
136
140
  **options,
137
141
  ) -> CommandResult:
138
142
  """
@@ -174,7 +178,7 @@ def deploy(
174
178
  snowpark_entities=snowpark_entities,
175
179
  )
176
180
 
177
- create_stages_and_upload_artifacts(stages_to_artifact_map)
181
+ create_stages_and_upload_artifacts(stages_to_artifact_map, prune=prune)
178
182
 
179
183
  # Create snowpark entities
180
184
  with cli_console.phase("Creating Snowpark entities"):
@@ -242,8 +246,17 @@ def build_artifacts_mappings(
242
246
  return entities_to_imports_map, stages_to_artifact_map
243
247
 
244
248
 
245
- def create_stages_and_upload_artifacts(stages_to_artifact_map: StageToArtifactMapping):
249
+ def create_stages_and_upload_artifacts(
250
+ stages_to_artifact_map: StageToArtifactMapping, prune: bool
251
+ ):
246
252
  stage_manager = StageManager()
253
+ if prune:
254
+ # snowflake.cli._plugins.snowpark.snowpark_project_paths.Artifact class assumes that "stage"
255
+ # is a stage object, not path on stage - whole stage is managed by snowpark - it can be removed
256
+ for stage in stages_to_artifact_map.keys():
257
+ cli_console.step(f"Removing contents of stage {stage}")
258
+ stage_manager.remove(stage, path="")
259
+
247
260
  for stage, artifacts in stages_to_artifact_map.items():
248
261
  cli_console.step(f"Creating (if not exists) stage: {stage}")
249
262
  stage = FQN.from_stage(stage).using_context()
@@ -88,9 +88,17 @@ MaxNodesOption = OverrideableOption(
88
88
  _AUTO_RESUME_HELP = "The compute pool will automatically resume when a service or job is submitted to it."
89
89
 
90
90
  AutoResumeOption = OverrideableOption(
91
- True,
92
- "--auto-resume/--no-auto-resume",
91
+ False,
92
+ "--auto-resume",
93
93
  help=_AUTO_RESUME_HELP,
94
+ mutually_exclusive=["no_auto_resume"],
95
+ )
96
+
97
+ NoAutoResumeOption = OverrideableOption(
98
+ False,
99
+ "--no-auto-resume",
100
+ help=_AUTO_RESUME_HELP,
101
+ mutually_exclusive=["auto_resume"],
94
102
  )
95
103
 
96
104
  _AUTO_SUSPEND_SECS_HELP = "Number of seconds of inactivity after which you want Snowflake to automatically suspend the compute pool."
@@ -126,6 +134,7 @@ def create(
126
134
  min_nodes: int = MinNodesOption(),
127
135
  max_nodes: Optional[int] = MaxNodesOption(),
128
136
  auto_resume: bool = AutoResumeOption(),
137
+ no_auto_resume: bool = NoAutoResumeOption(),
129
138
  initially_suspended: bool = typer.Option(
130
139
  False,
131
140
  "--init-suspend/--no-init-suspend",
@@ -140,13 +149,14 @@ def create(
140
149
  """
141
150
  Creates a new compute pool.
142
151
  """
152
+ resume_option = True if auto_resume else False if no_auto_resume else True
143
153
  max_nodes = validate_and_set_instances(min_nodes, max_nodes, "nodes")
144
154
  cursor = ComputePoolManager().create(
145
155
  pool_name=name.identifier,
146
156
  min_nodes=min_nodes,
147
157
  max_nodes=max_nodes,
148
158
  instance_family=instance_family,
149
- auto_resume=auto_resume,
159
+ auto_resume=resume_option,
150
160
  initially_suspended=initially_suspended,
151
161
  auto_suspend_secs=auto_suspend_secs,
152
162
  tags=tags,
@@ -223,7 +233,8 @@ def set_property(
223
233
  name: FQN = ComputePoolNameArgument,
224
234
  min_nodes: Optional[int] = MinNodesOption(default=None, show_default=False),
225
235
  max_nodes: Optional[int] = MaxNodesOption(show_default=False),
226
- auto_resume: Optional[bool] = AutoResumeOption(default=None, show_default=False),
236
+ auto_resume: bool = AutoResumeOption(default=None, show_default=False),
237
+ no_auto_resume: bool = NoAutoResumeOption(default=None, show_default=False),
227
238
  auto_suspend_secs: Optional[int] = AutoSuspendSecsOption(
228
239
  default=None, show_default=False
229
240
  ),
@@ -235,11 +246,12 @@ def set_property(
235
246
  """
236
247
  Sets one or more properties for the compute pool.
237
248
  """
249
+ resume_option = True if auto_resume else False if no_auto_resume else None
238
250
  cursor = ComputePoolManager().set_property(
239
251
  pool_name=name.identifier,
240
252
  min_nodes=min_nodes,
241
253
  max_nodes=max_nodes,
242
- auto_resume=auto_resume,
254
+ auto_resume=resume_option,
243
255
  auto_suspend_secs=auto_suspend_secs,
244
256
  comment=comment,
245
257
  )
@@ -14,23 +14,29 @@
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
+ import logging
17
18
  import sys
18
- from io import StringIO
19
- from itertools import chain
19
+ from functools import partial
20
20
  from pathlib import Path
21
21
  from typing import Dict, Iterable, List, Tuple
22
22
 
23
- from click import ClickException, UsageError
24
- from jinja2 import UndefinedError
25
23
  from snowflake.cli._plugins.sql.snowsql_templating import transpile_snowsql_templates
24
+ from snowflake.cli._plugins.sql.source_reader import (
25
+ compile_statements,
26
+ files_reader,
27
+ query_reader,
28
+ )
29
+ from snowflake.cli.api.console import cli_console
30
+ from snowflake.cli.api.exceptions import CliArgumentError, CliSqlError
26
31
  from snowflake.cli.api.rendering.sql_templates import snowflake_sql_jinja_render
27
- from snowflake.cli.api.secure_path import UNLIMITED, SecurePath
32
+ from snowflake.cli.api.secure_path import SecurePath
28
33
  from snowflake.cli.api.sql_execution import SqlExecutionMixin, VerboseCursor
29
34
  from snowflake.connector.cursor import SnowflakeCursor
30
- from snowflake.connector.util_text import split_statements
31
35
 
32
36
  IsSingleStatement = bool
33
37
 
38
+ logger = logging.getLogger(__name__)
39
+
34
40
 
35
41
  class SqlManager(SqlExecutionMixin):
36
42
  def execute(
@@ -41,57 +47,42 @@ class SqlManager(SqlExecutionMixin):
41
47
  data: Dict | None = None,
42
48
  retain_comments: bool = False,
43
49
  ) -> Tuple[IsSingleStatement, Iterable[SnowflakeCursor]]:
44
- inputs = [query, files, std_in]
45
- # Check if any two inputs were provided simultaneously
46
- if len([i for i in inputs if i]) > 1:
47
- raise UsageError(
48
- "Multiple input sources specified. Please specify only one."
49
- )
50
+ """Reads, transforms and execute statements from input.
50
51
 
51
- if std_in:
52
- query = sys.stdin.read()
53
- if query:
54
- return self._execute_single_query(
55
- query=query, data=data, retain_comments=retain_comments
56
- )
52
+ Only one input can be consumed at a time.
53
+ When no compilation errors are detected, the sequence on queries
54
+ in executed and returned as tuple.
57
55
 
58
- if files:
59
- # Multiple files
60
- results = []
61
- single_statement = False
62
- for file in files:
63
- query_from_file = SecurePath(file).read_text(
64
- file_size_limit_mb=UNLIMITED
65
- )
66
- single_statement, result = self._execute_single_query(
67
- query=query_from_file, data=data, retain_comments=retain_comments
68
- )
69
- results.append(result)
56
+ Throws an exception ff multiple inputs are provided.
57
+ """
58
+ query = sys.stdin.read() if std_in else query
70
59
 
71
- # Use single_statement if there's only one, otherwise this is multi statement result
72
- single_statement = len(files) == 1 and single_statement
73
- return single_statement, chain.from_iterable(results)
60
+ stmt_operators = (
61
+ transpile_snowsql_templates,
62
+ partial(snowflake_sql_jinja_render, data=data),
63
+ )
64
+ remove_comments = not retain_comments
74
65
 
75
- # At that point, no stdin, query or files were provided
76
- raise UsageError("Use either query, filename or input option.")
66
+ if query:
67
+ stmt_reader = query_reader(query, stmt_operators, remove_comments)
68
+ elif files:
69
+ secured_files = [SecurePath(f) for f in files]
70
+ stmt_reader = files_reader(secured_files, stmt_operators, remove_comments)
71
+ else:
72
+ raise CliArgumentError("Use either query, filename or input option.")
77
73
 
78
- def _execute_single_query(
79
- self, query: str, data: Dict | None = None, retain_comments: bool = False
80
- ) -> Tuple[IsSingleStatement, Iterable[SnowflakeCursor]]:
81
- try:
82
- query = transpile_snowsql_templates(query)
83
- query = snowflake_sql_jinja_render(content=query, data=data)
84
- except UndefinedError as err:
85
- raise ClickException(f"SQL template rendering error: {err}")
74
+ errors, stmt_count, compiled_statements = compile_statements(stmt_reader)
75
+ if not any((errors, stmt_count, compiled_statements)):
76
+ raise CliArgumentError("Use either query, filename or input option.")
86
77
 
87
- statements = tuple(
88
- statement
89
- for statement, _ in split_statements(
90
- StringIO(query), remove_comments=not retain_comments
91
- )
92
- )
93
- single_statement = len(statements) == 1
78
+ if errors:
79
+ for error in errors:
80
+ logger.info("Statement compilation error: %s", error)
81
+ cli_console.warning(error)
82
+ raise CliSqlError("SQL rendering error")
94
83
 
95
- return single_statement, self._execute_string(
96
- "\n".join(statements), cursor_class=VerboseCursor
84
+ is_single_statement = not (stmt_count > 1)
85
+ return is_single_statement, self.execute_string(
86
+ "\n".join(compiled_statements),
87
+ cursor_class=VerboseCursor,
97
88
  )