snowflake-cli-labs 2.5.0rc3__py3-none-any.whl → 2.6.0.dev0__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 (67) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/api/cli_global_context.py +31 -3
  3. snowflake/cli/api/commands/decorators.py +21 -6
  4. snowflake/cli/api/commands/flags.py +60 -51
  5. snowflake/cli/api/commands/snow_typer.py +24 -0
  6. snowflake/cli/api/commands/typer_pre_execute.py +26 -0
  7. snowflake/cli/api/console/abc.py +8 -0
  8. snowflake/cli/api/console/console.py +29 -4
  9. snowflake/cli/api/constants.py +3 -0
  10. snowflake/cli/api/project/definition.py +17 -35
  11. snowflake/cli/api/project/definition_manager.py +22 -19
  12. snowflake/cli/api/project/errors.py +9 -6
  13. snowflake/cli/api/project/schemas/identifier_model.py +1 -1
  14. snowflake/cli/api/project/schemas/native_app/application.py +15 -3
  15. snowflake/cli/api/project/schemas/native_app/native_app.py +5 -1
  16. snowflake/cli/api/project/schemas/native_app/path_mapping.py +14 -3
  17. snowflake/cli/api/project/schemas/project_definition.py +37 -6
  18. snowflake/cli/api/project/schemas/streamlit/streamlit.py +3 -0
  19. snowflake/cli/api/project/schemas/updatable_model.py +2 -6
  20. snowflake/cli/api/rest_api.py +113 -0
  21. snowflake/cli/api/sanitizers.py +43 -0
  22. snowflake/cli/api/sql_execution.py +7 -0
  23. snowflake/cli/api/utils/definition_rendering.py +95 -25
  24. snowflake/cli/api/utils/models.py +31 -26
  25. snowflake/cli/api/utils/rendering.py +24 -3
  26. snowflake/cli/app/cli_app.py +2 -0
  27. snowflake/cli/app/commands_registration/command_plugins_loader.py +8 -0
  28. snowflake/cli/app/dev/docs/commands_docs_generator.py +100 -0
  29. snowflake/cli/app/dev/docs/generator.py +8 -67
  30. snowflake/cli/app/dev/docs/project_definition_docs_generator.py +58 -0
  31. snowflake/cli/app/dev/docs/project_definition_generate_json_schema.py +227 -0
  32. snowflake/cli/app/dev/docs/template_utils.py +23 -0
  33. snowflake/cli/app/dev/docs/templates/definition_description.rst.jinja2 +38 -0
  34. snowflake/cli/app/dev/docs/templates/usage.rst.jinja2 +6 -1
  35. snowflake/cli/app/loggers.py +25 -0
  36. snowflake/cli/app/printing.py +7 -5
  37. snowflake/cli/app/telemetry.py +11 -0
  38. snowflake/cli/plugins/nativeapp/artifacts.py +78 -9
  39. snowflake/cli/plugins/nativeapp/codegen/artifact_processor.py +3 -11
  40. snowflake/cli/plugins/nativeapp/codegen/compiler.py +6 -24
  41. snowflake/cli/plugins/nativeapp/codegen/snowpark/python_processor.py +27 -27
  42. snowflake/cli/plugins/nativeapp/commands.py +23 -12
  43. snowflake/cli/plugins/nativeapp/constants.py +2 -0
  44. snowflake/cli/plugins/nativeapp/errno.py +15 -0
  45. snowflake/cli/plugins/nativeapp/feature_flags.py +24 -0
  46. snowflake/cli/plugins/nativeapp/init.py +5 -0
  47. snowflake/cli/plugins/nativeapp/manager.py +101 -103
  48. snowflake/cli/plugins/nativeapp/project_model.py +181 -0
  49. snowflake/cli/plugins/nativeapp/run_processor.py +178 -110
  50. snowflake/cli/plugins/nativeapp/teardown_processor.py +89 -64
  51. snowflake/cli/plugins/nativeapp/utils.py +2 -2
  52. snowflake/cli/plugins/nativeapp/version/commands.py +3 -3
  53. snowflake/cli/plugins/object/commands.py +69 -3
  54. snowflake/cli/plugins/object/manager.py +44 -3
  55. snowflake/cli/plugins/snowpark/commands.py +2 -2
  56. snowflake/cli/plugins/sql/commands.py +2 -10
  57. snowflake/cli/plugins/sql/manager.py +4 -2
  58. snowflake/cli/plugins/stage/commands.py +23 -4
  59. snowflake/cli/plugins/stage/diff.py +81 -51
  60. snowflake/cli/plugins/stage/manager.py +2 -1
  61. snowflake/cli/plugins/streamlit/commands.py +2 -1
  62. snowflake/cli/plugins/streamlit/manager.py +6 -0
  63. {snowflake_cli_labs-2.5.0rc3.dist-info → snowflake_cli_labs-2.6.0.dev0.dist-info}/METADATA +15 -9
  64. {snowflake_cli_labs-2.5.0rc3.dist-info → snowflake_cli_labs-2.6.0.dev0.dist-info}/RECORD +67 -56
  65. {snowflake_cli_labs-2.5.0rc3.dist-info → snowflake_cli_labs-2.6.0.dev0.dist-info}/WHEEL +1 -1
  66. {snowflake_cli_labs-2.5.0rc3.dist-info → snowflake_cli_labs-2.6.0.dev0.dist-info}/entry_points.txt +0 -0
  67. {snowflake_cli_labs-2.5.0rc3.dist-info → snowflake_cli_labs-2.6.0.dev0.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 typing import Optional, Tuple, Union
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 OBJECT_TO_NAMES, ObjectNames
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("\n".join(statements))
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 DiffResult, compute_stage_diff
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
- ) -> ObjectResult:
182
+ ) -> Optional[CommandResult]:
172
183
  """
173
184
  Diffs a stage with a local folder.
174
185
  """
175
- diff: DiffResult = compute_stage_diff(Path(folder_name), stage_name)
176
- return ObjectResult(str(diff))
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 __str__(self) -> str:
66
- """
67
- Method override for the standard behavior of string representation for this class.
68
- """
69
- components: List[
70
- str
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=false parallel={parallel} overwrite={overwrite}"
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.5.0rc3
3
+ Version: 2.6.0.dev0
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 :: 3 - Alpha
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.7.3
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.0.0
231
- Requires-Dist: snowflake-connector-python[secure-local-storage]==3.10.1
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.21.1
235
+ Requires-Dist: urllib3<2.3,>=1.24.3
236
236
  Provides-Extra: development
237
- Requires-Dist: coverage==7.5.3; extra == 'development'
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). Docs at https://docs.snowflake.com/en/developer-guide/snowflake-cli-v2/index
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 Snowflake-Labs/snowflake-cli
298
+ brew tap snowflakedb/snowflake-cli
293
299
  brew install snowflake-cli
294
300
  snow --help
295
301
  ```