snowflake-cli 3.2.2__py3-none-any.whl → 3.4.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/_app/__main__.py +2 -2
  3. snowflake/cli/_app/cli_app.py +224 -192
  4. snowflake/cli/_app/commands_registration/commands_registration_with_callbacks.py +1 -27
  5. snowflake/cli/_app/constants.py +4 -0
  6. snowflake/cli/_app/snow_connector.py +12 -0
  7. snowflake/cli/_app/telemetry.py +10 -3
  8. snowflake/cli/_plugins/connection/util.py +12 -19
  9. snowflake/cli/_plugins/cortex/commands.py +2 -4
  10. snowflake/cli/_plugins/git/manager.py +1 -1
  11. snowflake/cli/_plugins/helpers/commands.py +207 -1
  12. snowflake/cli/_plugins/nativeapp/artifacts.py +16 -628
  13. snowflake/cli/_plugins/nativeapp/bundle_context.py +1 -1
  14. snowflake/cli/_plugins/nativeapp/codegen/artifact_processor.py +1 -1
  15. snowflake/cli/_plugins/nativeapp/codegen/compiler.py +42 -20
  16. snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +9 -2
  17. snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +6 -3
  18. snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +44 -34
  19. snowflake/cli/_plugins/nativeapp/commands.py +113 -21
  20. snowflake/cli/_plugins/nativeapp/constants.py +5 -0
  21. snowflake/cli/_plugins/nativeapp/entities/application.py +226 -296
  22. snowflake/cli/_plugins/nativeapp/entities/application_package.py +911 -141
  23. snowflake/cli/_plugins/nativeapp/entities/application_package_child_interface.py +43 -0
  24. snowflake/cli/_plugins/nativeapp/feature_flags.py +5 -1
  25. snowflake/cli/_plugins/nativeapp/release_channel/__init__.py +13 -0
  26. snowflake/cli/_plugins/nativeapp/release_channel/commands.py +246 -0
  27. snowflake/cli/_plugins/nativeapp/release_directive/__init__.py +13 -0
  28. snowflake/cli/_plugins/nativeapp/release_directive/commands.py +243 -0
  29. snowflake/cli/_plugins/nativeapp/same_account_install_method.py +9 -17
  30. snowflake/cli/_plugins/nativeapp/sf_facade_exceptions.py +80 -0
  31. snowflake/cli/_plugins/nativeapp/sf_sql_facade.py +1184 -80
  32. snowflake/cli/_plugins/nativeapp/utils.py +11 -0
  33. snowflake/cli/_plugins/nativeapp/v2_conversions/compat.py +7 -3
  34. snowflake/cli/_plugins/nativeapp/version/commands.py +32 -5
  35. snowflake/cli/_plugins/notebook/commands.py +55 -2
  36. snowflake/cli/_plugins/notebook/exceptions.py +1 -1
  37. snowflake/cli/_plugins/notebook/manager.py +7 -5
  38. snowflake/cli/_plugins/notebook/notebook_entity.py +120 -0
  39. snowflake/cli/_plugins/notebook/notebook_entity_model.py +42 -0
  40. snowflake/cli/_plugins/notebook/notebook_project_paths.py +15 -0
  41. snowflake/cli/_plugins/notebook/types.py +3 -0
  42. snowflake/cli/_plugins/snowpark/commands.py +48 -30
  43. snowflake/cli/_plugins/snowpark/common.py +47 -2
  44. snowflake/cli/_plugins/snowpark/snowpark_entity.py +247 -4
  45. snowflake/cli/_plugins/snowpark/snowpark_entity_model.py +18 -30
  46. snowflake/cli/_plugins/snowpark/snowpark_project_paths.py +156 -23
  47. snowflake/cli/_plugins/snowpark/zipper.py +33 -1
  48. snowflake/cli/_plugins/spcs/common.py +129 -0
  49. snowflake/cli/_plugins/spcs/services/commands.py +131 -14
  50. snowflake/cli/_plugins/spcs/services/manager.py +169 -1
  51. snowflake/cli/_plugins/stage/commands.py +2 -1
  52. snowflake/cli/_plugins/stage/diff.py +60 -39
  53. snowflake/cli/_plugins/stage/manager.py +34 -13
  54. snowflake/cli/_plugins/stage/utils.py +1 -1
  55. snowflake/cli/_plugins/streamlit/commands.py +10 -1
  56. snowflake/cli/_plugins/streamlit/manager.py +70 -22
  57. snowflake/cli/_plugins/streamlit/streamlit_entity.py +131 -1
  58. snowflake/cli/_plugins/streamlit/streamlit_entity_model.py +14 -24
  59. snowflake/cli/_plugins/streamlit/streamlit_project_paths.py +30 -0
  60. snowflake/cli/_plugins/workspace/commands.py +6 -5
  61. snowflake/cli/_plugins/workspace/manager.py +9 -5
  62. snowflake/cli/api/artifacts/__init__.py +13 -0
  63. snowflake/cli/api/artifacts/bundle_map.py +500 -0
  64. snowflake/cli/api/artifacts/common.py +78 -0
  65. snowflake/cli/api/artifacts/utils.py +82 -0
  66. snowflake/cli/api/cli_global_context.py +36 -2
  67. snowflake/cli/api/commands/flags.py +10 -4
  68. snowflake/cli/api/commands/utils.py +28 -2
  69. snowflake/cli/api/config.py +6 -2
  70. snowflake/cli/api/connections.py +12 -1
  71. snowflake/cli/api/constants.py +10 -1
  72. snowflake/cli/api/entities/common.py +81 -14
  73. snowflake/cli/api/entities/resolver.py +160 -0
  74. snowflake/cli/api/entities/utils.py +65 -23
  75. snowflake/cli/api/errno.py +63 -3
  76. snowflake/cli/api/feature_flags.py +19 -4
  77. snowflake/cli/api/metrics.py +21 -27
  78. snowflake/cli/api/project/definition_conversion.py +4 -4
  79. snowflake/cli/api/project/project_paths.py +28 -0
  80. snowflake/cli/api/project/schemas/entities/common.py +130 -1
  81. snowflake/cli/api/project/schemas/entities/entities.py +4 -0
  82. snowflake/cli/api/project/schemas/project_definition.py +54 -6
  83. snowflake/cli/api/project/schemas/updatable_model.py +2 -2
  84. snowflake/cli/api/project/schemas/v1/native_app/native_app.py +5 -7
  85. snowflake/cli/api/project/schemas/v1/streamlit/streamlit.py +1 -1
  86. snowflake/cli/api/project/util.py +45 -0
  87. snowflake/cli/api/secure_path.py +6 -0
  88. snowflake/cli/api/sql_execution.py +5 -1
  89. snowflake/cli/api/stage_path.py +7 -2
  90. snowflake/cli/api/utils/graph.py +3 -0
  91. snowflake/cli/api/utils/path_utils.py +24 -0
  92. {snowflake_cli-3.2.2.dist-info → snowflake_cli-3.4.1.dist-info}/METADATA +14 -15
  93. {snowflake_cli-3.2.2.dist-info → snowflake_cli-3.4.1.dist-info}/RECORD +96 -82
  94. {snowflake_cli-3.2.2.dist-info → snowflake_cli-3.4.1.dist-info}/WHEEL +1 -1
  95. snowflake/cli/api/project/schemas/v1/native_app/path_mapping.py +0 -65
  96. {snowflake_cli-3.2.2.dist-info → snowflake_cli-3.4.1.dist-info}/entry_points.txt +0 -0
  97. {snowflake_cli-3.2.2.dist-info → snowflake_cli-3.4.1.dist-info}/licenses/LICENSE +0 -0
@@ -33,23 +33,12 @@ from snowflake.cli._plugins.nativeapp.codegen.snowpark.python_processor import (
33
33
  from snowflake.cli._plugins.nativeapp.codegen.templates.templates_processor import (
34
34
  TemplatesProcessor,
35
35
  )
36
- from snowflake.cli._plugins.nativeapp.feature_flags import FeatureFlag
37
36
  from snowflake.cli.api.cli_global_context import get_cli_context
38
37
  from snowflake.cli.api.console import cli_console as cc
39
38
  from snowflake.cli.api.metrics import CLICounterField
40
- from snowflake.cli.api.project.schemas.v1.native_app.path_mapping import (
41
- ProcessorMapping,
42
- )
43
-
44
- SNOWPARK_PROCESSOR = "snowpark"
45
- NA_SETUP_PROCESSOR = "native app setup"
46
- TEMPLATES_PROCESSOR = "templates"
39
+ from snowflake.cli.api.project.schemas.entities.common import ProcessorMapping
47
40
 
48
- _REGISTERED_PROCESSORS_BY_NAME = {
49
- SNOWPARK_PROCESSOR: SnowparkAnnotationProcessor,
50
- NA_SETUP_PROCESSOR: NativeAppSetupProcessor,
51
- TEMPLATES_PROCESSOR: TemplatesProcessor,
52
- }
41
+ ProcessorClassType = type[ArtifactProcessor]
53
42
 
54
43
 
55
44
  class NativeAppCompiler:
@@ -66,10 +55,28 @@ class NativeAppCompiler:
66
55
  bundle_ctx: BundleContext,
67
56
  ):
68
57
  self._assert_absolute_paths(bundle_ctx)
58
+ self._processor_classes_by_name: Dict[str, ProcessorClassType] = {}
69
59
  self._bundle_ctx = bundle_ctx
70
60
  # dictionary of all processors created and shared between different artifact objects.
71
61
  self.cached_processors: Dict[str, ArtifactProcessor] = {}
72
62
 
63
+ self.register(SnowparkAnnotationProcessor)
64
+ self.register(NativeAppSetupProcessor)
65
+ self.register(TemplatesProcessor)
66
+
67
+ def register(self, processor_cls: ProcessorClassType):
68
+ """
69
+ Registers a processor class to enable.
70
+ """
71
+
72
+ name = getattr(processor_cls, "NAME", None)
73
+ assert name is not None
74
+
75
+ if name in self._processor_classes_by_name:
76
+ raise ValueError(f"Processor {name} is already registered")
77
+
78
+ self._processor_classes_by_name[str(name)] = processor_cls
79
+
73
80
  @staticmethod
74
81
  def _assert_absolute_paths(bundle_ctx: BundleContext):
75
82
  for name in ["Project", "Deploy", "Bundle", "Generated"]:
@@ -88,7 +95,10 @@ class NativeAppCompiler:
88
95
  if not self._should_invoke_processors():
89
96
  return
90
97
 
91
- with cc.phase("Invoking artifact processors"):
98
+ with (
99
+ cc.phase("Invoking artifact processors"),
100
+ get_cli_context().metrics.span("artifact_processors"),
101
+ ):
92
102
  if self._bundle_ctx.generated_root.exists():
93
103
  raise ClickException(
94
104
  f"Path {self._bundle_ctx.generated_root} already exists. Please choose a different name for your generated directory in the project definition file."
@@ -125,8 +135,8 @@ class NativeAppCompiler:
125
135
  if current_processor is not None:
126
136
  return current_processor
127
137
 
128
- processor_factory = _REGISTERED_PROCESSORS_BY_NAME.get(processor_name)
129
- if processor_factory is None:
138
+ processor_cls = self._processor_classes_by_name.get(processor_name)
139
+ if processor_cls is None:
130
140
  # No registered processor with the specified name
131
141
  return None
132
142
 
@@ -138,7 +148,7 @@ class NativeAppCompiler:
138
148
  processor_ctx.generated_root = (
139
149
  self._bundle_ctx.generated_root / processor_subdirectory
140
150
  )
141
- current_processor = processor_factory(processor_ctx)
151
+ current_processor = processor_cls(processor_ctx)
142
152
  self.cached_processors[processor_name] = current_processor
143
153
 
144
154
  return current_processor
@@ -151,6 +161,18 @@ class NativeAppCompiler:
151
161
  return False
152
162
 
153
163
  def _is_enabled(self, processor: ProcessorMapping) -> bool:
154
- if processor.name.lower() == NA_SETUP_PROCESSOR:
155
- return FeatureFlag.ENABLE_NATIVE_APP_PYTHON_SETUP.is_enabled()
156
- return True
164
+ """
165
+ Determines is a process is enabled. All processors are considered enabled
166
+ unless they are explicitly disabled, typically via a feature flag.
167
+ """
168
+ processor_name = processor.name.lower()
169
+ processor_cls = self._processor_classes_by_name.get(processor_name)
170
+ if processor_cls is None:
171
+ # Unknown processor, consider it enabled, even though trying to
172
+ # invoke it later will raise an exception
173
+ return True
174
+
175
+ # if the processor class defines a static method named "is_enabled", then
176
+ # call it. Otherwise, it's considered enabled by default.
177
+ is_enabled_fn = getattr(processor_cls, "is_enabled", lambda: True)
178
+ return is_enabled_fn()
@@ -23,7 +23,6 @@ from typing import List, Optional
23
23
  import yaml
24
24
  from click import ClickException
25
25
  from snowflake.cli._plugins.nativeapp.artifacts import (
26
- BundleMap,
27
26
  find_manifest_file,
28
27
  find_setup_script_file,
29
28
  )
@@ -36,9 +35,11 @@ from snowflake.cli._plugins.nativeapp.codegen.sandbox import (
36
35
  SandboxEnvBuilder,
37
36
  execute_script_in_sandbox,
38
37
  )
38
+ from snowflake.cli._plugins.nativeapp.feature_flags import FeatureFlag
39
39
  from snowflake.cli._plugins.stage.diff import to_stage_path
40
+ from snowflake.cli.api.artifacts.bundle_map import BundleMap
40
41
  from snowflake.cli.api.console import cli_console as cc
41
- from snowflake.cli.api.project.schemas.v1.native_app.path_mapping import (
42
+ from snowflake.cli.api.project.schemas.entities.common import (
42
43
  PathMapping,
43
44
  ProcessorMapping,
44
45
  )
@@ -74,9 +75,15 @@ def safe_set(d: dict, *keys: str, **kwargs) -> None:
74
75
 
75
76
 
76
77
  class NativeAppSetupProcessor(ArtifactProcessor):
78
+ NAME = "native app setup"
79
+
77
80
  def __init__(self, *args, **kwargs):
78
81
  super().__init__(*args, **kwargs)
79
82
 
83
+ @staticmethod
84
+ def is_enabled() -> bool:
85
+ return FeatureFlag.ENABLE_NATIVE_APP_PYTHON_SETUP.is_enabled()
86
+
80
87
  def process(
81
88
  self,
82
89
  artifact_to_process: PathMapping,
@@ -22,7 +22,6 @@ from typing import Any, Dict, List, Optional, Set
22
22
 
23
23
  from pydantic import ValidationError
24
24
  from snowflake.cli._plugins.nativeapp.artifacts import (
25
- BundleMap,
26
25
  find_setup_script_file,
27
26
  )
28
27
  from snowflake.cli._plugins.nativeapp.codegen.artifact_processor import (
@@ -48,10 +47,11 @@ from snowflake.cli._plugins.nativeapp.codegen.snowpark.models import (
48
47
  NativeAppExtensionFunction,
49
48
  )
50
49
  from snowflake.cli._plugins.stage.diff import to_stage_path
51
- from snowflake.cli.api.cli_global_context import get_cli_context
50
+ from snowflake.cli.api.artifacts.bundle_map import BundleMap
51
+ from snowflake.cli.api.cli_global_context import get_cli_context, span
52
52
  from snowflake.cli.api.console import cli_console as cc
53
53
  from snowflake.cli.api.metrics import CLICounterField
54
- from snowflake.cli.api.project.schemas.v1.native_app.path_mapping import (
54
+ from snowflake.cli.api.project.schemas.entities.common import (
55
55
  PathMapping,
56
56
  ProcessorMapping,
57
57
  )
@@ -164,9 +164,12 @@ class SnowparkAnnotationProcessor(ArtifactProcessor):
164
164
  and generate SQL code for creation of extension functions based on those discovered objects.
165
165
  """
166
166
 
167
+ NAME = "snowpark"
168
+
167
169
  def __init__(self, *args, **kwargs):
168
170
  super().__init__(*args, **kwargs)
169
171
 
172
+ @span("snowpark_processor")
170
173
  def process(
171
174
  self,
172
175
  artifact_to_process: PathMapping,
@@ -18,15 +18,15 @@ from pathlib import Path
18
18
  from typing import Any, Optional
19
19
 
20
20
  import jinja2
21
- from snowflake.cli._plugins.nativeapp.artifacts import BundleMap
22
21
  from snowflake.cli._plugins.nativeapp.codegen.artifact_processor import (
23
22
  ArtifactProcessor,
24
23
  )
25
24
  from snowflake.cli._plugins.nativeapp.exceptions import InvalidTemplateInFileError
26
- from snowflake.cli.api.cli_global_context import get_cli_context
25
+ from snowflake.cli.api.artifacts.bundle_map import BundleMap
26
+ from snowflake.cli.api.cli_global_context import get_cli_context, span
27
27
  from snowflake.cli.api.console import cli_console as cc
28
28
  from snowflake.cli.api.metrics import CLICounterField
29
- from snowflake.cli.api.project.schemas.v1.native_app.path_mapping import (
29
+ from snowflake.cli.api.project.schemas.entities.common import (
30
30
  PathMapping,
31
31
  ProcessorMapping,
32
32
  )
@@ -49,6 +49,8 @@ class TemplatesProcessor(ArtifactProcessor):
49
49
  Processor class to perform template expansion on all relevant artifacts (specified in the project definition file).
50
50
  """
51
51
 
52
+ NAME = "templates"
53
+
52
54
  def expand_templates_in_file(
53
55
  self, src: Path, dest: Path, template_context: dict[str, Any] | None = None
54
56
  ) -> None:
@@ -58,39 +60,47 @@ class TemplatesProcessor(ArtifactProcessor):
58
60
  if src.is_dir():
59
61
  return
60
62
 
61
- with self.edit_file(dest) as file:
62
- if not has_client_side_templates(file.contents) and not (
63
- _is_sql_file(dest) and has_sql_templates(file.contents)
64
- ):
65
- return
66
-
67
- src_file_name = src.relative_to(self._bundle_ctx.project_root)
68
- cc.step(f"Expanding templates in {src_file_name}")
69
- with cc.indented():
70
- try:
71
- jinja_env = (
72
- choose_sql_jinja_env_based_on_template_syntax(
73
- file.contents, reference_name=src_file_name
63
+ src_file_name = src.relative_to(self._bundle_ctx.project_root)
64
+
65
+ try:
66
+ with self.edit_file(dest) as file:
67
+ if not has_client_side_templates(file.contents) and not (
68
+ _is_sql_file(dest) and has_sql_templates(file.contents)
69
+ ):
70
+ return
71
+ cc.step(f"Expanding templates in {src_file_name}")
72
+ with cc.indented():
73
+ try:
74
+ jinja_env = (
75
+ choose_sql_jinja_env_based_on_template_syntax(
76
+ file.contents, reference_name=src_file_name
77
+ )
78
+ if _is_sql_file(dest)
79
+ else get_client_side_jinja_env()
80
+ )
81
+ expanded_template = jinja_env.from_string(file.contents).render(
82
+ template_context or get_cli_context().template_context
74
83
  )
75
- if _is_sql_file(dest)
76
- else get_client_side_jinja_env()
77
- )
78
- expanded_template = jinja_env.from_string(file.contents).render(
79
- template_context or get_cli_context().template_context
80
- )
81
-
82
- # For now, we are printing the source file path in the error message
83
- # instead of the destination file path to make it easier for the user
84
- # to identify the file that has the error, and edit the correct file.
85
- except jinja2.TemplateSyntaxError as e:
86
- raise InvalidTemplateInFileError(src_file_name, e, e.lineno) from e
87
-
88
- except jinja2.UndefinedError as e:
89
- raise InvalidTemplateInFileError(src_file_name, e) from e
90
-
91
- if expanded_template != file.contents:
92
- file.edited_contents = expanded_template
93
84
 
85
+ # For now, we are printing the source file path in the error message
86
+ # instead of the destination file path to make it easier for the user
87
+ # to identify the file that has the error, and edit the correct file.
88
+ except jinja2.TemplateSyntaxError as e:
89
+ raise InvalidTemplateInFileError(
90
+ src_file_name, e, e.lineno
91
+ ) from e
92
+
93
+ except jinja2.UndefinedError as e:
94
+ raise InvalidTemplateInFileError(src_file_name, e) from e
95
+
96
+ if expanded_template != file.contents:
97
+ file.edited_contents = expanded_template
98
+ except UnicodeDecodeError as err:
99
+ cc.warning(
100
+ f"Could not read file {src_file_name}, error: {err.reason}. Skipping this file."
101
+ )
102
+
103
+ @span("templates_processor")
94
104
  def process(
95
105
  self,
96
106
  artifact_to_process: PathMapping,
@@ -22,6 +22,7 @@ from textwrap import dedent
22
22
  from typing import Generator, Iterable, List, Optional, cast
23
23
 
24
24
  import typer
25
+ from snowflake.cli._plugins.nativeapp.artifacts import VersionInfo
25
26
  from snowflake.cli._plugins.nativeapp.common_flags import (
26
27
  ForceOption,
27
28
  InteractiveOption,
@@ -31,23 +32,25 @@ from snowflake.cli._plugins.nativeapp.entities.application import ApplicationEnt
31
32
  from snowflake.cli._plugins.nativeapp.entities.application_package import (
32
33
  ApplicationPackageEntityModel,
33
34
  )
35
+ from snowflake.cli._plugins.nativeapp.release_channel.commands import (
36
+ app as release_channels_app,
37
+ )
38
+ from snowflake.cli._plugins.nativeapp.release_directive.commands import (
39
+ app as release_directives_app,
40
+ )
41
+ from snowflake.cli._plugins.nativeapp.sf_facade import get_snowflake_facade
34
42
  from snowflake.cli._plugins.nativeapp.v2_conversions.compat import (
35
43
  find_entity,
36
44
  force_project_definition_v2,
37
45
  )
38
46
  from snowflake.cli._plugins.nativeapp.version.commands import app as versions_app
39
- from snowflake.cli._plugins.stage.diff import (
40
- DiffResult,
41
- compute_stage_diff,
42
- )
43
- from snowflake.cli._plugins.stage.utils import print_diff_to_console
44
47
  from snowflake.cli._plugins.workspace.manager import WorkspaceManager
45
48
  from snowflake.cli.api.cli_global_context import get_cli_context
46
49
  from snowflake.cli.api.commands.decorators import (
47
50
  with_project_definition,
48
51
  )
49
52
  from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
50
- from snowflake.cli.api.entities.common import EntityActions
53
+ from snowflake.cli.api.entities.utils import EntityActions
51
54
  from snowflake.cli.api.exceptions import (
52
55
  IncompatibleParametersError,
53
56
  UnmetParametersError,
@@ -59,6 +62,7 @@ from snowflake.cli.api.output.types import (
59
62
  ObjectResult,
60
63
  StreamResult,
61
64
  )
65
+ from snowflake.cli.api.project.util import same_identifiers
62
66
  from typing_extensions import Annotated
63
67
 
64
68
  app = SnowTyperFactory(
@@ -66,6 +70,8 @@ app = SnowTyperFactory(
66
70
  help="Manages a Snowflake Native App",
67
71
  )
68
72
  app.add_typer(versions_app)
73
+ app.add_typer(release_directives_app)
74
+ app.add_typer(release_channels_app)
69
75
 
70
76
  log = logging.getLogger(__name__)
71
77
 
@@ -108,20 +114,15 @@ def app_diff(
108
114
  project_root=cli_context.project_root,
109
115
  )
110
116
  package_id = options["package_entity_id"]
111
- package = cli_context.project_definition.entities[package_id]
112
- bundle_map = ws.perform_action(
117
+ diff = ws.perform_action(
113
118
  package_id,
114
- EntityActions.BUNDLE,
115
- )
116
- stage_fqn = f"{package.fqn.name}.{package.stage}"
117
- diff: DiffResult = compute_stage_diff(
118
- local_root=Path(package.deploy_root), stage_fqn=stage_fqn
119
+ EntityActions.DIFF,
120
+ print_to_console=cli_context.output_format != OutputFormat.JSON,
119
121
  )
120
122
  if cli_context.output_format == OutputFormat.JSON:
121
123
  return ObjectResult(diff.to_dict())
122
- else:
123
- print_diff_to_console(diff, bundle_map)
124
- return None # don't print any output
124
+
125
+ return None
125
126
 
126
127
 
127
128
  @app.command("run", requires_connection=True)
@@ -146,6 +147,12 @@ def app_run(
146
147
  The command fails if no release directive exists for your Snowflake account for a given application package, which is determined from the project definition file. Default: unset.""",
147
148
  is_flag=True,
148
149
  ),
150
+ channel: str = typer.Option(
151
+ None,
152
+ show_default=False,
153
+ help=f"""The name of the release channel to use when creating or upgrading an application instance from a release directive.
154
+ Requires the `--from-release-directive` flag to be set. If unset, the default channel will be used.""",
155
+ ),
149
156
  interactive: bool = InteractiveOption,
150
157
  force: Optional[bool] = ForceOption,
151
158
  validate: bool = ValidateOption,
@@ -174,6 +181,7 @@ def app_run(
174
181
  paths=[],
175
182
  interactive=interactive,
176
183
  force=force,
184
+ release_channel=channel,
177
185
  )
178
186
  app = ws.get_entity(app_id)
179
187
  return MessageResult(
@@ -198,7 +206,7 @@ def app_open(
198
206
  )
199
207
  app_id = options["app_entity_id"]
200
208
  app = ws.get_entity(app_id)
201
- if app.get_existing_app_info():
209
+ if get_snowflake_facade().get_existing_app_info(app.name, app.role):
202
210
  typer.launch(app.get_snowsight_url())
203
211
  return MessageResult(f"Snowflake Native App opened in browser.")
204
212
  else:
@@ -221,7 +229,7 @@ def app_teardown(
221
229
  # Same as the param auto-added by @force_project_definition_v2 if single_app_and_package were true
222
230
  package_entity_id: Optional[str] = typer.Option(
223
231
  default="",
224
- help="The ID of the package entity on which to operate when definition_version is 2 or higher.",
232
+ help="The ID of the package entity on which to operate when the definition_version is 2 or higher.",
225
233
  ),
226
234
  **options,
227
235
  ) -> CommandResult:
@@ -246,11 +254,22 @@ def app_teardown(
246
254
  project_definition=cli_context.project_definition,
247
255
  project_root=cli_context.project_root,
248
256
  )
257
+
258
+ # TODO: get all apps created from this application package from snowflake, compare, confirm and drop.
259
+ # TODO: add messaging/confirmation here for extra apps found as part of above
260
+ all_packages_with_id = [
261
+ package_entity.entity_id
262
+ for package_entity in project.get_entities_by_type(
263
+ ApplicationPackageEntityModel.get_type()
264
+ ).values()
265
+ if same_identifiers(package_entity.fqn.name, app_package_entity.fqn.name)
266
+ ]
267
+
249
268
  for app_entity in project.get_entities_by_type(
250
269
  ApplicationEntityModel.get_type()
251
270
  ).values():
252
271
  # Drop each app
253
- if app_entity.from_.target == app_package_entity.entity_id:
272
+ if app_entity.from_.target in all_packages_with_id:
254
273
  ws.perform_action(
255
274
  app_entity.entity_id,
256
275
  EntityActions.DROP,
@@ -289,7 +308,7 @@ def app_deploy(
289
308
  show_default=False,
290
309
  help=dedent(
291
310
  f"""
292
- Paths, relative to the the project root, of files or directories you want to upload to a stage. If a file is
311
+ Paths, relative to the project root, of files or directories you want to upload to a stage. If a file is
293
312
  specified, it must match one of the artifacts src pattern entries in snowflake.yml. If a directory is
294
313
  specified, it will be searched for subfolders or files to deploy based on artifacts src pattern entries. If
295
314
  unspecified, the command syncs all local changes to the stage."""
@@ -357,7 +376,10 @@ def app_validate(
357
376
  if cli_context.output_format == OutputFormat.JSON:
358
377
  return ObjectResult(
359
378
  package.get_validation_result(
360
- use_scratch_stage=True, interactive=False, force=True
379
+ action_ctx=ws.action_ctx,
380
+ use_scratch_stage=True,
381
+ interactive=False,
382
+ force=True,
361
383
  )
362
384
  )
363
385
 
@@ -513,3 +535,73 @@ class EventResult(ObjectResult, MessageResult):
513
535
  @property
514
536
  def result(self):
515
537
  return self._element
538
+
539
+
540
+ @app.command("publish", requires_connection=True)
541
+ @with_project_definition()
542
+ @force_project_definition_v2()
543
+ def app_publish(
544
+ version: Optional[str] = typer.Option(
545
+ default=None,
546
+ show_default=False,
547
+ help="The version to publish to the provided release channel and release directive. Version is required to exist unless `--create-version` flag is used.",
548
+ ),
549
+ patch: Optional[int] = typer.Option(
550
+ default=None,
551
+ show_default=False,
552
+ help="The patch number under the given version. This will be used when setting the release directive. Patch is required to exist unless `--create-version` flag is used.",
553
+ ),
554
+ channel: Optional[str] = typer.Option(
555
+ "DEFAULT",
556
+ help="The name of the release channel to publish to. If not provided, the default release channel is used.",
557
+ ),
558
+ directive: Optional[str] = typer.Option(
559
+ "DEFAULT",
560
+ help="The name of the release directive to update with the specified version and patch. If not provided, the default release directive is used.",
561
+ ),
562
+ interactive: bool = InteractiveOption,
563
+ force: Optional[bool] = ForceOption,
564
+ create_version: bool = typer.Option(
565
+ False,
566
+ "--create-version",
567
+ help="Create a new version or patch based on the provided `--version` and `--patch` values. Fallback to the manifest values if not provided.",
568
+ is_flag=True,
569
+ ),
570
+ from_stage: bool = typer.Option(
571
+ False,
572
+ "--from-stage",
573
+ help="When enabled, the Snowflake CLI creates a version from the current application package stage without syncing to the stage first. Can only be used with `--create-version` flag.",
574
+ is_flag=True,
575
+ ),
576
+ label: Optional[str] = typer.Option(
577
+ None,
578
+ "--label",
579
+ help="A label for the version that is displayed to consumers. Can only be used with `--create-version` flag.",
580
+ ),
581
+ **options,
582
+ ) -> CommandResult:
583
+ """
584
+ Adds the version to the release channel and updates the release directive with the new version and patch.
585
+ """
586
+ cli_context = get_cli_context()
587
+ ws = WorkspaceManager(
588
+ project_definition=cli_context.project_definition,
589
+ project_root=cli_context.project_root,
590
+ )
591
+ package_id = options["package_entity_id"]
592
+ version_info: VersionInfo = ws.perform_action(
593
+ package_id,
594
+ EntityActions.PUBLISH,
595
+ version=version,
596
+ patch=patch,
597
+ release_channel=channel,
598
+ release_directive=directive,
599
+ interactive=interactive,
600
+ force=force,
601
+ create_version=create_version,
602
+ from_stage=from_stage,
603
+ label=label,
604
+ )
605
+ return MessageResult(
606
+ f"Version {version_info.version_name} and patch {version_info.patch_number} published to release directive {directive} of release channel {channel}."
607
+ )
@@ -22,7 +22,12 @@ COMMENT_COL = "comment"
22
22
  OWNER_COL = "owner"
23
23
  VERSION_COL = "version"
24
24
  PATCH_COL = "patch"
25
+ CHANNEL_COL = "release_channel_name"
25
26
  AUTHORIZE_TELEMETRY_COL = "authorize_telemetry_event_sharing"
26
27
 
27
28
  INTERNAL_DISTRIBUTION = "internal"
28
29
  EXTERNAL_DISTRIBUTION = "external"
30
+
31
+ DEFAULT_CHANNEL = "DEFAULT"
32
+ DEFAULT_DIRECTIVE = "DEFAULT"
33
+ MAX_VERSIONS_IN_RELEASE_CHANNEL = 2