snowflake-cli-labs 2.3.1__py3-none-any.whl → 2.4.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.
Files changed (96) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/api/__init__.py +2 -0
  3. snowflake/cli/api/cli_global_context.py +8 -1
  4. snowflake/cli/api/commands/decorators.py +2 -2
  5. snowflake/cli/api/commands/flags.py +49 -4
  6. snowflake/cli/api/commands/snow_typer.py +2 -0
  7. snowflake/cli/api/console/abc.py +2 -0
  8. snowflake/cli/api/console/console.py +6 -5
  9. snowflake/cli/api/constants.py +5 -0
  10. snowflake/cli/api/exceptions.py +12 -0
  11. snowflake/cli/api/identifiers.py +123 -0
  12. snowflake/cli/api/plugins/command/__init__.py +2 -0
  13. snowflake/cli/api/plugins/plugin_config.py +2 -0
  14. snowflake/cli/api/project/definition.py +2 -0
  15. snowflake/cli/api/project/errors.py +3 -3
  16. snowflake/cli/api/project/schemas/identifier_model.py +35 -0
  17. snowflake/cli/api/project/schemas/native_app/native_app.py +4 -0
  18. snowflake/cli/api/project/schemas/native_app/path_mapping.py +21 -3
  19. snowflake/cli/api/project/schemas/project_definition.py +58 -6
  20. snowflake/cli/api/project/schemas/snowpark/argument.py +2 -0
  21. snowflake/cli/api/project/schemas/snowpark/callable.py +8 -17
  22. snowflake/cli/api/project/schemas/streamlit/streamlit.py +2 -2
  23. snowflake/cli/api/project/schemas/updatable_model.py +2 -0
  24. snowflake/cli/api/project/util.py +2 -0
  25. snowflake/cli/api/secure_path.py +2 -0
  26. snowflake/cli/api/sql_execution.py +14 -54
  27. snowflake/cli/api/utils/cursor.py +2 -0
  28. snowflake/cli/api/utils/models.py +23 -0
  29. snowflake/cli/api/utils/naming_utils.py +0 -27
  30. snowflake/cli/api/utils/rendering.py +178 -23
  31. snowflake/cli/app/api_impl/plugin/plugin_config_provider_impl.py +2 -0
  32. snowflake/cli/app/cli_app.py +4 -1
  33. snowflake/cli/app/commands_registration/builtin_plugins.py +8 -0
  34. snowflake/cli/app/commands_registration/command_plugins_loader.py +2 -0
  35. snowflake/cli/app/commands_registration/commands_registration_with_callbacks.py +2 -0
  36. snowflake/cli/app/commands_registration/typer_registration.py +2 -0
  37. snowflake/cli/app/dev/pycharm_remote_debug.py +2 -0
  38. snowflake/cli/app/loggers.py +2 -0
  39. snowflake/cli/app/main_typer.py +1 -1
  40. snowflake/cli/app/printing.py +3 -1
  41. snowflake/cli/app/snow_connector.py +2 -2
  42. snowflake/cli/plugins/connection/commands.py +5 -14
  43. snowflake/cli/plugins/connection/util.py +1 -1
  44. snowflake/cli/plugins/cortex/__init__.py +0 -0
  45. snowflake/cli/plugins/cortex/commands.py +312 -0
  46. snowflake/cli/plugins/cortex/constants.py +3 -0
  47. snowflake/cli/plugins/cortex/manager.py +175 -0
  48. snowflake/cli/plugins/cortex/plugin_spec.py +16 -0
  49. snowflake/cli/plugins/cortex/types.py +8 -0
  50. snowflake/cli/plugins/git/commands.py +15 -0
  51. snowflake/cli/plugins/nativeapp/artifacts.py +368 -123
  52. snowflake/cli/plugins/nativeapp/codegen/artifact_processor.py +45 -0
  53. snowflake/cli/plugins/nativeapp/codegen/compiler.py +104 -0
  54. snowflake/cli/plugins/nativeapp/codegen/sandbox.py +2 -0
  55. snowflake/cli/plugins/nativeapp/codegen/snowpark/callback_source.py.jinja +181 -0
  56. snowflake/cli/plugins/nativeapp/codegen/snowpark/extension_function_utils.py +196 -0
  57. snowflake/cli/plugins/nativeapp/codegen/snowpark/models.py +47 -0
  58. snowflake/cli/plugins/nativeapp/codegen/snowpark/python_processor.py +489 -0
  59. snowflake/cli/plugins/nativeapp/commands.py +11 -4
  60. snowflake/cli/plugins/nativeapp/common_flags.py +12 -5
  61. snowflake/cli/plugins/nativeapp/manager.py +49 -16
  62. snowflake/cli/plugins/nativeapp/policy.py +2 -0
  63. snowflake/cli/plugins/nativeapp/run_processor.py +2 -0
  64. snowflake/cli/plugins/nativeapp/teardown_processor.py +78 -8
  65. snowflake/cli/plugins/nativeapp/utils.py +7 -6
  66. snowflake/cli/plugins/nativeapp/version/commands.py +6 -5
  67. snowflake/cli/plugins/nativeapp/version/version_processor.py +2 -0
  68. snowflake/cli/plugins/notebook/commands.py +21 -0
  69. snowflake/cli/plugins/notebook/exceptions.py +6 -0
  70. snowflake/cli/plugins/notebook/manager.py +46 -3
  71. snowflake/cli/plugins/notebook/types.py +2 -0
  72. snowflake/cli/plugins/object/command_aliases.py +80 -0
  73. snowflake/cli/plugins/object/commands.py +10 -6
  74. snowflake/cli/plugins/object/common.py +2 -0
  75. snowflake/cli/plugins/object_stage_deprecated/__init__.py +1 -0
  76. snowflake/cli/plugins/object_stage_deprecated/plugin_spec.py +20 -0
  77. snowflake/cli/plugins/snowpark/commands.py +62 -6
  78. snowflake/cli/plugins/snowpark/common.py +17 -6
  79. snowflake/cli/plugins/spcs/compute_pool/commands.py +22 -1
  80. snowflake/cli/plugins/spcs/compute_pool/manager.py +2 -0
  81. snowflake/cli/plugins/spcs/image_repository/commands.py +25 -1
  82. snowflake/cli/plugins/spcs/image_repository/manager.py +3 -1
  83. snowflake/cli/plugins/spcs/services/commands.py +39 -5
  84. snowflake/cli/plugins/spcs/services/manager.py +2 -0
  85. snowflake/cli/plugins/sql/commands.py +13 -5
  86. snowflake/cli/plugins/sql/manager.py +40 -19
  87. snowflake/cli/plugins/stage/commands.py +29 -3
  88. snowflake/cli/plugins/stage/diff.py +2 -0
  89. snowflake/cli/plugins/streamlit/commands.py +26 -10
  90. snowflake/cli/plugins/streamlit/manager.py +9 -10
  91. {snowflake_cli_labs-2.3.1.dist-info → snowflake_cli_labs-2.4.0rc0.dist-info}/METADATA +4 -2
  92. {snowflake_cli_labs-2.3.1.dist-info → snowflake_cli_labs-2.4.0rc0.dist-info}/RECORD +96 -76
  93. /snowflake/cli/plugins/{object/stage_deprecated → object_stage_deprecated}/commands.py +0 -0
  94. {snowflake_cli_labs-2.3.1.dist-info → snowflake_cli_labs-2.4.0rc0.dist-info}/WHEEL +0 -0
  95. {snowflake_cli_labs-2.3.1.dist-info → snowflake_cli_labs-2.4.0rc0.dist-info}/entry_points.txt +0 -0
  96. {snowflake_cli_labs-2.3.1.dist-info → snowflake_cli_labs-2.4.0rc0.dist-info}/licenses/LICENSE +0 -0
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import logging
4
4
  from enum import Enum
5
- from typing import Dict, List, Optional, Set
5
+ from typing import Dict, List, Optional, Set, Tuple
6
6
 
7
7
  import typer
8
8
  from click import ClickException
@@ -14,6 +14,8 @@ from snowflake.cli.api.commands.flags import (
14
14
  ReplaceOption,
15
15
  deprecated_flag_callback_enum,
16
16
  execution_identifier_argument,
17
+ identifier_argument,
18
+ like_option,
17
19
  )
18
20
  from snowflake.cli.api.commands.project_initialisation import add_init_command
19
21
  from snowflake.cli.api.commands.snow_typer import SnowTyper
@@ -25,6 +27,7 @@ from snowflake.cli.api.constants import (
25
27
  from snowflake.cli.api.exceptions import (
26
28
  SecretsWithoutExternalAccessIntegrationError,
27
29
  )
30
+ from snowflake.cli.api.identifiers import FQN
28
31
  from snowflake.cli.api.output.types import (
29
32
  CollectionResult,
30
33
  CommandResult,
@@ -32,11 +35,22 @@ from snowflake.cli.api.output.types import (
32
35
  SingleQueryResult,
33
36
  )
34
37
  from snowflake.cli.api.project.schemas.snowpark.callable import (
35
- Callable,
36
38
  FunctionSchema,
37
39
  ProcedureSchema,
38
40
  )
39
41
  from snowflake.cli.api.secure_path import SecurePath
42
+ from snowflake.cli.plugins.object.commands import (
43
+ describe as object_describe,
44
+ )
45
+ from snowflake.cli.plugins.object.commands import (
46
+ drop as object_drop,
47
+ )
48
+ from snowflake.cli.plugins.object.commands import (
49
+ list_ as object_list,
50
+ )
51
+ from snowflake.cli.plugins.object.commands import (
52
+ scope_option,
53
+ )
40
54
  from snowflake.cli.plugins.object.manager import ObjectManager
41
55
  from snowflake.cli.plugins.snowpark import package_utils
42
56
  from snowflake.cli.plugins.snowpark.common import (
@@ -73,8 +87,15 @@ app = SnowTyper(
73
87
  ObjectTypeArgument = typer.Argument(
74
88
  help="Type of Snowpark object",
75
89
  case_sensitive=False,
90
+ show_default=False,
91
+ )
92
+ IdentifierArgument = identifier_argument(
93
+ "function/procedure",
94
+ example="hello(int, string)",
95
+ )
96
+ LikeOption = like_option(
97
+ help_example='`list function --like "my%"` lists all functions that begin with “my”',
76
98
  )
77
-
78
99
  add_init_command(app, project_type="Snowpark", template="default_snowpark")
79
100
 
80
101
 
@@ -132,7 +153,7 @@ def deploy(
132
153
  # Create stage
133
154
  stage_name = snowpark.stage_name
134
155
  stage_manager = StageManager()
135
- stage_name = stage_manager.to_fully_qualified_name(stage_name)
156
+ stage_name = FQN.from_string(stage_name).using_context()
136
157
  stage_manager.create(
137
158
  stage_name=stage_name, comment="deployments managed by Snowflake CLI"
138
159
  )
@@ -181,7 +202,7 @@ def deploy(
181
202
 
182
203
 
183
204
  def _assert_object_definitions_are_correct(
184
- object_type, object_definitions: List[Callable]
205
+ object_type, object_definitions: List[FunctionSchema | ProcedureSchema]
185
206
  ):
186
207
  for definition in object_definitions:
187
208
  database = definition.database
@@ -256,7 +277,7 @@ def get_app_stage_path(stage_name: Optional[str], project_name: str) -> str:
256
277
  def _deploy_single_object(
257
278
  manager: FunctionManager | ProcedureManager,
258
279
  object_type: ObjectType,
259
- object_definition: Callable,
280
+ object_definition: FunctionSchema | ProcedureSchema,
260
281
  existing_objects: Dict[str, Dict],
261
282
  snowflake_dependencies: List[str],
262
283
  stage_artifact_path: str,
@@ -275,6 +296,7 @@ def _deploy_single_object(
275
296
  handler = object_definition.handler
276
297
  returns = object_definition.returns
277
298
  imports = object_definition.imports
299
+ external_access_integrations = object_definition.external_access_integrations
278
300
  replace_object = False
279
301
 
280
302
  object_exists = identifier in existing_objects
@@ -285,6 +307,7 @@ def _deploy_single_object(
285
307
  handler=handler,
286
308
  return_type=returns,
287
309
  snowflake_dependencies=snowflake_dependencies,
310
+ external_access_integrations=external_access_integrations,
288
311
  imports=imports,
289
312
  stage_artifact_file=stage_artifact_path,
290
313
  )
@@ -463,3 +486,36 @@ def execute(
463
486
  "execute", object_type=object_type, execution_identifier=execution_identifier
464
487
  )
465
488
  return SingleQueryResult(cursor)
489
+
490
+
491
+ @app.command("list", requires_connection=True)
492
+ def list_(
493
+ object_type: _SnowparkObject = ObjectTypeArgument,
494
+ like: str = LikeOption,
495
+ scope: Tuple[str, str] = scope_option(
496
+ help_example="`list function --in database my_db`"
497
+ ),
498
+ **options,
499
+ ):
500
+ """Lists all available procedures or functions."""
501
+ object_list(object_type=object_type.value, like=like, scope=scope, **options)
502
+
503
+
504
+ @app.command("drop", requires_connection=True)
505
+ def drop(
506
+ object_type: _SnowparkObject = ObjectTypeArgument,
507
+ identifier: str = IdentifierArgument,
508
+ **options,
509
+ ):
510
+ """Drop procedure or function."""
511
+ object_drop(object_type=object_type.value, object_name=identifier, **options)
512
+
513
+
514
+ @app.command("describe", requires_connection=True)
515
+ def describe(
516
+ object_type: _SnowparkObject = ObjectTypeArgument,
517
+ identifier: str = IdentifierArgument,
518
+ **options,
519
+ ):
520
+ """Provides description of a procedure or function."""
521
+ object_describe(object_type=object_type.value, object_name=identifier, **options)
@@ -4,6 +4,7 @@ import re
4
4
  from typing import Dict, List, Optional, Set
5
5
 
6
6
  from snowflake.cli.api.constants import ObjectType
7
+ from snowflake.cli.api.identifiers import FQN
7
8
  from snowflake.cli.api.project.schemas.snowpark.argument import Argument
8
9
  from snowflake.cli.api.sql_execution import SqlExecutionMixin
9
10
  from snowflake.cli.plugins.snowpark.models import Requirement
@@ -21,6 +22,7 @@ def check_if_replace_is_required(
21
22
  handler: str,
22
23
  return_type: str,
23
24
  snowflake_dependencies: List[str],
25
+ external_access_integrations: List[str],
24
26
  imports: List[str],
25
27
  stage_artifact_file: str,
26
28
  ) -> bool:
@@ -42,6 +44,15 @@ def check_if_replace_is_required(
42
44
  )
43
45
  return True
44
46
 
47
+ if set(external_access_integrations) != set(
48
+ resource_json.get("external_access_integrations", [])
49
+ ):
50
+ log.info(
51
+ "Found difference of external access integrations. Replacing the %s.",
52
+ object_type,
53
+ )
54
+ return True
55
+
45
56
  if (
46
57
  resource_json["handler"].lower() != handler.lower()
47
58
  or _sql_to_python_return_type_mapper(resource_json["returns"]).lower()
@@ -189,12 +200,12 @@ def build_udf_sproc_identifier(
189
200
  result += f" default {val}"
190
201
  return result
191
202
 
192
- arguments = ", ".join(format_arg(arg) for arg in udf_sproc.signature)
193
- name = slq_exec_mixin.to_fully_qualified_name(
194
- udf_sproc.name,
195
- database=udf_sproc.database,
196
- schema=udf_sproc.schema_name,
197
- )
203
+ if udf_sproc.signature and udf_sproc.signature != "null":
204
+ arguments = ", ".join(format_arg(arg) for arg in udf_sproc.signature)
205
+ else:
206
+ arguments = ""
207
+
208
+ name = FQN.from_identifier_model(udf_sproc).using_context().identifier
198
209
  return f"{name}({arguments})"
199
210
 
200
211
 
@@ -1,11 +1,21 @@
1
+ from __future__ import annotations
2
+
1
3
  from typing import Optional
2
4
 
3
5
  import typer
4
6
  from click import ClickException
5
- from snowflake.cli.api.commands.flags import IfNotExistsOption, OverrideableOption
7
+ from snowflake.cli.api.commands.flags import (
8
+ IfNotExistsOption,
9
+ OverrideableOption,
10
+ like_option,
11
+ )
6
12
  from snowflake.cli.api.commands.snow_typer import SnowTyper
13
+ from snowflake.cli.api.constants import ObjectType
7
14
  from snowflake.cli.api.output.types import CommandResult, SingleQueryResult
8
15
  from snowflake.cli.api.project.util import is_valid_object_name
16
+ from snowflake.cli.plugins.object.command_aliases import (
17
+ add_object_command_aliases,
18
+ )
9
19
  from snowflake.cli.plugins.object.common import CommentOption
10
20
  from snowflake.cli.plugins.spcs.common import (
11
21
  validate_and_set_instances,
@@ -69,6 +79,16 @@ AutoSuspendSecsOption = OverrideableOption(
69
79
 
70
80
  _COMMENT_HELP = "Comment for the compute pool."
71
81
 
82
+ add_object_command_aliases(
83
+ app=app,
84
+ object_type=ObjectType.COMPUTE_POOL,
85
+ name_argument=ComputePoolNameArgument,
86
+ like_option=like_option(
87
+ help_example='`list --like "my%"` lists all compute pools that begin with “my”.'
88
+ ),
89
+ scope_option=None,
90
+ )
91
+
72
92
 
73
93
  @app.command(requires_connection=True)
74
94
  def create(
@@ -77,6 +97,7 @@ def create(
77
97
  ...,
78
98
  "--family",
79
99
  help="Name of the instance family. For more information about instance families, refer to the SQL CREATE COMPUTE POOL command.",
100
+ show_default=False,
80
101
  ),
81
102
  min_nodes: int = MinNodesOption(),
82
103
  max_nodes: Optional[int] = MaxNodesOption(),
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from typing import List, Optional
2
4
 
3
5
  from snowflake.cli.api.constants import ObjectType
@@ -1,18 +1,29 @@
1
+ from __future__ import annotations
2
+
1
3
  import json
2
4
  from typing import Optional
3
5
 
4
6
  import requests
5
7
  import typer
6
8
  from click import ClickException
7
- from snowflake.cli.api.commands.flags import IfNotExistsOption, ReplaceOption
9
+ from snowflake.cli.api.commands.flags import (
10
+ IfNotExistsOption,
11
+ ReplaceOption,
12
+ like_option,
13
+ )
8
14
  from snowflake.cli.api.commands.snow_typer import SnowTyper
9
15
  from snowflake.cli.api.console import cli_console
16
+ from snowflake.cli.api.constants import ObjectType
10
17
  from snowflake.cli.api.output.types import (
11
18
  CollectionResult,
12
19
  MessageResult,
13
20
  SingleQueryResult,
14
21
  )
15
22
  from snowflake.cli.api.project.util import is_valid_object_name
23
+ from snowflake.cli.plugins.object.command_aliases import (
24
+ add_object_command_aliases,
25
+ scope_option,
26
+ )
16
27
  from snowflake.cli.plugins.spcs.image_registry.manager import RegistryManager
17
28
  from snowflake.cli.plugins.spcs.image_repository.manager import ImageRepositoryManager
18
29
 
@@ -34,6 +45,18 @@ def _repo_name_callback(name: str):
34
45
  REPO_NAME_ARGUMENT = typer.Argument(
35
46
  help="Name of the image repository.",
36
47
  callback=_repo_name_callback,
48
+ show_default=False,
49
+ )
50
+
51
+ add_object_command_aliases(
52
+ app=app,
53
+ object_type=ObjectType.IMAGE_REPOSITORY,
54
+ name_argument=REPO_NAME_ARGUMENT,
55
+ like_option=like_option(
56
+ help_example='`list --like "my%"` lists all image repositories that begin with “my”.'
57
+ ),
58
+ scope_option=scope_option(help_example="`list --in database my_db`"),
59
+ ommit_commands=["describe"],
37
60
  )
38
61
 
39
62
 
@@ -106,6 +129,7 @@ def list_tags(
106
129
  "--image_name",
107
130
  "-i",
108
131
  help="Fully qualified name of the image as shown in the output of list-images",
132
+ show_default=False,
109
133
  ),
110
134
  **options,
111
135
  ) -> CollectionResult:
@@ -1,6 +1,7 @@
1
1
  from urllib.parse import urlparse
2
2
 
3
3
  from snowflake.cli.api.constants import ObjectType
4
+ from snowflake.cli.api.identifiers import FQN
4
5
  from snowflake.cli.api.sql_execution import SqlExecutionMixin
5
6
  from snowflake.cli.plugins.spcs.common import handle_object_already_exists
6
7
  from snowflake.connector.errors import ProgrammingError
@@ -22,8 +23,9 @@ class ImageRepositoryManager(SqlExecutionMixin):
22
23
  "image repositories", repo_name, check_schema=True
23
24
  )
24
25
  if repo_row is None:
26
+ fqn = FQN.from_string(repo_name).using_connection(self._conn)
25
27
  raise ProgrammingError(
26
- f"Image repository '{self.to_fully_qualified_name(repo_name)}' does not exist or not authorized."
28
+ f"Image repository '{fqn.identifier}' does not exist or not authorized."
27
29
  )
28
30
  if with_scheme:
29
31
  return f"https://{repo_row['repository_url']}"
@@ -1,11 +1,18 @@
1
+ from __future__ import annotations
2
+
1
3
  import sys
2
4
  from pathlib import Path
3
5
  from typing import List, Optional
4
6
 
5
7
  import typer
6
8
  from click import ClickException
7
- from snowflake.cli.api.commands.flags import IfNotExistsOption, OverrideableOption
9
+ from snowflake.cli.api.commands.flags import (
10
+ IfNotExistsOption,
11
+ OverrideableOption,
12
+ like_option,
13
+ )
8
14
  from snowflake.cli.api.commands.snow_typer import SnowTyper
15
+ from snowflake.cli.api.constants import ObjectType
9
16
  from snowflake.cli.api.output.types import (
10
17
  CommandResult,
11
18
  QueryJsonValueResult,
@@ -13,6 +20,10 @@ from snowflake.cli.api.output.types import (
13
20
  SingleQueryResult,
14
21
  )
15
22
  from snowflake.cli.api.project.util import is_valid_object_name
23
+ from snowflake.cli.plugins.object.command_aliases import (
24
+ add_object_command_aliases,
25
+ scope_option,
26
+ )
16
27
  from snowflake.cli.plugins.object.common import CommentOption, Tag, TagOption
17
28
  from snowflake.cli.plugins.spcs.common import (
18
29
  print_log_lines,
@@ -36,7 +47,10 @@ def _service_name_callback(name: str) -> str:
36
47
 
37
48
 
38
49
  ServiceNameArgument = typer.Argument(
39
- ..., help="Name of the service.", callback=_service_name_callback
50
+ ...,
51
+ help="Name of the service.",
52
+ callback=_service_name_callback,
53
+ show_default=False,
40
54
  )
41
55
 
42
56
  SpecPathOption = typer.Option(
@@ -46,6 +60,7 @@ SpecPathOption = typer.Option(
46
60
  file_okay=True,
47
61
  dir_okay=False,
48
62
  exists=True,
63
+ show_default=False,
49
64
  )
50
65
 
51
66
  _MIN_INSTANCES_HELP = "Minimum number of service instances to run."
@@ -74,12 +89,25 @@ AutoResumeOption = OverrideableOption(
74
89
 
75
90
  _COMMENT_HELP = "Comment for the service."
76
91
 
92
+ add_object_command_aliases(
93
+ app=app,
94
+ object_type=ObjectType.SERVICE,
95
+ name_argument=ServiceNameArgument,
96
+ like_option=like_option(
97
+ help_example='`list --like "my%"` lists all services that begin with “my”.'
98
+ ),
99
+ scope_option=scope_option(help_example="`list --in compute-pool my_pool`"),
100
+ )
101
+
77
102
 
78
103
  @app.command(requires_connection=True)
79
104
  def create(
80
105
  name: str = ServiceNameArgument,
81
106
  compute_pool: str = typer.Option(
82
- ..., "--compute-pool", help="Compute pool to run the service on."
107
+ ...,
108
+ "--compute-pool",
109
+ help="Compute pool to run the service on.",
110
+ show_default=False,
83
111
  ),
84
112
  spec_path: Path = SpecPathOption,
85
113
  min_instances: int = MinInstancesOption(),
@@ -131,10 +159,16 @@ def status(name: str = ServiceNameArgument, **options) -> CommandResult:
131
159
  def logs(
132
160
  name: str = ServiceNameArgument,
133
161
  container_name: str = typer.Option(
134
- ..., "--container-name", help="Name of the container."
162
+ ...,
163
+ "--container-name",
164
+ help="Name of the container.",
165
+ show_default=False,
135
166
  ),
136
167
  instance_id: str = typer.Option(
137
- ..., "--instance-id", help="ID of the service instance, starting with 0."
168
+ ...,
169
+ "--instance-id",
170
+ help="ID of the service instance, starting with 0.",
171
+ show_default=False,
138
172
  ),
139
173
  num_lines: int = typer.Option(
140
174
  500, "--num-lines", help="Number of lines to retrieve."
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import json
2
4
  from pathlib import Path
3
5
  from typing import List, Optional
@@ -1,8 +1,13 @@
1
+ from __future__ import annotations
2
+
1
3
  from pathlib import Path
2
4
  from typing import List, Optional
3
5
 
4
6
  import typer
5
- from snowflake.cli.api.commands.flags import parse_key_value_variables
7
+ from snowflake.cli.api.commands.flags import (
8
+ parse_key_value_variables,
9
+ project_definition_option,
10
+ )
6
11
  from snowflake.cli.api.commands.snow_typer import SnowTyper
7
12
  from snowflake.cli.api.output.types import CommandResult, MultipleResults, QueryResult
8
13
  from snowflake.cli.plugins.sql.manager import SqlManager
@@ -19,7 +24,7 @@ def _parse_key_value(key_value_str: str):
19
24
  return parts[0], "=".join(parts[1:])
20
25
 
21
26
 
22
- @app.command(name="sql", requires_connection=True)
27
+ @app.command(name="sql", requires_connection=True, no_args_is_help=True)
23
28
  def execute_sql(
24
29
  query: Optional[str] = typer.Option(
25
30
  None,
@@ -27,7 +32,7 @@ def execute_sql(
27
32
  "-q",
28
33
  help="Query to execute.",
29
34
  ),
30
- file: Optional[Path] = typer.Option(
35
+ files: Optional[List[Path]] = typer.Option(
31
36
  None,
32
37
  "--filename",
33
38
  "-f",
@@ -50,22 +55,25 @@ def execute_sql(
50
55
  help="String in format of key=value. If provided the SQL content will "
51
56
  "be treated as template and rendered using provided data.",
52
57
  ),
58
+ _: Optional[str] = project_definition_option(optional=True),
53
59
  **options,
54
60
  ) -> CommandResult:
55
61
  """
56
62
  Executes Snowflake query.
57
63
 
64
+ Use either query, filename or input option.
65
+
58
66
  Query to execute can be specified using query option, filename option (all queries from file will be executed)
59
67
  or via stdin by piping output from other command. For example `cat my.sql | snow sql -i`.
60
68
 
61
- The command supports variable substitution that happens on client-side. Both $VARIABLE or ${ VARIABLE }
69
+ The command supports variable substitution that happens on client-side. Both &VARIABLE or &{ VARIABLE }
62
70
  syntax are supported.
63
71
  """
64
72
  data = {}
65
73
  if data_override:
66
74
  data = {v.key: v.value for v in parse_key_value_variables(data_override)}
67
75
 
68
- single_statement, cursors = SqlManager().execute(query, file, std_in, data=data)
76
+ single_statement, cursors = SqlManager().execute(query, files, std_in, data=data)
69
77
  if single_statement:
70
78
  return QueryResult(next(cursors))
71
79
  return MultipleResults((QueryResult(c) for c in cursors))
@@ -2,31 +2,31 @@ from __future__ import annotations
2
2
 
3
3
  import sys
4
4
  from io import StringIO
5
+ from itertools import chain
5
6
  from pathlib import Path
6
- from typing import Dict, Iterable, Optional, Tuple
7
+ from typing import Dict, Iterable, List, Tuple
7
8
 
8
9
  from click import ClickException, UsageError
9
10
  from jinja2 import UndefinedError
10
11
  from snowflake.cli.api.secure_path import UNLIMITED, SecurePath
11
12
  from snowflake.cli.api.sql_execution import SqlExecutionMixin
12
- from snowflake.cli.api.utils.rendering import snowflake_cli_jinja_render
13
+ from snowflake.cli.api.utils.rendering import snowflake_sql_jinja_render
13
14
  from snowflake.cli.plugins.sql.snowsql_templating import transpile_snowsql_templates
14
15
  from snowflake.connector.cursor import SnowflakeCursor
15
16
  from snowflake.connector.util_text import split_statements
16
17
 
18
+ IsSingleStatement = bool
19
+
17
20
 
18
21
  class SqlManager(SqlExecutionMixin):
19
22
  def execute(
20
23
  self,
21
- query: Optional[str],
22
- file: Optional[Path],
24
+ query: str | None,
25
+ files: List[Path] | None,
23
26
  std_in: bool,
24
27
  data: Dict | None = None,
25
- ) -> Tuple[int, Iterable[SnowflakeCursor]]:
26
- inputs = [query, file, std_in]
27
- if not any(inputs):
28
- raise UsageError("Use either query, filename or input option.")
29
-
28
+ ) -> Tuple[IsSingleStatement, Iterable[SnowflakeCursor]]:
29
+ inputs = [query, files, std_in]
30
30
  # Check if any two inputs were provided simultaneously
31
31
  if len([i for i in inputs if i]) > 1:
32
32
  raise UsageError(
@@ -35,16 +35,37 @@ class SqlManager(SqlExecutionMixin):
35
35
 
36
36
  if std_in:
37
37
  query = sys.stdin.read()
38
- elif file:
39
- query = SecurePath(file).read_text(file_size_limit_mb=UNLIMITED)
40
-
41
- if data:
42
- # Do rendering if any data was provided
43
- try:
44
- query = transpile_snowsql_templates(query)
45
- query = snowflake_cli_jinja_render(content=query, data=data)
46
- except UndefinedError as err:
47
- raise ClickException(f"SQL template rendering error: {err}")
38
+ if query:
39
+ return self._execute_single_query(query=query, data=data)
40
+
41
+ if files:
42
+ # Multiple files
43
+ results = []
44
+ single_statement = False
45
+ for file in files:
46
+ query_from_file = SecurePath(file).read_text(
47
+ file_size_limit_mb=UNLIMITED
48
+ )
49
+ single_statement, result = self._execute_single_query(
50
+ query=query_from_file, data=data
51
+ )
52
+ results.append(result)
53
+
54
+ # Use single_statement if there's only one, otherwise this is multi statement result
55
+ single_statement = len(files) == 1 and single_statement
56
+ return single_statement, chain.from_iterable(results)
57
+
58
+ # At that point, no stdin, query or files were provided
59
+ raise UsageError("Use either query, filename or input option.")
60
+
61
+ def _execute_single_query(
62
+ self, query: str, data: Dict | None = None
63
+ ) -> Tuple[IsSingleStatement, Iterable[SnowflakeCursor]]:
64
+ try:
65
+ query = transpile_snowsql_templates(query)
66
+ query = snowflake_sql_jinja_render(content=query, data=data)
67
+ except UndefinedError as err:
68
+ raise ClickException(f"SQL template rendering error: {err}")
48
69
 
49
70
  statements = tuple(
50
71
  statement
@@ -11,9 +11,11 @@ from snowflake.cli.api.commands.flags import (
11
11
  OnErrorOption,
12
12
  PatternOption,
13
13
  VariablesOption,
14
+ like_option,
14
15
  )
15
16
  from snowflake.cli.api.commands.snow_typer import SnowTyper
16
17
  from snowflake.cli.api.console import cli_console
18
+ from snowflake.cli.api.constants import ObjectType
17
19
  from snowflake.cli.api.output.types import (
18
20
  CollectionResult,
19
21
  CommandResult,
@@ -22,6 +24,10 @@ from snowflake.cli.api.output.types import (
22
24
  SingleQueryResult,
23
25
  )
24
26
  from snowflake.cli.api.utils.path_utils import is_stage_path
27
+ from snowflake.cli.plugins.object.command_aliases import (
28
+ add_object_command_aliases,
29
+ scope_option,
30
+ )
25
31
  from snowflake.cli.plugins.stage.diff import DiffResult, compute_stage_diff
26
32
  from snowflake.cli.plugins.stage.manager import OnErrorType, StageManager
27
33
 
@@ -32,6 +38,16 @@ app = SnowTyper(
32
38
 
33
39
  StageNameArgument = typer.Argument(..., help="Name of the stage.", show_default=False)
34
40
 
41
+ add_object_command_aliases(
42
+ app=app,
43
+ object_type=ObjectType.STAGE,
44
+ name_argument=StageNameArgument,
45
+ like_option=like_option(
46
+ help_example='`list --like "my%"` lists all stages that begin with “my”',
47
+ ),
48
+ scope_option=scope_option(help_example="`list --in database my_db`"),
49
+ )
50
+
35
51
 
36
52
  @app.command("list-files", requires_connection=True)
37
53
  def stage_list_files(
@@ -112,7 +128,11 @@ def stage_create(stage_name: str = StageNameArgument, **options) -> CommandResul
112
128
  @app.command("remove", requires_connection=True)
113
129
  def stage_remove(
114
130
  stage_name: str = StageNameArgument,
115
- file_name: str = typer.Argument(..., help="Name of the file to remove."),
131
+ file_name: str = typer.Argument(
132
+ ...,
133
+ help="Name of the file to remove.",
134
+ show_default=False,
135
+ ),
116
136
  **options,
117
137
  ) -> CommandResult:
118
138
  """
@@ -125,8 +145,14 @@ def stage_remove(
125
145
 
126
146
  @app.command("diff", hidden=True, requires_connection=True)
127
147
  def stage_diff(
128
- stage_name: str = typer.Argument(help="Fully qualified name of a stage"),
129
- folder_name: str = typer.Argument(help="Path to local folder"),
148
+ stage_name: str = typer.Argument(
149
+ help="Fully qualified name of a stage",
150
+ show_default=False,
151
+ ),
152
+ folder_name: str = typer.Argument(
153
+ help="Path to local folder",
154
+ show_default=False,
155
+ ),
130
156
  **options,
131
157
  ) -> ObjectResult:
132
158
  """
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import hashlib
2
4
  import logging
3
5
  import re