snowflake-cli 3.4.1__py3-none-any.whl → 3.5.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 (53) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/_app/cli_app.py +1 -10
  3. snowflake/cli/_app/commands_registration/builtin_plugins.py +5 -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/_plugins/connection/commands.py +2 -4
  8. snowflake/cli/_plugins/helpers/commands.py +3 -4
  9. snowflake/cli/_plugins/notebook/commands.py +3 -4
  10. snowflake/cli/_plugins/plugin/commands.py +79 -0
  11. snowflake/cli/_plugins/plugin/manager.py +74 -0
  12. snowflake/cli/_plugins/plugin/plugin_spec.py +30 -0
  13. snowflake/cli/_plugins/project/__init__.py +0 -0
  14. snowflake/cli/_plugins/project/commands.py +157 -0
  15. snowflake/cli/{_app/api_impl/plugin/__init__.py → _plugins/project/feature_flags.py} +9 -0
  16. snowflake/cli/_plugins/project/manager.py +76 -0
  17. snowflake/cli/_plugins/project/plugin_spec.py +30 -0
  18. snowflake/cli/_plugins/project/project_entity_model.py +40 -0
  19. snowflake/cli/_plugins/snowpark/commands.py +2 -1
  20. snowflake/cli/_plugins/spcs/compute_pool/commands.py +53 -5
  21. snowflake/cli/_plugins/spcs/compute_pool/compute_pool_entity.py +8 -0
  22. snowflake/cli/_plugins/spcs/compute_pool/compute_pool_entity_model.py +37 -0
  23. snowflake/cli/_plugins/spcs/compute_pool/manager.py +45 -0
  24. snowflake/cli/_plugins/spcs/image_repository/commands.py +29 -0
  25. snowflake/cli/_plugins/spcs/image_repository/image_repository_entity.py +8 -0
  26. snowflake/cli/_plugins/spcs/image_repository/image_repository_entity_model.py +8 -0
  27. snowflake/cli/_plugins/spcs/image_repository/manager.py +1 -1
  28. snowflake/cli/_plugins/spcs/services/commands.py +53 -0
  29. snowflake/cli/_plugins/spcs/services/manager.py +114 -0
  30. snowflake/cli/_plugins/spcs/services/service_entity.py +6 -0
  31. snowflake/cli/_plugins/spcs/services/service_entity_model.py +45 -0
  32. snowflake/cli/_plugins/spcs/services/service_project_paths.py +15 -0
  33. snowflake/cli/_plugins/stage/manager.py +2 -2
  34. snowflake/cli/_plugins/streamlit/commands.py +9 -24
  35. snowflake/cli/_plugins/streamlit/manager.py +5 -36
  36. snowflake/cli/api/artifacts/upload.py +51 -0
  37. snowflake/cli/api/commands/flags.py +24 -9
  38. snowflake/cli/api/commands/snow_typer.py +12 -0
  39. snowflake/cli/api/commands/utils.py +2 -0
  40. snowflake/cli/api/config.py +15 -10
  41. snowflake/cli/api/exceptions.py +8 -1
  42. snowflake/cli/api/feature_flags.py +1 -0
  43. snowflake/cli/api/plugins/plugin_config.py +43 -4
  44. snowflake/cli/api/project/definition_helper.py +31 -0
  45. snowflake/cli/api/project/schemas/entities/entities.py +26 -0
  46. {snowflake_cli-3.4.1.dist-info → snowflake_cli-3.5.0.dist-info}/METADATA +9 -9
  47. {snowflake_cli-3.4.1.dist-info → snowflake_cli-3.5.0.dist-info}/RECORD +51 -36
  48. snowflake/cli/_app/api_impl/plugin/plugin_config_provider_impl.py +0 -66
  49. snowflake/cli/api/__init__.py +0 -48
  50. /snowflake/cli/{_app/api_impl → _plugins/plugin}/__init__.py +0 -0
  51. {snowflake_cli-3.4.1.dist-info → snowflake_cli-3.5.0.dist-info}/WHEEL +0 -0
  52. {snowflake_cli-3.4.1.dist-info → snowflake_cli-3.5.0.dist-info}/entry_points.txt +0 -0
  53. {snowflake_cli-3.4.1.dist-info → snowflake_cli-3.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -14,4 +14,4 @@
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
- VERSION = "3.4.1"
17
+ VERSION = "3.5.0"
@@ -25,9 +25,6 @@ import click
25
25
  import typer
26
26
  from click import Context as ClickContext
27
27
  from snowflake.cli import __about__
28
- from snowflake.cli._app.api_impl.plugin.plugin_config_provider_impl import (
29
- PluginConfigProviderImpl,
30
- )
31
28
  from snowflake.cli._app.commands_registration.commands_registration_with_callbacks import (
32
29
  CommandsRegistrationWithCallbacks,
33
30
  )
@@ -42,7 +39,6 @@ from snowflake.cli._app.version_check import (
42
39
  get_new_version_msg,
43
40
  show_new_version_banner_callback,
44
41
  )
45
- from snowflake.cli.api import Api, api_provider
46
42
  from snowflake.cli.api.config import config_init, get_feature_flags_section
47
43
  from snowflake.cli.api.output.formats import OutputFormat
48
44
  from snowflake.cli.api.output.types import CollectionResult
@@ -63,12 +59,7 @@ def _do_not_execute_on_completion(callback):
63
59
 
64
60
  class CliAppFactory:
65
61
  def __init__(self):
66
- api = Api(plugin_config_provider=PluginConfigProviderImpl())
67
- self._api = api
68
- self._commands_registration = CommandsRegistrationWithCallbacks(
69
- api.plugin_config_provider
70
- )
71
- api_provider.register_api(api)
62
+ self._commands_registration = CommandsRegistrationWithCallbacks()
72
63
  self._app: Optional[SnowCliMainTyper] = None
73
64
  self._click_context: Optional[ClickContext] = None
74
65
 
@@ -20,6 +20,8 @@ from snowflake.cli._plugins.init import plugin_spec as init_plugin_spec
20
20
  from snowflake.cli._plugins.nativeapp import plugin_spec as nativeapp_plugin_spec
21
21
  from snowflake.cli._plugins.notebook import plugin_spec as notebook_plugin_spec
22
22
  from snowflake.cli._plugins.object import plugin_spec as object_plugin_spec
23
+ from snowflake.cli._plugins.plugin import plugin_spec as plugin_plugin_spec
24
+ from snowflake.cli._plugins.project import plugin_spec as project_plugin_spec
23
25
  from snowflake.cli._plugins.snowpark import plugin_spec as snowpark_plugin_spec
24
26
  from snowflake.cli._plugins.spcs import plugin_spec as spcs_plugin_spec
25
27
  from snowflake.cli._plugins.sql import plugin_spec as sql_plugin_spec
@@ -34,8 +36,9 @@ def get_builtin_plugin_name_to_plugin_spec():
34
36
  "connection": connection_plugin_spec,
35
37
  "helpers": migrate_plugin_spec,
36
38
  "spcs": spcs_plugin_spec,
37
- "nativeapp": nativeapp_plugin_spec,
39
+ "app": nativeapp_plugin_spec,
38
40
  "object": object_plugin_spec,
41
+ "project": project_plugin_spec,
39
42
  "snowpark": snowpark_plugin_spec,
40
43
  "stage": stage_plugin_spec,
41
44
  "sql": sql_plugin_spec,
@@ -45,6 +48,7 @@ def get_builtin_plugin_name_to_plugin_spec():
45
48
  "cortex": cortex_plugin_spec,
46
49
  "init": init_plugin_spec,
47
50
  "workspace": workspace_plugin_spec,
51
+ "plugin": plugin_plugin_spec,
48
52
  }
49
53
 
50
54
  return plugin_specs
@@ -47,7 +47,9 @@ class CommandPluginsLoader:
47
47
  self._loaded_command_paths: Dict[CommandPath, LoadedCommandPlugin] = {}
48
48
 
49
49
  def register_builtin_plugins(self) -> None:
50
- for plugin_name, plugin in get_builtin_plugin_name_to_plugin_spec().items():
50
+ for plugin_name, plugin in sorted(
51
+ get_builtin_plugin_name_to_plugin_spec().items()
52
+ ):
51
53
  try:
52
54
  self._plugin_manager.register(plugin=plugin, name=plugin_name)
53
55
  except Exception as ex:
@@ -33,8 +33,8 @@ class CommandRegistrationConfig:
33
33
 
34
34
 
35
35
  class CommandsRegistrationWithCallbacks:
36
- def __init__(self, plugin_config_provider: PluginConfigProvider):
37
- self._plugin_config_provider = plugin_config_provider
36
+ def __init__(self):
37
+ self._plugin_config_manager = PluginConfigProvider()
38
38
  self._callbacks_after_registration: List[Callable[[], None]] = []
39
39
  self._commands_registration_config: CommandRegistrationConfig = (
40
40
  CommandRegistrationConfig(enable_external_command_plugins=True)
@@ -58,7 +58,7 @@ class CommandsRegistrationWithCallbacks:
58
58
 
59
59
  def _register_builtin_and_enabled_external_plugin_commands(self):
60
60
  enabled_external_plugins = (
61
- self._plugin_config_provider.get_enabled_plugin_names()
61
+ self._plugin_config_manager.get_enabled_plugin_names()
62
62
  )
63
63
  loaded_command_plugins = load_builtin_and_external_command_plugins(
64
64
  enabled_external_plugins
@@ -16,7 +16,7 @@ from __future__ import annotations
16
16
 
17
17
  import json
18
18
  import sys
19
- from datetime import datetime
19
+ from datetime import date, datetime
20
20
  from json import JSONEncoder
21
21
  from pathlib import Path
22
22
  from textwrap import indent
@@ -57,7 +57,7 @@ class CustomJSONEncoder(JSONEncoder):
57
57
  return o.result
58
58
  if isinstance(o, (CollectionResult, MultipleResults)):
59
59
  return list(o.result)
60
- if isinstance(o, datetime):
60
+ if isinstance(o, (date, datetime)):
61
61
  return o.isoformat()
62
62
  if isinstance(o, Path):
63
63
  return str(o)
@@ -287,9 +287,7 @@ def add(
287
287
  ConnectionConfig(**connection_options),
288
288
  )
289
289
  if set_as_default:
290
- set_config_value(
291
- section=None, key="default_connection_name", value=connection_name
292
- )
290
+ set_config_value(path=["default_connection_name"], value=connection_name)
293
291
 
294
292
  return MessageResult(
295
293
  f"Wrote new connection {connection_name} to {connections_file}"
@@ -357,7 +355,7 @@ def set_default(
357
355
  ):
358
356
  """Changes default connection to provided value."""
359
357
  get_connection_dict(connection_name=name)
360
- set_config_value(section=None, key="default_connection_name", value=name)
358
+ set_config_value(path=["default_connection_name"], value=name)
361
359
  return MessageResult(f"Default connection set to: {name}")
362
360
 
363
361
 
@@ -49,7 +49,7 @@ def v1_to_v2(
49
49
  accept_templates: bool = typer.Option(
50
50
  False, "-t", "--accept-templates", help="Allows the migration of templates."
51
51
  ),
52
- migrate_local_yml: (bool | None) = typer.Option(
52
+ migrate_local_yml: Optional[bool] = typer.Option(
53
53
  None,
54
54
  "-l",
55
55
  "--migrate-local-overrides/--no-migrate-local-overrides",
@@ -234,7 +234,7 @@ def _validate_imported_default_connection_name(
234
234
 
235
235
 
236
236
  def _convert_connection_from_snowsql_config_section(
237
- snowsql_connection: list[tuple[str, Any]]
237
+ snowsql_connection: list[tuple[str, Any]],
238
238
  ) -> dict[str, Any]:
239
239
  from ast import literal_eval
240
240
 
@@ -290,7 +290,6 @@ def _validate_and_save_connections_imported_from_snowsql(
290
290
  f"Setting [{default_cli_connection_name}] connection as Snowflake CLI's default connection."
291
291
  )
292
292
  set_config_value(
293
- section=None,
294
- key="default_connection_name",
293
+ path=["default_connection_name"],
295
294
  value=default_cli_connection_name,
296
295
  )
@@ -21,9 +21,7 @@ from snowflake.cli._plugins.notebook.notebook_entity_model import NotebookEntity
21
21
  from snowflake.cli._plugins.notebook.types import NotebookStagePath
22
22
  from snowflake.cli._plugins.workspace.manager import WorkspaceManager
23
23
  from snowflake.cli.api.cli_global_context import get_cli_context
24
- from snowflake.cli.api.commands.decorators import (
25
- with_project_definition,
26
- )
24
+ from snowflake.cli.api.commands.decorators import with_project_definition
27
25
  from snowflake.cli.api.commands.flags import (
28
26
  ReplaceOption,
29
27
  entity_argument,
@@ -107,7 +105,8 @@ def create(
107
105
  def deploy(
108
106
  entity_id: str = entity_argument("notebook"),
109
107
  replace: bool = ReplaceOption(
110
- help="Replace notebook object if it already exists.",
108
+ help="Replace notebook object if it already exists. It only uploads new and overwrites existing files, "
109
+ "but does not remove any files already on the stage.",
111
110
  ),
112
111
  **options,
113
112
  ) -> CommandResult:
@@ -0,0 +1,79 @@
1
+ # Copyright (c) 2024 Snowflake Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ import logging
18
+ from typing import Dict, List
19
+
20
+ import typer
21
+ from snowflake.cli._plugins.plugin.manager import PluginManager
22
+ from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
23
+ from snowflake.cli.api.output.types import (
24
+ CollectionResult,
25
+ CommandResult,
26
+ MessageResult,
27
+ )
28
+
29
+ log = logging.getLogger(__name__)
30
+
31
+ app = SnowTyperFactory(
32
+ name="plugin",
33
+ help="Plugin management commands.",
34
+ is_hidden=lambda: True,
35
+ )
36
+
37
+
38
+ @app.command(name="enable", requires_connection=False)
39
+ def enable(
40
+ plugin_name: str = typer.Argument(None, help="Plugin name"),
41
+ **options,
42
+ ) -> CommandResult:
43
+ """Enables a plugin with a given name."""
44
+ plugin_manager = PluginManager()
45
+ plugin_manager.assert_plugin_is_installed(plugin_name)
46
+ plugin_manager.enable_plugin(plugin_name)
47
+
48
+ return MessageResult(f"Plugin {plugin_name} successfully enabled.")
49
+
50
+
51
+ @app.command(name="disable", requires_connection=False)
52
+ def disable(
53
+ plugin_name: str = typer.Argument(None, help="Plugin name"),
54
+ **options,
55
+ ) -> CommandResult:
56
+ """Disables a plugin with a given name."""
57
+ plugin_manager = PluginManager()
58
+ plugin_manager.assert_plugin_is_installed(plugin_name)
59
+ plugin_manager.disable_plugin(plugin_name)
60
+
61
+ return MessageResult(f"Plugin {plugin_name} successfully disabled.")
62
+
63
+
64
+ @app.command(name="list", requires_connection=False)
65
+ def list_(
66
+ **options,
67
+ ) -> CommandResult:
68
+ """Lists all installed plugins."""
69
+ plugin_manager = PluginManager()
70
+ result: List[Dict[str, str]] = []
71
+ for plugin_name in sorted(plugin_manager.get_installed_plugin_names()):
72
+ result.append(
73
+ {
74
+ "plugin name": plugin_name,
75
+ "enabled": plugin_manager.is_plugin_enabled(plugin_name),
76
+ }
77
+ )
78
+
79
+ return CollectionResult(result)
@@ -0,0 +1,74 @@
1
+ # Copyright (c) 2024 Snowflake Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ import importlib
15
+ from typing import List
16
+
17
+ from snowflake.cli.api.config import (
18
+ PLUGIN_ENABLED_KEY,
19
+ PLUGINS_SECTION_PATH,
20
+ config_section_exists,
21
+ get_config_section,
22
+ set_config_value,
23
+ )
24
+ from snowflake.cli.api.exceptions import PluginNotInstalledError
25
+ from snowflake.cli.api.plugins.command import SNOWCLI_COMMAND_PLUGIN_NAMESPACE
26
+ from snowflake.cli.api.plugins.plugin_config import PluginConfigProvider
27
+
28
+
29
+ class PluginManager:
30
+ """
31
+ Manage installation of plugins.
32
+ """
33
+
34
+ def enable_plugin(self, plugin_name: str):
35
+ self._change_plugin_enabled(plugin_name, enable=True)
36
+
37
+ def disable_plugin(self, plugin_name: str):
38
+ self._change_plugin_enabled(plugin_name, enable=False)
39
+
40
+ @staticmethod
41
+ def _change_plugin_enabled(plugin_name: str, enable: bool):
42
+ plugin_config_path = PLUGINS_SECTION_PATH + [plugin_name]
43
+
44
+ if config_section_exists(*plugin_config_path):
45
+ plugin_config = get_config_section(*plugin_config_path)
46
+ elif enable:
47
+ plugin_config = {}
48
+ else:
49
+ # do not add a new plugin config if user wants to disable a plugin which is not configured
50
+ # (plugins are disabled by default)
51
+ return
52
+
53
+ plugin_config[PLUGIN_ENABLED_KEY] = enable
54
+ set_config_value(path=plugin_config_path, value=plugin_config)
55
+
56
+ @staticmethod
57
+ def is_plugin_enabled(plugin_name: str) -> bool:
58
+ return PluginConfigProvider.get_config(plugin_name).is_plugin_enabled
59
+
60
+ def assert_plugin_is_installed(self, plugin_name: str):
61
+ installed_plugins = self.get_installed_plugin_names()
62
+ if plugin_name not in installed_plugins:
63
+ raise PluginNotInstalledError(
64
+ plugin_name, installed_plugins=sorted(installed_plugins)
65
+ )
66
+
67
+ @staticmethod
68
+ def get_installed_plugin_names() -> List[str]:
69
+ return [
70
+ entry_point.name
71
+ for entry_point in importlib.metadata.entry_points(
72
+ group=SNOWCLI_COMMAND_PLUGIN_NAMESPACE
73
+ )
74
+ ]
@@ -0,0 +1,30 @@
1
+ # Copyright (c) 2024 Snowflake Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from snowflake.cli._plugins.plugin import commands
16
+ from snowflake.cli.api.plugins.command import (
17
+ SNOWCLI_ROOT_COMMAND_PATH,
18
+ CommandSpec,
19
+ CommandType,
20
+ plugin_hook_impl,
21
+ )
22
+
23
+
24
+ @plugin_hook_impl
25
+ def command_spec():
26
+ return CommandSpec(
27
+ parent_command_path=SNOWCLI_ROOT_COMMAND_PATH,
28
+ command_type=CommandType.COMMAND_GROUP,
29
+ typer_instance=commands.app.create_instance(),
30
+ )
File without changes
@@ -0,0 +1,157 @@
1
+ # Copyright (c) 2024 Snowflake Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from typing import List, Optional
16
+
17
+ import typer
18
+ from snowflake.cli._plugins.project.feature_flags import FeatureFlag
19
+ from snowflake.cli._plugins.project.manager import ProjectManager
20
+ from snowflake.cli._plugins.project.project_entity_model import (
21
+ ProjectEntityModel,
22
+ )
23
+ from snowflake.cli._plugins.stage.manager import StageManager
24
+ from snowflake.cli.api.artifacts.upload import put_files
25
+ from snowflake.cli.api.cli_global_context import get_cli_context
26
+ from snowflake.cli.api.commands.decorators import with_project_definition
27
+ from snowflake.cli.api.commands.flags import (
28
+ entity_argument,
29
+ identifier_argument,
30
+ variables_option,
31
+ )
32
+ from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
33
+ from snowflake.cli.api.commands.utils import get_entity_for_operation
34
+ from snowflake.cli.api.console.console import cli_console
35
+ 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
38
+
39
+ app = SnowTyperFactory(
40
+ name="project",
41
+ help="Manages projects in Snowflake.",
42
+ is_hidden=FeatureFlag.ENABLE_SNOWFLAKE_PROJECTS.is_disabled,
43
+ )
44
+
45
+ project_identifier = identifier_argument(sf_object="project", example="MY_PROJECT")
46
+ version_flag = typer.Option(
47
+ ..., "--version", help="Version of the project to use.", show_default=False
48
+ )
49
+ variables_flag = variables_option(
50
+ 'Variables for the execution context; for example: `-D "<key>=<value>"`.'
51
+ )
52
+
53
+
54
+ @app.command(requires_connection=True)
55
+ def execute(
56
+ identifier: FQN = project_identifier,
57
+ version: str = version_flag,
58
+ variables: Optional[List[str]] = variables_flag,
59
+ **options,
60
+ ):
61
+ """
62
+ Executes a project.
63
+ """
64
+ result = ProjectManager().execute(
65
+ project_name=identifier, version=version, variables=variables
66
+ )
67
+ return SingleQueryResult(result)
68
+
69
+
70
+ @app.command(requires_connection=True)
71
+ def dry_run(
72
+ identifier: FQN = project_identifier,
73
+ version: str = version_flag,
74
+ variables: Optional[List[str]] = variables_flag,
75
+ **options,
76
+ ):
77
+ """
78
+ Validates a project.
79
+ """
80
+ result = ProjectManager().execute(
81
+ project_name=identifier, version=version, dry_run=True, variables=variables
82
+ )
83
+ return SingleQueryResult(result)
84
+
85
+
86
+ @app.command(requires_connection=True)
87
+ @with_project_definition()
88
+ def create_version(
89
+ entity_id: str = entity_argument("project"),
90
+ **options,
91
+ ):
92
+ """
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.
94
+ """
95
+ cli_context = get_cli_context()
96
+ project: ProjectEntityModel = get_entity_for_operation(
97
+ cli_context=cli_context,
98
+ entity_id=entity_id,
99
+ project_definition=cli_context.project_definition,
100
+ entity_type="project",
101
+ )
102
+
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)
122
+
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.")
126
+
127
+
128
+ @app.command(requires_connection=True)
129
+ @with_project_definition()
130
+ def add_version(
131
+ 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(
140
+ None, "--alias", help="Alias for the version.", show_default=False
141
+ ),
142
+ comment: str
143
+ | None = typer.Option(
144
+ None, "--comment", help="Version comment.", show_default=False
145
+ ),
146
+ **options,
147
+ ):
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,
153
+ from_stage=_from,
154
+ alias=alias,
155
+ comment=comment,
156
+ )
157
+ return MessageResult("Version added.")
@@ -11,3 +11,12 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
+
15
+ from enum import unique
16
+
17
+ from snowflake.cli.api.feature_flags import BooleanFlag, FeatureFlagMixin
18
+
19
+
20
+ @unique
21
+ class FeatureFlag(FeatureFlagMixin):
22
+ ENABLE_SNOWFLAKE_PROJECTS = BooleanFlag("ENABLE_SNOWFLAKE_PROJECTS", False)
@@ -0,0 +1,76 @@
1
+ # Copyright (c) 2024 Snowflake Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from textwrap import dedent
15
+ from typing import List
16
+
17
+ from snowflake.cli._plugins.stage.manager import StageManager
18
+ from snowflake.cli.api.commands.utils import parse_key_value_variables
19
+ from snowflake.cli.api.identifiers import FQN
20
+ from snowflake.cli.api.sql_execution import SqlExecutionMixin
21
+ from snowflake.cli.api.stage_path import StagePath
22
+
23
+
24
+ class ProjectManager(SqlExecutionMixin):
25
+ def execute(
26
+ self,
27
+ project_name: FQN,
28
+ version: str | None = None,
29
+ variables: List[str] | None = None,
30
+ dry_run: bool = False,
31
+ ):
32
+ query = f"EXECUTE PROJECT {project_name.sql_identifier}"
33
+ if variables:
34
+ query += StageManager.parse_execute_variables(
35
+ parse_key_value_variables(variables)
36
+ )
37
+ if version:
38
+ query += f" WITH VERSION {version}"
39
+ if dry_run:
40
+ query += " DRY_RUN=TRUE"
41
+ return self.execute_query(query=query)
42
+
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
+
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
+ )
57
+
58
+ def add_version(
59
+ self,
60
+ project_name: str | FQN,
61
+ from_stage: str,
62
+ alias: str | None = None,
63
+ comment: str | None = None,
64
+ ):
65
+ project_name = (
66
+ project_name
67
+ if isinstance(project_name, FQN)
68
+ else FQN.from_string(project_name)
69
+ )
70
+ query = f"ALTER PROJECT {project_name.identifier} ADD VERSION"
71
+ if alias:
72
+ query += f" IF NOT EXIST {alias}"
73
+ query += f" FROM {from_stage}"
74
+ if comment:
75
+ query += f" COMMENT = '{comment}'"
76
+ return self.execute_query(query=query)
@@ -0,0 +1,30 @@
1
+ # Copyright (c) 2024 Snowflake Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from snowflake.cli._plugins.project import commands
16
+ from snowflake.cli.api.plugins.command import (
17
+ SNOWCLI_ROOT_COMMAND_PATH,
18
+ CommandSpec,
19
+ CommandType,
20
+ plugin_hook_impl,
21
+ )
22
+
23
+
24
+ @plugin_hook_impl
25
+ def command_spec():
26
+ return CommandSpec(
27
+ parent_command_path=SNOWCLI_ROOT_COMMAND_PATH,
28
+ command_type=CommandType.COMMAND_GROUP,
29
+ typer_instance=commands.app.create_instance(),
30
+ )