snowflake-cli 3.9.0__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.
- snowflake/cli/__about__.py +1 -1
- snowflake/cli/_app/printing.py +53 -13
- snowflake/cli/_app/snow_connector.py +1 -0
- snowflake/cli/_app/telemetry.py +2 -0
- snowflake/cli/_app/version_check.py +73 -6
- snowflake/cli/_plugins/cortex/commands.py +8 -3
- snowflake/cli/_plugins/cortex/manager.py +24 -20
- snowflake/cli/_plugins/dbt/commands.py +5 -2
- snowflake/cli/_plugins/git/manager.py +1 -11
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +4 -0
- snowflake/cli/_plugins/nativeapp/commands.py +3 -4
- snowflake/cli/_plugins/nativeapp/entities/application_package.py +1 -1
- snowflake/cli/_plugins/nativeapp/release_channel/commands.py +1 -2
- snowflake/cli/_plugins/nativeapp/version/commands.py +1 -2
- snowflake/cli/_plugins/project/commands.py +61 -10
- snowflake/cli/_plugins/project/manager.py +20 -1
- snowflake/cli/_plugins/snowpark/common.py +23 -11
- snowflake/cli/_plugins/snowpark/snowpark_entity.py +13 -5
- snowflake/cli/_plugins/snowpark/snowpark_entity_model.py +10 -2
- snowflake/cli/_plugins/spcs/image_registry/commands.py +2 -2
- snowflake/cli/_plugins/spcs/image_registry/manager.py +2 -2
- snowflake/cli/_plugins/sql/commands.py +49 -1
- snowflake/cli/_plugins/sql/manager.py +14 -4
- snowflake/cli/_plugins/sql/repl.py +4 -0
- snowflake/cli/_plugins/stage/commands.py +30 -11
- snowflake/cli/_plugins/stage/diff.py +2 -0
- snowflake/cli/_plugins/stage/manager.py +79 -55
- snowflake/cli/_plugins/streamlit/streamlit_entity.py +17 -30
- snowflake/cli/api/artifacts/upload.py +1 -1
- snowflake/cli/api/cli_global_context.py +5 -14
- snowflake/cli/api/commands/decorators.py +7 -0
- snowflake/cli/api/commands/flags.py +12 -0
- snowflake/cli/api/commands/snow_typer.py +23 -2
- snowflake/cli/api/config.py +9 -5
- snowflake/cli/api/connections.py +1 -0
- snowflake/cli/api/entities/common.py +16 -13
- snowflake/cli/api/entities/utils.py +15 -9
- snowflake/cli/api/feature_flags.py +2 -5
- snowflake/cli/api/output/formats.py +6 -0
- snowflake/cli/api/output/types.py +48 -2
- snowflake/cli/api/rendering/sql_templates.py +67 -11
- snowflake/cli/api/stage_path.py +37 -5
- {snowflake_cli-3.9.0.dist-info → snowflake_cli-3.10.0.dist-info}/METADATA +45 -12
- {snowflake_cli-3.9.0.dist-info → snowflake_cli-3.10.0.dist-info}/RECORD +47 -48
- snowflake/cli/_plugins/project/feature_flags.py +0 -22
- {snowflake_cli-3.9.0.dist-info → snowflake_cli-3.10.0.dist-info}/WHEEL +0 -0
- {snowflake_cli-3.9.0.dist-info → snowflake_cli-3.10.0.dist-info}/entry_points.txt +0 -0
- {snowflake_cli-3.9.0.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
|
|
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
|
|
136
|
+
def _get_fqn(
|
|
144
137
|
self, schema: Optional[str] = None, database: Optional[str] = None
|
|
145
|
-
) ->
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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 =
|
|
124
|
-
stage_fqn =
|
|
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: {
|
|
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=
|
|
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=
|
|
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)
|
|
@@ -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 (
|
|
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 =
|
|
70
|
-
new_syntax_env =
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
snowflake/cli/api/stage_path.py
CHANGED
|
@@ -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(
|
|
81
|
-
return text[len(
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
230
|
-
Requires-Dist: requirements-parser==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.
|
|
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.
|
|
235
|
-
Requires-Dist: snowflake-snowpark-python
|
|
236
|
-
Requires-Dist:
|
|
237
|
-
Requires-Dist:
|
|
238
|
-
Requires-Dist:
|
|
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.
|
|
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.
|
|
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
|