snowflake-cli 3.0.2__py3-none-any.whl → 3.2.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/cli_app.py +3 -0
- snowflake/cli/_app/dev/docs/templates/overview.rst.jinja2 +1 -1
- snowflake/cli/_app/dev/docs/templates/usage.rst.jinja2 +2 -2
- snowflake/cli/_app/telemetry.py +69 -4
- snowflake/cli/_plugins/connection/commands.py +152 -99
- snowflake/cli/_plugins/connection/util.py +54 -9
- snowflake/cli/_plugins/cortex/manager.py +1 -1
- snowflake/cli/_plugins/git/commands.py +6 -3
- snowflake/cli/_plugins/git/manager.py +9 -4
- snowflake/cli/_plugins/nativeapp/artifacts.py +77 -13
- snowflake/cli/_plugins/nativeapp/codegen/artifact_processor.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/compiler.py +7 -0
- snowflake/cli/_plugins/nativeapp/codegen/sandbox.py +10 -10
- snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +2 -2
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/extension_function_utils.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +8 -8
- snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +5 -3
- snowflake/cli/_plugins/nativeapp/commands.py +144 -188
- snowflake/cli/_plugins/nativeapp/constants.py +1 -0
- snowflake/cli/_plugins/nativeapp/entities/application.py +564 -351
- snowflake/cli/_plugins/nativeapp/entities/application_package.py +583 -929
- snowflake/cli/_plugins/nativeapp/entities/models/event_sharing_telemetry.py +58 -0
- snowflake/cli/_plugins/nativeapp/exceptions.py +12 -0
- snowflake/cli/_plugins/nativeapp/same_account_install_method.py +0 -2
- snowflake/cli/_plugins/nativeapp/sf_facade.py +30 -0
- snowflake/cli/_plugins/nativeapp/sf_facade_constants.py +25 -0
- snowflake/cli/_plugins/nativeapp/sf_facade_exceptions.py +117 -0
- snowflake/cli/_plugins/nativeapp/sf_sql_facade.py +525 -0
- snowflake/cli/_plugins/nativeapp/v2_conversions/{v2_to_v1_decorator.py → compat.py} +88 -117
- snowflake/cli/_plugins/nativeapp/version/commands.py +36 -32
- snowflake/cli/_plugins/notebook/manager.py +2 -2
- snowflake/cli/_plugins/object/commands.py +10 -1
- snowflake/cli/_plugins/object/manager.py +13 -5
- snowflake/cli/_plugins/snowpark/common.py +63 -21
- snowflake/cli/_plugins/snowpark/package/anaconda_packages.py +3 -3
- snowflake/cli/_plugins/spcs/common.py +29 -0
- snowflake/cli/_plugins/spcs/compute_pool/manager.py +7 -9
- snowflake/cli/_plugins/spcs/image_registry/manager.py +2 -2
- snowflake/cli/_plugins/spcs/image_repository/commands.py +4 -37
- snowflake/cli/_plugins/spcs/image_repository/manager.py +4 -1
- snowflake/cli/_plugins/spcs/services/commands.py +100 -17
- snowflake/cli/_plugins/spcs/services/manager.py +108 -16
- snowflake/cli/_plugins/sql/commands.py +9 -1
- snowflake/cli/_plugins/sql/manager.py +9 -4
- snowflake/cli/_plugins/stage/commands.py +28 -19
- snowflake/cli/_plugins/stage/diff.py +17 -17
- snowflake/cli/_plugins/stage/manager.py +304 -84
- snowflake/cli/_plugins/stage/md5.py +1 -1
- snowflake/cli/_plugins/streamlit/manager.py +5 -5
- snowflake/cli/_plugins/workspace/commands.py +27 -4
- snowflake/cli/_plugins/workspace/context.py +38 -0
- snowflake/cli/_plugins/workspace/manager.py +23 -13
- snowflake/cli/api/cli_global_context.py +4 -3
- snowflake/cli/api/commands/flags.py +23 -7
- snowflake/cli/api/config.py +30 -9
- snowflake/cli/api/connections.py +12 -1
- snowflake/cli/api/console/console.py +4 -19
- snowflake/cli/api/entities/common.py +4 -2
- snowflake/cli/api/entities/utils.py +36 -69
- snowflake/cli/api/errno.py +2 -0
- snowflake/cli/api/exceptions.py +41 -0
- snowflake/cli/api/identifiers.py +8 -0
- snowflake/cli/api/metrics.py +223 -7
- snowflake/cli/api/output/types.py +1 -1
- snowflake/cli/api/project/definition_conversion.py +293 -77
- snowflake/cli/api/project/schemas/entities/common.py +11 -0
- snowflake/cli/api/project/schemas/project_definition.py +30 -25
- snowflake/cli/api/rest_api.py +26 -4
- snowflake/cli/api/secure_utils.py +1 -1
- snowflake/cli/api/sql_execution.py +40 -29
- snowflake/cli/api/stage_path.py +244 -0
- snowflake/cli/api/utils/definition_rendering.py +3 -5
- {snowflake_cli-3.0.2.dist-info → snowflake_cli-3.2.0.dist-info}/METADATA +14 -15
- {snowflake_cli-3.0.2.dist-info → snowflake_cli-3.2.0.dist-info}/RECORD +78 -77
- {snowflake_cli-3.0.2.dist-info → snowflake_cli-3.2.0.dist-info}/WHEEL +1 -1
- snowflake/cli/_plugins/nativeapp/manager.py +0 -415
- snowflake/cli/_plugins/nativeapp/project_model.py +0 -211
- snowflake/cli/_plugins/nativeapp/run_processor.py +0 -184
- snowflake/cli/_plugins/nativeapp/teardown_processor.py +0 -70
- snowflake/cli/_plugins/nativeapp/version/version_processor.py +0 -98
- snowflake/cli/_plugins/workspace/action_context.py +0 -18
- {snowflake_cli-3.0.2.dist-info → snowflake_cli-3.2.0.dist-info}/entry_points.txt +0 -0
- {snowflake_cli-3.0.2.dist-info → snowflake_cli-3.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -25,8 +25,10 @@ from snowflake.cli.api.cli_global_context import get_cli_context
|
|
|
25
25
|
from snowflake.cli.api.console import cli_console
|
|
26
26
|
from snowflake.cli.api.constants import ObjectType
|
|
27
27
|
from snowflake.cli.api.exceptions import (
|
|
28
|
+
CouldNotUseObjectError,
|
|
28
29
|
DatabaseNotProvidedError,
|
|
29
30
|
SchemaNotProvidedError,
|
|
31
|
+
ShowSpecificObjectMultipleRowsError,
|
|
30
32
|
SnowflakeSQLExecutionError,
|
|
31
33
|
)
|
|
32
34
|
from snowflake.cli.api.identifiers import FQN
|
|
@@ -40,19 +42,27 @@ from snowflake.connector.cursor import DictCursor, SnowflakeCursor
|
|
|
40
42
|
from snowflake.connector.errors import ProgrammingError
|
|
41
43
|
|
|
42
44
|
|
|
43
|
-
class
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
class BaseSqlExecutor:
|
|
46
|
+
"""
|
|
47
|
+
Base class for executing SQL queries on a Snowflake connection.
|
|
48
|
+
|
|
49
|
+
This class provides methods to execute single or multiple SQL queries and handle the connection context.
|
|
50
|
+
It also includes logging capabilities.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(self, connection: SnowflakeConnection | None = None) -> None:
|
|
46
54
|
self._connection = connection
|
|
47
55
|
|
|
48
56
|
@property
|
|
49
57
|
def _conn(self) -> SnowflakeConnection:
|
|
58
|
+
"""Returns the current Snowflake connection, either from the instance or the global CLI context."""
|
|
50
59
|
if self._connection:
|
|
51
60
|
return self._connection
|
|
52
61
|
return get_cli_context().connection
|
|
53
62
|
|
|
54
63
|
@cached_property
|
|
55
64
|
def _log(self):
|
|
65
|
+
"""Returns a logger instance for logging debug information."""
|
|
56
66
|
return logging.getLogger(__name__)
|
|
57
67
|
|
|
58
68
|
def _execute_string(
|
|
@@ -75,30 +85,35 @@ class SqlExecutor:
|
|
|
75
85
|
)
|
|
76
86
|
return stream_generator if return_cursors else list()
|
|
77
87
|
|
|
78
|
-
def
|
|
79
|
-
|
|
88
|
+
def execute_query(self, query: str, **kwargs):
|
|
89
|
+
"""Executes a single SQL query and returns the result"""
|
|
90
|
+
*_, last_result = list(self._execute_string(dedent(query), **kwargs))
|
|
80
91
|
return last_result
|
|
81
92
|
|
|
82
|
-
def
|
|
93
|
+
def execute_queries(self, queries: str, **kwargs):
|
|
94
|
+
"""Executes multiple SQL queries (passed as one string) and returns the results as a list"""
|
|
83
95
|
return list(self._execute_string(dedent(queries), **kwargs))
|
|
84
96
|
|
|
85
|
-
def execute_query(self, query: str, **kwargs):
|
|
86
|
-
return self._execute_query(query, **kwargs)
|
|
87
97
|
|
|
88
|
-
|
|
89
|
-
|
|
98
|
+
class SqlExecutor(BaseSqlExecutor):
|
|
99
|
+
"""
|
|
100
|
+
SqlExecutor extends BaseSqlExecutor and provides additional methods to manage roles, warehouses,
|
|
101
|
+
and create specific objects like secrets and API integrations.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
def __init__(self, connection: SnowflakeConnection | None = None):
|
|
105
|
+
super().__init__(connection)
|
|
106
|
+
self._snowpark_session = None
|
|
90
107
|
|
|
91
108
|
def use(self, object_type: ObjectType, name: str):
|
|
92
109
|
try:
|
|
93
|
-
self.
|
|
94
|
-
except ProgrammingError:
|
|
110
|
+
self.execute_query(f"use {object_type.value.sf_name} {name}")
|
|
111
|
+
except ProgrammingError as err:
|
|
95
112
|
# Rewrite the error to make the message more useful.
|
|
96
|
-
raise
|
|
97
|
-
f"Could not use {object_type} {name}. Object does not exist, or operation cannot be performed."
|
|
98
|
-
)
|
|
113
|
+
raise CouldNotUseObjectError(object_type=object_type, name=name) from err
|
|
99
114
|
|
|
100
115
|
def current_role(self) -> str:
|
|
101
|
-
return self.
|
|
116
|
+
return self.execute_query(f"select current_role()").fetchone()[0]
|
|
102
117
|
|
|
103
118
|
@contextmanager
|
|
104
119
|
def use_role(self, new_role: str):
|
|
@@ -110,17 +125,15 @@ class SqlExecutor:
|
|
|
110
125
|
is_different_role = new_role.lower() != prev_role.lower()
|
|
111
126
|
if is_different_role:
|
|
112
127
|
self._log.debug("Assuming different role: %s", new_role)
|
|
113
|
-
self.
|
|
128
|
+
self.execute_query(f"use role {new_role}")
|
|
114
129
|
try:
|
|
115
130
|
yield
|
|
116
131
|
finally:
|
|
117
132
|
if is_different_role:
|
|
118
|
-
self.
|
|
133
|
+
self.execute_query(f"use role {prev_role}")
|
|
119
134
|
|
|
120
135
|
def session_has_warehouse(self) -> bool:
|
|
121
|
-
result = self.
|
|
122
|
-
"select current_warehouse() is not null"
|
|
123
|
-
).fetchone()
|
|
136
|
+
result = self.execute_query("select current_warehouse() is not null").fetchone()
|
|
124
137
|
return bool(result[0])
|
|
125
138
|
|
|
126
139
|
@contextmanager
|
|
@@ -131,7 +144,7 @@ class SqlExecutor:
|
|
|
131
144
|
If there is no default warehouse in the account, it will throw an error.
|
|
132
145
|
"""
|
|
133
146
|
|
|
134
|
-
wh_result = self.
|
|
147
|
+
wh_result = self.execute_query(f"select current_warehouse()").fetchone()
|
|
135
148
|
# If user has an assigned default warehouse, prev_wh will contain a value even if the warehouse is suspended.
|
|
136
149
|
try:
|
|
137
150
|
prev_wh = wh_result[0]
|
|
@@ -153,7 +166,7 @@ class SqlExecutor:
|
|
|
153
166
|
def create_password_secret(
|
|
154
167
|
self, name: FQN, username: str, password: str
|
|
155
168
|
) -> SnowflakeCursor:
|
|
156
|
-
return self.
|
|
169
|
+
return self.execute_query(
|
|
157
170
|
f"""
|
|
158
171
|
create secret {name.sql_identifier}
|
|
159
172
|
type = password
|
|
@@ -165,7 +178,7 @@ class SqlExecutor:
|
|
|
165
178
|
def create_api_integration(
|
|
166
179
|
self, name: FQN, api_provider: str, allowed_prefix: str, secret: Optional[str]
|
|
167
180
|
) -> SnowflakeCursor:
|
|
168
|
-
return self.
|
|
181
|
+
return self.execute_query(
|
|
169
182
|
f"""
|
|
170
183
|
create api integration {name.sql_identifier}
|
|
171
184
|
api_provider = {api_provider}
|
|
@@ -180,7 +193,7 @@ class SqlExecutor:
|
|
|
180
193
|
Check that a database and schema are provided before executing the query. Useful for operating on schema level objects.
|
|
181
194
|
"""
|
|
182
195
|
self.check_database_and_schema_provided(name)
|
|
183
|
-
return self.
|
|
196
|
+
return self.execute_query(query, **kwargs)
|
|
184
197
|
|
|
185
198
|
def check_database_and_schema_provided(self, name: Optional[str] = None) -> None:
|
|
186
199
|
"""
|
|
@@ -240,16 +253,14 @@ class SqlExecutor:
|
|
|
240
253
|
show_obj_query, name=name, cursor_class=DictCursor
|
|
241
254
|
)
|
|
242
255
|
else:
|
|
243
|
-
show_obj_cursor = self.
|
|
256
|
+
show_obj_cursor = self.execute_query( # type: ignore
|
|
244
257
|
show_obj_query, cursor_class=DictCursor
|
|
245
258
|
)
|
|
246
259
|
|
|
247
260
|
if show_obj_cursor.rowcount is None:
|
|
248
261
|
raise SnowflakeSQLExecutionError(show_obj_query)
|
|
249
262
|
elif show_obj_cursor.rowcount > 1:
|
|
250
|
-
raise
|
|
251
|
-
f"Received multiple rows from result of SQL statement: {show_obj_query}. Usage of 'show_specific_object' may not be properly scoped."
|
|
252
|
-
)
|
|
263
|
+
raise ShowSpecificObjectMultipleRowsError(show_obj_query=show_obj_query)
|
|
253
264
|
|
|
254
265
|
show_obj_row = find_first_row(
|
|
255
266
|
show_obj_cursor,
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from pathlib import Path, PurePosixPath
|
|
5
|
+
|
|
6
|
+
from snowflake.cli.api.identifiers import FQN
|
|
7
|
+
from snowflake.cli.api.project.util import (
|
|
8
|
+
to_string_literal,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
USER_STAGE_PREFIX = "~"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class StagePath:
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
stage_name: str,
|
|
18
|
+
path: str | PurePosixPath | None = None,
|
|
19
|
+
git_ref: str | None = None,
|
|
20
|
+
trailing_slash: bool = False,
|
|
21
|
+
):
|
|
22
|
+
self._stage_name = self.strip_stage_prefixes(stage_name)
|
|
23
|
+
self._path = PurePosixPath(path) if path else PurePosixPath(".")
|
|
24
|
+
|
|
25
|
+
self._trailing_slash = trailing_slash
|
|
26
|
+
# Check if user stage
|
|
27
|
+
self._is_user_stage = self._stage_name.startswith(USER_STAGE_PREFIX)
|
|
28
|
+
|
|
29
|
+
# Setup git information
|
|
30
|
+
self._git_ref = None
|
|
31
|
+
self._is_git_repo = False
|
|
32
|
+
if git_ref:
|
|
33
|
+
self._git_ref = git_ref
|
|
34
|
+
self._is_git_repo = True
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def get_user_stage(cls) -> StagePath:
|
|
38
|
+
return cls.from_stage_str("~")
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def stage(self) -> str:
|
|
42
|
+
return self._stage_name
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def path(self) -> PurePosixPath:
|
|
46
|
+
return self._path
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def stage_with_at(self) -> str:
|
|
50
|
+
return self.add_at_prefix(self._stage_name)
|
|
51
|
+
|
|
52
|
+
def is_user_stage(self) -> bool:
|
|
53
|
+
return self._is_user_stage
|
|
54
|
+
|
|
55
|
+
def is_git_repo(self) -> bool:
|
|
56
|
+
return self._is_git_repo
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def git_ref(self) -> str | None:
|
|
60
|
+
return self._git_ref
|
|
61
|
+
|
|
62
|
+
@staticmethod
|
|
63
|
+
def add_at_prefix(text: str):
|
|
64
|
+
if not text.startswith("@"):
|
|
65
|
+
return "@" + text
|
|
66
|
+
return text
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def strip_at_prefix(text: str):
|
|
70
|
+
if text.startswith("@"):
|
|
71
|
+
return text[1:]
|
|
72
|
+
return text
|
|
73
|
+
|
|
74
|
+
@staticmethod
|
|
75
|
+
def strip_snow_prefix(text: str):
|
|
76
|
+
if text.startswith("snow://"):
|
|
77
|
+
return text[len("snow://") :]
|
|
78
|
+
return text
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def strip_stage_prefixes(cls, text: str):
|
|
82
|
+
return cls.strip_at_prefix(cls.strip_snow_prefix(text))
|
|
83
|
+
|
|
84
|
+
@classmethod
|
|
85
|
+
def from_stage_str(cls, stage_str: str | FQN):
|
|
86
|
+
stage_str = cls.strip_stage_prefixes(str(stage_str))
|
|
87
|
+
parts = stage_str.split("/", maxsplit=1)
|
|
88
|
+
parts = [p for p in parts if p]
|
|
89
|
+
if len(parts) == 2:
|
|
90
|
+
stage_string, path = parts
|
|
91
|
+
else:
|
|
92
|
+
stage_string = parts[0]
|
|
93
|
+
path = None
|
|
94
|
+
return cls(
|
|
95
|
+
stage_name=stage_string, path=path, trailing_slash=stage_str.endswith("/")
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def from_git_str(cls, git_str: str):
|
|
100
|
+
"""
|
|
101
|
+
@configuration_repo / branches/main / scripts/setup.sql
|
|
102
|
+
@configuration_repo / branches/"foo/main" / scripts/setup.sql
|
|
103
|
+
"""
|
|
104
|
+
repo_name, git_ref, path = cls._split_repo_path(
|
|
105
|
+
cls.strip_stage_prefixes(git_str)
|
|
106
|
+
)
|
|
107
|
+
return cls(
|
|
108
|
+
stage_name=repo_name,
|
|
109
|
+
path=path,
|
|
110
|
+
git_ref=git_ref,
|
|
111
|
+
trailing_slash=git_str.endswith("/"),
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
@staticmethod
|
|
115
|
+
def _split_repo_path(git_str: str) -> tuple[str, str, str]:
|
|
116
|
+
parts = []
|
|
117
|
+
slash_index = 0
|
|
118
|
+
skipping_mode = False
|
|
119
|
+
for current_idx, (char, next_char) in enumerate(zip(git_str[:-1], git_str[1:])):
|
|
120
|
+
if not skipping_mode:
|
|
121
|
+
if char != "/":
|
|
122
|
+
continue
|
|
123
|
+
|
|
124
|
+
# Normal split
|
|
125
|
+
parts.append(git_str[slash_index:current_idx])
|
|
126
|
+
slash_index = current_idx + 1
|
|
127
|
+
|
|
128
|
+
if next_char == '"':
|
|
129
|
+
skipping_mode = not skipping_mode
|
|
130
|
+
# Add last part
|
|
131
|
+
parts.append(git_str[slash_index:])
|
|
132
|
+
repo_name = parts[0]
|
|
133
|
+
ref = parts[1] + "/" + parts[2]
|
|
134
|
+
path = "/".join(parts[3:]) if len(parts) > 2 else ""
|
|
135
|
+
return repo_name, ref, path
|
|
136
|
+
|
|
137
|
+
def absolute_path(self, no_fqn=False, at_prefix=True) -> str:
|
|
138
|
+
stage_name = self._stage_name
|
|
139
|
+
if not self.is_user_stage() and no_fqn:
|
|
140
|
+
stage_name = FQN.from_string(self._stage_name).name
|
|
141
|
+
|
|
142
|
+
path = PurePosixPath(stage_name)
|
|
143
|
+
if self.git_ref:
|
|
144
|
+
path = path / self.git_ref
|
|
145
|
+
if not self.is_root():
|
|
146
|
+
path = path / self._path
|
|
147
|
+
|
|
148
|
+
str_path = str(path)
|
|
149
|
+
if at_prefix:
|
|
150
|
+
str_path = self.add_at_prefix(str_path)
|
|
151
|
+
|
|
152
|
+
if self._trailing_slash:
|
|
153
|
+
return str_path.rstrip("/") + "/"
|
|
154
|
+
return str_path
|
|
155
|
+
|
|
156
|
+
def joinpath(self, path: str | Path) -> StagePath:
|
|
157
|
+
if self.is_file():
|
|
158
|
+
raise ValueError("Cannot join path to a file")
|
|
159
|
+
|
|
160
|
+
return StagePath(
|
|
161
|
+
stage_name=self._stage_name,
|
|
162
|
+
path=PurePosixPath(self._path) / str(path).lstrip("/"),
|
|
163
|
+
git_ref=self._git_ref,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
def __truediv__(self, path: str):
|
|
167
|
+
return self.joinpath(path)
|
|
168
|
+
|
|
169
|
+
def with_stage(self, stage_name: str) -> StagePath:
|
|
170
|
+
"""Returns a new path with new stage name"""
|
|
171
|
+
return StagePath(
|
|
172
|
+
stage_name=stage_name,
|
|
173
|
+
path=self._path,
|
|
174
|
+
git_ref=self._git_ref,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
@property
|
|
178
|
+
def parts(self) -> tuple[str, ...]:
|
|
179
|
+
return self._path.parts
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
def name(self) -> str:
|
|
183
|
+
return self._path.name
|
|
184
|
+
|
|
185
|
+
def is_dir(self) -> bool:
|
|
186
|
+
return "." not in self.name
|
|
187
|
+
|
|
188
|
+
def is_file(self) -> bool:
|
|
189
|
+
return not self.is_dir()
|
|
190
|
+
|
|
191
|
+
@property
|
|
192
|
+
def suffix(self) -> str:
|
|
193
|
+
return self._path.suffix
|
|
194
|
+
|
|
195
|
+
@property
|
|
196
|
+
def stem(self) -> str:
|
|
197
|
+
return self._path.stem
|
|
198
|
+
|
|
199
|
+
@property
|
|
200
|
+
def parent(self) -> StagePath:
|
|
201
|
+
return StagePath(
|
|
202
|
+
stage_name=self._stage_name, path=self._path.parent, git_ref=self._git_ref
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
def is_root(self) -> bool:
|
|
206
|
+
return self._path == PurePosixPath(".")
|
|
207
|
+
|
|
208
|
+
def root_path(self) -> StagePath:
|
|
209
|
+
if self.is_git_repo():
|
|
210
|
+
return StagePath(stage_name=self._stage_name, git_ref=self._git_ref)
|
|
211
|
+
return StagePath(stage_name=self._stage_name)
|
|
212
|
+
|
|
213
|
+
def is_quoted(self) -> bool:
|
|
214
|
+
path = self.absolute_path()
|
|
215
|
+
return path.startswith("'") and path.endswith("'")
|
|
216
|
+
|
|
217
|
+
def path_for_sql(self) -> str:
|
|
218
|
+
path = self.absolute_path()
|
|
219
|
+
if not re.fullmatch(r"@([\w./$])+", path):
|
|
220
|
+
return to_string_literal(path)
|
|
221
|
+
return path
|
|
222
|
+
|
|
223
|
+
def quoted_absolute_path(self) -> str:
|
|
224
|
+
if self.is_quoted():
|
|
225
|
+
return self.absolute_path()
|
|
226
|
+
return to_string_literal(self.absolute_path())
|
|
227
|
+
|
|
228
|
+
def relative_to(self, stage_path: StagePath) -> PurePosixPath:
|
|
229
|
+
return self.path.relative_to(stage_path.path)
|
|
230
|
+
|
|
231
|
+
def get_local_target_path(self, target_dir: Path, stage_root: StagePath):
|
|
232
|
+
# Case for downloading @stage/aa/file.py with root @stage/aa
|
|
233
|
+
if self.relative_to(stage_root) == PurePosixPath("."):
|
|
234
|
+
return target_dir
|
|
235
|
+
return (target_dir / self.relative_to(stage_root)).parent
|
|
236
|
+
|
|
237
|
+
def __str__(self):
|
|
238
|
+
return self.absolute_path()
|
|
239
|
+
|
|
240
|
+
def __repr__(self):
|
|
241
|
+
return str(self)
|
|
242
|
+
|
|
243
|
+
def __eq__(self, other):
|
|
244
|
+
return self.absolute_path() == other.absolute_path()
|
|
@@ -285,9 +285,7 @@ def _add_defaults_to_definition(original_definition: Definition) -> Definition:
|
|
|
285
285
|
with context({"skip_validation_on_templates": True}):
|
|
286
286
|
# pass a flag to Pydantic to skip validation for templated scalars
|
|
287
287
|
# populate the defaults
|
|
288
|
-
project_definition = build_project_definition(
|
|
289
|
-
**copy.deepcopy(original_definition)
|
|
290
|
-
)
|
|
288
|
+
project_definition = build_project_definition(**original_definition)
|
|
291
289
|
|
|
292
290
|
definition_with_defaults = project_definition.model_dump(
|
|
293
291
|
exclude_none=True, warnings=False, by_alias=True
|
|
@@ -392,8 +390,8 @@ def render_definition_template(
|
|
|
392
390
|
definition,
|
|
393
391
|
update_action=lambda val: template_env.render(val, final_context),
|
|
394
392
|
)
|
|
395
|
-
|
|
396
|
-
|
|
393
|
+
with context({"is_duplicated_run": True}):
|
|
394
|
+
project_definition = build_project_definition(**definition)
|
|
397
395
|
|
|
398
396
|
# Use the values originally provided by the user as the template context
|
|
399
397
|
# This intentionally doesn't reflect any field changes made by
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: snowflake-cli
|
|
3
|
-
Version: 3.0
|
|
3
|
+
Version: 3.2.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
|
|
@@ -206,7 +206,6 @@ License: Apache License
|
|
|
206
206
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
207
207
|
See the License for the specific language governing permissions and
|
|
208
208
|
limitations under the License.
|
|
209
|
-
License-File: LICENSE
|
|
210
209
|
Classifier: Development Status :: 5 - Production/Stable
|
|
211
210
|
Classifier: Environment :: Console
|
|
212
211
|
Classifier: Intended Audience :: Developers
|
|
@@ -226,20 +225,22 @@ Requires-Dist: pydantic==2.9.2
|
|
|
226
225
|
Requires-Dist: pyyaml==6.0.2
|
|
227
226
|
Requires-Dist: requests==2.32.3
|
|
228
227
|
Requires-Dist: requirements-parser==0.11.0
|
|
229
|
-
Requires-Dist: rich==13.
|
|
230
|
-
Requires-Dist: setuptools==75.
|
|
231
|
-
Requires-Dist: snowflake-connector-python[secure-local-storage]==3.12.
|
|
228
|
+
Requires-Dist: rich==13.9.4
|
|
229
|
+
Requires-Dist: setuptools==75.3.0
|
|
230
|
+
Requires-Dist: snowflake-connector-python[secure-local-storage]==3.12.3
|
|
232
231
|
Requires-Dist: snowflake-core==0.12.1; python_version < '3.12'
|
|
233
232
|
Requires-Dist: snowflake-snowpark-python>=1.15.0; python_version < '3.12'
|
|
234
233
|
Requires-Dist: tomlkit==0.13.2
|
|
235
234
|
Requires-Dist: typer==0.12.5
|
|
236
235
|
Requires-Dist: urllib3<2.3,>=1.24.3
|
|
237
236
|
Provides-Extra: development
|
|
238
|
-
Requires-Dist: coverage==7.6.
|
|
237
|
+
Requires-Dist: coverage==7.6.4; extra == 'development'
|
|
238
|
+
Requires-Dist: factory-boy==3.3.1; extra == 'development'
|
|
239
|
+
Requires-Dist: faker==30.8.2; extra == 'development'
|
|
239
240
|
Requires-Dist: pre-commit>=3.5.0; extra == 'development'
|
|
240
|
-
Requires-Dist: pytest-randomly==3.
|
|
241
|
+
Requires-Dist: pytest-randomly==3.16.0; extra == 'development'
|
|
241
242
|
Requires-Dist: pytest==8.3.3; extra == 'development'
|
|
242
|
-
Requires-Dist: syrupy==4.7.
|
|
243
|
+
Requires-Dist: syrupy==4.7.2; extra == 'development'
|
|
243
244
|
Provides-Extra: packaging
|
|
244
245
|
Requires-Dist: pyinstaller~=6.10; extra == 'packaging'
|
|
245
246
|
Description-Content-Type: text/markdown
|
|
@@ -272,15 +273,13 @@ Snowflake CLI is an open-source command-line tool explicitly designed for develo
|
|
|
272
273
|
|
|
273
274
|
With Snowflake CLI, developers can create, manage, update, and view apps running on Snowflake across workloads such as Streamlit in Snowflake, the Snowflake Native App Framework, Snowpark Container Services, and Snowpark. It supports a range of Snowflake features, including user-defined functions, stored procedures, Streamlit in Snowflake, and SQL execution.
|
|
274
275
|
|
|
275
|
-
|
|
276
276
|
**Note**: Snowflake CLI is in Public Preview (PuPr).
|
|
277
277
|
|
|
278
|
-
Docs: https://docs.snowflake.com/en/developer-guide/snowflake-cli-v2/index
|
|
279
|
-
|
|
280
|
-
Quick start: https://quickstarts.snowflake.com/guide/getting-started-with-snowflake-cli
|
|
278
|
+
Docs: <https://docs.snowflake.com/en/developer-guide/snowflake-cli-v2/index>.
|
|
281
279
|
|
|
282
|
-
|
|
280
|
+
Quick start: <https://quickstarts.snowflake.com/guide/getting-started-with-snowflake-cli>
|
|
283
281
|
|
|
282
|
+
Cheatsheet: <https://github.com/Snowflake-Labs/sf-cheatsheets/blob/main/snowflake-cli.md>
|
|
284
283
|
|
|
285
284
|
## Install Snowflake CLI
|
|
286
285
|
|
|
@@ -289,7 +288,7 @@ Cheatsheet: https://github.com/Snowflake-Labs/sf-cheatsheets/blob/main/snowflake
|
|
|
289
288
|
We recommend installing Snowflake CLI in isolated environment using [pipx](https://pipx.pypa.io/stable/). Requires Python >= 3.10
|
|
290
289
|
|
|
291
290
|
```bash
|
|
292
|
-
pipx install snowflake-cli
|
|
291
|
+
pipx install snowflake-cli
|
|
293
292
|
snow --help
|
|
294
293
|
```
|
|
295
294
|
|
|
@@ -322,4 +321,4 @@ You should now be able to run `snow` and get the CLI message.
|
|
|
322
321
|
## Get involved
|
|
323
322
|
|
|
324
323
|
Have a feature idea? Running into a bug? Want to contribute? We'd love to hear from you!
|
|
325
|
-
Please open or review issues, open pull requests, or reach out to us on developers@snowflake.com
|
|
324
|
+
Please open or review issues, open pull requests, or reach out to us on <developers@snowflake.com>
|