snowflake-cli 3.4.1__py3-none-any.whl → 3.6.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 (73) hide show
  1. snowflake/cli/__about__.py +13 -1
  2. snowflake/cli/_app/cli_app.py +1 -10
  3. snowflake/cli/_app/commands_registration/builtin_plugins.py +7 -1
  4. snowflake/cli/_app/commands_registration/command_plugins_loader.py +3 -1
  5. snowflake/cli/_app/commands_registration/commands_registration_with_callbacks.py +3 -3
  6. snowflake/cli/_app/printing.py +2 -2
  7. snowflake/cli/_app/snow_connector.py +5 -4
  8. snowflake/cli/_app/telemetry.py +3 -15
  9. snowflake/cli/_app/version_check.py +4 -4
  10. snowflake/cli/_plugins/auth/__init__.py +11 -0
  11. snowflake/cli/_plugins/auth/keypair/__init__.py +0 -0
  12. snowflake/cli/_plugins/auth/keypair/commands.py +151 -0
  13. snowflake/cli/_plugins/auth/keypair/manager.py +331 -0
  14. snowflake/cli/_plugins/auth/keypair/plugin_spec.py +30 -0
  15. snowflake/cli/_plugins/connection/commands.py +79 -5
  16. snowflake/cli/_plugins/helpers/commands.py +3 -4
  17. snowflake/cli/_plugins/nativeapp/entities/application.py +4 -1
  18. snowflake/cli/_plugins/nativeapp/sf_sql_facade.py +33 -6
  19. snowflake/cli/_plugins/notebook/commands.py +3 -4
  20. snowflake/cli/_plugins/object/command_aliases.py +3 -1
  21. snowflake/cli/_plugins/object/manager.py +4 -2
  22. snowflake/cli/_plugins/plugin/commands.py +79 -0
  23. snowflake/cli/_plugins/plugin/manager.py +74 -0
  24. snowflake/cli/_plugins/plugin/plugin_spec.py +30 -0
  25. snowflake/cli/_plugins/project/__init__.py +0 -0
  26. snowflake/cli/_plugins/project/commands.py +173 -0
  27. snowflake/cli/{_app/api_impl/plugin/__init__.py → _plugins/project/feature_flags.py} +9 -0
  28. snowflake/cli/_plugins/project/manager.py +76 -0
  29. snowflake/cli/_plugins/project/plugin_spec.py +30 -0
  30. snowflake/cli/_plugins/project/project_entity_model.py +40 -0
  31. snowflake/cli/_plugins/snowpark/commands.py +2 -1
  32. snowflake/cli/_plugins/spcs/compute_pool/commands.py +70 -10
  33. snowflake/cli/_plugins/spcs/compute_pool/compute_pool_entity.py +8 -0
  34. snowflake/cli/_plugins/spcs/compute_pool/compute_pool_entity_model.py +37 -0
  35. snowflake/cli/_plugins/spcs/compute_pool/manager.py +45 -0
  36. snowflake/cli/_plugins/spcs/image_repository/commands.py +29 -0
  37. snowflake/cli/_plugins/spcs/image_repository/image_repository_entity.py +8 -0
  38. snowflake/cli/_plugins/spcs/image_repository/image_repository_entity_model.py +8 -0
  39. snowflake/cli/_plugins/spcs/image_repository/manager.py +1 -1
  40. snowflake/cli/_plugins/spcs/services/commands.py +53 -0
  41. snowflake/cli/_plugins/spcs/services/manager.py +114 -0
  42. snowflake/cli/_plugins/spcs/services/service_entity.py +6 -0
  43. snowflake/cli/_plugins/spcs/services/service_entity_model.py +45 -0
  44. snowflake/cli/_plugins/spcs/services/service_project_paths.py +15 -0
  45. snowflake/cli/_plugins/sql/manager.py +42 -51
  46. snowflake/cli/_plugins/sql/source_reader.py +230 -0
  47. snowflake/cli/_plugins/stage/manager.py +10 -4
  48. snowflake/cli/_plugins/streamlit/commands.py +9 -24
  49. snowflake/cli/_plugins/streamlit/manager.py +5 -36
  50. snowflake/cli/api/artifacts/upload.py +51 -0
  51. snowflake/cli/api/commands/flags.py +35 -10
  52. snowflake/cli/api/commands/snow_typer.py +12 -0
  53. snowflake/cli/api/commands/utils.py +2 -0
  54. snowflake/cli/api/config.py +15 -10
  55. snowflake/cli/api/constants.py +2 -0
  56. snowflake/cli/api/errno.py +1 -0
  57. snowflake/cli/api/exceptions.py +15 -1
  58. snowflake/cli/api/feature_flags.py +2 -0
  59. snowflake/cli/api/plugins/plugin_config.py +43 -4
  60. snowflake/cli/api/project/definition_helper.py +31 -0
  61. snowflake/cli/api/project/schemas/entities/entities.py +26 -0
  62. snowflake/cli/api/rest_api.py +2 -3
  63. snowflake/cli/{_app → api}/secret.py +4 -1
  64. snowflake/cli/api/secure_path.py +16 -4
  65. snowflake/cli/api/sql_execution.py +7 -3
  66. {snowflake_cli-3.4.1.dist-info → snowflake_cli-3.6.0.dist-info}/METADATA +12 -12
  67. {snowflake_cli-3.4.1.dist-info → snowflake_cli-3.6.0.dist-info}/RECORD +71 -50
  68. snowflake/cli/_app/api_impl/plugin/plugin_config_provider_impl.py +0 -66
  69. snowflake/cli/api/__init__.py +0 -48
  70. /snowflake/cli/{_app/api_impl → _plugins/plugin}/__init__.py +0 -0
  71. {snowflake_cli-3.4.1.dist-info → snowflake_cli-3.6.0.dist-info}/WHEEL +0 -0
  72. {snowflake_cli-3.4.1.dist-info → snowflake_cli-3.6.0.dist-info}/entry_points.txt +0 -0
  73. {snowflake_cli-3.4.1.dist-info → snowflake_cli-3.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -14,7 +14,6 @@
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
- import tempfile
18
17
  from pathlib import Path
19
18
  from typing import Any, Callable, Optional
20
19
 
@@ -33,6 +32,7 @@ from snowflake.cli.api.connections import ConnectionContext
33
32
  from snowflake.cli.api.console import cli_console
34
33
  from snowflake.cli.api.identifiers import FQN
35
34
  from snowflake.cli.api.output.formats import OutputFormat
35
+ from snowflake.cli.api.secret import SecretType
36
36
  from snowflake.cli.api.stage_path import StagePath
37
37
 
38
38
  DEFAULT_CONTEXT_SETTINGS = {"help_option_names": ["--help", "-h"]}
@@ -215,7 +215,7 @@ MasterTokenOption = typer.Option(
215
215
  TokenFilePathOption = typer.Option(
216
216
  None,
217
217
  "--token-file-path",
218
- help="Path to file with an OAuth token that should be used when connecting to Snowflake",
218
+ help="Path to file with an OAuth token to use when connecting to Snowflake.",
219
219
  callback=_connection_callback("token_file_path"),
220
220
  show_default=False,
221
221
  rich_help_panel=_CONNECTION_SECTION,
@@ -277,7 +277,7 @@ MfaPasscodeOption = typer.Option(
277
277
  EnableDiagOption = typer.Option(
278
278
  False,
279
279
  "--enable-diag",
280
- help="Run Python connector diagnostic test",
280
+ help="Whether to generate a connection diagnostic report.",
281
281
  callback=_connection_callback("enable_diag"),
282
282
  show_default=False,
283
283
  is_flag=True,
@@ -286,20 +286,29 @@ EnableDiagOption = typer.Option(
286
286
 
287
287
  # Set default via callback to avoid including tempdir path in generated docs (snow --docs).
288
288
  # Use constant instead of None, as None is removed from telemetry data.
289
- _DIAG_LOG_DEFAULT_VALUE = "<temporary_directory>"
289
+ _DIAG_LOG_DEFAULT_VALUE = "<system_temporary_directory>"
290
290
 
291
291
 
292
292
  def _diag_log_path_callback(path: str):
293
293
  if path == _DIAG_LOG_DEFAULT_VALUE:
294
+ import tempfile
295
+
294
296
  path = tempfile.gettempdir()
295
- get_cli_context_manager().connection_context.diag_log_path = Path(path)
296
- return path
297
+
298
+ absolute_path = Path(path).absolute()
299
+ if not absolute_path.exists():
300
+ # if the path does not exist the report is not generated
301
+ from snowflake.cli.api.secure_path import SecurePath
302
+
303
+ SecurePath(absolute_path).mkdir(parents=True)
304
+
305
+ return _connection_callback("diag_log_path")(absolute_path)
297
306
 
298
307
 
299
308
  DiagLogPathOption: Path = typer.Option(
300
309
  _DIAG_LOG_DEFAULT_VALUE,
301
310
  "--diag-log-path",
302
- help="Diagnostic report path",
311
+ help="Path for the generated report. Defaults to system temporary directory.",
303
312
  callback=_diag_log_path_callback,
304
313
  show_default=False,
305
314
  rich_help_panel=_CONNECTION_SECTION,
@@ -307,11 +316,17 @@ DiagLogPathOption: Path = typer.Option(
307
316
  writable=True,
308
317
  )
309
318
 
319
+
320
+ def _diag_log_allowlist_path_callback(path: str):
321
+ absolute_path = Path(path).absolute() if path else None
322
+ return _connection_callback("diag_allowlist_path")(absolute_path)
323
+
324
+
310
325
  DiagAllowlistPathOption: Path = typer.Option(
311
326
  None,
312
327
  "--diag-allowlist-path",
313
- help="Diagnostic report path to optional allowlist",
314
- callback=_connection_callback("diag_allowlist_path"),
328
+ help="Path to a JSON file that contains allowlist parameters.",
329
+ callback=_diag_log_allowlist_path_callback,
315
330
  show_default=False,
316
331
  rich_help_panel=_CONNECTION_SECTION,
317
332
  exists=True,
@@ -487,14 +502,24 @@ class IdentifierStagePathType(click.ParamType):
487
502
  return StagePath.from_stage_str(value)
488
503
 
489
504
 
505
+ class SecretTypeParser(click.ParamType):
506
+ name = "TEXT"
507
+
508
+ def convert(self, value, param, ctx):
509
+ if not isinstance(value, SecretType):
510
+ return SecretType(value)
511
+ return value
512
+
513
+
490
514
  def identifier_argument(
491
515
  sf_object: str,
492
516
  example: str,
493
517
  click_type: click.ParamType = IdentifierType(),
494
518
  callback: Callable | None = None,
519
+ is_optional: bool = False,
495
520
  ) -> typer.Argument:
496
521
  return typer.Argument(
497
- ...,
522
+ None if is_optional else ...,
498
523
  help=f"Identifier of the {sf_object}; for example: {example}",
499
524
  show_default=False,
500
525
  click_type=click_type,
@@ -19,6 +19,7 @@ import logging
19
19
  from functools import wraps
20
20
  from typing import Any, Callable, Dict, List, Optional, Tuple
21
21
 
22
+ import click
22
23
  import typer
23
24
  from click import ClickException
24
25
  from snowflake.cli.api.commands.decorators import (
@@ -35,10 +36,20 @@ from snowflake.cli.api.output.types import CommandResult
35
36
  from snowflake.cli.api.sanitizers import sanitize_for_terminal
36
37
  from snowflake.cli.api.sql_execution import SqlExecutionMixin
37
38
  from snowflake.connector import DatabaseError
39
+ from typer.core import TyperGroup
38
40
 
39
41
  log = logging.getLogger(__name__)
40
42
 
41
43
 
44
+ class SortedTyperGroup(TyperGroup):
45
+ def list_commands(self, ctx: click.Context) -> List[str]:
46
+ """
47
+ From Typer 0.13.0 help items are in order of definition, this function override that approach.
48
+ Returns a list of subcommand names in the alphabetic order.
49
+ """
50
+ return sorted(self.commands)
51
+
52
+
42
53
  class SnowTyper(typer.Typer):
43
54
  def __init__(self, /, **kwargs):
44
55
  self._sanitize_kwargs(kwargs)
@@ -49,6 +60,7 @@ class SnowTyper(typer.Typer):
49
60
  no_args_is_help=True,
50
61
  add_completion=True,
51
62
  rich_markup_mode="markdown",
63
+ cls=SortedTyperGroup,
52
64
  )
53
65
 
54
66
  @staticmethod
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from typing import Dict, List, Optional
2
4
 
3
5
  from click import ClickException, UsageError
@@ -20,7 +20,7 @@ import warnings
20
20
  from contextlib import contextmanager
21
21
  from dataclasses import asdict, dataclass, field
22
22
  from pathlib import Path
23
- from typing import Any, Dict, Optional, Union
23
+ from typing import Any, Dict, List, Optional, Union
24
24
 
25
25
  import tomlkit
26
26
  from click import ClickException
@@ -58,6 +58,7 @@ PLUGINS_SECTION = "plugins"
58
58
 
59
59
  LOGS_SECTION_PATH = [CLI_SECTION, LOGS_SECTION]
60
60
  PLUGINS_SECTION_PATH = [CLI_SECTION, PLUGINS_SECTION]
61
+ PLUGIN_ENABLED_KEY = "enabled"
61
62
  FEATURE_FLAGS_SECTION_PATH = [CLI_SECTION, "features"]
62
63
 
63
64
  CONFIG_MANAGER.add_option(
@@ -140,8 +141,7 @@ def add_connection_to_proper_file(name: str, connection_config: ConnectionConfig
140
141
  return CONNECTIONS_FILE
141
142
  else:
142
143
  set_config_value(
143
- section=CONNECTIONS_SECTION,
144
- key=name,
144
+ path=[CONNECTIONS_SECTION, name],
145
145
  value=connection_config.to_dict_of_all_non_empty_values(),
146
146
  )
147
147
  return CONFIG_MANAGER.file_path
@@ -200,14 +200,19 @@ def _initialise_logs_section():
200
200
  conf_file_cache[CLI_SECTION][LOGS_SECTION] = _DEFAULT_LOGS_CONFIG
201
201
 
202
202
 
203
- def set_config_value(section: str | None, key: str, value: Any):
203
+ def set_config_value(path: List[str], value: Any) -> None:
204
+ """Sets value in config.
205
+ For example to set value "val" to key "key" in section [a.b.c], call
206
+ set_config_value(["a", "b", "c", "key"], "val").
207
+ If you want to override a whole section, value should be a dictionary.
208
+ """
204
209
  with _config_file() as conf_file_cache:
205
- if section:
206
- if conf_file_cache.get(section) is None:
207
- conf_file_cache[section] = {}
208
- conf_file_cache[section][key] = value
209
- else:
210
- conf_file_cache[key] = value
210
+ current_config_dict = conf_file_cache
211
+ for key in path[:-1]:
212
+ if key not in current_config_dict:
213
+ current_config_dict[key] = {}
214
+ current_config_dict = current_config_dict[key]
215
+ current_config_dict[path[-1]] = value
211
216
 
212
217
 
213
218
  def get_logs_config() -> dict:
@@ -47,6 +47,7 @@ class ObjectType(Enum):
47
47
  NETWORK_RULE = ObjectNames("network-rule", "network rule", "network rules")
48
48
  NOTEBOOK = ObjectNames("notebook", "notebook", "notebooks")
49
49
  PROCEDURE = ObjectNames("procedure", "procedure", "procedures")
50
+ PROJECT = ObjectNames("project", "project", "projects")
50
51
  ROLE = ObjectNames("role", "role", "roles")
51
52
  SCHEMA = ObjectNames("schema", "schema", "schemas")
52
53
  SERVICE = ObjectNames("service", "service", "services")
@@ -77,6 +78,7 @@ OBJECT_TO_NAMES = {o.value.cli_name: o.value for o in ObjectType}
77
78
  UNSUPPORTED_OBJECTS = {
78
79
  ObjectType.APPLICATION.value.cli_name,
79
80
  ObjectType.APPLICATION_PACKAGE.value.cli_name,
81
+ ObjectType.PROJECT.value.cli_name,
80
82
  }
81
83
  SUPPORTED_OBJECTS = sorted(OBJECT_TO_NAMES.keys() - UNSUPPORTED_OBJECTS)
82
84
 
@@ -78,6 +78,7 @@ MAX_VERSIONS_IN_RELEASE_CHANNEL_REACHED = 512004
78
78
  MAX_UNBOUND_VERSIONS_REACHED = 512023
79
79
  CANNOT_DEREGISTER_VERSION_ASSOCIATED_WITH_CHANNEL = 512021
80
80
  TARGET_ACCOUNT_USED_BY_OTHER_RELEASE_DIRECTIVE = 93091
81
+ CANNOT_SET_DEBUG_MODE_WITH_MANIFEST_VERSION = 93362
81
82
 
82
83
 
83
84
  ERR_JAVASCRIPT_EXECUTION = 100132
@@ -15,7 +15,7 @@
15
15
  from __future__ import annotations
16
16
 
17
17
  from pathlib import Path
18
- from typing import Optional
18
+ from typing import List, Optional
19
19
 
20
20
  from click.exceptions import ClickException, UsageError
21
21
  from snowflake.cli.api.constants import ObjectType
@@ -54,6 +54,13 @@ class InvalidPluginConfiguration(ClickException):
54
54
  return f"Invalid plugin configuration. {self.message}"
55
55
 
56
56
 
57
+ class PluginNotInstalledError(ClickException):
58
+ def __init__(self, plugin_name, installed_plugins: List[str]):
59
+ super().__init__(
60
+ f"Plugin {plugin_name} is not installed. Available plugins: {', '.join(installed_plugins)}."
61
+ )
62
+
63
+
57
64
  class SnowflakeConnectionError(ClickException):
58
65
  def __init__(self, snowflake_err: Exception):
59
66
  super().__init__(f"Could not connect to Snowflake. Reason: {snowflake_err}")
@@ -229,3 +236,10 @@ class ShowSpecificObjectMultipleRowsError(RuntimeError):
229
236
  super().__init__(
230
237
  f"Received multiple rows from result of SQL statement: {show_obj_query}. Usage of 'show_specific_object' may not be properly scoped."
231
238
  )
239
+
240
+
241
+ class CouldNotSetKeyPairError(ClickException):
242
+ def __init__(self):
243
+ super().__init__(
244
+ "The public key is set already. Use the rotate command instead."
245
+ )
@@ -67,3 +67,5 @@ class FeatureFlag(FeatureFlagMixin):
67
67
  "ENABLE_SEPARATE_AUTHENTICATION_POLICY_ID", False
68
68
  )
69
69
  ENABLE_SNOWPARK_GLOB_SUPPORT = BooleanFlag("ENABLE_SNOWPARK_GLOB_SUPPORT", False)
70
+ ENABLE_SPCS_SERVICE_EVENTS = BooleanFlag("ENABLE_SPCS_SERVICE_EVENTS", False)
71
+ ENABLE_AUTH_KEYPAIR = BooleanFlag("ENABLE_AUTH_KEYPAIR", False)
@@ -17,6 +17,16 @@ from __future__ import annotations
17
17
  from dataclasses import dataclass
18
18
  from typing import Any, Dict, List
19
19
 
20
+ from snowflake.cli.api.config import (
21
+ PLUGIN_ENABLED_KEY,
22
+ PLUGINS_SECTION_PATH,
23
+ config_section_exists,
24
+ get_config_section,
25
+ get_config_value,
26
+ get_plugins_config,
27
+ )
28
+ from snowflake.cli.api.exceptions import InvalidPluginConfiguration
29
+
20
30
 
21
31
  @dataclass
22
32
  class PluginConfig:
@@ -25,8 +35,37 @@ class PluginConfig:
25
35
 
26
36
 
27
37
  class PluginConfigProvider:
28
- def get_enabled_plugin_names(self) -> List[str]:
29
- raise NotImplementedError()
38
+ @staticmethod
39
+ def get_enabled_plugin_names() -> List[str]:
40
+ enabled_plugins = []
41
+ for plugin_name, plugin_config_section in get_plugins_config().items():
42
+ enabled = plugin_config_section.get(PLUGIN_ENABLED_KEY, False)
43
+ _assert_value_is_bool(
44
+ enabled, value_name=PLUGIN_ENABLED_KEY, plugin_name=plugin_name
45
+ )
46
+ if enabled:
47
+ enabled_plugins.append(plugin_name)
48
+ return enabled_plugins
49
+
50
+ @staticmethod
51
+ def get_config(plugin_name: str) -> PluginConfig:
52
+ config_path = PLUGINS_SECTION_PATH + [plugin_name]
53
+ plugin_config = PluginConfig(is_plugin_enabled=False, internal_config={})
54
+ plugin_config.is_plugin_enabled = get_config_value(
55
+ *config_path, key=PLUGIN_ENABLED_KEY, default=False
56
+ )
57
+ _assert_value_is_bool(
58
+ plugin_config.is_plugin_enabled,
59
+ value_name=PLUGIN_ENABLED_KEY,
60
+ plugin_name=plugin_name,
61
+ )
62
+ if config_section_exists(*config_path, "config"):
63
+ plugin_config.internal_config = get_config_section(*config_path, "config")
64
+ return plugin_config
65
+
30
66
 
31
- def get_config(self, plugin_name: str) -> PluginConfig:
32
- raise NotImplementedError()
67
+ def _assert_value_is_bool(value, *, value_name: str, plugin_name: str) -> None:
68
+ if type(value) is not bool:
69
+ raise InvalidPluginConfiguration(
70
+ f'[{plugin_name}]: "{value_name}" must be a boolean'
71
+ )
@@ -0,0 +1,31 @@
1
+ from typing import Optional
2
+
3
+ from click import UsageError
4
+ from snowflake.cli.api.cli_global_context import get_cli_context
5
+ from snowflake.cli.api.constants import ObjectType
6
+ from snowflake.cli.api.exceptions import NoProjectDefinitionError
7
+
8
+
9
+ def get_entity_from_project_definition(
10
+ entity_type: ObjectType, entity_id: Optional[str] = None
11
+ ):
12
+ cli_context = get_cli_context()
13
+ pd = cli_context.project_definition
14
+ entities = pd.get_entities_by_type(entity_type=entity_type.value.cli_name)
15
+
16
+ if not entities:
17
+ raise NoProjectDefinitionError(
18
+ project_type=entity_type.value.sf_name,
19
+ project_root=cli_context.project_root,
20
+ )
21
+
22
+ if entity_id and entity_id not in entities:
23
+ raise UsageError(f"No '{entity_id}' entity in project definition file.")
24
+ elif len(entities.keys()) == 1:
25
+ entity_id = list(entities.keys())[0]
26
+
27
+ if entity_id is None:
28
+ raise UsageError(
29
+ f"Multiple {entity_type.value.sf_plural_name} found. Please provide entity id for the operation."
30
+ )
31
+ return entities[entity_id]
@@ -26,6 +26,10 @@ from snowflake.cli._plugins.nativeapp.entities.application_package import (
26
26
  )
27
27
  from snowflake.cli._plugins.notebook.notebook_entity import NotebookEntity
28
28
  from snowflake.cli._plugins.notebook.notebook_entity_model import NotebookEntityModel
29
+ from snowflake.cli._plugins.project.project_entity_model import (
30
+ ProjectEntity,
31
+ ProjectEntityModel,
32
+ )
29
33
  from snowflake.cli._plugins.snowpark.snowpark_entity import (
30
34
  FunctionEntity,
31
35
  ProcedureEntity,
@@ -34,6 +38,20 @@ from snowflake.cli._plugins.snowpark.snowpark_entity_model import (
34
38
  FunctionEntityModel,
35
39
  ProcedureEntityModel,
36
40
  )
41
+ from snowflake.cli._plugins.spcs.compute_pool.compute_pool_entity import (
42
+ ComputePoolEntity,
43
+ )
44
+ from snowflake.cli._plugins.spcs.compute_pool.compute_pool_entity_model import (
45
+ ComputePoolEntityModel,
46
+ )
47
+ from snowflake.cli._plugins.spcs.image_repository.image_repository_entity import (
48
+ ImageRepositoryEntity,
49
+ )
50
+ from snowflake.cli._plugins.spcs.image_repository.image_repository_entity_model import (
51
+ ImageRepositoryEntityModel,
52
+ )
53
+ from snowflake.cli._plugins.spcs.services.service_entity import ServiceEntity
54
+ from snowflake.cli._plugins.spcs.services.service_entity_model import ServiceEntityModel
37
55
  from snowflake.cli._plugins.streamlit.streamlit_entity import StreamlitEntity
38
56
  from snowflake.cli._plugins.streamlit.streamlit_entity_model import (
39
57
  StreamlitEntityModel,
@@ -44,7 +62,11 @@ Entity = Union[
44
62
  ApplicationPackageEntity,
45
63
  StreamlitEntity,
46
64
  ProcedureEntity,
65
+ ProjectEntity,
47
66
  FunctionEntity,
67
+ ComputePoolEntity,
68
+ ImageRepositoryEntity,
69
+ ServiceEntity,
48
70
  NotebookEntity,
49
71
  ]
50
72
  EntityModel = Union[
@@ -53,7 +75,11 @@ EntityModel = Union[
53
75
  StreamlitEntityModel,
54
76
  FunctionEntityModel,
55
77
  ProcedureEntityModel,
78
+ ComputePoolEntityModel,
79
+ ImageRepositoryEntityModel,
80
+ ServiceEntityModel,
56
81
  NotebookEntityModel,
82
+ ProjectEntityModel,
57
83
  ]
58
84
 
59
85
  ALL_ENTITIES: List[Entity] = [*get_args(Entity)]
@@ -155,9 +155,8 @@ class RestApi:
155
155
  raise SchemaNotDefinedException(
156
156
  "Schema not defined in connection. Please try again with `--schema` flag."
157
157
  )
158
- # temporarily disable this check due to an issue on server side: SNOW-1747450
159
- # if not self._schema_exists(db_name=db, schema_name=schema):
160
- # raise SchemaNotExistsException(f"Schema '{schema}' does not exist.")
158
+ if not self._schema_exists(db_name=db, schema_name=schema):
159
+ raise SchemaNotExistsException(f"Schema '{schema}' does not exist.")
161
160
  if self.get_endpoint_exists(
162
161
  url := f"{SF_REST_API_URL_PREFIX}/databases/{self.conn.database}/schemas/{self.conn.schema}/{plural_object_type}/"
163
162
  ):
@@ -5,5 +5,8 @@ class SecretType:
5
5
  def __repr__(self):
6
6
  return "SecretType(***)"
7
7
 
8
- def __str___(self):
8
+ def __str__(self):
9
9
  return "***"
10
+
11
+ def __bool__(self):
12
+ return self.value is not None and self.value != ""
@@ -21,7 +21,7 @@ import shutil
21
21
  import tempfile
22
22
  from contextlib import contextmanager
23
23
  from pathlib import Path
24
- from typing import Optional, Union
24
+ from typing import IO, Any, Generator, Optional, Union
25
25
 
26
26
  from snowflake.cli.api.exceptions import DirectoryIsNotEmptyError, FileTooLargeError
27
27
  from snowflake.cli.api.secure_utils import (
@@ -38,7 +38,7 @@ UNLIMITED = -1
38
38
 
39
39
  class SecurePath:
40
40
  def __init__(self, path: Union[Path, str]):
41
- self._path = Path(path)
41
+ self._path = Path(os.path.expanduser(path))
42
42
 
43
43
  def __repr__(self):
44
44
  return f'SecurePath("{self._path}")'
@@ -72,6 +72,12 @@ class SecurePath:
72
72
  """
73
73
  return SecurePath(self._path.absolute())
74
74
 
75
+ def resolve(self):
76
+ """
77
+ Make the path absolute, resolving symlinks
78
+ """
79
+ return SecurePath(self._path.resolve())
80
+
75
81
  def iterdir(self):
76
82
  """
77
83
  When the path points to a directory, yield path objects of the directory contents.
@@ -102,7 +108,7 @@ class SecurePath:
102
108
  Return True if the path points to a regular file (or a symbolic link pointing to a regular file),
103
109
  False if it points to another kind of file.
104
110
  """
105
- return self._path.is_file()
111
+ return self._path.expanduser().absolute().is_file()
106
112
 
107
113
  def glob(self, pattern: str):
108
114
  """
@@ -110,6 +116,12 @@ class SecurePath:
110
116
  """
111
117
  return self._path.glob(pattern)
112
118
 
119
+ def as_posix(self) -> str:
120
+ """
121
+ Return the string representation of the path with forward slashes (/) as the path separator.
122
+ """
123
+ return self._path.as_posix()
124
+
113
125
  @property
114
126
  def name(self) -> str:
115
127
  """A string representing the final path component."""
@@ -172,7 +184,7 @@ class SecurePath:
172
184
  mode="r",
173
185
  read_file_limit_mb: Optional[int] = None,
174
186
  **open_kwargs,
175
- ):
187
+ ) -> Generator[IO[Any], None, None]:
176
188
  """
177
189
  Open the file pointed by this path and return a file object, as
178
190
  the built-in open() function does.
@@ -85,9 +85,13 @@ class BaseSqlExecutor:
85
85
  )
86
86
  return stream_generator if return_cursors else list()
87
87
 
88
+ def execute_string(self, query: str, **kwargs) -> Iterable[SnowflakeCursor]:
89
+ """Executes a single SQL query and returns the results"""
90
+ return self._execute_string(query, **kwargs)
91
+
88
92
  def execute_query(self, query: str, **kwargs):
89
- """Executes a single SQL query and returns the result"""
90
- *_, last_result = list(self._execute_string(dedent(query), **kwargs))
93
+ """Executes a single SQL query and returns the last result"""
94
+ *_, last_result = list(self.execute_string(dedent(query), **kwargs))
91
95
  return last_result
92
96
 
93
97
  def execute_queries(self, queries: str, **kwargs):
@@ -95,7 +99,7 @@ class BaseSqlExecutor:
95
99
 
96
100
  # Without remove_comments=True, connectors might throw an error if there is a comment at the end of the file
97
101
  return list(
98
- self._execute_string(dedent(queries), remove_comments=True, **kwargs)
102
+ self.execute_string(dedent(queries), remove_comments=True, **kwargs)
99
103
  )
100
104
 
101
105
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: snowflake-cli
3
- Version: 3.4.1
3
+ Version: 3.6.0
4
4
  Summary: Snowflake CLI
5
5
  Project-URL: Source code, https://github.com/snowflakedb/snowflake-cli
6
6
  Project-URL: Bug Tracker, https://github.com/snowflakedb/snowflake-cli/issues
@@ -218,32 +218,32 @@ Classifier: Programming Language :: SQL
218
218
  Classifier: Topic :: Database
219
219
  Requires-Python: >=3.10
220
220
  Requires-Dist: gitpython==3.1.44
221
- Requires-Dist: jinja2==3.1.5
221
+ Requires-Dist: jinja2==3.1.6
222
222
  Requires-Dist: packaging
223
223
  Requires-Dist: pip
224
224
  Requires-Dist: pluggy==1.5.0
225
- Requires-Dist: pydantic==2.10.4
225
+ Requires-Dist: pydantic==2.10.6
226
226
  Requires-Dist: pyyaml==6.0.2
227
227
  Requires-Dist: requests==2.32.3
228
228
  Requires-Dist: requirements-parser==0.11.0
229
229
  Requires-Dist: rich==13.9.4
230
- Requires-Dist: setuptools==75.8.0
231
- Requires-Dist: snowflake-connector-python[secure-local-storage]==3.13.2
230
+ Requires-Dist: setuptools==76.0.0
231
+ Requires-Dist: snowflake-connector-python[secure-local-storage]==3.14.0
232
232
  Requires-Dist: snowflake-core==1.0.2; python_version < '3.12'
233
233
  Requires-Dist: snowflake-snowpark-python<1.26.0,>=1.15.0; python_version < '3.12'
234
234
  Requires-Dist: tomlkit==0.13.2
235
- Requires-Dist: typer==0.12.5
235
+ Requires-Dist: typer==0.15.2
236
236
  Requires-Dist: urllib3<2.4,>=1.24.3
237
237
  Provides-Extra: development
238
- Requires-Dist: coverage==7.6.10; extra == 'development'
239
- Requires-Dist: factory-boy==3.3.1; extra == 'development'
240
- Requires-Dist: faker==35.2.0; extra == 'development'
238
+ Requires-Dist: coverage==7.6.12; extra == 'development'
239
+ Requires-Dist: factory-boy==3.3.3; extra == 'development'
240
+ Requires-Dist: faker==37.0.0; extra == 'development'
241
241
  Requires-Dist: pre-commit>=3.5.0; extra == 'development'
242
+ Requires-Dist: pytest-httpserver==1.1.2; extra == 'development'
242
243
  Requires-Dist: pytest-randomly==3.16.0; extra == 'development'
243
- Requires-Dist: pytest==8.3.4; extra == 'development'
244
- Requires-Dist: syrupy==4.8.1; extra == 'development'
244
+ Requires-Dist: pytest==8.3.5; extra == 'development'
245
+ Requires-Dist: syrupy==4.9.0; extra == 'development'
245
246
  Provides-Extra: packaging
246
- Requires-Dist: pyinstaller~=6.10; extra == 'packaging'
247
247
  Description-Content-Type: text/markdown
248
248
 
249
249
  <!--