snowflake-cli 3.11.0__py3-none-any.whl → 3.12.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 (38) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/_app/cli_app.py +0 -1
  3. snowflake/cli/_app/printing.py +153 -19
  4. snowflake/cli/_plugins/dbt/commands.py +37 -8
  5. snowflake/cli/_plugins/dbt/manager.py +144 -10
  6. snowflake/cli/_plugins/dcm/commands.py +65 -90
  7. snowflake/cli/_plugins/dcm/manager.py +137 -50
  8. snowflake/cli/_plugins/logs/commands.py +7 -0
  9. snowflake/cli/_plugins/logs/manager.py +21 -1
  10. snowflake/cli/_plugins/nativeapp/sf_sql_facade.py +3 -1
  11. snowflake/cli/_plugins/snowpark/common.py +1 -0
  12. snowflake/cli/_plugins/snowpark/package/anaconda_packages.py +29 -5
  13. snowflake/cli/_plugins/snowpark/package_utils.py +44 -3
  14. snowflake/cli/_plugins/spcs/services/manager.py +5 -4
  15. snowflake/cli/_plugins/sql/lexer/types.py +1 -0
  16. snowflake/cli/_plugins/sql/repl.py +100 -26
  17. snowflake/cli/_plugins/sql/repl_commands.py +607 -0
  18. snowflake/cli/_plugins/sql/statement_reader.py +44 -20
  19. snowflake/cli/api/artifacts/bundle_map.py +32 -2
  20. snowflake/cli/api/artifacts/regex_resolver.py +54 -0
  21. snowflake/cli/api/artifacts/upload.py +5 -1
  22. snowflake/cli/api/artifacts/utils.py +12 -1
  23. snowflake/cli/api/cli_global_context.py +7 -0
  24. snowflake/cli/api/console/abc.py +13 -2
  25. snowflake/cli/api/console/console.py +20 -0
  26. snowflake/cli/api/constants.py +9 -0
  27. snowflake/cli/api/entities/utils.py +10 -6
  28. snowflake/cli/api/feature_flags.py +1 -0
  29. snowflake/cli/api/identifiers.py +18 -1
  30. snowflake/cli/api/project/schemas/entities/entities.py +0 -6
  31. snowflake/cli/api/rendering/sql_templates.py +2 -0
  32. {snowflake_cli-3.11.0.dist-info → snowflake_cli-3.12.0.dist-info}/METADATA +5 -5
  33. {snowflake_cli-3.11.0.dist-info → snowflake_cli-3.12.0.dist-info}/RECORD +36 -36
  34. snowflake/cli/_plugins/dcm/dcm_project_entity_model.py +0 -59
  35. snowflake/cli/_plugins/sql/snowsql_commands.py +0 -331
  36. {snowflake_cli-3.11.0.dist-info → snowflake_cli-3.12.0.dist-info}/WHEEL +0 -0
  37. {snowflake_cli-3.11.0.dist-info → snowflake_cli-3.12.0.dist-info}/entry_points.txt +0 -0
  38. {snowflake_cli-3.11.0.dist-info → snowflake_cli-3.12.0.dist-info}/licenses/LICENSE +0 -0
@@ -7,9 +7,10 @@ from typing import Any, Callable, Generator, List, Literal, Sequence, Tuple
7
7
  from urllib.request import urlopen
8
8
 
9
9
  from jinja2 import UndefinedError
10
- from snowflake.cli._plugins.sql.snowsql_commands import (
11
- SnowSQLCommand,
12
- compile_snowsql_command,
10
+ from snowflake.cli._plugins.sql.repl_commands import (
11
+ ReplCommand,
12
+ UnknownCommandError,
13
+ compile_repl_command,
13
14
  )
14
15
  from snowflake.cli.api.secure_path import UNLIMITED, SecurePath
15
16
  from snowflake.connector.util_text import split_statements
@@ -38,7 +39,7 @@ class StatementType(enum.Enum):
38
39
  QUERY = "query"
39
40
  UNKNOWN = "unknown"
40
41
  URL = "url"
41
- SNOWSQL_COMMAND = "snowsql_command"
42
+ REPL_COMMAND = "repl_command"
42
43
 
43
44
 
44
45
  class ParsedStatement:
@@ -88,12 +89,21 @@ class ParsedStatement:
88
89
  def __repr__(self):
89
90
  return f"{self.__class__.__name__}(statement_type={self.statement_type}, source_path={self.source_path}, error={self.error})"
90
91
 
92
+ @staticmethod
93
+ def drop_comments_from_path_parts(path_part: str) -> str:
94
+ """Clean up path_part from trailing comments."""
95
+ uncommented, _ = next(
96
+ split_statements(io.StringIO(path_part), remove_comments=True)
97
+ )
98
+ return uncommented
99
+
91
100
  @classmethod
92
101
  def from_url(cls, path_part: str, raw_source: str) -> "ParsedStatement":
93
102
  """Constructor for loading from URL."""
103
+ stripped_comments_path_part = cls.drop_comments_from_path_parts(path_part)
94
104
  try:
95
- payload = urlopen(path_part, timeout=10.0).read().decode()
96
- return cls(payload, StatementType.URL, path_part)
105
+ payload = urlopen(stripped_comments_path_part, timeout=10.0).read().decode()
106
+ return cls(payload, StatementType.URL, stripped_comments_path_part)
97
107
 
98
108
  except urllib.error.HTTPError as err:
99
109
  error = f"Could not fetch {path_part}: {err}"
@@ -102,14 +112,20 @@ class ParsedStatement:
102
112
  @classmethod
103
113
  def from_file(cls, path_part: str, raw_source: str) -> "ParsedStatement":
104
114
  """Constructor for loading from file."""
105
- path = SecurePath(path_part)
115
+ stripped_comments_path_part = cls.drop_comments_from_path_parts(path_part)
116
+ path = SecurePath(stripped_comments_path_part)
106
117
 
107
118
  if path.is_file():
108
119
  payload = path.read_text(file_size_limit_mb=UNLIMITED)
109
120
  return cls(payload, StatementType.FILE, path.as_posix())
110
121
 
111
122
  error_msg = f"Could not read: {path_part}"
112
- return cls(path_part, StatementType.FILE, raw_source, error_msg)
123
+ return cls(
124
+ path_part,
125
+ StatementType.FILE,
126
+ raw_source,
127
+ error_msg,
128
+ )
113
129
 
114
130
 
115
131
  RecursiveStatementReader = Generator[ParsedStatement, Any, Any]
@@ -154,7 +170,10 @@ def parse_statement(source: str, operators: OperatorFunctions) -> ParsedStatemen
154
170
  )
155
171
 
156
172
  case "queries" | "result" | "abort", (str(),):
157
- return ParsedStatement(statement, StatementType.SNOWSQL_COMMAND, None)
173
+ return ParsedStatement(statement, StatementType.REPL_COMMAND, None)
174
+
175
+ case "edit", (str(),):
176
+ return ParsedStatement(statement, StatementType.REPL_COMMAND, command_args)
158
177
 
159
178
  case _:
160
179
  error_msg = f"Unknown command: {command}"
@@ -240,7 +259,7 @@ def query_reader(
240
259
  class CompiledStatement:
241
260
  statement: str | None = None
242
261
  execute_async: bool = False
243
- command: SnowSQLCommand | None = None
262
+ command: ReplCommand | None = None
244
263
 
245
264
 
246
265
  def _is_empty_statement(statement: str) -> bool:
@@ -274,21 +293,26 @@ def compile_statements(
274
293
  if not is_async:
275
294
  expected_results_cnt += 1
276
295
 
277
- if stmt.statement_type == StatementType.SNOWSQL_COMMAND:
296
+ if stmt.statement_type == StatementType.REPL_COMMAND:
278
297
  if not stmt.error:
279
- cmd = (
298
+ command_text = (
280
299
  stmt.statement.read()
281
300
  .removesuffix(ASYNC_SUFFIX)
282
301
  .removesuffix(";")
283
- .split()
284
- )
285
- parsed_command = compile_snowsql_command(
286
- command=cmd[0], cmd_args=cmd[1:]
302
+ .strip()
287
303
  )
288
- if parsed_command.error_message:
289
- errors.append(parsed_command.error_message)
290
- else:
291
- compiled.append(CompiledStatement(command=parsed_command.command))
304
+ try:
305
+ parsed_command = compile_repl_command(command_text)
306
+ if parsed_command.error_message:
307
+ errors.append(parsed_command.error_message)
308
+ else:
309
+ compiled.append(
310
+ CompiledStatement(command=parsed_command.command)
311
+ )
312
+ except UnknownCommandError as e:
313
+ errors.append(str(e))
314
+ except Exception as e:
315
+ errors.append(f"Error parsing command: {e}")
292
316
 
293
317
  if stmt.error:
294
318
  errors.append(stmt.error)
@@ -11,6 +11,9 @@ from snowflake.cli.api.artifacts.common import (
11
11
  SourceNotFoundError,
12
12
  TooManyFilesError,
13
13
  )
14
+ from snowflake.cli.api.artifacts.regex_resolver import RegexResolver
15
+ from snowflake.cli.api.constants import PatternMatchingType
16
+ from snowflake.cli.api.exceptions import CliError
14
17
  from snowflake.cli.api.project.schemas.entities.common import PathMapping
15
18
  from snowflake.cli.api.utils.path_utils import resolve_without_follow
16
19
 
@@ -38,9 +41,16 @@ class BundleMap:
38
41
 
39
42
  :param project_root: The root directory of the project and base for all relative paths. Must be an absolute path.
40
43
  :param deploy_root: The directory where artifacts should be copied to. Must be an absolute path.
44
+ :param pattern_type: The pattern matching type to use for artifact resolution. Defaults to GLOB.
41
45
  """
42
46
 
43
- def __init__(self, *, project_root: Path, deploy_root: Path):
47
+ def __init__(
48
+ self,
49
+ *,
50
+ project_root: Path,
51
+ deploy_root: Path,
52
+ pattern_type: PatternMatchingType = PatternMatchingType.GLOB,
53
+ ):
44
54
  # If a relative path ends up here, it's a bug in the app and can lead to other
45
55
  # subtle bugs as paths would be resolved relative to the current working directory.
46
56
  assert (
@@ -52,6 +62,7 @@ class BundleMap:
52
62
 
53
63
  self._project_root: Path = resolve_without_follow(project_root)
54
64
  self._deploy_root: Path = resolve_without_follow(deploy_root)
65
+ self._pattern_type: PatternMatchingType = pattern_type
55
66
  self._artifact_map = _ArtifactPathMap(project_root=self._project_root)
56
67
 
57
68
  def is_empty(self) -> bool:
@@ -112,7 +123,14 @@ class BundleMap:
112
123
  if src_path.is_absolute():
113
124
  raise ArtifactError("Source path must be a relative path")
114
125
 
115
- for resolved_src in self._project_root.glob(src):
126
+ if self._pattern_type == PatternMatchingType.REGEX:
127
+ resolved_sources = self._resolve_regex_pattern(src)
128
+ elif self._pattern_type == PatternMatchingType.GLOB:
129
+ resolved_sources = self._project_root.glob(src)
130
+ else:
131
+ raise CliError(f"Unsupported pattern type: {self._pattern_type}")
132
+
133
+ for resolved_src in resolved_sources:
116
134
  match_found = True
117
135
 
118
136
  if dest:
@@ -140,6 +158,18 @@ class BundleMap:
140
158
  if not match_found:
141
159
  raise SourceNotFoundError(src)
142
160
 
161
+ def _resolve_regex_pattern(self, pattern: str):
162
+ """
163
+ Resolve files matching a regex pattern.
164
+ """
165
+ resolver = RegexResolver()
166
+ for path in self._project_root.rglob("*"):
167
+ if path.is_file():
168
+ relative_path = str(path.relative_to(self._project_root))
169
+ relative_path = relative_path.replace("\\", "/")
170
+ if resolver.does_match(pattern, relative_path):
171
+ yield path
172
+
143
173
  def add(self, mapping: PathMapping) -> None:
144
174
  """
145
175
  Adds an artifact mapping rule to this instance.
@@ -0,0 +1,54 @@
1
+ # Copyright (c) 2025 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
+ from pydantic import BaseModel, Field, ValidationError
18
+ from pydantic_core import SchemaError
19
+ from snowflake.cli.api.artifacts.common import ArtifactError
20
+
21
+
22
+ class RegexResolver:
23
+ def __init__(self):
24
+ self._pattern_classes = {}
25
+
26
+ def does_match(self, pattern: str, text: str) -> bool:
27
+ """
28
+ Check if text matches pattern.
29
+ """
30
+ if len(pattern) > 1000:
31
+ raise ArtifactError(
32
+ f"Regex pattern too long ({len(pattern)} chars, max 1000): "
33
+ "potentially unsafe for performance"
34
+ )
35
+ if pattern not in self._pattern_classes:
36
+ self._pattern_classes[pattern] = self._generate_pattern_class(pattern)
37
+
38
+ pattern_class = self._pattern_classes[pattern]
39
+ try:
40
+ pattern_class(test_field=text)
41
+ return True
42
+ except ValidationError:
43
+ return False
44
+
45
+ @staticmethod
46
+ def _generate_pattern_class(pattern: str) -> type:
47
+ try:
48
+
49
+ class _RegexTestModel(BaseModel):
50
+ test_field: str = Field(pattern=pattern)
51
+
52
+ return _RegexTestModel
53
+ except SchemaError as e:
54
+ raise ArtifactError(f"Invalid regex pattern: {e}") from e
@@ -3,6 +3,7 @@ from typing import List, Optional
3
3
  from snowflake.cli._plugins.stage.manager import StageManager
4
4
  from snowflake.cli.api.artifacts.utils import bundle_artifacts
5
5
  from snowflake.cli.api.console import cli_console
6
+ from snowflake.cli.api.constants import PatternMatchingType
6
7
  from snowflake.cli.api.entities.utils import sync_deploy_root_with_stage
7
8
  from snowflake.cli.api.project.project_paths import ProjectPaths
8
9
  from snowflake.cli.api.project.schemas.entities.common import PathMapping
@@ -12,8 +13,10 @@ from snowflake.cli.api.secure_path import SecurePath
12
13
  def sync_artifacts_with_stage(
13
14
  project_paths: ProjectPaths,
14
15
  stage_root: str,
16
+ use_temporary_stage: bool = False,
15
17
  prune: bool = False,
16
18
  artifacts: Optional[List[PathMapping]] = None,
19
+ pattern_type: PatternMatchingType = PatternMatchingType.GLOB,
17
20
  ):
18
21
  if artifacts is None:
19
22
  artifacts = []
@@ -21,7 +24,7 @@ def sync_artifacts_with_stage(
21
24
  project_paths.remove_up_bundle_root()
22
25
  SecurePath(project_paths.bundle_root).mkdir(parents=True, exist_ok=True)
23
26
 
24
- bundle_map = bundle_artifacts(project_paths, artifacts)
27
+ bundle_map = bundle_artifacts(project_paths, artifacts, pattern_type=pattern_type)
25
28
  stage_path_parts = StageManager().stage_path_parts_from_str(stage_root)
26
29
  # We treat the bundle root as deploy root
27
30
  sync_deploy_root_with_stage(
@@ -31,6 +34,7 @@ def sync_artifacts_with_stage(
31
34
  prune=prune,
32
35
  recursive=True,
33
36
  stage_path_parts=stage_path_parts,
37
+ use_temporary_stage=use_temporary_stage,
34
38
  print_diff=True,
35
39
  )
36
40
  project_paths.clean_up_output()
@@ -5,6 +5,7 @@ from pathlib import Path
5
5
 
6
6
  from snowflake.cli.api.artifacts.bundle_map import BundleMap
7
7
  from snowflake.cli.api.artifacts.common import NotInDeployRootError
8
+ from snowflake.cli.api.constants import PatternMatchingType
8
9
  from snowflake.cli.api.project.project_paths import ProjectPaths
9
10
  from snowflake.cli.api.project.schemas.entities.common import Artifacts
10
11
  from snowflake.cli.api.secure_path import SecurePath
@@ -54,16 +55,26 @@ def symlink_or_copy(src: Path, dst: Path, deploy_root: Path) -> None:
54
55
  )
55
56
 
56
57
 
57
- def bundle_artifacts(project_paths: ProjectPaths, artifacts: Artifacts) -> BundleMap:
58
+ def bundle_artifacts(
59
+ project_paths: ProjectPaths,
60
+ artifacts: Artifacts,
61
+ pattern_type: PatternMatchingType = PatternMatchingType.GLOB,
62
+ ) -> BundleMap:
58
63
  """
59
64
  Creates a bundle directory (project_paths.bundle_root) with all artifacts (using symlink_or_copy function above).
60
65
  Previous contents of the directory are deleted.
61
66
 
62
67
  Returns a BundleMap containing the mapping between artifacts and their location in bundle directory.
68
+
69
+ Args:
70
+ project_paths: Project paths configuration
71
+ artifacts: List of artifacts to bundle
72
+ pattern_type: The pattern matching type to use for artifact resolution. Defaults to GLOB.
63
73
  """
64
74
  bundle_map = BundleMap(
65
75
  project_root=project_paths.project_root,
66
76
  deploy_root=project_paths.bundle_root,
77
+ pattern_type=pattern_type,
67
78
  )
68
79
  for artifact in artifacts:
69
80
  bundle_map.add(artifact)
@@ -29,6 +29,7 @@ from snowflake.cli.api.rendering.jinja import CONTEXT_KEY
29
29
  from snowflake.connector import SnowflakeConnection
30
30
 
31
31
  if TYPE_CHECKING:
32
+ from snowflake.cli._plugins.sql.repl import Repl
32
33
  from snowflake.cli.api.project.definition_manager import DefinitionManager
33
34
  from snowflake.cli.api.project.schemas.project_definition import ProjectDefinition
34
35
 
@@ -48,6 +49,7 @@ class _CliGlobalContextManager:
48
49
  experimental: bool = False
49
50
  enable_tracebacks: bool = True
50
51
  is_repl: bool = False
52
+ repl_instance: Repl | None = None
51
53
 
52
54
  metrics: CLIMetrics = field(default_factory=CLIMetrics)
53
55
 
@@ -209,6 +211,11 @@ class _CliGlobalContextAccess:
209
211
  def is_repl(self) -> bool:
210
212
  return self._manager.is_repl
211
213
 
214
+ @property
215
+ def repl(self) -> Repl | None:
216
+ """Get the current REPL instance if running in REPL mode."""
217
+ return self._manager.repl_instance
218
+
212
219
 
213
220
  _CLI_CONTEXT_MANAGER: ContextVar[_CliGlobalContextManager | None] = ContextVar(
214
221
  "cli_context", default=None
@@ -29,11 +29,15 @@ from snowflake.cli.api.cli_global_context import (
29
29
  class AbstractConsole(ABC):
30
30
  """Interface for cli console implementation.
31
31
 
32
- Each console should have three methods implemented:
32
+ Each console should have the following methods implemented:
33
33
  - `step` - for more detailed information on steps
34
34
  - `warning` - for displaying messages in a style that makes it
35
35
  visually stand out from other output
36
- - `phase` a context manager for organising steps into logical group
36
+ - `phase` - a context manager for organising steps into logical group
37
+ - `indented` - a context manager for temporarily indenting messages and warnings
38
+ - 'message' - displays an informational message to output
39
+ - 'panel' - displays visually separated messages
40
+ - 'spinner' - context manager for indicating a long-running operation
37
41
  """
38
42
 
39
43
  _print_fn: Callable[[str], None]
@@ -98,3 +102,10 @@ class AbstractConsole(ABC):
98
102
  """Displays message in a panel that makes it visually stand out from other output.
99
103
 
100
104
  Intended for displaying visually separated messages."""
105
+
106
+ @contextmanager
107
+ @abstractmethod
108
+ def spinner(self):
109
+ """
110
+ A context manager for indicating a long-running operation.
111
+ """
@@ -19,6 +19,7 @@ from typing import Optional
19
19
 
20
20
  from rich import get_console
21
21
  from rich.panel import Panel
22
+ from rich.progress import Progress, SpinnerColumn, TextColumn
22
23
  from rich.style import Style
23
24
  from rich.text import Text
24
25
  from snowflake.cli.api.console.abc import AbstractConsole
@@ -31,6 +32,7 @@ get_console().soft_wrap = True
31
32
  get_console()._markup = False # noqa: SLF001
32
33
 
33
34
  PHASE_STYLE: Style = Style(bold=True)
35
+ SPINNER_STYLE: Style = Style(bold=True)
34
36
  STEP_STYLE: Style = Style(italic=True)
35
37
  INFO_STYLE: Style = Style()
36
38
  PANEL_STYLE: Style = Style()
@@ -98,6 +100,24 @@ class CliConsole(AbstractConsole):
98
100
  finally:
99
101
  self._extra_indent -= 1
100
102
 
103
+ @contextmanager
104
+ def spinner(self):
105
+ """
106
+ A context manager for displaying a spinner while executing a long-running operation.
107
+
108
+ Usage:
109
+ with cli_console.spinner("Processing data") as spinner:
110
+ spinner.add_task(description="Long operation", total=None)
111
+ # Long running operation here
112
+ result = some_operation()
113
+ """
114
+ with Progress(
115
+ SpinnerColumn(),
116
+ TextColumn("[progress.description]{task.description}", style=SPINNER_STYLE),
117
+ transient=True,
118
+ ) as progress:
119
+ yield progress
120
+
101
121
  def step(self, message: str):
102
122
  """Displays a message to output.
103
123
 
@@ -75,6 +75,15 @@ class ObjectType(Enum):
75
75
  return self.value.cli_name
76
76
 
77
77
 
78
+ class PatternMatchingType(Enum):
79
+ """
80
+ Enum for different pattern matching types used in artifact resolution.
81
+ """
82
+
83
+ GLOB = "glob"
84
+ REGEX = "regex"
85
+
86
+
78
87
  OBJECT_TO_NAMES = {o.value.cli_name: o.value for o in ObjectType}
79
88
  UNSUPPORTED_OBJECTS = {
80
89
  ObjectType.APPLICATION.value.cli_name,
@@ -87,6 +87,7 @@ def sync_deploy_root_with_stage(
87
87
  prune: bool,
88
88
  recursive: bool,
89
89
  stage_path_parts: StagePathParts,
90
+ use_temporary_stage: bool = False,
90
91
  role: str | None = None,
91
92
  package_name: str | None = None,
92
93
  local_paths_to_sync: List[Path] | None = None,
@@ -103,6 +104,7 @@ def sync_deploy_root_with_stage(
103
104
  prune (bool): Whether to prune artifacts from the stage that don't exist locally.
104
105
  recursive (bool): Whether to traverse directories recursively.
105
106
  stage_path_parts (StagePathParts): stage path parts object.
107
+ use_temporary_stage (bool): specifies if new stage should be temporary.
106
108
 
107
109
  package_name (str): supported for Native App compatibility. Should be None out of Native App context.
108
110
 
@@ -120,8 +122,11 @@ def sync_deploy_root_with_stage(
120
122
  elif not package_name:
121
123
  # ensure stage exists
122
124
  stage_fqn = FQN.from_stage(stage_path_parts.stage)
123
- console.step(f"Creating stage {stage_fqn} if not exists.")
124
- StageManager().create(fqn=stage_fqn)
125
+ if use_temporary_stage:
126
+ console.step(f"Creating temporary stage {stage_fqn}.")
127
+ else:
128
+ console.step(f"Creating stage {stage_fqn} if not exists.")
129
+ StageManager().create(fqn=stage_fqn, temporary=use_temporary_stage)
125
130
  else:
126
131
  # ensure stage exists - nativeapp behavior
127
132
  sql_facade = get_snowflake_facade()
@@ -134,10 +139,10 @@ def sync_deploy_root_with_stage(
134
139
  )
135
140
  if not sql_facade.stage_exists(stage_fqn):
136
141
  sql_facade.create_schema(schema, database=package_name)
137
- sql_facade.create_stage(stage_fqn)
142
+ sql_facade.create_stage(stage_fqn, temporary=use_temporary_stage)
138
143
 
139
144
  # Perform a diff operation and display results to the user for informational purposes
140
- if print_diff:
145
+ if print_diff and not use_temporary_stage:
141
146
  console.step(
142
147
  f"Performing a diff between the Snowflake stage: {stage_path_parts.path} and your local deploy_root: {deploy_root.resolve()}."
143
148
  )
@@ -199,8 +204,7 @@ def sync_deploy_root_with_stage(
199
204
  # Upload diff-ed files to the stage
200
205
  if diff.has_changes():
201
206
  console.step(
202
- "Updating the Snowflake stage from your local %s directory."
203
- % deploy_root.resolve(),
207
+ f"Uploading files from local {deploy_root.resolve()} directory to{' temporary' if use_temporary_stage else ''} stage."
204
208
  )
205
209
  sync_local_diff_with_stage(
206
210
  role=role,
@@ -65,6 +65,7 @@ class FeatureFlag(FeatureFlagMixin):
65
65
  ENABLE_SNOWPARK_GLOB_SUPPORT = BooleanFlag("ENABLE_SNOWPARK_GLOB_SUPPORT", False)
66
66
  ENABLE_SPCS_SERVICE_EVENTS = BooleanFlag("ENABLE_SPCS_SERVICE_EVENTS", False)
67
67
  ENABLE_DBT = BooleanFlag("ENABLE_DBT", False)
68
+ ENABLE_DBT_GA_FEATURES = BooleanFlag("ENABLE_DBT_GA_FEATURES", False)
68
69
  ENABLE_AUTH_KEYPAIR = BooleanFlag("ENABLE_AUTH_KEYPAIR", False)
69
70
  ENABLE_NATIVE_APP_PYTHON_SETUP = BooleanFlag(
70
71
  "ENABLE_NATIVE_APP_PYTHON_SETUP", False
@@ -15,14 +15,20 @@
15
15
  from __future__ import annotations
16
16
 
17
17
  import re
18
+ import time
18
19
  from pathlib import Path
19
20
 
20
21
  from click import ClickException
22
+ from snowflake.cli.api.constants import ObjectType
21
23
  from snowflake.cli.api.exceptions import FQNInconsistencyError, FQNNameError
22
24
  from snowflake.cli.api.project.schemas.v1.identifier_model import (
23
25
  ObjectIdentifierBaseModel,
24
26
  )
25
- from snowflake.cli.api.project.util import VALID_IDENTIFIER_REGEX, identifier_for_url
27
+ from snowflake.cli.api.project.util import (
28
+ VALID_IDENTIFIER_REGEX,
29
+ identifier_for_url,
30
+ unquote_identifier,
31
+ )
26
32
 
27
33
 
28
34
  class FQN:
@@ -167,6 +173,17 @@ class FQN:
167
173
 
168
174
  return fqn.set_database(model.database).set_schema(model.schema_)
169
175
 
176
+ @classmethod
177
+ def from_resource(
178
+ cls, resource_type: ObjectType, resource_fqn: FQN, purpose: str
179
+ ) -> "FQN":
180
+ """Create an instance related to another Snowflake resource."""
181
+ unquoted_name = unquote_identifier(resource_fqn.name)
182
+ safe_cli_name = resource_type.value.cli_name.upper().replace("-", "_")
183
+ return cls.from_string(
184
+ f"{safe_cli_name}_{unquoted_name}_{int(time.time())}_{purpose.upper()}"
185
+ ).using_context()
186
+
170
187
  def set_database(self, database: str | None) -> "FQN":
171
188
  if database:
172
189
  self._database = database
@@ -16,10 +16,6 @@ from __future__ import annotations
16
16
 
17
17
  from typing import Dict, List, Union, get_args
18
18
 
19
- from snowflake.cli._plugins.dcm.dcm_project_entity_model import (
20
- DCMProjectEntity,
21
- DCMProjectEntityModel,
22
- )
23
19
  from snowflake.cli._plugins.nativeapp.entities.application import (
24
20
  ApplicationEntity,
25
21
  ApplicationEntityModel,
@@ -62,7 +58,6 @@ Entity = Union[
62
58
  ApplicationPackageEntity,
63
59
  StreamlitEntity,
64
60
  ProcedureEntity,
65
- DCMProjectEntity,
66
61
  FunctionEntity,
67
62
  ComputePoolEntity,
68
63
  ImageRepositoryEntity,
@@ -79,7 +74,6 @@ EntityModel = Union[
79
74
  ImageRepositoryEntityModel,
80
75
  ServiceEntityModel,
81
76
  NotebookEntityModel,
82
- DCMProjectEntityModel,
83
77
  ]
84
78
 
85
79
  ALL_ENTITIES: List[Entity] = [*get_args(Entity)]
@@ -35,6 +35,7 @@ _SQL_TEMPLATE_START = "<%"
35
35
  _SQL_TEMPLATE_END = "%>"
36
36
  _OLD_SQL_TEMPLATE_START = "&{"
37
37
  _OLD_SQL_TEMPLATE_END = "}"
38
+ _JINJA_TEMPLATE_START, _JINJA_TEMPLATE_END = "{{", "}}"
38
39
  RESERVED_KEYS = [CONTEXT_KEY, FUNCTION_KEY]
39
40
 
40
41
 
@@ -62,6 +63,7 @@ def has_sql_templates(template_content: str) -> bool:
62
63
  return (
63
64
  _OLD_SQL_TEMPLATE_START in template_content
64
65
  or _SQL_TEMPLATE_START in template_content
66
+ or _JINJA_TEMPLATE_START in template_content
65
67
  )
66
68
 
67
69
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: snowflake-cli
3
- Version: 3.11.0
3
+ Version: 3.12.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
@@ -231,14 +231,14 @@ Requires-Dist: requests==2.32.4
231
231
  Requires-Dist: requirements-parser==0.13.0
232
232
  Requires-Dist: rich==14.0.0
233
233
  Requires-Dist: setuptools==80.8.0
234
- Requires-Dist: snowflake-connector-python[secure-local-storage]==3.17.2
235
- Requires-Dist: snowflake-core==1.6.0
234
+ Requires-Dist: snowflake-connector-python[secure-local-storage]==3.17.3
235
+ Requires-Dist: snowflake-core==1.7.0
236
236
  Requires-Dist: snowflake-snowpark-python==1.33.0; python_version < '3.12'
237
237
  Requires-Dist: tomlkit==0.13.3
238
- Requires-Dist: typer==0.16.0
238
+ Requires-Dist: typer==0.17.3
239
239
  Requires-Dist: urllib3<2.6,>=1.24.3
240
240
  Provides-Extra: development
241
- Requires-Dist: coverage==7.8.0; extra == 'development'
241
+ Requires-Dist: coverage==7.10.4; extra == 'development'
242
242
  Requires-Dist: factory-boy==3.3.3; extra == 'development'
243
243
  Requires-Dist: faker==37.4.0; extra == 'development'
244
244
  Requires-Dist: pre-commit>=3.5.0; extra == 'development'