snowflake-cli 3.9.1__py3-none-any.whl → 3.10.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 (46) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/_app/printing.py +53 -13
  3. snowflake/cli/_app/snow_connector.py +1 -0
  4. snowflake/cli/_app/telemetry.py +2 -0
  5. snowflake/cli/_app/version_check.py +73 -6
  6. snowflake/cli/_plugins/cortex/commands.py +8 -3
  7. snowflake/cli/_plugins/cortex/manager.py +24 -20
  8. snowflake/cli/_plugins/dbt/commands.py +5 -2
  9. snowflake/cli/_plugins/git/manager.py +1 -11
  10. snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +4 -0
  11. snowflake/cli/_plugins/nativeapp/commands.py +3 -4
  12. snowflake/cli/_plugins/nativeapp/entities/application_package.py +1 -1
  13. snowflake/cli/_plugins/nativeapp/release_channel/commands.py +1 -2
  14. snowflake/cli/_plugins/nativeapp/version/commands.py +1 -2
  15. snowflake/cli/_plugins/project/commands.py +54 -7
  16. snowflake/cli/_plugins/project/manager.py +20 -1
  17. snowflake/cli/_plugins/snowpark/common.py +23 -11
  18. snowflake/cli/_plugins/snowpark/snowpark_entity.py +13 -5
  19. snowflake/cli/_plugins/snowpark/snowpark_entity_model.py +10 -2
  20. snowflake/cli/_plugins/sql/commands.py +49 -1
  21. snowflake/cli/_plugins/sql/manager.py +14 -4
  22. snowflake/cli/_plugins/sql/repl.py +4 -0
  23. snowflake/cli/_plugins/stage/commands.py +30 -11
  24. snowflake/cli/_plugins/stage/diff.py +2 -0
  25. snowflake/cli/_plugins/stage/manager.py +79 -55
  26. snowflake/cli/_plugins/streamlit/streamlit_entity.py +17 -30
  27. snowflake/cli/api/artifacts/upload.py +1 -1
  28. snowflake/cli/api/cli_global_context.py +5 -14
  29. snowflake/cli/api/commands/decorators.py +7 -0
  30. snowflake/cli/api/commands/flags.py +12 -0
  31. snowflake/cli/api/commands/snow_typer.py +23 -2
  32. snowflake/cli/api/config.py +9 -5
  33. snowflake/cli/api/connections.py +1 -0
  34. snowflake/cli/api/entities/common.py +16 -13
  35. snowflake/cli/api/entities/utils.py +15 -9
  36. snowflake/cli/api/feature_flags.py +2 -5
  37. snowflake/cli/api/output/formats.py +6 -0
  38. snowflake/cli/api/output/types.py +48 -2
  39. snowflake/cli/api/rendering/sql_templates.py +67 -11
  40. snowflake/cli/api/stage_path.py +37 -5
  41. {snowflake_cli-3.9.1.dist-info → snowflake_cli-3.10.0.dist-info}/METADATA +45 -12
  42. {snowflake_cli-3.9.1.dist-info → snowflake_cli-3.10.0.dist-info}/RECORD +45 -46
  43. snowflake/cli/_plugins/project/feature_flags.py +0 -22
  44. {snowflake_cli-3.9.1.dist-info → snowflake_cli-3.10.0.dist-info}/WHEEL +0 -0
  45. {snowflake_cli-3.9.1.dist-info → snowflake_cli-3.10.0.dist-info}/entry_points.txt +0 -0
  46. {snowflake_cli-3.9.1.dist-info → snowflake_cli-3.10.0.dist-info}/licenses/LICENSE +0 -0
@@ -4,7 +4,7 @@ from pathlib import Path
4
4
  from typing import Generic, List, Optional, Type, TypeVar, get_args
5
5
 
6
6
  from snowflake.cli._plugins.workspace.context import ActionContext, WorkspaceContext
7
- from snowflake.cli.api.cli_global_context import get_cli_context, span
7
+ from snowflake.cli.api.cli_global_context import span
8
8
  from snowflake.cli.api.entities.resolver import Dependency, DependencyResolver
9
9
  from snowflake.cli.api.entities.utils import EntityActions, get_sql_executor
10
10
  from snowflake.cli.api.identifiers import FQN
@@ -102,8 +102,8 @@ class EntityBase(Generic[T]):
102
102
  ) -> SqlExecutor:
103
103
  return get_sql_executor()
104
104
 
105
- def _execute_query(self, sql: str) -> SnowflakeCursor:
106
- return self._sql_executor.execute_query(sql)
105
+ def _execute_query(self, sql: str, **kwargs) -> SnowflakeCursor:
106
+ return self._sql_executor.execute_query(sql, **kwargs)
107
107
 
108
108
  @functools.cached_property
109
109
  def _conn(self) -> SnowflakeConnection:
@@ -117,13 +117,6 @@ class EntityBase(Generic[T]):
117
117
  def schema(self) -> Optional[str]:
118
118
  return self.get_from_fqn_or_conn("schema")
119
119
 
120
- @property
121
- def snow_api_root(self) -> Optional[object]:
122
- root = get_cli_context().snow_api_root
123
- if root is None:
124
- raise ValueError("snow_api_root is not set")
125
- return root
126
-
127
120
  @property
128
121
  def model(self) -> T:
129
122
  return self._entity_model
@@ -140,12 +133,22 @@ class EntityBase(Generic[T]):
140
133
  def get_drop_sql(self) -> str:
141
134
  return f"DROP {self.model.type.upper()} {self.identifier};" # type: ignore[attr-defined]
142
135
 
143
- def _get_identifier(
136
+ def _get_fqn(
144
137
  self, schema: Optional[str] = None, database: Optional[str] = None
145
- ) -> str:
138
+ ) -> FQN:
146
139
  schema_to_use = schema or self._entity_model.fqn.schema or self._conn.schema # type: ignore
147
140
  db_to_use = database or self._entity_model.fqn.database or self._conn.database # type: ignore
148
- return f"{self._entity_model.fqn.set_schema(schema_to_use).set_database(db_to_use).sql_identifier}" # type: ignore
141
+ return self._entity_model.fqn.set_schema(schema_to_use).set_database(db_to_use) # type: ignore
142
+
143
+ def _get_sql_identifier(
144
+ self, schema: Optional[str] = None, database: Optional[str] = None
145
+ ) -> str:
146
+ return str(self._get_fqn(schema, database).sql_identifier)
147
+
148
+ def _get_identifier(
149
+ self, schema: Optional[str] = None, database: Optional[str] = None
150
+ ) -> str:
151
+ return str(self._get_fqn(schema, database).identifier)
149
152
 
150
153
  def get_from_fqn_or_conn(self, attribute_name: str) -> str:
151
154
  attribute = getattr(self.fqn, attribute_name, None) or getattr(
@@ -86,11 +86,12 @@ def sync_deploy_root_with_stage(
86
86
  bundle_map: BundleMap,
87
87
  prune: bool,
88
88
  recursive: bool,
89
- stage_path: StagePathParts,
89
+ stage_path_parts: StagePathParts,
90
90
  role: str | None = None,
91
91
  package_name: str | None = None,
92
92
  local_paths_to_sync: List[Path] | None = None,
93
93
  print_diff: bool = True,
94
+ force_overwrite: bool = False,
94
95
  ) -> DiffResult:
95
96
  """
96
97
  Ensures that the files on our remote stage match the artifacts we have in
@@ -101,27 +102,31 @@ def sync_deploy_root_with_stage(
101
102
  role (str): The name of the role to use for queries and commands.
102
103
  prune (bool): Whether to prune artifacts from the stage that don't exist locally.
103
104
  recursive (bool): Whether to traverse directories recursively.
104
- stage_path (DefaultStagePathParts): stage path object.
105
+ stage_path_parts (StagePathParts): stage path parts object.
105
106
 
106
107
  package_name (str): supported for Native App compatibility. Should be None out of Native App context.
107
108
 
108
109
  local_paths_to_sync (List[Path], optional): List of local paths to sync. Defaults to None to sync all
109
110
  local paths. Note that providing an empty list here is equivalent to None.
110
111
  print_diff (bool): Whether to print the diff between the local files and the remote stage. Defaults to True
112
+ force_overwrite (bool): Some resources (e.g. streamlit) need to overwrite files on the stage. Defaults to False.
111
113
 
112
114
  Returns:
113
115
  A `DiffResult` instance describing the changes that were performed.
114
116
  """
115
- if not package_name:
117
+ if stage_path_parts.is_vstage:
118
+ # vstages are created by FBE, so no need to do it manually
119
+ pass
120
+ elif not package_name:
116
121
  # ensure stage exists
117
- stage_fqn = FQN.from_stage(stage_path.stage)
122
+ stage_fqn = FQN.from_stage(stage_path_parts.stage)
118
123
  console.step(f"Creating stage {stage_fqn} if not exists.")
119
124
  StageManager().create(fqn=stage_fqn)
120
125
  else:
121
126
  # ensure stage exists - nativeapp behavior
122
127
  sql_facade = get_snowflake_facade()
123
- schema = stage_path.schema
124
- stage_fqn = stage_path.stage
128
+ schema = stage_path_parts.schema
129
+ stage_fqn = stage_path_parts.stage
125
130
  # Does a stage already exist within the application package, or we need to create one?
126
131
  # Using "if not exists" should take care of either case.
127
132
  console.step(
@@ -134,12 +139,12 @@ def sync_deploy_root_with_stage(
134
139
  # Perform a diff operation and display results to the user for informational purposes
135
140
  if print_diff:
136
141
  console.step(
137
- f"Performing a diff between the Snowflake stage: {stage_path.path} and your local deploy_root: {deploy_root.resolve()}."
142
+ f"Performing a diff between the Snowflake stage: {stage_path_parts.path} and your local deploy_root: {deploy_root.resolve()}."
138
143
  )
139
144
 
140
145
  diff: DiffResult = compute_stage_diff(
141
146
  local_root=deploy_root,
142
- stage_path=stage_path,
147
+ stage_path=stage_path_parts,
143
148
  )
144
149
 
145
150
  if local_paths_to_sync:
@@ -201,7 +206,8 @@ def sync_deploy_root_with_stage(
201
206
  role=role,
202
207
  deploy_root_path=deploy_root,
203
208
  diff_result=diff,
204
- stage_full_path=stage_path.full_path,
209
+ stage_full_path=stage_path_parts.full_path,
210
+ force_overwrite=force_overwrite,
205
211
  )
206
212
  return diff
207
213
 
@@ -50,16 +50,12 @@ class FeatureFlagMixin(Enum):
50
50
  is not None
51
51
  )
52
52
 
53
- def env_variable(self):
53
+ def env_variable(self) -> str:
54
54
  return get_env_variable_name(*FEATURE_FLAGS_SECTION_PATH, key=self.value.name)
55
55
 
56
56
 
57
57
  @unique
58
58
  class FeatureFlag(FeatureFlagMixin):
59
- ENABLE_STREAMLIT_EMBEDDED_STAGE = BooleanFlag(
60
- "ENABLE_STREAMLIT_EMBEDDED_STAGE", False
61
- )
62
- ENABLE_STREAMLIT_NO_CHECKOUTS = BooleanFlag("ENABLE_STREAMLIT_NO_CHECKOUTS", False)
63
59
  ENABLE_STREAMLIT_VERSIONED_STAGE = BooleanFlag(
64
60
  "ENABLE_STREAMLIT_VERSIONED_STAGE", False
65
61
  )
@@ -76,3 +72,4 @@ class FeatureFlag(FeatureFlagMixin):
76
72
  ENABLE_NATIVE_APP_CHILDREN = BooleanFlag("ENABLE_NATIVE_APP_CHILDREN", False)
77
73
  # TODO 4.0: remove ENABLE_RELEASE_CHANNELS
78
74
  ENABLE_RELEASE_CHANNELS = BooleanFlag("ENABLE_RELEASE_CHANNELS", None)
75
+ ENABLE_SNOWFLAKE_PROJECTS = BooleanFlag("ENABLE_SNOWFLAKE_PROJECTS", False)
@@ -18,3 +18,9 @@ from enum import Enum
18
18
  class OutputFormat(Enum):
19
19
  TABLE = "TABLE"
20
20
  JSON = "JSON"
21
+ JSON_EXT = "JSON_EXT"
22
+ CSV = "CSV"
23
+
24
+ @property
25
+ def is_json(self) -> bool:
26
+ return self in (OutputFormat.JSON, OutputFormat.JSON_EXT)
@@ -16,11 +16,22 @@ from __future__ import annotations
16
16
 
17
17
  import json
18
18
  import typing as t
19
+ from enum import IntEnum
19
20
 
21
+ from snowflake.cli.api.cli_global_context import get_cli_context
22
+ from snowflake.cli.api.output.formats import OutputFormat
20
23
  from snowflake.connector import DictCursor
21
24
  from snowflake.connector.cursor import SnowflakeCursor
22
25
 
23
26
 
27
+ class SnowflakeColumnType(IntEnum):
28
+ """Snowflake column type codes for JSON-capable data types."""
29
+
30
+ VARIANT = 5
31
+ OBJECT = 9
32
+ ARRAY = 10
33
+
34
+
24
35
  class CommandResult:
25
36
  @property
26
37
  def result(self):
@@ -69,13 +80,48 @@ class StreamResult(CommandResult):
69
80
  class QueryResult(CollectionResult):
70
81
  def __init__(self, cursor: SnowflakeCursor | DictCursor):
71
82
  self.column_names = [col.name for col in cursor.description]
83
+ # Store column type information to identify VARIANT columns (JSON data)
84
+ self.column_types = [col.type_code for col in cursor.description]
72
85
  super().__init__(elements=self._prepare_payload(cursor))
73
86
  self._query = cursor.query
74
87
 
75
88
  def _prepare_payload(self, cursor: SnowflakeCursor | DictCursor):
76
89
  if isinstance(cursor, DictCursor):
77
- return (k for k in cursor)
78
- return ({k: v for k, v in zip(self.column_names, row)} for row in cursor)
90
+ return (self._process_columns(k) for k in cursor)
91
+ return (
92
+ self._process_columns({k: v for k, v in zip(self.column_names, row)})
93
+ for row in cursor
94
+ )
95
+
96
+ def _process_columns(self, row_dict):
97
+ if get_cli_context().output_format != OutputFormat.JSON_EXT:
98
+ return row_dict
99
+
100
+ processed_row = {}
101
+ for i, (column_name, value) in enumerate(row_dict.items()):
102
+ # Check if this column can contain JSON data
103
+ if i < len(self.column_types) and self.column_types[i] in (
104
+ SnowflakeColumnType.VARIANT,
105
+ SnowflakeColumnType.OBJECT,
106
+ SnowflakeColumnType.ARRAY,
107
+ ):
108
+ # For ARRAY and OBJECT types, the values are always JSON strings that need parsing
109
+ # For VARIANT types, only parse if the value is a string
110
+ if self.column_types[i] in (
111
+ SnowflakeColumnType.OBJECT,
112
+ SnowflakeColumnType.ARRAY,
113
+ ) or isinstance(value, str):
114
+ try:
115
+ # Try to parse as JSON
116
+ processed_row[column_name] = json.loads(value)
117
+ except (json.JSONDecodeError, TypeError):
118
+ # If parsing fails, keep the original value
119
+ processed_row[column_name] = value
120
+ else:
121
+ processed_row[column_name] = value
122
+ else:
123
+ processed_row[column_name] = value
124
+ return processed_row
79
125
 
80
126
  @property
81
127
  def query(self):
@@ -14,19 +14,21 @@
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
+ from dataclasses import dataclass
17
18
  from typing import Dict, Optional
18
19
 
19
20
  from click import ClickException
20
21
  from jinja2 import Environment, StrictUndefined, loaders, meta
21
22
  from snowflake.cli.api.cli_global_context import get_cli_context
22
23
  from snowflake.cli.api.console.console import cli_console
23
- from snowflake.cli.api.exceptions import InvalidTemplateError
24
+ from snowflake.cli.api.exceptions import CliArgumentError, InvalidTemplateError
24
25
  from snowflake.cli.api.metrics import CLICounterField
25
26
  from snowflake.cli.api.rendering.jinja import (
26
27
  CONTEXT_KEY,
27
28
  FUNCTION_KEY,
28
29
  IgnoreAttrEnvironment,
29
30
  env_bootstrap,
31
+ get_basic_jinja_env,
30
32
  )
31
33
 
32
34
  _SQL_TEMPLATE_START = "<%"
@@ -63,11 +65,19 @@ def has_sql_templates(template_content: str) -> bool:
63
65
  )
64
66
 
65
67
 
68
+ def _get_legacy_sql_env() -> Environment:
69
+ return _get_sql_jinja_env(_OLD_SQL_TEMPLATE_START, _OLD_SQL_TEMPLATE_END)
70
+
71
+
72
+ def _get_standard_sql_env() -> Environment:
73
+ return _get_sql_jinja_env(_SQL_TEMPLATE_START, _SQL_TEMPLATE_END)
74
+
75
+
66
76
  def choose_sql_jinja_env_based_on_template_syntax(
67
77
  template_content: str, reference_name: Optional[str] = None
68
78
  ) -> Environment:
69
- old_syntax_env = _get_sql_jinja_env(_OLD_SQL_TEMPLATE_START, _OLD_SQL_TEMPLATE_END)
70
- new_syntax_env = _get_sql_jinja_env(_SQL_TEMPLATE_START, _SQL_TEMPLATE_END)
79
+ old_syntax_env = _get_legacy_sql_env()
80
+ new_syntax_env = _get_standard_sql_env()
71
81
  has_old_syntax = _does_template_have_env_syntax(old_syntax_env, template_content)
72
82
  has_new_syntax = _does_template_have_env_syntax(new_syntax_env, template_content)
73
83
  reference_name_str = f" in {reference_name}" if reference_name else ""
@@ -86,7 +96,29 @@ def choose_sql_jinja_env_based_on_template_syntax(
86
96
  return new_syntax_env
87
97
 
88
98
 
89
- def snowflake_sql_jinja_render(content: str, data: Dict | None = None) -> str:
99
+ @dataclass
100
+ class SQLTemplateSyntaxConfig:
101
+ """Class defining which syntax should be used for the template resolution.
102
+ Jinja syntax is not recommended and should be disabled by default."""
103
+
104
+ enable_legacy_syntax: bool = True
105
+ enable_standard_syntax: bool = True
106
+ enable_jinja_syntax: bool = False
107
+
108
+
109
+ def snowflake_sql_jinja_render(
110
+ content: str,
111
+ template_syntax_config: SQLTemplateSyntaxConfig,
112
+ data: Dict | None = None,
113
+ ) -> str:
114
+ """
115
+ If both legacy and standard syntax are enabled, CLI chooses one basing on provided content.
116
+ If jinja syntax is enabled, it is resolved after standard and legacy syntax.
117
+ """
118
+ # Jinja syntax is server-side templating, it should not be resolved by CLI by default.
119
+ # The main use case for adding support for it on CLI side is for testing scripts before running them on server,
120
+ # which is why jinja templates are resolved after standard CLI templates.
121
+
90
122
  data = data or {}
91
123
 
92
124
  for reserved_key in RESERVED_KEYS:
@@ -94,13 +126,37 @@ def snowflake_sql_jinja_render(content: str, data: Dict | None = None) -> str:
94
126
  raise ClickException(
95
127
  f"{reserved_key} in user defined data. The `{reserved_key}` variable is reserved for CLI usage."
96
128
  )
97
-
98
- context_data = get_cli_context().template_context
99
- context_data.update(data)
100
- env = choose_sql_jinja_env_based_on_template_syntax(content)
101
-
129
+ has_templates = has_sql_templates(content)
102
130
  get_cli_context().metrics.set_counter(
103
- CLICounterField.SQL_TEMPLATES, int(has_sql_templates(content))
131
+ CLICounterField.SQL_TEMPLATES, int(has_templates)
104
132
  )
133
+ context_data = {}
134
+ if has_templates:
135
+ try:
136
+ context_data = get_cli_context().template_context
137
+ except Exception as e:
138
+ raise CliArgumentError(f"Failed to read snowflake.yml file: {e}")
139
+ context_data.update(data)
105
140
 
106
- return env.from_string(content).render(context_data)
141
+ # resolve legacy and standard SQL templating:
142
+ if (
143
+ template_syntax_config.enable_legacy_syntax
144
+ and template_syntax_config.enable_standard_syntax
145
+ ):
146
+ env = choose_sql_jinja_env_based_on_template_syntax(content)
147
+ elif template_syntax_config.enable_legacy_syntax:
148
+ env = _get_legacy_sql_env()
149
+ elif template_syntax_config.enable_standard_syntax:
150
+ env = _get_standard_sql_env()
151
+ else:
152
+ env = None
153
+
154
+ if env:
155
+ content = env.from_string(content).render(context_data)
156
+
157
+ # resolve jinja templating
158
+ if template_syntax_config.enable_jinja_syntax:
159
+ jinja_env = get_basic_jinja_env()
160
+ content = jinja_env.from_string(content).render(context_data)
161
+
162
+ return content
@@ -9,6 +9,7 @@ from snowflake.cli.api.project.util import (
9
9
  )
10
10
 
11
11
  USER_STAGE_PREFIX = "~"
12
+ SNOW_PREFIX = "snow://"
12
13
 
13
14
 
14
15
  class StagePath:
@@ -19,6 +20,7 @@ class StagePath:
19
20
  git_ref: str | None = None,
20
21
  trailing_slash: bool = False,
21
22
  ):
23
+ self._is_snow_prefixed_stage = stage_name.startswith(SNOW_PREFIX)
22
24
  self._stage_name = self.strip_stage_prefixes(stage_name)
23
25
  self._path = PurePosixPath(path) if path else PurePosixPath(".")
24
26
 
@@ -53,6 +55,10 @@ class StagePath:
53
55
  def stage_with_at(self) -> str:
54
56
  return self.add_at_prefix(self._stage_name)
55
57
 
58
+ @property
59
+ def stage_with_snow(self) -> str:
60
+ return self.add_snow_prefix(self._stage_name)
61
+
56
62
  def is_user_stage(self) -> bool:
57
63
  return self._is_user_stage
58
64
 
@@ -69,6 +75,12 @@ class StagePath:
69
75
  return "@" + text
70
76
  return text
71
77
 
78
+ @staticmethod
79
+ def add_snow_prefix(text: str):
80
+ if not text.startswith(SNOW_PREFIX):
81
+ return SNOW_PREFIX + text
82
+ return text
83
+
72
84
  @staticmethod
73
85
  def strip_at_prefix(text: str):
74
86
  if text.startswith("@"):
@@ -77,8 +89,8 @@ class StagePath:
77
89
 
78
90
  @staticmethod
79
91
  def strip_snow_prefix(text: str):
80
- if text.startswith("snow://"):
81
- return text[len("snow://") :]
92
+ if text.startswith(SNOW_PREFIX):
93
+ return text[len(SNOW_PREFIX) :]
82
94
  return text
83
95
 
84
96
  @classmethod
@@ -86,8 +98,15 @@ class StagePath:
86
98
  return cls.strip_at_prefix(cls.strip_snow_prefix(text))
87
99
 
88
100
  @classmethod
89
- def from_stage_str(cls, stage_str: str | FQN):
101
+ def from_stage_str(cls, stage_str: str | FQN) -> StagePath:
102
+ _is_at_prefixed = str(stage_str).startswith("@")
103
+ _is_snow_prefixed = str(stage_str).startswith(SNOW_PREFIX)
90
104
  stage_str = cls.strip_stage_prefixes(str(stage_str))
105
+ if _is_snow_prefixed:
106
+ resource_type = stage_str.split("/", maxsplit=1)[0]
107
+ stage_str = stage_str.removeprefix(resource_type + "/")
108
+ else:
109
+ resource_type = ""
91
110
  parts = stage_str.split("/", maxsplit=1)
92
111
  parts = [p for p in parts if p]
93
112
  if len(parts) == 2:
@@ -95,9 +114,14 @@ class StagePath:
95
114
  else:
96
115
  stage_string = parts[0]
97
116
  path = None
98
- return cls(
117
+ if _is_at_prefixed:
118
+ stage_string = cls.add_at_prefix(stage_string)
119
+ if _is_snow_prefixed:
120
+ stage_string = cls.add_snow_prefix(resource_type + "/" + stage_string)
121
+ stage_path = cls(
99
122
  stage_name=stage_string, path=path, trailing_slash=stage_str.endswith("/")
100
123
  )
124
+ return stage_path
101
125
 
102
126
  @classmethod
103
127
  def from_git_str(cls, git_str: str):
@@ -105,9 +129,15 @@ class StagePath:
105
129
  @configuration_repo / branches/main / scripts/setup.sql
106
130
  @configuration_repo / branches/"foo/main" / scripts/setup.sql
107
131
  """
132
+ _is_at_prefixed = git_str.startswith("@")
133
+ _is_snow_prefixed = git_str.startswith(SNOW_PREFIX)
108
134
  repo_name, git_ref, path = cls._split_repo_path(
109
135
  cls.strip_stage_prefixes(git_str)
110
136
  )
137
+ if _is_at_prefixed:
138
+ repo_name = cls.add_at_prefix(repo_name)
139
+ if _is_snow_prefixed:
140
+ repo_name = cls.add_snow_prefix(repo_name)
111
141
  return cls(
112
142
  stage_name=repo_name,
113
143
  path=path,
@@ -150,7 +180,9 @@ class StagePath:
150
180
  path = path / self._path
151
181
 
152
182
  str_path = str(path)
153
- if at_prefix:
183
+ if self._is_snow_prefixed_stage:
184
+ str_path = self.add_snow_prefix(str_path)
185
+ elif at_prefix:
154
186
  str_path = self.add_at_prefix(str_path)
155
187
 
156
188
  if self._trailing_slash:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: snowflake-cli
3
- Version: 3.9.1
3
+ Version: 3.10.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
@@ -217,33 +217,66 @@ Classifier: Programming Language :: Python :: 3 :: Only
217
217
  Classifier: Programming Language :: SQL
218
218
  Classifier: Topic :: Database
219
219
  Requires-Python: >=3.10
220
+ Requires-Dist: annotated-types==0.7.0
221
+ Requires-Dist: asn1crypto==1.5.1
222
+ Requires-Dist: boto3==1.39.2
223
+ Requires-Dist: botocore==1.39.2
224
+ Requires-Dist: certifi==2025.6.15
225
+ Requires-Dist: cffi==1.17.1
226
+ Requires-Dist: cfgv==3.4.0
227
+ Requires-Dist: charset-normalizer==3.4.2
220
228
  Requires-Dist: click==8.1.8
229
+ Requires-Dist: cryptography==45.0.5
230
+ Requires-Dist: faker==37.4.0
231
+ Requires-Dist: filelock==3.18.0
232
+ Requires-Dist: gitdb==4.0.12
221
233
  Requires-Dist: gitpython==3.1.44
234
+ Requires-Dist: identify==2.6.12
235
+ Requires-Dist: idna==3.10
236
+ Requires-Dist: iniconfig==2.1.0
222
237
  Requires-Dist: jinja2==3.1.6
238
+ Requires-Dist: keyring==25.6.0
239
+ Requires-Dist: markdown-it-py==3.0.0
240
+ Requires-Dist: markupsafe==3.0.2
241
+ Requires-Dist: nodeenv==1.9.1
223
242
  Requires-Dist: packaging
224
243
  Requires-Dist: pip
244
+ Requires-Dist: platformdirs==4.3.8
225
245
  Requires-Dist: pluggy==1.6.0
226
246
  Requires-Dist: prompt-toolkit==3.0.51
227
- Requires-Dist: pydantic==2.11.4
247
+ Requires-Dist: pydantic-core==2.33.2
248
+ Requires-Dist: pydantic==2.11.7
249
+ Requires-Dist: pygments==2.19.2
250
+ Requires-Dist: pyjwt==2.10.1
251
+ Requires-Dist: pyopenssl==25.1.0
252
+ Requires-Dist: python-dateutil==2.9.0.post0
253
+ Requires-Dist: pytz==2025.2
228
254
  Requires-Dist: pyyaml==6.0.2
229
- Requires-Dist: requests==2.32.3
230
- Requires-Dist: requirements-parser==0.11.0
255
+ Requires-Dist: requests==2.32.4
256
+ Requires-Dist: requirements-parser==0.13.0
231
257
  Requires-Dist: rich==14.0.0
232
- Requires-Dist: setuptools==80.3.1
258
+ Requires-Dist: setuptools==80.8.0
259
+ Requires-Dist: shellingham==1.5.4
233
260
  Requires-Dist: snowflake-connector-python[secure-local-storage]==3.15.0
234
- Requires-Dist: snowflake-core==1.4.0
235
- Requires-Dist: snowflake-snowpark-python<1.26.0,>=1.15.0; python_version < '3.12'
236
- Requires-Dist: tomlkit==0.13.2
237
- Requires-Dist: typer==0.15.2
238
- Requires-Dist: urllib3<2.5,>=1.24.3
261
+ Requires-Dist: snowflake-core==1.5.1
262
+ Requires-Dist: snowflake-snowpark-python==1.33.0; python_version < '3.12'
263
+ Requires-Dist: sortedcontainers==2.4.0
264
+ Requires-Dist: tomlkit==0.13.3
265
+ Requires-Dist: typer==0.16.0
266
+ Requires-Dist: typing-extensions==4.14.0
267
+ Requires-Dist: typing-inspection==0.4.1
268
+ Requires-Dist: urllib3<2.6,>=1.24.3
269
+ Requires-Dist: virtualenv==20.31.2
270
+ Requires-Dist: wcwidth==0.2.13
271
+ Requires-Dist: werkzeug==3.1.3
239
272
  Provides-Extra: development
240
273
  Requires-Dist: coverage==7.8.0; extra == 'development'
241
274
  Requires-Dist: factory-boy==3.3.3; extra == 'development'
242
- Requires-Dist: faker==37.3.0; extra == 'development'
275
+ Requires-Dist: faker==37.4.0; extra == 'development'
243
276
  Requires-Dist: pre-commit>=3.5.0; extra == 'development'
244
277
  Requires-Dist: pytest-httpserver==1.1.3; extra == 'development'
245
278
  Requires-Dist: pytest-randomly==3.16.0; extra == 'development'
246
- Requires-Dist: pytest==8.3.5; extra == 'development'
279
+ Requires-Dist: pytest==8.4.1; extra == 'development'
247
280
  Requires-Dist: syrupy==4.9.1; extra == 'development'
248
281
  Provides-Extra: packaging
249
282
  Description-Content-Type: text/markdown