snowflake-cli-labs 2.5.0rc3__py3-none-any.whl → 2.6.0rc0__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/api/cli_global_context.py +31 -3
- snowflake/cli/api/commands/decorators.py +21 -6
- snowflake/cli/api/commands/flags.py +60 -51
- snowflake/cli/api/commands/snow_typer.py +24 -0
- snowflake/cli/api/commands/typer_pre_execute.py +26 -0
- snowflake/cli/api/console/abc.py +8 -0
- snowflake/cli/api/console/console.py +29 -4
- snowflake/cli/api/constants.py +3 -0
- snowflake/cli/api/project/definition.py +17 -35
- snowflake/cli/api/project/definition_manager.py +22 -19
- snowflake/cli/api/project/errors.py +9 -6
- snowflake/cli/api/project/schemas/identifier_model.py +1 -1
- snowflake/cli/api/project/schemas/native_app/application.py +15 -3
- snowflake/cli/api/project/schemas/native_app/native_app.py +5 -1
- snowflake/cli/api/project/schemas/native_app/path_mapping.py +14 -3
- snowflake/cli/api/project/schemas/project_definition.py +37 -6
- snowflake/cli/api/project/schemas/streamlit/streamlit.py +3 -0
- snowflake/cli/api/project/schemas/updatable_model.py +2 -6
- snowflake/cli/api/rest_api.py +113 -0
- snowflake/cli/api/sanitizers.py +43 -0
- snowflake/cli/api/sql_execution.py +7 -0
- snowflake/cli/api/utils/definition_rendering.py +95 -25
- snowflake/cli/api/utils/models.py +31 -26
- snowflake/cli/api/utils/rendering.py +24 -3
- snowflake/cli/app/cli_app.py +2 -0
- snowflake/cli/app/commands_registration/command_plugins_loader.py +8 -0
- snowflake/cli/app/dev/docs/commands_docs_generator.py +100 -0
- snowflake/cli/app/dev/docs/generator.py +8 -67
- snowflake/cli/app/dev/docs/project_definition_docs_generator.py +58 -0
- snowflake/cli/app/dev/docs/project_definition_generate_json_schema.py +227 -0
- snowflake/cli/app/dev/docs/template_utils.py +23 -0
- snowflake/cli/app/dev/docs/templates/definition_description.rst.jinja2 +38 -0
- snowflake/cli/app/dev/docs/templates/usage.rst.jinja2 +6 -1
- snowflake/cli/app/loggers.py +25 -0
- snowflake/cli/app/printing.py +7 -5
- snowflake/cli/app/telemetry.py +11 -0
- snowflake/cli/plugins/nativeapp/artifacts.py +78 -9
- snowflake/cli/plugins/nativeapp/codegen/artifact_processor.py +3 -11
- snowflake/cli/plugins/nativeapp/codegen/compiler.py +6 -24
- snowflake/cli/plugins/nativeapp/codegen/snowpark/python_processor.py +27 -27
- snowflake/cli/plugins/nativeapp/commands.py +23 -12
- snowflake/cli/plugins/nativeapp/constants.py +2 -0
- snowflake/cli/plugins/nativeapp/errno.py +15 -0
- snowflake/cli/plugins/nativeapp/feature_flags.py +24 -0
- snowflake/cli/plugins/nativeapp/init.py +5 -0
- snowflake/cli/plugins/nativeapp/manager.py +101 -103
- snowflake/cli/plugins/nativeapp/project_model.py +181 -0
- snowflake/cli/plugins/nativeapp/run_processor.py +178 -110
- snowflake/cli/plugins/nativeapp/teardown_processor.py +89 -64
- snowflake/cli/plugins/nativeapp/utils.py +2 -2
- snowflake/cli/plugins/nativeapp/version/commands.py +3 -3
- snowflake/cli/plugins/object/commands.py +69 -3
- snowflake/cli/plugins/object/manager.py +44 -3
- snowflake/cli/plugins/snowpark/commands.py +2 -2
- snowflake/cli/plugins/sql/commands.py +2 -10
- snowflake/cli/plugins/sql/manager.py +4 -2
- snowflake/cli/plugins/stage/commands.py +23 -4
- snowflake/cli/plugins/stage/diff.py +81 -51
- snowflake/cli/plugins/stage/manager.py +2 -1
- snowflake/cli/plugins/streamlit/commands.py +2 -1
- snowflake/cli/plugins/streamlit/manager.py +6 -0
- {snowflake_cli_labs-2.5.0rc3.dist-info → snowflake_cli_labs-2.6.0rc0.dist-info}/METADATA +15 -9
- {snowflake_cli_labs-2.5.0rc3.dist-info → snowflake_cli_labs-2.6.0rc0.dist-info}/RECORD +67 -56
- {snowflake_cli_labs-2.5.0rc3.dist-info → snowflake_cli_labs-2.6.0rc0.dist-info}/WHEEL +1 -1
- {snowflake_cli_labs-2.5.0rc3.dist-info → snowflake_cli_labs-2.6.0rc0.dist-info}/entry_points.txt +0 -0
- {snowflake_cli_labs-2.5.0rc3.dist-info → snowflake_cli_labs-2.6.0rc0.dist-info}/licenses/LICENSE +0 -0
|
@@ -14,14 +14,14 @@
|
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
-
from typing import Tuple
|
|
17
|
+
from typing import List, Optional, Tuple
|
|
18
18
|
|
|
19
19
|
import typer
|
|
20
20
|
from click import ClickException
|
|
21
|
-
from snowflake.cli.api.commands.flags import like_option
|
|
21
|
+
from snowflake.cli.api.commands.flags import like_option, parse_key_value_variables
|
|
22
22
|
from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
|
|
23
23
|
from snowflake.cli.api.constants import SUPPORTED_OBJECTS, VALID_SCOPES
|
|
24
|
-
from snowflake.cli.api.output.types import QueryResult
|
|
24
|
+
from snowflake.cli.api.output.types import MessageResult, QueryResult
|
|
25
25
|
from snowflake.cli.api.project.util import is_valid_identifier
|
|
26
26
|
from snowflake.cli.plugins.object.manager import ObjectManager
|
|
27
27
|
|
|
@@ -35,6 +35,30 @@ NameArgument = typer.Argument(help="Name of the object")
|
|
|
35
35
|
ObjectArgument = typer.Argument(
|
|
36
36
|
help="Type of object. For example table, procedure, streamlit.",
|
|
37
37
|
case_sensitive=False,
|
|
38
|
+
show_default=False,
|
|
39
|
+
)
|
|
40
|
+
# TODO: add documentation link
|
|
41
|
+
ObjectAttributesArgument = typer.Argument(
|
|
42
|
+
None,
|
|
43
|
+
help="""Object attributes provided as a list of key=value pairs,
|
|
44
|
+
for example name=my_db comment='created with Snowflake CLI'.
|
|
45
|
+
|
|
46
|
+
Check documentation for the full list of available parameters
|
|
47
|
+
for every object.
|
|
48
|
+
""",
|
|
49
|
+
show_default=False,
|
|
50
|
+
)
|
|
51
|
+
# TODO: add documentation link
|
|
52
|
+
ObjectDefinitionJsonOption = typer.Option(
|
|
53
|
+
None,
|
|
54
|
+
"--json",
|
|
55
|
+
help="""Object definition in JSON format, for example
|
|
56
|
+
\'{"name": "my_db", "comment": "created with Snowflake CLI"}\'.
|
|
57
|
+
|
|
58
|
+
Check documentation for the full list of available parameters
|
|
59
|
+
for every object.
|
|
60
|
+
""",
|
|
61
|
+
show_default=False,
|
|
38
62
|
)
|
|
39
63
|
LikeOption = like_option(
|
|
40
64
|
help_example='`list function --like "my%"` lists all functions that begin with “my”',
|
|
@@ -106,3 +130,45 @@ def describe(
|
|
|
106
130
|
return QueryResult(
|
|
107
131
|
ObjectManager().describe(object_type=object_type, name=object_name)
|
|
108
132
|
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@app.command(name="create", requires_connection=True)
|
|
136
|
+
def create(
|
|
137
|
+
object_type: str = ObjectArgument,
|
|
138
|
+
object_attributes: Optional[List[str]] = ObjectAttributesArgument,
|
|
139
|
+
object_json: str = ObjectDefinitionJsonOption,
|
|
140
|
+
**options,
|
|
141
|
+
):
|
|
142
|
+
"""
|
|
143
|
+
Create an object of a given type. Check documentation for the list of supported objects
|
|
144
|
+
and parameters.
|
|
145
|
+
"""
|
|
146
|
+
import json
|
|
147
|
+
|
|
148
|
+
if object_attributes and object_json:
|
|
149
|
+
raise ClickException(
|
|
150
|
+
"Conflict: both object attributes and JSON definition are provided"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
if object_json:
|
|
154
|
+
object_data = json.loads(object_json)
|
|
155
|
+
elif object_attributes:
|
|
156
|
+
|
|
157
|
+
def _parse_if_json(value: str):
|
|
158
|
+
try:
|
|
159
|
+
return json.loads(value)
|
|
160
|
+
except json.JSONDecodeError:
|
|
161
|
+
return value
|
|
162
|
+
|
|
163
|
+
object_data = {
|
|
164
|
+
v.key: _parse_if_json(v.value)
|
|
165
|
+
for v in parse_key_value_variables(object_attributes)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
else:
|
|
169
|
+
raise ClickException(
|
|
170
|
+
"Provide either list of object attributes, or object definition in JSON format"
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
result = ObjectManager().create(object_type=object_type, object_data=object_data)
|
|
174
|
+
return MessageResult(result)
|
|
@@ -14,13 +14,19 @@
|
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
-
from
|
|
17
|
+
from textwrap import dedent
|
|
18
|
+
from typing import Any, Dict, Optional, Tuple, Union
|
|
18
19
|
|
|
19
20
|
from click import ClickException
|
|
20
|
-
from snowflake.cli.api.constants import
|
|
21
|
+
from snowflake.cli.api.constants import (
|
|
22
|
+
OBJECT_TO_NAMES,
|
|
23
|
+
ObjectNames,
|
|
24
|
+
)
|
|
25
|
+
from snowflake.cli.api.rest_api import RestApi
|
|
21
26
|
from snowflake.cli.api.sql_execution import SqlExecutionMixin
|
|
22
27
|
from snowflake.connector import ProgrammingError
|
|
23
28
|
from snowflake.connector.cursor import SnowflakeCursor
|
|
29
|
+
from snowflake.connector.errors import BadRequest
|
|
24
30
|
|
|
25
31
|
|
|
26
32
|
def _get_object_names(object_type: str) -> ObjectNames:
|
|
@@ -30,6 +36,16 @@ def _get_object_names(object_type: str) -> ObjectNames:
|
|
|
30
36
|
return OBJECT_TO_NAMES[object_type]
|
|
31
37
|
|
|
32
38
|
|
|
39
|
+
def _pluralize_object_type(object_type: str) -> str:
|
|
40
|
+
"""
|
|
41
|
+
Pluralize object type without depending on OBJECT_TO_NAMES.
|
|
42
|
+
"""
|
|
43
|
+
if object_type.endswith("y"):
|
|
44
|
+
return object_type[:-1].lower() + "ies"
|
|
45
|
+
else:
|
|
46
|
+
return object_type.lower() + "s"
|
|
47
|
+
|
|
48
|
+
|
|
33
49
|
class ObjectManager(SqlExecutionMixin):
|
|
34
50
|
def show(
|
|
35
51
|
self,
|
|
@@ -47,7 +63,7 @@ class ObjectManager(SqlExecutionMixin):
|
|
|
47
63
|
query += f" in {scope[0].replace('-', ' ')} {scope[1]}"
|
|
48
64
|
return self._execute_query(query, **kwargs)
|
|
49
65
|
|
|
50
|
-
def drop(self, *, object_type, name: str) -> SnowflakeCursor:
|
|
66
|
+
def drop(self, *, object_type: str, name: str) -> SnowflakeCursor:
|
|
51
67
|
object_name = _get_object_names(object_type).sf_name
|
|
52
68
|
return self._execute_query(f"drop {object_name} {name}")
|
|
53
69
|
|
|
@@ -66,3 +82,28 @@ class ObjectManager(SqlExecutionMixin):
|
|
|
66
82
|
return True
|
|
67
83
|
except ProgrammingError:
|
|
68
84
|
return False
|
|
85
|
+
|
|
86
|
+
def create(self, object_type: str, object_data: Dict[str, Any]) -> str:
|
|
87
|
+
rest = RestApi(self._conn)
|
|
88
|
+
url = rest.determine_url_for_create_query(
|
|
89
|
+
plural_object_type=_pluralize_object_type(object_type)
|
|
90
|
+
)
|
|
91
|
+
if not url:
|
|
92
|
+
return f"Create operation for type {object_type} is not supported. Try using `sql -q 'CREATE ...'` command"
|
|
93
|
+
try:
|
|
94
|
+
response = rest.send_rest_request(url=url, method="post", data=object_data)
|
|
95
|
+
# workaround as SnowflakeRestful class ignores some errors, dropping their info and returns {} instead.
|
|
96
|
+
if not response:
|
|
97
|
+
raise ClickException(
|
|
98
|
+
dedent(
|
|
99
|
+
""" An unexpected error occurred while creating the object. Try again with --debug for more info.
|
|
100
|
+
Most probable reasons:
|
|
101
|
+
* object you are trying to create already exists
|
|
102
|
+
* role you are using do not have permissions to create this object"""
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
return response["status"]
|
|
106
|
+
except BadRequest:
|
|
107
|
+
raise ClickException(
|
|
108
|
+
"Incorrect object definition (arguments misspelled or malformatted)."
|
|
109
|
+
)
|
|
@@ -128,7 +128,7 @@ def deploy(
|
|
|
128
128
|
By default, if any of the objects exist already the commands will fail unless `--replace` flag is provided.
|
|
129
129
|
All deployed objects use the same artifact which is deployed only once.
|
|
130
130
|
"""
|
|
131
|
-
snowpark = cli_context.project_definition
|
|
131
|
+
snowpark = cli_context.project_definition.snowpark
|
|
132
132
|
paths = SnowparkPackagePaths.for_snowpark_project(
|
|
133
133
|
project_root=SecurePath(cli_context.project_root),
|
|
134
134
|
snowpark_project_definition=snowpark,
|
|
@@ -400,7 +400,7 @@ def build(
|
|
|
400
400
|
ignore_anaconda = True
|
|
401
401
|
snowpark_paths = SnowparkPackagePaths.for_snowpark_project(
|
|
402
402
|
project_root=SecurePath(cli_context.project_root),
|
|
403
|
-
snowpark_project_definition=cli_context.project_definition,
|
|
403
|
+
snowpark_project_definition=cli_context.project_definition.snowpark,
|
|
404
404
|
)
|
|
405
405
|
log.info("Building package using sources from: %s", snowpark_paths.source.path)
|
|
406
406
|
|
|
@@ -18,9 +18,9 @@ from pathlib import Path
|
|
|
18
18
|
from typing import List, Optional
|
|
19
19
|
|
|
20
20
|
import typer
|
|
21
|
+
from snowflake.cli.api.commands.decorators import with_project_definition
|
|
21
22
|
from snowflake.cli.api.commands.flags import (
|
|
22
23
|
parse_key_value_variables,
|
|
23
|
-
project_definition_option,
|
|
24
24
|
)
|
|
25
25
|
from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
|
|
26
26
|
from snowflake.cli.api.output.types import CommandResult, MultipleResults, QueryResult
|
|
@@ -30,15 +30,8 @@ from snowflake.cli.plugins.sql.manager import SqlManager
|
|
|
30
30
|
app = SnowTyperFactory()
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
def _parse_key_value(key_value_str: str):
|
|
34
|
-
parts = key_value_str.split("=")
|
|
35
|
-
if len(parts) < 2:
|
|
36
|
-
raise ValueError("Passed key-value pair does not comform with key=value format")
|
|
37
|
-
|
|
38
|
-
return parts[0], "=".join(parts[1:])
|
|
39
|
-
|
|
40
|
-
|
|
41
33
|
@app.command(name="sql", requires_connection=True, no_args_is_help=True)
|
|
34
|
+
@with_project_definition(is_optional=True)
|
|
42
35
|
def execute_sql(
|
|
43
36
|
query: Optional[str] = typer.Option(
|
|
44
37
|
None,
|
|
@@ -69,7 +62,6 @@ def execute_sql(
|
|
|
69
62
|
help="String in format of key=value. If provided the SQL content will "
|
|
70
63
|
"be treated as template and rendered using provided data.",
|
|
71
64
|
),
|
|
72
|
-
_: Optional[str] = project_definition_option(optional=True),
|
|
73
65
|
**options,
|
|
74
66
|
) -> CommandResult:
|
|
75
67
|
"""
|
|
@@ -23,7 +23,7 @@ from typing import Dict, Iterable, List, Tuple
|
|
|
23
23
|
from click import ClickException, UsageError
|
|
24
24
|
from jinja2 import UndefinedError
|
|
25
25
|
from snowflake.cli.api.secure_path import UNLIMITED, SecurePath
|
|
26
|
-
from snowflake.cli.api.sql_execution import SqlExecutionMixin
|
|
26
|
+
from snowflake.cli.api.sql_execution import SqlExecutionMixin, VerboseCursor
|
|
27
27
|
from snowflake.cli.api.utils.rendering import snowflake_sql_jinja_render
|
|
28
28
|
from snowflake.cli.plugins.sql.snowsql_templating import transpile_snowsql_templates
|
|
29
29
|
from snowflake.connector.cursor import SnowflakeCursor
|
|
@@ -87,4 +87,6 @@ class SqlManager(SqlExecutionMixin):
|
|
|
87
87
|
)
|
|
88
88
|
single_statement = len(statements) == 1
|
|
89
89
|
|
|
90
|
-
return single_statement, self._execute_string(
|
|
90
|
+
return single_statement, self._execute_string(
|
|
91
|
+
"\n".join(statements), cursor_class=VerboseCursor
|
|
92
|
+
)
|
|
@@ -21,6 +21,7 @@ from typing import List, Optional
|
|
|
21
21
|
|
|
22
22
|
import click
|
|
23
23
|
import typer
|
|
24
|
+
from snowflake.cli.api.cli_global_context import cli_context
|
|
24
25
|
from snowflake.cli.api.commands.flags import (
|
|
25
26
|
OnErrorOption,
|
|
26
27
|
PatternOption,
|
|
@@ -30,6 +31,7 @@ from snowflake.cli.api.commands.flags import (
|
|
|
30
31
|
from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
|
|
31
32
|
from snowflake.cli.api.console import cli_console
|
|
32
33
|
from snowflake.cli.api.constants import ObjectType
|
|
34
|
+
from snowflake.cli.api.output.formats import OutputFormat
|
|
33
35
|
from snowflake.cli.api.output.types import (
|
|
34
36
|
CollectionResult,
|
|
35
37
|
CommandResult,
|
|
@@ -42,7 +44,11 @@ from snowflake.cli.plugins.object.command_aliases import (
|
|
|
42
44
|
add_object_command_aliases,
|
|
43
45
|
scope_option,
|
|
44
46
|
)
|
|
45
|
-
from snowflake.cli.plugins.stage.diff import
|
|
47
|
+
from snowflake.cli.plugins.stage.diff import (
|
|
48
|
+
DiffResult,
|
|
49
|
+
compute_stage_diff,
|
|
50
|
+
print_diff_to_console,
|
|
51
|
+
)
|
|
46
52
|
from snowflake.cli.plugins.stage.manager import OnErrorType, StageManager
|
|
47
53
|
|
|
48
54
|
app = SnowTyperFactory(
|
|
@@ -96,6 +102,10 @@ def copy(
|
|
|
96
102
|
False,
|
|
97
103
|
help="Copy files recursively with directory structure.",
|
|
98
104
|
),
|
|
105
|
+
auto_compress: bool = typer.Option(
|
|
106
|
+
default=False,
|
|
107
|
+
help="Specifies whether Snowflake uses gzip to compress files during upload. Ignored when downloading.",
|
|
108
|
+
),
|
|
99
109
|
**options,
|
|
100
110
|
) -> CommandResult:
|
|
101
111
|
"""
|
|
@@ -127,6 +137,7 @@ def copy(
|
|
|
127
137
|
destination_path=destination_path,
|
|
128
138
|
parallel=parallel,
|
|
129
139
|
overwrite=overwrite,
|
|
140
|
+
auto_compress=auto_compress,
|
|
130
141
|
)
|
|
131
142
|
|
|
132
143
|
|
|
@@ -168,12 +179,18 @@ def stage_diff(
|
|
|
168
179
|
show_default=False,
|
|
169
180
|
),
|
|
170
181
|
**options,
|
|
171
|
-
) ->
|
|
182
|
+
) -> Optional[CommandResult]:
|
|
172
183
|
"""
|
|
173
184
|
Diffs a stage with a local folder.
|
|
174
185
|
"""
|
|
175
|
-
diff: DiffResult = compute_stage_diff(
|
|
176
|
-
|
|
186
|
+
diff: DiffResult = compute_stage_diff(
|
|
187
|
+
local_root=Path(folder_name), stage_fqn=stage_name
|
|
188
|
+
)
|
|
189
|
+
if cli_context.output_format == OutputFormat.JSON:
|
|
190
|
+
return ObjectResult(diff.to_dict())
|
|
191
|
+
else:
|
|
192
|
+
print_diff_to_console(diff)
|
|
193
|
+
return None # don't print any output
|
|
177
194
|
|
|
178
195
|
|
|
179
196
|
@app.command("execute", requires_connection=True)
|
|
@@ -226,6 +243,7 @@ def _put(
|
|
|
226
243
|
destination_path: str,
|
|
227
244
|
parallel: int,
|
|
228
245
|
overwrite: bool,
|
|
246
|
+
auto_compress: bool,
|
|
229
247
|
):
|
|
230
248
|
if recursive:
|
|
231
249
|
raise click.ClickException("Recursive flag for upload is not supported.")
|
|
@@ -238,5 +256,6 @@ def _put(
|
|
|
238
256
|
stage_path=destination_path,
|
|
239
257
|
overwrite=overwrite,
|
|
240
258
|
parallel=parallel,
|
|
259
|
+
auto_compress=auto_compress,
|
|
241
260
|
)
|
|
242
261
|
return QueryResult(cursor)
|
|
@@ -19,12 +19,14 @@ import logging
|
|
|
19
19
|
import re
|
|
20
20
|
from dataclasses import dataclass, field
|
|
21
21
|
from pathlib import Path, PurePosixPath
|
|
22
|
-
from typing import Collection, Dict, List, Optional
|
|
22
|
+
from typing import Collection, Dict, List, Optional, Tuple
|
|
23
23
|
|
|
24
|
+
from snowflake.cli.api.console import cli_console as cc
|
|
24
25
|
from snowflake.cli.api.exceptions import (
|
|
25
26
|
SnowflakeSQLExecutionError,
|
|
26
27
|
)
|
|
27
28
|
from snowflake.cli.api.secure_path import UNLIMITED, SecurePath
|
|
29
|
+
from snowflake.cli.plugins.nativeapp.artifacts import BundleMap
|
|
28
30
|
from snowflake.connector.cursor import DictCursor
|
|
29
31
|
|
|
30
32
|
from .manager import StageManager
|
|
@@ -62,56 +64,12 @@ class DiffResult:
|
|
|
62
64
|
or len(self.only_on_stage) > 0
|
|
63
65
|
)
|
|
64
66
|
|
|
65
|
-
def
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
] = (
|
|
72
|
-
[]
|
|
73
|
-
) # py3.8 does not support subscriptions for builtin list, hence using List
|
|
74
|
-
|
|
75
|
-
# The specific order of conditionals is for an aesthetically pleasing output and ease of readability.
|
|
76
|
-
if not self.only_local:
|
|
77
|
-
components.append(
|
|
78
|
-
"There are no new files that exist only in your local directory."
|
|
79
|
-
)
|
|
80
|
-
if not self.only_on_stage:
|
|
81
|
-
components.append("There are no new files that exist only on the stage.")
|
|
82
|
-
if not self.different:
|
|
83
|
-
components.append(
|
|
84
|
-
"There are no existing files that have been modified, or their status is unknown."
|
|
85
|
-
)
|
|
86
|
-
if not self.identical:
|
|
87
|
-
components.append(
|
|
88
|
-
"There are no existing files that are identical to the ones on the stage."
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
if self.only_local:
|
|
92
|
-
components.extend(
|
|
93
|
-
["New files only on your local:", *[str(p) for p in self.only_local]]
|
|
94
|
-
)
|
|
95
|
-
if self.only_on_stage:
|
|
96
|
-
components.extend(
|
|
97
|
-
["New files only on the stage:", *[str(p) for p in self.only_on_stage]]
|
|
98
|
-
)
|
|
99
|
-
if self.different:
|
|
100
|
-
components.extend(
|
|
101
|
-
[
|
|
102
|
-
"Existing files modified or status unknown:",
|
|
103
|
-
*[str(p) for p in self.different],
|
|
104
|
-
]
|
|
105
|
-
)
|
|
106
|
-
if self.identical:
|
|
107
|
-
components.extend(
|
|
108
|
-
[
|
|
109
|
-
"Existing files identical to the stage:",
|
|
110
|
-
*[str(p) for p in self.identical],
|
|
111
|
-
]
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
return "\n".join(components)
|
|
67
|
+
def to_dict(self) -> dict:
|
|
68
|
+
return {
|
|
69
|
+
"modified": [str(p) for p in sorted(self.different)],
|
|
70
|
+
"added": [str(p) for p in sorted(self.only_local)],
|
|
71
|
+
"deleted": [str(p) for p in sorted(self.only_on_stage)],
|
|
72
|
+
}
|
|
115
73
|
|
|
116
74
|
|
|
117
75
|
def is_valid_md5sum(checksum: str) -> bool:
|
|
@@ -331,3 +289,75 @@ def sync_local_diff_with_stage(
|
|
|
331
289
|
# Could be ProgrammingError or IntegrityError from SnowflakeCursor
|
|
332
290
|
log.error(err)
|
|
333
291
|
raise SnowflakeSQLExecutionError()
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _to_src_dest_pair(
|
|
295
|
+
stage_path: StagePath, bundle_map: Optional[BundleMap]
|
|
296
|
+
) -> Tuple[Optional[str], str]:
|
|
297
|
+
if not bundle_map:
|
|
298
|
+
return None, str(stage_path)
|
|
299
|
+
|
|
300
|
+
dest_path = to_local_path(stage_path)
|
|
301
|
+
src = bundle_map.to_project_path(dest_path)
|
|
302
|
+
if src:
|
|
303
|
+
return str(src), str(stage_path)
|
|
304
|
+
|
|
305
|
+
return "?", str(stage_path)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _to_diff_line(status: str, src: Optional[str], dest: str) -> str:
|
|
309
|
+
if src is None:
|
|
310
|
+
src_prefix = ""
|
|
311
|
+
else:
|
|
312
|
+
src_prefix = f"{src} -> "
|
|
313
|
+
|
|
314
|
+
longest_status = "modified"
|
|
315
|
+
padding = " " * (len(longest_status) - len(status))
|
|
316
|
+
status_prefix = f"[red]{status}[/red]: {padding}"
|
|
317
|
+
|
|
318
|
+
return f"{status_prefix}{src_prefix}{dest}"
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def print_diff_to_console(
|
|
322
|
+
diff: DiffResult,
|
|
323
|
+
bundle_map: Optional[BundleMap] = None,
|
|
324
|
+
):
|
|
325
|
+
if not diff.has_changes():
|
|
326
|
+
cc.message("Your stage is up-to-date with your local deploy root.")
|
|
327
|
+
return
|
|
328
|
+
|
|
329
|
+
blank_line_needed = False
|
|
330
|
+
if diff.only_local or diff.different:
|
|
331
|
+
cc.message("Local changes to be deployed:")
|
|
332
|
+
messages_to_output = []
|
|
333
|
+
for p in diff.different:
|
|
334
|
+
src_dest_pair = _to_src_dest_pair(p, bundle_map)
|
|
335
|
+
messages_to_output.append(
|
|
336
|
+
(
|
|
337
|
+
src_dest_pair,
|
|
338
|
+
_to_diff_line("modified", src_dest_pair[0], src_dest_pair[1]),
|
|
339
|
+
)
|
|
340
|
+
)
|
|
341
|
+
for p in diff.only_local:
|
|
342
|
+
src_dest_pair = _to_src_dest_pair(p, bundle_map)
|
|
343
|
+
messages_to_output.append(
|
|
344
|
+
(
|
|
345
|
+
src_dest_pair,
|
|
346
|
+
_to_diff_line("added", src_dest_pair[0], src_dest_pair[1]),
|
|
347
|
+
)
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
with cc.indented():
|
|
351
|
+
for key, message in sorted(messages_to_output, key=lambda pair: pair[0]):
|
|
352
|
+
cc.message(message)
|
|
353
|
+
|
|
354
|
+
blank_line_needed = True
|
|
355
|
+
|
|
356
|
+
if diff.only_on_stage:
|
|
357
|
+
if blank_line_needed:
|
|
358
|
+
cc.message("")
|
|
359
|
+
cc.message(f"Deleted paths to be removed from your stage:")
|
|
360
|
+
with cc.indented():
|
|
361
|
+
for p in sorted(diff.only_on_stage):
|
|
362
|
+
diff_line = _to_diff_line("deleted", src=None, dest=str(p))
|
|
363
|
+
cc.message(diff_line)
|
|
@@ -161,6 +161,7 @@ class StageManager(SqlExecutionMixin):
|
|
|
161
161
|
parallel: int = 4,
|
|
162
162
|
overwrite: bool = False,
|
|
163
163
|
role: Optional[str] = None,
|
|
164
|
+
auto_compress: bool = False,
|
|
164
165
|
) -> SnowflakeCursor:
|
|
165
166
|
"""
|
|
166
167
|
This method will take a file path from the user's system and put it into a Snowflake stage,
|
|
@@ -174,7 +175,7 @@ class StageManager(SqlExecutionMixin):
|
|
|
174
175
|
log.info("Uploading %s to %s", local_resolved_path, stage_path)
|
|
175
176
|
cursor = self._execute_query(
|
|
176
177
|
f"put {self._to_uri(local_resolved_path)} {self.quote_stage_name(stage_path)} "
|
|
177
|
-
f"auto_compress=
|
|
178
|
+
f"auto_compress={str(auto_compress).lower()} parallel={parallel} overwrite={overwrite}"
|
|
178
179
|
)
|
|
179
180
|
return cursor
|
|
180
181
|
|
|
@@ -122,7 +122,7 @@ def streamlit_deploy(
|
|
|
122
122
|
environment.yml and any other pages or folders, if present. If you don’t specify a stage name, the `streamlit`
|
|
123
123
|
stage is used. If the specified stage does not exist, the command creates it.
|
|
124
124
|
"""
|
|
125
|
-
streamlit: Streamlit = cli_context.project_definition
|
|
125
|
+
streamlit: Streamlit = cli_context.project_definition.streamlit
|
|
126
126
|
if not streamlit:
|
|
127
127
|
return MessageResult("No streamlit were specified in project definition.")
|
|
128
128
|
|
|
@@ -149,6 +149,7 @@ def streamlit_deploy(
|
|
|
149
149
|
replace=replace,
|
|
150
150
|
query_warehouse=streamlit.query_warehouse,
|
|
151
151
|
additional_source_files=streamlit.additional_source_files,
|
|
152
|
+
title=streamlit.title,
|
|
152
153
|
**options,
|
|
153
154
|
)
|
|
154
155
|
|
|
@@ -78,6 +78,7 @@ class StreamlitManager(SqlExecutionMixin):
|
|
|
78
78
|
experimental: Optional[bool] = None,
|
|
79
79
|
query_warehouse: Optional[str] = None,
|
|
80
80
|
from_stage_name: Optional[str] = None,
|
|
81
|
+
title: Optional[str] = None,
|
|
81
82
|
):
|
|
82
83
|
query = []
|
|
83
84
|
if replace:
|
|
@@ -98,6 +99,8 @@ class StreamlitManager(SqlExecutionMixin):
|
|
|
98
99
|
|
|
99
100
|
if query_warehouse:
|
|
100
101
|
query.append(f"QUERY_WAREHOUSE = {query_warehouse}")
|
|
102
|
+
if title:
|
|
103
|
+
query.append(f"TITLE = '{title}'")
|
|
101
104
|
|
|
102
105
|
self._execute_query("\n".join(query))
|
|
103
106
|
|
|
@@ -111,6 +114,7 @@ class StreamlitManager(SqlExecutionMixin):
|
|
|
111
114
|
query_warehouse: Optional[str] = None,
|
|
112
115
|
replace: Optional[bool] = False,
|
|
113
116
|
additional_source_files: Optional[List[str]] = None,
|
|
117
|
+
title: Optional[str] = None,
|
|
114
118
|
**options,
|
|
115
119
|
):
|
|
116
120
|
# for backwards compatibility - quoted stage path might be case-sensitive
|
|
@@ -134,6 +138,7 @@ class StreamlitManager(SqlExecutionMixin):
|
|
|
134
138
|
replace=replace,
|
|
135
139
|
query_warehouse=query_warehouse,
|
|
136
140
|
experimental=True,
|
|
141
|
+
title=title,
|
|
137
142
|
)
|
|
138
143
|
try:
|
|
139
144
|
self._execute_query(f"ALTER streamlit {fully_qualified_name} CHECKOUT")
|
|
@@ -187,6 +192,7 @@ class StreamlitManager(SqlExecutionMixin):
|
|
|
187
192
|
query_warehouse=query_warehouse,
|
|
188
193
|
from_stage_name=root_location,
|
|
189
194
|
experimental=False,
|
|
195
|
+
title=title,
|
|
190
196
|
)
|
|
191
197
|
|
|
192
198
|
return self.get_url(fully_qualified_name)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: snowflake-cli-labs
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.6.0rc0
|
|
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
|
|
@@ -207,7 +207,7 @@ License: Apache License
|
|
|
207
207
|
See the License for the specific language governing permissions and
|
|
208
208
|
limitations under the License.
|
|
209
209
|
License-File: LICENSE
|
|
210
|
-
Classifier: Development Status ::
|
|
210
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
211
211
|
Classifier: Environment :: Console
|
|
212
212
|
Classifier: Intended Audience :: Developers
|
|
213
213
|
Classifier: Intended Audience :: Information Technology
|
|
@@ -222,19 +222,19 @@ Requires-Dist: jinja2==3.1.4
|
|
|
222
222
|
Requires-Dist: packaging
|
|
223
223
|
Requires-Dist: pip
|
|
224
224
|
Requires-Dist: pluggy==1.5.0
|
|
225
|
-
Requires-Dist: pydantic==2.
|
|
225
|
+
Requires-Dist: pydantic==2.8.0
|
|
226
226
|
Requires-Dist: pyyaml==6.0.1
|
|
227
227
|
Requires-Dist: requests==2.32.3
|
|
228
228
|
Requires-Dist: requirements-parser==0.9.0
|
|
229
229
|
Requires-Dist: rich==13.7.1
|
|
230
|
-
Requires-Dist: setuptools==70.
|
|
231
|
-
Requires-Dist: snowflake-connector-python[secure-local-storage]==3.
|
|
230
|
+
Requires-Dist: setuptools==70.1.1
|
|
231
|
+
Requires-Dist: snowflake-connector-python[secure-local-storage]==3.11.0
|
|
232
232
|
Requires-Dist: snowflake-core==0.8.0; python_version < '3.12'
|
|
233
233
|
Requires-Dist: tomlkit==0.12.5
|
|
234
234
|
Requires-Dist: typer==0.12.3
|
|
235
|
-
Requires-Dist: urllib3<2.3,>=1.
|
|
235
|
+
Requires-Dist: urllib3<2.3,>=1.24.3
|
|
236
236
|
Provides-Extra: development
|
|
237
|
-
Requires-Dist: coverage==7.5.
|
|
237
|
+
Requires-Dist: coverage==7.5.4; extra == 'development'
|
|
238
238
|
Requires-Dist: pre-commit>=3.5.0; extra == 'development'
|
|
239
239
|
Requires-Dist: pytest-randomly==3.15.0; extra == 'development'
|
|
240
240
|
Requires-Dist: pytest==8.2.2; extra == 'development'
|
|
@@ -270,7 +270,13 @@ Snowflake CLI is an open-source command-line tool explicitly designed for develo
|
|
|
270
270
|
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.
|
|
271
271
|
|
|
272
272
|
|
|
273
|
-
**Note**: Snowflake CLI is in Public Preview (PuPr).
|
|
273
|
+
**Note**: Snowflake CLI is in Public Preview (PuPr).
|
|
274
|
+
|
|
275
|
+
Docs: https://docs.snowflake.com/en/developer-guide/snowflake-cli-v2/index.
|
|
276
|
+
|
|
277
|
+
Quick start: https://quickstarts.snowflake.com/guide/getting-started-with-snowflake-cli
|
|
278
|
+
|
|
279
|
+
Cheatsheet: https://github.com/Snowflake-Labs/sf-cheatsheets/blob/main/snowflake-cli.md
|
|
274
280
|
|
|
275
281
|
|
|
276
282
|
## Install Snowflake CLI
|
|
@@ -289,7 +295,7 @@ snow --help
|
|
|
289
295
|
Requires [Homebrew](https://brew.sh/).
|
|
290
296
|
|
|
291
297
|
```bash
|
|
292
|
-
brew tap
|
|
298
|
+
brew tap snowflakedb/snowflake-cli
|
|
293
299
|
brew install snowflake-cli
|
|
294
300
|
snow --help
|
|
295
301
|
```
|