snowflake-cli 3.6.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 (45) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/_app/commands_registration/builtin_plugins.py +2 -0
  3. snowflake/cli/_app/loggers.py +2 -2
  4. snowflake/cli/_app/snow_connector.py +2 -2
  5. snowflake/cli/_plugins/connection/commands.py +2 -1
  6. snowflake/cli/_plugins/helpers/commands.py +25 -1
  7. snowflake/cli/_plugins/helpers/snowsl_vars_reader.py +133 -0
  8. snowflake/cli/_plugins/init/commands.py +9 -6
  9. snowflake/cli/_plugins/logs/__init__.py +0 -0
  10. snowflake/cli/_plugins/logs/commands.py +105 -0
  11. snowflake/cli/_plugins/logs/manager.py +107 -0
  12. snowflake/cli/_plugins/logs/plugin_spec.py +16 -0
  13. snowflake/cli/_plugins/logs/utils.py +60 -0
  14. snowflake/cli/_plugins/notebook/commands.py +3 -0
  15. snowflake/cli/_plugins/notebook/notebook_entity.py +16 -27
  16. snowflake/cli/_plugins/project/commands.py +73 -48
  17. snowflake/cli/_plugins/project/manager.py +57 -23
  18. snowflake/cli/_plugins/project/project_entity_model.py +22 -3
  19. snowflake/cli/_plugins/snowpark/commands.py +15 -2
  20. snowflake/cli/_plugins/sql/manager.py +4 -4
  21. snowflake/cli/_plugins/stage/manager.py +17 -10
  22. snowflake/cli/_plugins/streamlit/commands.py +3 -0
  23. snowflake/cli/_plugins/streamlit/manager.py +19 -15
  24. snowflake/cli/api/artifacts/upload.py +30 -34
  25. snowflake/cli/api/artifacts/utils.py +8 -6
  26. snowflake/cli/api/cli_global_context.py +7 -2
  27. snowflake/cli/api/commands/decorators.py +11 -2
  28. snowflake/cli/api/commands/flags.py +23 -2
  29. snowflake/cli/api/commands/snow_typer.py +20 -2
  30. snowflake/cli/api/config.py +5 -3
  31. snowflake/cli/api/entities/utils.py +29 -16
  32. snowflake/cli/api/exceptions.py +69 -28
  33. snowflake/cli/api/identifiers.py +2 -0
  34. snowflake/cli/api/plugins/plugin_config.py +2 -2
  35. snowflake/cli/api/project/schemas/template.py +3 -3
  36. snowflake/cli/api/rendering/project_templates.py +3 -3
  37. snowflake/cli/api/rendering/sql_templates.py +2 -2
  38. snowflake/cli/api/sql_execution.py +1 -1
  39. snowflake/cli/api/utils/definition_rendering.py +14 -8
  40. snowflake/cli/api/utils/templating_functions.py +4 -4
  41. {snowflake_cli-3.6.0.dist-info → snowflake_cli-3.7.0.dist-info}/METADATA +8 -8
  42. {snowflake_cli-3.6.0.dist-info → snowflake_cli-3.7.0.dist-info}/RECORD +45 -39
  43. {snowflake_cli-3.6.0.dist-info → snowflake_cli-3.7.0.dist-info}/WHEEL +0 -0
  44. {snowflake_cli-3.6.0.dist-info → snowflake_cli-3.7.0.dist-info}/entry_points.txt +0 -0
  45. {snowflake_cli-3.6.0.dist-info → snowflake_cli-3.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -31,7 +31,7 @@ from snowflake.cli._plugins.streamlit.streamlit_entity_model import (
31
31
  from snowflake.cli._plugins.streamlit.streamlit_project_paths import (
32
32
  StreamlitProjectPaths,
33
33
  )
34
- from snowflake.cli.api.artifacts.upload import put_files
34
+ from snowflake.cli.api.artifacts.upload import sync_artifacts_with_stage
35
35
  from snowflake.cli.api.commands.experimental_behaviour import (
36
36
  experimental_behaviour_enabled,
37
37
  )
@@ -56,18 +56,20 @@ class StreamlitManager(SqlExecutionMixin):
56
56
  f"grant usage on streamlit {streamlit_name.sql_identifier} to role {to_role}"
57
57
  )
58
58
 
59
- def _put_streamlit_files(
59
+ def _upload_artifacts(
60
60
  self,
61
61
  streamlit_project_paths: StreamlitProjectPaths,
62
62
  stage_root: str,
63
+ prune: bool,
63
64
  artifacts: Optional[List[PathMapping]] = None,
64
65
  ):
65
- cli_console.step(f"Deploying files to {stage_root}")
66
- put_files(
67
- project_paths=streamlit_project_paths,
68
- stage_root=stage_root,
69
- artifacts=artifacts,
70
- )
66
+ with cli_console.phase(f"Deploying files to {stage_root}"):
67
+ sync_artifacts_with_stage(
68
+ project_paths=streamlit_project_paths,
69
+ stage_root=stage_root,
70
+ prune=prune,
71
+ artifacts=artifacts,
72
+ )
71
73
 
72
74
  def _create_streamlit(
73
75
  self,
@@ -126,6 +128,7 @@ class StreamlitManager(SqlExecutionMixin):
126
128
  streamlit: StreamlitEntityModel,
127
129
  streamlit_project_paths: StreamlitProjectPaths,
128
130
  replace: bool = False,
131
+ prune: bool = False,
129
132
  ):
130
133
  streamlit_id = streamlit.fqn.using_connection(self._conn)
131
134
  if (
@@ -182,10 +185,11 @@ class StreamlitManager(SqlExecutionMixin):
182
185
  else:
183
186
  stage_root = f"{embedded_stage_name}/default_checkout"
184
187
 
185
- self._put_streamlit_files(
188
+ self._upload_artifacts(
186
189
  streamlit_project_paths,
187
190
  stage_root,
188
- streamlit.artifacts,
191
+ prune=prune,
192
+ artifacts=streamlit.artifacts,
189
193
  )
190
194
  else:
191
195
  """
@@ -198,15 +202,15 @@ class StreamlitManager(SqlExecutionMixin):
198
202
  stage_name = streamlit.stage or "streamlit"
199
203
  stage_name = FQN.from_string(stage_name).using_connection(self._conn)
200
204
 
201
- cli_console.step(f"Creating {stage_name} stage")
202
- stage_manager.create(fqn=stage_name)
203
-
204
205
  stage_root = stage_manager.get_standard_stage_prefix(
205
206
  f"{stage_name}/{streamlit_name_for_root_location}"
206
207
  )
207
208
 
208
- self._put_streamlit_files(
209
- streamlit_project_paths, stage_root, streamlit.artifacts
209
+ self._upload_artifacts(
210
+ streamlit_project_paths,
211
+ stage_root,
212
+ prune=prune,
213
+ artifacts=streamlit.artifacts,
210
214
  )
211
215
 
212
216
  self._create_streamlit(
@@ -1,51 +1,47 @@
1
- from pathlib import PurePosixPath
2
1
  from typing import List, Optional
3
2
 
4
3
  from snowflake.cli._plugins.stage.manager import StageManager
5
- from snowflake.cli.api.artifacts.bundle_map import BundleMap
6
- from snowflake.cli.api.artifacts.utils import symlink_or_copy
4
+ from snowflake.cli.api.artifacts.utils import bundle_artifacts
7
5
  from snowflake.cli.api.console import cli_console
6
+ from snowflake.cli.api.entities.utils import sync_deploy_root_with_stage
8
7
  from snowflake.cli.api.project.project_paths import ProjectPaths
9
8
  from snowflake.cli.api.project.schemas.entities.common import PathMapping
10
9
 
11
10
 
12
- def put_files(
11
+ def sync_artifacts_with_stage(
13
12
  project_paths: ProjectPaths,
14
13
  stage_root: str,
14
+ prune: bool = False,
15
15
  artifacts: Optional[List[PathMapping]] = None,
16
16
  ):
17
- if not artifacts:
18
- return
19
- stage_manager = StageManager()
17
+ if artifacts is None:
18
+ artifacts = []
19
+
20
+ bundle_map = bundle_artifacts(project_paths, artifacts)
21
+ stage_path_parts = StageManager().stage_path_parts_from_str(stage_root)
20
22
  # We treat the bundle root as deploy root
21
- bundle_map = BundleMap(
22
- project_root=project_paths.project_root,
23
+ sync_deploy_root_with_stage(
24
+ console=cli_console,
23
25
  deploy_root=project_paths.bundle_root,
26
+ bundle_map=bundle_map,
27
+ prune=prune,
28
+ recursive=True,
29
+ stage_path=stage_path_parts,
30
+ print_diff=True,
24
31
  )
25
- for artifact in artifacts:
26
- bundle_map.add(PathMapping(src=str(artifact.src), dest=artifact.dest))
27
32
 
28
- # Clean up bundle root
29
- project_paths.remove_up_bundle_root()
30
33
 
31
- for (absolute_src, absolute_dest) in bundle_map.all_mappings(
32
- absolute=True, expand_directories=True
33
- ):
34
- if absolute_src.is_file():
35
- # We treat the bundle/streamlit root as deploy root
36
- symlink_or_copy(
37
- absolute_src,
38
- absolute_dest,
39
- deploy_root=project_paths.bundle_root,
40
- )
41
- # Temporary solution, will be replaced with diff
42
- stage_path = (
43
- PurePosixPath(absolute_dest)
44
- .relative_to(project_paths.bundle_root)
45
- .parent
46
- )
47
- full_stage_path = f"{stage_root}/{stage_path}".rstrip("/")
48
- cli_console.step(f"Uploading {absolute_dest} to {full_stage_path}")
49
- stage_manager.put(
50
- local_path=absolute_dest, stage_path=full_stage_path, overwrite=True
51
- )
34
+ def put_files(
35
+ project_paths: ProjectPaths,
36
+ stage_root: str,
37
+ artifacts: Optional[List[PathMapping]] = None,
38
+ ):
39
+ if not artifacts:
40
+ return
41
+
42
+ sync_artifacts_with_stage(
43
+ project_paths=project_paths,
44
+ stage_root=stage_root,
45
+ prune=False,
46
+ artifacts=artifacts,
47
+ )
@@ -69,14 +69,16 @@ def bundle_artifacts(project_paths: ProjectPaths, artifacts: Artifacts) -> Bundl
69
69
  bundle_map.add(artifact)
70
70
 
71
71
  project_paths.remove_up_bundle_root()
72
+ SecurePath(project_paths.bundle_root).mkdir(parents=True, exist_ok=True)
72
73
  for absolute_src, absolute_dest in bundle_map.all_mappings(
73
74
  absolute=True, expand_directories=True
74
75
  ):
75
- # We treat the bundle root as deploy root
76
- symlink_or_copy(
77
- absolute_src,
78
- absolute_dest,
79
- deploy_root=project_paths.bundle_root,
80
- )
76
+ if absolute_src.is_file():
77
+ # We treat the bundle root as deploy root
78
+ symlink_or_copy(
79
+ absolute_src,
80
+ absolute_dest,
81
+ deploy_root=project_paths.bundle_root,
82
+ )
81
83
 
82
84
  return bundle_map
@@ -22,7 +22,7 @@ from pathlib import Path
22
22
  from typing import TYPE_CHECKING, Iterator, Optional
23
23
 
24
24
  from snowflake.cli.api.connections import ConnectionContext, OpenConnectionCache
25
- from snowflake.cli.api.exceptions import MissingConfiguration
25
+ from snowflake.cli.api.exceptions import MissingConfigurationError
26
26
  from snowflake.cli.api.metrics import CLIMetrics
27
27
  from snowflake.cli.api.output.formats import OutputFormat
28
28
  from snowflake.cli.api.rendering.jinja import CONTEXT_KEY
@@ -61,6 +61,7 @@ class _CliGlobalContextManager:
61
61
  override_project_definition: ProjectDefinition | None = None
62
62
 
63
63
  _definition_manager: DefinitionManager | None = None
64
+ enhanced_exit_codes: bool = False
64
65
 
65
66
  # which properties invalidate our current DefinitionManager?
66
67
  DEFINITION_MANAGER_DEPENDENCIES = [
@@ -126,7 +127,7 @@ class _CliGlobalContextManager:
126
127
  {CONTEXT_KEY: {"env": self.project_env_overrides_args}},
127
128
  )
128
129
  if not dm.has_definition_file and not self.project_is_optional:
129
- raise MissingConfiguration(
130
+ raise MissingConfigurationError(
130
131
  "Cannot find project definition (snowflake.yml). Please provide a path to the project or run this command in a valid project directory."
131
132
  )
132
133
  self._definition_manager = dm
@@ -209,6 +210,10 @@ class _CliGlobalContextAccess:
209
210
  else:
210
211
  return None
211
212
 
213
+ @property
214
+ def enhanced_exit_codes(self) -> bool:
215
+ return self._manager.enhanced_exit_codes
216
+
212
217
 
213
218
  _CLI_CONTEXT_MANAGER: ContextVar[_CliGlobalContextManager | None] = ContextVar(
214
219
  "cli_context", default=None
@@ -29,6 +29,7 @@ from snowflake.cli.api.commands.flags import (
29
29
  DiagAllowlistPathOption,
30
30
  DiagLogPathOption,
31
31
  EnableDiagOption,
32
+ EnhancedExitCodesOption,
32
33
  HostOption,
33
34
  MasterTokenOption,
34
35
  MfaPasscodeOption,
@@ -78,7 +79,6 @@ def global_options_with_connection(func: Callable):
78
79
 
79
80
  def with_project_definition(is_optional: bool = False):
80
81
  def _decorator(func: Callable):
81
-
82
82
  return _options_decorator_factory(
83
83
  func,
84
84
  additional_options=[
@@ -159,7 +159,10 @@ def _options_decorator_factory(
159
159
  execute_before_command_using_new_options(**options)
160
160
  return func(**options)
161
161
 
162
- wrapper.__signature__ = _extend_signature_with_additional_options(func, additional_options) # type: ignore
162
+ wrapper.__signature__ = _extend_signature_with_additional_options( # type: ignore
163
+ func, additional_options
164
+ )
165
+
163
166
  return wrapper
164
167
 
165
168
 
@@ -353,6 +356,12 @@ GLOBAL_OPTIONS = [
353
356
  annotation=Optional[bool],
354
357
  default=SilentOption,
355
358
  ),
359
+ inspect.Parameter(
360
+ "enhanced_exit_codes",
361
+ inspect.Parameter.KEYWORD_ONLY,
362
+ annotation=Optional[bool],
363
+ default=EnhancedExitCodesOption,
364
+ ),
356
365
  ]
357
366
 
358
367
 
@@ -371,6 +371,17 @@ DebugOption = typer.Option(
371
371
  rich_help_panel=_CLI_BEHAVIOUR,
372
372
  )
373
373
 
374
+ EnhancedExitCodesOption = typer.Option(
375
+ False,
376
+ "--enhanced-exit-codes",
377
+ help="Differentiate exit error codes based on failure type.",
378
+ callback=_context_callback("enhanced_exit_codes"),
379
+ is_flag=True,
380
+ rich_help_panel=_CLI_BEHAVIOUR,
381
+ is_eager=True,
382
+ envvar="SNOWFLAKE_ENHANCED_EXIT_CODES",
383
+ )
384
+
374
385
 
375
386
  # If IfExistsOption, IfNotExistsOption, or ReplaceOption are used with names other than those in CREATE_MODE_OPTION_NAMES,
376
387
  # you must also override mutually_exclusive if you want to retain the validation that at most one of these flags is
@@ -412,9 +423,19 @@ OnErrorOption = typer.Option(
412
423
 
413
424
  NoInteractiveOption = typer.Option(False, "--no-interactive", help="Disable prompting.")
414
425
 
426
+ PruneOption = OverrideableOption(
427
+ False,
428
+ "--prune",
429
+ help=f"Delete files that exist in the stage, but not in the local filesystem.",
430
+ show_default=False,
431
+ )
432
+
415
433
 
416
- def entity_argument(entity_type: str) -> typer.Argument:
417
- return typer.Argument(None, help=f"ID of {entity_type} entity.")
434
+ def entity_argument(entity_type: str, required=False) -> typer.Argument:
435
+ _help = f"ID of {entity_type} entity."
436
+ if not required:
437
+ return typer.Argument(None, help=_help)
438
+ return typer.Argument(..., help=_help, show_default=False)
418
439
 
419
440
 
420
441
  def variables_option(description: str):
@@ -31,7 +31,13 @@ from snowflake.cli.api.commands.execution_metadata import (
31
31
  ExecutionStatus,
32
32
  )
33
33
  from snowflake.cli.api.commands.flags import DEFAULT_CONTEXT_SETTINGS
34
- from snowflake.cli.api.exceptions import CommandReturnTypeError
34
+ from snowflake.cli.api.exceptions import (
35
+ BaseCliError,
36
+ CliArgumentError,
37
+ CliError,
38
+ CliSqlError,
39
+ CommandReturnTypeError,
40
+ )
35
41
  from snowflake.cli.api.output.types import CommandResult
36
42
  from snowflake.cli.api.sanitizers import sanitize_for_terminal
37
43
  from snowflake.cli.api.sql_execution import SqlExecutionMixin
@@ -168,8 +174,20 @@ class SnowTyper(typer.Typer):
168
174
 
169
175
  log.debug("Executing command exception callback")
170
176
  log_command_execution_error(exception, execution)
177
+ exception = SnowTyper._cli_base_exception_migration_dispatcher(exception)
178
+ return exception
179
+
180
+ @staticmethod
181
+ def _cli_base_exception_migration_dispatcher(exception):
182
+ """Handler used for dispatching exception until migration completed."""
183
+ if isinstance(
184
+ exception, (BaseCliError, CliError, CliArgumentError, CliSqlError)
185
+ ):
186
+ return exception
187
+
171
188
  if isinstance(exception, DatabaseError):
172
- return ClickException(exception.msg)
189
+ return CliSqlError(exception.msg)
190
+
173
191
  return exception
174
192
 
175
193
  @staticmethod
@@ -26,7 +26,7 @@ import tomlkit
26
26
  from click import ClickException
27
27
  from snowflake.cli.api.exceptions import (
28
28
  ConfigFileTooWidePermissionsError,
29
- MissingConfiguration,
29
+ MissingConfigurationError,
30
30
  UnsupportedConfigSectionTypeError,
31
31
  )
32
32
  from snowflake.cli.api.secure_path import SecurePath
@@ -252,7 +252,9 @@ def get_connection_dict(connection_name: str) -> dict:
252
252
  try:
253
253
  return get_config_section(CONNECTIONS_SECTION, connection_name)
254
254
  except KeyError:
255
- raise MissingConfiguration(f"Connection {connection_name} is not configured")
255
+ raise MissingConfigurationError(
256
+ f"Connection {connection_name} is not configured"
257
+ )
256
258
 
257
259
 
258
260
  def get_default_connection_name() -> str:
@@ -262,7 +264,7 @@ def get_default_connection_name() -> str:
262
264
  def get_default_connection_dict() -> dict:
263
265
  def_connection_name = get_default_connection_name()
264
266
  if not connection_exists(def_connection_name):
265
- raise MissingConfiguration(
267
+ raise MissingConfigurationError(
266
268
  f"Couldn't find connection for default connection `{def_connection_name}`. "
267
269
  f"Specify connection name or configure default connection."
268
270
  )
@@ -19,7 +19,10 @@ from snowflake.cli._plugins.stage.diff import (
19
19
  sync_local_diff_with_stage,
20
20
  to_stage_path,
21
21
  )
22
- from snowflake.cli._plugins.stage.manager import DefaultStagePathParts
22
+ from snowflake.cli._plugins.stage.manager import (
23
+ StageManager,
24
+ StagePathParts,
25
+ )
23
26
  from snowflake.cli._plugins.stage.utils import print_diff_to_console
24
27
  from snowflake.cli.api.artifacts.bundle_map import BundleMap
25
28
  from snowflake.cli.api.cli_global_context import get_cli_context, span
@@ -33,6 +36,7 @@ from snowflake.cli.api.exceptions import (
33
36
  NoWarehouseSelectedInSessionError,
34
37
  SnowflakeSQLExecutionError,
35
38
  )
39
+ from snowflake.cli.api.identifiers import FQN
36
40
  from snowflake.cli.api.metrics import CLICounterField
37
41
  from snowflake.cli.api.project.schemas.entities.common import PostDeployHook
38
42
  from snowflake.cli.api.rendering.sql_templates import (
@@ -79,12 +83,12 @@ def _get_stage_paths_to_sync(
79
83
  def sync_deploy_root_with_stage(
80
84
  console: AbstractConsole,
81
85
  deploy_root: Path,
82
- package_name: str,
83
86
  bundle_map: BundleMap,
84
- role: str,
85
87
  prune: bool,
86
88
  recursive: bool,
87
- stage_path: DefaultStagePathParts,
89
+ stage_path: StagePathParts,
90
+ role: str | None = None,
91
+ package_name: str | None = None,
88
92
  local_paths_to_sync: List[Path] | None = None,
89
93
  print_diff: bool = True,
90
94
  ) -> DiffResult:
@@ -99,6 +103,8 @@ def sync_deploy_root_with_stage(
99
103
  recursive (bool): Whether to traverse directories recursively.
100
104
  stage_path (DefaultStagePathParts): stage path object.
101
105
 
106
+ package_name (str): supported for Native App compatibility. Should be None out of Native App context.
107
+
102
108
  local_paths_to_sync (List[Path], optional): List of local paths to sync. Defaults to None to sync all
103
109
  local paths. Note that providing an empty list here is equivalent to None.
104
110
  print_diff (bool): Whether to print the diff between the local files and the remote stage. Defaults to True
@@ -106,17 +112,24 @@ def sync_deploy_root_with_stage(
106
112
  Returns:
107
113
  A `DiffResult` instance describing the changes that were performed.
108
114
  """
109
- sql_facade = get_snowflake_facade()
110
- schema = stage_path.schema
111
- stage_fqn = stage_path.stage
112
- # Does a stage already exist within the application package, or we need to create one?
113
- # Using "if not exists" should take care of either case.
114
- console.step(
115
- f"Checking if stage {stage_fqn} exists, or creating a new one if none exists."
116
- )
117
- if not sql_facade.stage_exists(stage_fqn):
118
- sql_facade.create_schema(schema, database=package_name)
119
- sql_facade.create_stage(stage_fqn)
115
+ if not package_name:
116
+ # ensure stage exists
117
+ stage_fqn = FQN.from_stage(stage_path.stage)
118
+ console.step(f"Creating stage {stage_fqn} if not exists.")
119
+ StageManager().create(fqn=stage_fqn)
120
+ else:
121
+ # ensure stage exists - nativeapp behavior
122
+ sql_facade = get_snowflake_facade()
123
+ schema = stage_path.schema
124
+ stage_fqn = stage_path.stage
125
+ # Does a stage already exist within the application package, or we need to create one?
126
+ # Using "if not exists" should take care of either case.
127
+ console.step(
128
+ f"Checking if stage {stage_fqn} exists, or creating a new one if none exists."
129
+ )
130
+ if not sql_facade.stage_exists(stage_fqn):
131
+ sql_facade.create_schema(schema, database=package_name)
132
+ sql_facade.create_stage(stage_fqn)
120
133
 
121
134
  # Perform a diff operation and display results to the user for informational purposes
122
135
  if print_diff:
@@ -178,7 +191,7 @@ def sync_deploy_root_with_stage(
178
191
  if print_diff:
179
192
  print_diff_to_console(diff, bundle_map)
180
193
 
181
- # Upload diff-ed files to application package stage
194
+ # Upload diff-ed files to the stage
182
195
  if diff.has_changes():
183
196
  console.step(
184
197
  "Updating the Snowflake stage from your local %s directory."