snowflake-cli-labs 2.3.1__py3-none-any.whl → 2.4.0rc1__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 +80 -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.0rc1.dist-info}/METADATA +4 -2
  92. {snowflake_cli_labs-2.3.1.dist-info → snowflake_cli_labs-2.4.0rc1.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.0rc1.dist-info}/WHEEL +0 -0
  95. {snowflake_cli_labs-2.3.1.dist-info → snowflake_cli_labs-2.4.0rc1.dist-info}/entry_points.txt +0 -0
  96. {snowflake_cli_labs-2.3.1.dist-info → snowflake_cli_labs-2.4.0rc1.dist-info}/licenses/LICENSE +0 -0
@@ -14,12 +14,12 @@ from snowflake.cli.api.exceptions import (
14
14
  SchemaNotProvidedError,
15
15
  SnowflakeSQLExecutionError,
16
16
  )
17
+ from snowflake.cli.api.identifiers import FQN
17
18
  from snowflake.cli.api.project.util import (
18
19
  identifier_to_show_like_pattern,
19
20
  unquote_identifier,
20
21
  )
21
22
  from snowflake.cli.api.utils.cursor import find_first_row
22
- from snowflake.cli.api.utils.naming_utils import from_qualified_name
23
23
  from snowflake.connector.cursor import DictCursor, SnowflakeCursor
24
24
  from snowflake.connector.errors import ProgrammingError
25
25
 
@@ -128,56 +128,22 @@ class SqlExecutionMixin:
128
128
  """
129
129
  Checks if a database and schema are provided, either through the connection context or a qualified name.
130
130
  """
131
- if name:
132
- _, schema, database = from_qualified_name(name)
133
- else:
134
- schema, database = None, None
135
- schema = schema or self._conn.schema
136
- database = database or self._conn.database
137
- if not database:
131
+ fqn = FQN.from_string(name).using_connection(self._conn)
132
+ if not fqn.database:
138
133
  raise DatabaseNotProvidedError()
139
- if not schema:
134
+ if not fqn.schema:
140
135
  raise SchemaNotProvidedError()
141
136
 
142
- def to_fully_qualified_name(
143
- self, name: str, database: Optional[str] = None, schema: Optional[str] = None
144
- ):
145
- current_parts = name.split(".")
146
- if len(current_parts) == 3:
147
- # already fully qualified name
148
- return name.upper()
149
-
150
- if not database:
151
- if not self._conn.database:
152
- raise DatabaseNotProvidedError()
153
- database = self._conn.database
154
-
155
- if len(current_parts) == 2:
156
- # we assume name is in form of `schema.object`
157
- return f"{database}.{name}".upper()
158
-
159
- schema = schema or self._conn.schema or "public"
160
- database = database or self._conn.database
161
- return f"{database}.{schema}.{name}".upper()
162
-
163
- @staticmethod
164
- def get_name_from_fully_qualified_name(name):
165
- """
166
- Returns name of the object from the fully-qualified name.
167
- Assumes that [name] is in format [[database.]schema.]name
168
- """
169
- return from_qualified_name(name)[0]
170
-
171
137
  @staticmethod
172
- def _qualified_name_to_in_clause(name: str) -> Tuple[str, Optional[str]]:
173
- unqualified_name, schema, database = from_qualified_name(name)
174
- if database:
175
- in_clause = f"in schema {database}.{schema}"
176
- elif schema:
177
- in_clause = f"in schema {schema}"
138
+ def _qualified_name_to_in_clause(identifier: FQN) -> Tuple[str, Optional[str]]:
139
+ if identifier.database:
140
+ schema = identifier.schema or "PUBLIC"
141
+ in_clause = f"in schema {identifier.database}.{schema}"
142
+ elif identifier.schema:
143
+ in_clause = f"in schema {identifier.schema}"
178
144
  else:
179
145
  in_clause = None
180
- return unqualified_name, in_clause
146
+ return identifier.name, in_clause
181
147
 
182
148
  class InClauseWithQualifiedNameError(ValueError):
183
149
  def __init__(self):
@@ -202,7 +168,9 @@ class SqlExecutionMixin:
202
168
  the connection or a qualified name, before executing the query.
203
169
  """
204
170
 
205
- unqualified_name, name_in_clause = self._qualified_name_to_in_clause(name)
171
+ unqualified_name, name_in_clause = self._qualified_name_to_in_clause(
172
+ FQN.from_string(name)
173
+ )
206
174
  if in_clause and name_in_clause:
207
175
  raise self.InClauseWithQualifiedNameError()
208
176
  elif name_in_clause:
@@ -230,11 +198,3 @@ class SqlExecutionMixin:
230
198
  lambda row: row[name_col] == unquote_identifier(unqualified_name),
231
199
  )
232
200
  return show_obj_row
233
-
234
- def qualified_name_for_url(
235
- self, object_name: str, database: str | None = None, schema: str | None = None
236
- ):
237
- fqn = self.to_fully_qualified_name(
238
- object_name, database=database, schema=schema
239
- )
240
- return ".".join(unquote_identifier(part) for part in fqn.split("."))
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from typing import Callable, List, Optional
2
4
 
3
5
  from snowflake.connector.cursor import DictCursor
@@ -0,0 +1,23 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from typing import Any, Dict
5
+
6
+
7
+ class EnvironWithDefinedDictFallback(Dict):
8
+ def __getattr__(self, item):
9
+ try:
10
+ return self[item]
11
+ except KeyError as e:
12
+ raise AttributeError(e)
13
+
14
+ def __getitem__(self, item):
15
+ if item in os.environ:
16
+ return os.environ[item]
17
+ return super().__getitem__(item)
18
+
19
+ def __contains__(self, item):
20
+ return item in os.environ or super().__contains__(item)
21
+
22
+ def update_from_dict(self, update_values: Dict[str, Any]):
23
+ return super().update(update_values)
@@ -1,27 +0,0 @@
1
- import re
2
- from typing import Optional, Tuple
3
-
4
- from snowflake.cli.api.project.util import (
5
- VALID_IDENTIFIER_REGEX,
6
- )
7
-
8
-
9
- def from_qualified_name(name: str) -> Tuple[str, Optional[str], Optional[str]]:
10
- """
11
- Takes in an object name in the form [[database.]schema.]name. Returns a tuple (name, [schema], [database])
12
- """
13
- # TODO: Use regex to match object name to a valid identifier or valid identifier (args). Second case is for sprocs and UDFs
14
- qualifier_pattern = rf"(?:(?P<first_qualifier>{VALID_IDENTIFIER_REGEX})\.)?(?:(?P<second_qualifier>{VALID_IDENTIFIER_REGEX})\.)?(?P<name>.*)"
15
- result = re.fullmatch(qualifier_pattern, name)
16
-
17
- if result is None:
18
- raise ValueError(f"'{name}' is not a valid qualified name")
19
-
20
- unqualified_name = result.group("name")
21
- if result.group("second_qualifier") is not None:
22
- database = result.group("first_qualifier")
23
- schema = result.group("second_qualifier")
24
- else:
25
- database = None
26
- schema = result.group("first_qualifier")
27
- return unqualified_name, schema, database
@@ -1,13 +1,24 @@
1
1
  from __future__ import annotations
2
2
 
3
- import os
3
+ import re
4
+ from collections import defaultdict, deque
4
5
  from pathlib import Path
5
6
  from textwrap import dedent
6
- from typing import Dict, Optional
7
+ from typing import Dict, List, Optional, Set, cast
7
8
 
8
9
  import jinja2
9
- from jinja2 import Environment, StrictUndefined, loaders
10
+ from click import ClickException
11
+ from jinja2 import Environment, StrictUndefined, UndefinedError, loaders
12
+ from snowflake.cli.api.cli_global_context import cli_context
13
+ from snowflake.cli.api.project.schemas.project_definition import (
14
+ ProjectDefinition,
15
+ )
10
16
  from snowflake.cli.api.secure_path import UNLIMITED, SecurePath
17
+ from snowflake.cli.api.utils.models import EnvironWithDefinedDictFallback
18
+
19
+ _CONTEXT_KEY = "ctx"
20
+ _YML_TEMPLATE_START = "<%"
21
+ _YML_TEMPLATE_END = "%>"
11
22
 
12
23
 
13
24
  def read_file_content(file_name: str):
@@ -45,6 +56,21 @@ def _env_bootstrap(env: Environment) -> Environment:
45
56
 
46
57
 
47
58
  def get_snowflake_cli_jinja_env():
59
+ _random_block = "___very___unique___block___to___disable___logic___blocks___"
60
+ return _env_bootstrap(
61
+ Environment(
62
+ loader=loaders.BaseLoader(),
63
+ keep_trailing_newline=True,
64
+ variable_start_string=_YML_TEMPLATE_START,
65
+ variable_end_string=_YML_TEMPLATE_END,
66
+ block_start_string=_random_block,
67
+ block_end_string=_random_block,
68
+ undefined=StrictUndefined,
69
+ )
70
+ )
71
+
72
+
73
+ def get_sql_cli_jinja_env():
48
74
  _random_block = "___very___unique___block___to___disable___logic___blocks___"
49
75
  return _env_bootstrap(
50
76
  Environment(
@@ -61,9 +87,9 @@ def get_snowflake_cli_jinja_env():
61
87
 
62
88
  def jinja_render_from_file(
63
89
  template_path: Path, data: Dict, output_file_path: Optional[Path] = None
64
- ):
90
+ ) -> Optional[str]:
65
91
  """
66
- Create a file from a jinja template.
92
+ Renders a jinja template and outputs either the rendered contents as string or writes to a file.
67
93
 
68
94
  Args:
69
95
  template_path (Path): Path to the template
@@ -71,7 +97,7 @@ def jinja_render_from_file(
71
97
  output_file_path (Optional[Path]): If provided then rendered template will be written to this file
72
98
 
73
99
  Returns:
74
- None
100
+ None if file path is provided, else returns the rendered string.
75
101
  """
76
102
  env = _env_bootstrap(
77
103
  Environment(
@@ -84,28 +110,157 @@ def jinja_render_from_file(
84
110
  rendered_result = loaded_template.render(**data)
85
111
  if output_file_path:
86
112
  SecurePath(output_file_path).write_text(rendered_result)
113
+ return None
87
114
  else:
88
- print(rendered_result)
115
+ return rendered_result
116
+
117
+
118
+ def _add_project_context(project_definition: ProjectDefinition) -> Dict:
119
+ """
120
+ Updates the external data with variables from snowflake.yml definition file.
121
+ """
122
+ context_data = _resolve_variables_in_project(project_definition)
123
+ return context_data
124
+
125
+
126
+ def _remove_ctx_env_prefix(text: str) -> str:
127
+ prefix = "ctx.env."
128
+ if text.startswith(prefix):
129
+ return text[len(prefix) :]
130
+ return text
131
+
132
+
133
+ def string_includes_template(text: str) -> bool:
134
+ return bool(re.search(rf"{_YML_TEMPLATE_START}.+{_YML_TEMPLATE_END}", text))
135
+
136
+
137
+ def _resolve_variables_in_project(project_definition: ProjectDefinition):
138
+ # If there's project definition file then resolve variables from it
139
+ if not project_definition or not project_definition.meets_version_requirement(
140
+ "1.1"
141
+ ):
142
+ return {_CONTEXT_KEY: {"env": EnvironWithDefinedDictFallback({})}}
143
+
144
+ variables_data: EnvironWithDefinedDictFallback = cast(
145
+ EnvironWithDefinedDictFallback, project_definition.env
146
+ )
147
+
148
+ env_with_unresolved_keys: List[str] = _get_variables_with_dependencies(
149
+ variables_data
150
+ )
151
+ other_env = set(variables_data) - set(env_with_unresolved_keys)
152
+ env = get_snowflake_cli_jinja_env()
153
+ context_data = {_CONTEXT_KEY: project_definition}
154
+
155
+ # Resolve env section dependencies
156
+ while env_with_unresolved_keys:
157
+ key = env_with_unresolved_keys.pop()
158
+ value = variables_data[key]
159
+ if not isinstance(value, str):
160
+ continue
161
+ try: # try to evaluate the template given current state of know variables
162
+ variables_data[key] = env.from_string(value).render(context_data)
163
+ if string_includes_template(variables_data[key]):
164
+ env_with_unresolved_keys.append(key)
165
+ except UndefinedError:
166
+ env_with_unresolved_keys.append(key)
167
+
168
+ # Resolve templates in variables without references, for example
169
+ for key in other_env:
170
+ variable_value = variables_data[key]
171
+ if not isinstance(variable_value, str):
172
+ continue
173
+ variables_data[key] = env.from_string(variable_value).render(context_data)
174
+
175
+ return context_data
176
+
177
+
178
+ def _check_for_cycles(nodes: defaultdict):
179
+ nodes = nodes.copy()
180
+ for key in list(nodes):
181
+ q = deque([key])
182
+ visited: List[str] = []
183
+ while q:
184
+ curr = q.popleft()
185
+ if curr in visited:
186
+ raise ClickException(
187
+ "Cycle detected between variables: {}".format(" -> ".join(visited))
188
+ )
189
+ # Only nodes that have references can cause cycles
190
+ if curr in nodes:
191
+ visited.append(curr)
192
+ q.extendleft(nodes[curr])
193
+
194
+
195
+ def _get_variables_with_dependencies(variables_data: EnvironWithDefinedDictFallback):
196
+ """
197
+ Checks consistency of provided dictionary by
198
+ 1. checking reference cycles
199
+ 2. checking for missing variables
200
+ """
201
+ # Variables that are not specified in env section
202
+ missing_variables: Set[str] = set()
203
+ # Variables that require other variables
204
+ variables_with_dependencies = defaultdict(list)
89
205
 
206
+ for key, value in variables_data.items():
207
+ # Templates are reserved only to string variables
208
+ if not isinstance(value, str):
209
+ continue
90
210
 
91
- class _AttrGetter:
92
- def __init__(self, data_dict):
93
- self._data_dict = data_dict
211
+ required_variables = _search_for_required_variables(value)
94
212
 
95
- def __getattr__(self, item):
96
- if item not in self._data_dict:
97
- raise AttributeError(f"No attribute {item}")
98
- return self._data_dict[item]
213
+ if required_variables:
214
+ variables_with_dependencies[key] = required_variables
99
215
 
216
+ for variable in required_variables:
217
+ if variable not in variables_data:
218
+ missing_variables.add(variable)
100
219
 
101
- def _add_project_context(data: Dict):
102
- context_key = "ctx"
103
- if context_key in data:
104
- raise ValueError(f"{context_key} in user defined data")
105
- context_data = {context_key: {"env": _AttrGetter(os.environ)}}
106
- return {**data, **context_data}
220
+ # If there are unknown env variables then we raise an error
221
+ if missing_variables:
222
+ raise ClickException(
223
+ "The following variables are used in environment definition but are not defined: {}".format(
224
+ ", ".join(missing_variables)
225
+ )
226
+ )
227
+
228
+ # Look for cycles between variables
229
+ _check_for_cycles(variables_with_dependencies)
230
+
231
+ # Sort by number of dependencies
232
+ return sorted(
233
+ list(variables_with_dependencies.keys()),
234
+ key=lambda k: len(variables_with_dependencies[k]),
235
+ )
236
+
237
+
238
+ def _search_for_required_variables(variable_value: str):
239
+ """
240
+ Look for pattern in variable value. Returns a list of env variables required
241
+ to expand this template.`
242
+ """
243
+ ctx_env_prefix = f"{_CONTEXT_KEY}.env."
244
+ found_variables = re.findall(
245
+ rf"({_YML_TEMPLATE_START}([\.\w ]+){_YML_TEMPLATE_END})+", variable_value
246
+ )
247
+ required_variables = []
248
+ for _, variable in found_variables:
249
+ var: str = variable.strip()
250
+ if var.startswith(ctx_env_prefix):
251
+ required_variables.append(var[len(ctx_env_prefix) :])
252
+ return required_variables
107
253
 
108
254
 
109
- def snowflake_cli_jinja_render(content: str, data: Dict | None = None) -> str:
110
- data = _add_project_context(data or dict())
111
- return get_snowflake_cli_jinja_env().from_string(content).render(**data)
255
+ def snowflake_sql_jinja_render(content: str, data: Dict | None = None) -> str:
256
+ data = data or {}
257
+ if _CONTEXT_KEY in data:
258
+ raise ClickException(
259
+ f"{_CONTEXT_KEY} in user defined data. The `{_CONTEXT_KEY}` variable is reserved for CLI usage."
260
+ )
261
+
262
+ context_data = _add_project_context(
263
+ project_definition=cli_context.project_definition
264
+ )
265
+ context_data.update(data)
266
+ return get_sql_cli_jinja_env().from_string(content).render(**context_data)
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from typing import List
2
4
 
3
5
  from snowflake.cli.api import PluginConfigProvider
@@ -132,8 +132,9 @@ def _info_callback(value: bool):
132
132
  def app_factory() -> SnowCliMainTyper:
133
133
  app = SnowCliMainTyper()
134
134
 
135
- @app.callback()
135
+ @app.callback(invoke_without_command=True)
136
136
  def default(
137
+ ctx: typer.Context,
137
138
  version: bool = typer.Option(
138
139
  None,
139
140
  "--version",
@@ -217,6 +218,8 @@ def app_factory() -> SnowCliMainTyper:
217
218
  """
218
219
  Snowflake CLI tool for developers.
219
220
  """
221
+ if not ctx.invoked_subcommand:
222
+ typer.echo(ctx.get_help())
220
223
  setup_pycharm_remote_debugger_if_provided(
221
224
  pycharm_debug_library_path=pycharm_debug_library_path,
222
225
  pycharm_debug_server_host=pycharm_debug_server_host,
@@ -1,8 +1,14 @@
1
1
  from snowflake.cli.plugins.connection import plugin_spec as connection_plugin_spec
2
+ from snowflake.cli.plugins.cortex import plugin_spec as cortex_plugin_spec
2
3
  from snowflake.cli.plugins.git import plugin_spec as git_plugin_spec
3
4
  from snowflake.cli.plugins.nativeapp import plugin_spec as nativeapp_plugin_spec
4
5
  from snowflake.cli.plugins.notebook import plugin_spec as notebook_plugin_spec
5
6
  from snowflake.cli.plugins.object import plugin_spec as object_plugin_spec
7
+
8
+ # TODO 3.0: remove this import
9
+ from snowflake.cli.plugins.object_stage_deprecated import (
10
+ plugin_spec as object_stage_deprecated_plugin_spec,
11
+ )
6
12
  from snowflake.cli.plugins.snowpark import plugin_spec as snowpark_plugin_spec
7
13
  from snowflake.cli.plugins.spcs import plugin_spec as spcs_plugin_spec
8
14
  from snowflake.cli.plugins.sql import plugin_spec as sql_plugin_spec
@@ -23,6 +29,8 @@ def get_builtin_plugin_name_to_plugin_spec():
23
29
  "streamlit": streamlit_plugin_spec,
24
30
  "git": git_plugin_spec,
25
31
  "notebook": notebook_plugin_spec,
32
+ "object-stage-deprecated": object_stage_deprecated_plugin_spec,
33
+ "cortex": cortex_plugin_spec,
26
34
  }
27
35
 
28
36
  return plugin_specs
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import logging
2
4
  from typing import Dict, List, Optional
3
5
 
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from dataclasses import dataclass
2
4
  from typing import Callable, List
3
5
 
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import logging
2
4
  from typing import List
3
5
 
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from typing import Optional
2
4
 
3
5
  from click import ClickException
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import logging
2
4
  import logging.config
3
5
  from dataclasses import asdict, dataclass, field
@@ -29,7 +29,7 @@ class SnowCliMainTyper(typer.Typer):
29
29
  super().__init__(
30
30
  context_settings=DEFAULT_CONTEXT_SETTINGS,
31
31
  pretty_exceptions_show_locals=False,
32
- add_completion=False,
32
+ add_completion=True,
33
33
  )
34
34
 
35
35
  def __call__(self, *args, **kwargs):
@@ -84,7 +84,9 @@ def print_structured(result: CommandResult):
84
84
  if isinstance(result, MultipleResults):
85
85
  _stream_json(result)
86
86
  else:
87
- return json.dump(result, sys.stdout, cls=CustomJSONEncoder, indent=4)
87
+ json.dump(result, sys.stdout, cls=CustomJSONEncoder, indent=4)
88
+ # Adds empty line at the end
89
+ print()
88
90
 
89
91
 
90
92
  def _stream_json(result):
@@ -157,8 +157,8 @@ def _update_connection_application_name(connection_parameters: Dict):
157
157
  """Update version and name of app handling connection."""
158
158
  connection_application_params = {
159
159
  "application_name": PARAM_APPLICATION_NAME,
160
- "_internal_application_name": PARAM_INTERNAL_APPLICATION_NAME,
161
- "_internal_application_version": VERSION,
160
+ "internal_application_name": PARAM_INTERNAL_APPLICATION_NAME,
161
+ "internal_application_version": VERSION,
162
162
  }
163
163
  connection_parameters.update(connection_application_params)
164
164
 
@@ -9,11 +9,6 @@ from click.types import StringParamType
9
9
  from snowflake.cli.api.cli_global_context import cli_context
10
10
  from snowflake.cli.api.commands.flags import (
11
11
  PLAIN_PASSWORD_MSG,
12
- ConnectionOption,
13
- DiagAllowlistPathOption,
14
- DiagLogPathOption,
15
- EnableDiagOption,
16
- MfaPasscodeOption,
17
12
  )
18
13
  from snowflake.cli.api.commands.snow_typer import SnowTyper
19
14
  from snowflake.cli.api.config import (
@@ -237,13 +232,8 @@ def add(
237
232
  )
238
233
 
239
234
 
240
- @app.command(requires_connection=False)
235
+ @app.command(requires_connection=True)
241
236
  def test(
242
- connection: str = ConnectionOption,
243
- mfa_passcode: str = MfaPasscodeOption,
244
- enable_diag: bool = EnableDiagOption,
245
- diag_log_path: str = DiagLogPathOption,
246
- diag_allowlist_path: str = DiagAllowlistPathOption,
247
237
  **options,
248
238
  ) -> CommandResult:
249
239
  """
@@ -272,8 +262,9 @@ def test(
272
262
  except ProgrammingError as err:
273
263
  raise ClickException(str(err))
274
264
 
265
+ conn_ctx = cli_context.connection_context
275
266
  result = {
276
- "Connection name": connection,
267
+ "Connection name": conn_ctx.connection_name,
277
268
  "Status": "OK",
278
269
  "Host": conn.host,
279
270
  "Account": conn.account,
@@ -283,10 +274,10 @@ def test(
283
274
  "Warehouse": f'{conn.warehouse or "not set"}',
284
275
  }
285
276
 
286
- if enable_diag:
277
+ if conn_ctx.enable_diag:
287
278
  result[
288
279
  "Diag Report Location"
289
- ] = f"{diag_log_path}/SnowflakeConnectionTestReport.txt"
280
+ ] = f"{conn_ctx.diag_log_path}/SnowflakeConnectionTestReport.txt"
290
281
 
291
282
  return ObjectResult(result)
292
283
 
@@ -37,7 +37,7 @@ def is_regionless_redirect(conn: SnowflakeConnection) -> bool:
37
37
  return cursor.fetchone()["REGIONLESS"].lower() == "true"
38
38
  except:
39
39
  # by default, assume that
40
- log.exception("Cannot determine regionless redirect; assuming True.")
40
+ log.warning("Cannot determine regionless redirect; assuming True.")
41
41
  return True
42
42
 
43
43
 
File without changes