snowflake-cli 3.7.2__py3-none-any.whl → 3.8.1__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 (57) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/_app/snow_connector.py +14 -0
  3. snowflake/cli/_app/telemetry.py +11 -0
  4. snowflake/cli/_app/version_check.py +4 -3
  5. snowflake/cli/_plugins/connection/commands.py +4 -2
  6. snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +1 -1
  7. snowflake/cli/_plugins/nativeapp/entities/application_package.py +20 -7
  8. snowflake/cli/_plugins/nativeapp/sf_sql_facade.py +5 -3
  9. snowflake/cli/_plugins/project/commands.py +16 -6
  10. snowflake/cli/_plugins/snowpark/common.py +31 -0
  11. snowflake/cli/_plugins/snowpark/package/anaconda_packages.py +3 -0
  12. snowflake/cli/_plugins/snowpark/snowpark_entity.py +21 -1
  13. snowflake/cli/_plugins/snowpark/snowpark_entity_model.py +23 -1
  14. snowflake/cli/_plugins/spcs/common.py +7 -0
  15. snowflake/cli/_plugins/spcs/image_repository/commands.py +7 -2
  16. snowflake/cli/_plugins/spcs/image_repository/manager.py +6 -2
  17. snowflake/cli/_plugins/spcs/services/commands.py +2 -2
  18. snowflake/cli/_plugins/spcs/services/manager.py +36 -1
  19. snowflake/cli/_plugins/sql/commands.py +57 -6
  20. snowflake/cli/_plugins/sql/lexer/__init__.py +7 -0
  21. snowflake/cli/_plugins/sql/lexer/completer.py +12 -0
  22. snowflake/cli/_plugins/sql/lexer/functions.py +421 -0
  23. snowflake/cli/_plugins/sql/lexer/keywords.py +529 -0
  24. snowflake/cli/_plugins/sql/lexer/lexer.py +56 -0
  25. snowflake/cli/_plugins/sql/lexer/types.py +37 -0
  26. snowflake/cli/_plugins/sql/manager.py +43 -9
  27. snowflake/cli/_plugins/sql/repl.py +221 -0
  28. snowflake/cli/_plugins/sql/snowsql_commands.py +331 -0
  29. snowflake/cli/_plugins/sql/statement_reader.py +296 -0
  30. snowflake/cli/_plugins/streamlit/commands.py +30 -15
  31. snowflake/cli/_plugins/streamlit/manager.py +0 -183
  32. snowflake/cli/_plugins/streamlit/streamlit_entity.py +163 -23
  33. snowflake/cli/api/artifacts/upload.py +5 -0
  34. snowflake/cli/api/artifacts/utils.py +0 -2
  35. snowflake/cli/api/cli_global_context.py +7 -3
  36. snowflake/cli/api/commands/decorators.py +70 -0
  37. snowflake/cli/api/commands/flags.py +95 -3
  38. snowflake/cli/api/config.py +10 -0
  39. snowflake/cli/api/connections.py +10 -0
  40. snowflake/cli/api/console/abc.py +8 -2
  41. snowflake/cli/api/console/console.py +16 -0
  42. snowflake/cli/api/console/enum.py +1 -1
  43. snowflake/cli/api/entities/common.py +99 -10
  44. snowflake/cli/api/entities/utils.py +1 -0
  45. snowflake/cli/api/feature_flags.py +6 -0
  46. snowflake/cli/api/project/project_paths.py +5 -0
  47. snowflake/cli/api/rendering/sql_templates.py +2 -1
  48. snowflake/cli/api/sql_execution.py +16 -4
  49. snowflake/cli/api/utils/path_utils.py +15 -0
  50. snowflake/cli/api/utils/python_api_utils.py +12 -0
  51. {snowflake_cli-3.7.2.dist-info → snowflake_cli-3.8.1.dist-info}/METADATA +10 -6
  52. {snowflake_cli-3.7.2.dist-info → snowflake_cli-3.8.1.dist-info}/RECORD +55 -47
  53. snowflake/cli/_plugins/nativeapp/feature_flags.py +0 -28
  54. snowflake/cli/_plugins/sql/source_reader.py +0 -230
  55. {snowflake_cli-3.7.2.dist-info → snowflake_cli-3.8.1.dist-info}/WHEEL +0 -0
  56. {snowflake_cli-3.7.2.dist-info → snowflake_cli-3.8.1.dist-info}/entry_points.txt +0 -0
  57. {snowflake_cli-3.7.2.dist-info → snowflake_cli-3.8.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,19 +1,27 @@
1
+ import logging
1
2
  from pathlib import Path
2
3
  from typing import Optional
3
4
 
4
5
  from click import ClickException
5
6
  from snowflake.cli._plugins.connection.util import make_snowsight_url
6
7
  from snowflake.cli._plugins.nativeapp.artifacts import build_bundle
7
- from snowflake.cli._plugins.nativeapp.feature_flags import FeatureFlag
8
+ from snowflake.cli._plugins.stage.manager import StageManager
8
9
  from snowflake.cli._plugins.streamlit.streamlit_entity_model import (
9
10
  StreamlitEntityModel,
10
11
  )
11
12
  from snowflake.cli._plugins.workspace.context import ActionContext
13
+ from snowflake.cli.api.artifacts.bundle_map import BundleMap
12
14
  from snowflake.cli.api.entities.common import EntityBase
15
+ from snowflake.cli.api.entities.utils import EntityActions, sync_deploy_root_with_stage
16
+ from snowflake.cli.api.feature_flags import FeatureFlag as GlobalFeatureFlag
17
+ from snowflake.cli.api.identifiers import FQN
13
18
  from snowflake.cli.api.project.project_paths import bundle_root
14
- from snowflake.cli.api.project.schemas.entities.common import PathMapping
19
+ from snowflake.cli.api.project.schemas.entities.common import Identifier, PathMapping
20
+ from snowflake.connector import ProgrammingError
15
21
  from snowflake.connector.cursor import SnowflakeCursor
16
22
 
23
+ log = logging.getLogger(__name__)
24
+
17
25
 
18
26
  class StreamlitEntity(EntityBase[StreamlitEntityModel]):
19
27
  """
@@ -21,8 +29,6 @@ class StreamlitEntity(EntityBase[StreamlitEntityModel]):
21
29
  """
22
30
 
23
31
  def __init__(self, *args, **kwargs):
24
- if not FeatureFlag.ENABLE_NATIVE_APP_CHILDREN.is_enabled():
25
- raise NotImplementedError("Streamlit entity is not implemented yet")
26
32
  super().__init__(*args, **kwargs)
27
33
 
28
34
  @property
@@ -37,10 +43,10 @@ class StreamlitEntity(EntityBase[StreamlitEntityModel]):
37
43
  return self.bundle()
38
44
 
39
45
  def action_deploy(self, action_ctx: ActionContext, *args, **kwargs):
40
- # After adding bundle map- we should use it's mapping here
41
- # To copy artifacts to destination on stage.
46
+ return self.deploy(action_ctx, *args, **kwargs)
42
47
 
43
- return self.deploy()
48
+ def action_describe(self, action_ctx: ActionContext, *args, **kwargs):
49
+ return self.describe()
44
50
 
45
51
  def action_drop(self, action_ctx: ActionContext, *args, **kwargs):
46
52
  return self._execute_query(self.get_drop_sql())
@@ -58,10 +64,10 @@ class StreamlitEntity(EntityBase[StreamlitEntityModel]):
58
64
  self._conn, f"/#/streamlit-apps/{name.url_identifier}"
59
65
  )
60
66
 
61
- def bundle(self, output_dir: Optional[Path] = None):
62
- build_bundle(
67
+ def bundle(self, output_dir: Optional[Path] = None) -> BundleMap:
68
+ return build_bundle(
63
69
  self.root,
64
- output_dir or bundle_root(self.root, "streamlit"),
70
+ output_dir or bundle_root(self.root, "streamlit") / self.entity_id,
65
71
  [
66
72
  PathMapping(
67
73
  src=artifact.src, dest=artifact.dest, processors=artifact.processors
@@ -70,14 +76,89 @@ class StreamlitEntity(EntityBase[StreamlitEntityModel]):
70
76
  ],
71
77
  )
72
78
 
73
- def deploy(self, *args, **kwargs):
74
- return self._execute_query(self.get_deploy_sql())
79
+ def deploy(
80
+ self,
81
+ action_context: ActionContext,
82
+ _open: bool,
83
+ replace: bool,
84
+ prune: bool = False,
85
+ bundle_map: Optional[BundleMap] = None,
86
+ experimental: Optional[bool] = False,
87
+ *args,
88
+ **kwargs,
89
+ ):
90
+ if (
91
+ bundle_map is None
92
+ ): # TODO: maybe we could hold bundle map as a cached property?
93
+ bundle_map = self.bundle()
94
+
95
+ console = self._workspace_ctx.console
96
+ console.step(f"Checking if object exists")
97
+ if self._object_exists() and not replace:
98
+ raise ClickException(
99
+ f"Streamlit {self.model.fqn.sql_identifier} already exists. Use 'replace' option to overwrite."
100
+ )
101
+
102
+ console.step(f"Creating stage {self.model.stage} if not exists")
103
+ stage = self._create_stage_if_not_exists()
104
+
105
+ if (
106
+ experimental
107
+ or GlobalFeatureFlag.ENABLE_STREAMLIT_VERSIONED_STAGE.is_enabled()
108
+ or GlobalFeatureFlag.ENABLE_STREAMLIT_EMBEDDED_STAGE.is_enabled()
109
+ ):
110
+ self._deploy_experimental(bundle_map=bundle_map, replace=replace)
111
+ else:
112
+ console.step(f"Uploading artifacts to stage {self.model.stage}")
113
+
114
+ # We use a static method from StageManager here, but maybe this logic could be implemented elswhere, as we implement entities?
115
+ name = (
116
+ self.model.identifier.name
117
+ if isinstance(self.model.identifier, Identifier)
118
+ else self.model.identifier
119
+ )
120
+ stage_root = StageManager.get_standard_stage_prefix(
121
+ f"{FQN.from_string(self.model.stage).using_connection(self._conn)}/{name}"
122
+ )
123
+ if prune:
124
+ sync_deploy_root_with_stage(
125
+ console=self._workspace_ctx.console,
126
+ deploy_root=bundle_map.deploy_root(),
127
+ bundle_map=bundle_map,
128
+ prune=prune,
129
+ recursive=True,
130
+ stage_path=StageManager().stage_path_parts_from_str(stage_root),
131
+ print_diff=True,
132
+ )
133
+ else:
134
+ self._upload_files_to_stage(stage, bundle_map, None)
135
+
136
+ console.step(f"Creating Streamlit object {self.model.fqn.sql_identifier}")
137
+
138
+ self._execute_query(
139
+ self.get_deploy_sql(replace=replace, from_stage_name=stage_root)
140
+ )
141
+
142
+ return self.perform(EntityActions.GET_URL, action_context, *args, **kwargs)
143
+
144
+ def describe(self) -> SnowflakeCursor:
145
+ return self._execute_query(self.get_describe_sql())
75
146
 
76
147
  def action_share(
77
148
  self, action_ctx: ActionContext, to_role: str, *args, **kwargs
78
149
  ) -> SnowflakeCursor:
79
150
  return self._execute_query(self.get_share_sql(to_role))
80
151
 
152
+ def get_add_live_version_sql(
153
+ self, schema: Optional[str] = None, database: Optional[str] = None
154
+ ):
155
+ return f"ALTER STREAMLIT {self._get_identifier(schema,database)} ADD LIVE VERSION FROM LAST;"
156
+
157
+ def get_checkout_sql(
158
+ self, schema: Optional[str] = None, database: Optional[str] = None
159
+ ):
160
+ return f"ALTER STREAMLIT {self._get_identifier(schema,database)} CHECKOUT;"
161
+
81
162
  def get_deploy_sql(
82
163
  self,
83
164
  if_not_exists: bool = False,
@@ -85,21 +166,19 @@ class StreamlitEntity(EntityBase[StreamlitEntityModel]):
85
166
  from_stage_name: Optional[str] = None,
86
167
  artifacts_dir: Optional[Path] = None,
87
168
  schema: Optional[str] = None,
169
+ database: Optional[str] = None,
88
170
  *args,
89
171
  **kwargs,
90
- ):
91
- if replace and if_not_exists:
92
- raise ClickException("Cannot specify both replace and if_not_exists")
172
+ ) -> str:
93
173
 
94
174
  if replace:
95
- query = "CREATE OR REPLACE "
175
+ query = "CREATE OR REPLACE STREAMLIT"
96
176
  elif if_not_exists:
97
- query = "CREATE IF NOT EXISTS "
177
+ query = "CREATE STREAMLIT IF NOT EXISTS"
98
178
  else:
99
- query = "CREATE "
179
+ query = "CREATE STREAMLIT"
100
180
 
101
- schema_to_use = schema or self._entity_model.fqn.schema
102
- query += f"STREAMLIT {self._entity_model.fqn.set_schema(schema_to_use).sql_identifier}"
181
+ query += f" {self._get_identifier(schema, database)}"
103
182
 
104
183
  if from_stage_name:
105
184
  query += f"\nROOT_LOCATION = '{from_stage_name}'"
@@ -112,7 +191,12 @@ class StreamlitEntity(EntityBase[StreamlitEntityModel]):
112
191
  query += "\n" + self.model.get_imports_sql()
113
192
 
114
193
  if self.model.query_warehouse:
115
- query += f"\nQUERY_WAREHOUSE = '{self.model.query_warehouse}'"
194
+ query += f"\nQUERY_WAREHOUSE = {self.model.query_warehouse}"
195
+ else:
196
+ self._workspace_ctx.console.warning(
197
+ "[Deprecation] In next major version we will remove default query_warehouse='streamlit'."
198
+ )
199
+ query += f"\nQUERY_WAREHOUSE = 'streamlit'"
116
200
 
117
201
  if self.model.title:
118
202
  query += f"\nTITLE = '{self.model.title}'"
@@ -128,11 +212,14 @@ class StreamlitEntity(EntityBase[StreamlitEntityModel]):
128
212
 
129
213
  return query + ";"
130
214
 
215
+ def get_describe_sql(self) -> str:
216
+ return f"DESCRIBE STREAMLIT {self._get_identifier()};"
217
+
131
218
  def get_share_sql(self, to_role: str) -> str:
132
- return f"GRANT USAGE ON STREAMLIT {self.model.fqn.sql_identifier} TO ROLE {to_role};"
219
+ return f"GRANT USAGE ON STREAMLIT {self._get_identifier()} TO ROLE {to_role};"
133
220
 
134
221
  def get_execute_sql(self):
135
- return f"EXECUTE STREAMLIT {self._entity_model.fqn}();"
222
+ return f"EXECUTE STREAMLIT {self._get_identifier()}();"
136
223
 
137
224
  def get_usage_grant_sql(self, app_role: str, schema: Optional[str] = None) -> str:
138
225
  entity_id = self.entity_id
@@ -140,3 +227,56 @@ class StreamlitEntity(EntityBase[StreamlitEntityModel]):
140
227
  return (
141
228
  f"GRANT USAGE ON STREAMLIT {streamlit_name} TO APPLICATION ROLE {app_role};"
142
229
  )
230
+
231
+ def _object_exists(self) -> bool:
232
+ try:
233
+ self.describe()
234
+ return True
235
+ except ProgrammingError:
236
+ return False
237
+
238
+ def _deploy_experimental(
239
+ self, bundle_map: BundleMap, replace: bool = False, prune: bool = False
240
+ ):
241
+ self._execute_query(
242
+ self.get_deploy_sql(
243
+ if_not_exists=True,
244
+ replace=replace,
245
+ )
246
+ )
247
+ try:
248
+ if GlobalFeatureFlag.ENABLE_STREAMLIT_VERSIONED_STAGE.is_enabled():
249
+ self._execute_query(self.get_add_live_version_sql())
250
+ elif not GlobalFeatureFlag.ENABLE_STREAMLIT_NO_CHECKOUTS.is_enabled():
251
+ self._execute_query(self.get_checkout_sql())
252
+ except ProgrammingError as e:
253
+ if "Checkout already exists" in str(
254
+ e
255
+ ) or "There is already a live version" in str(e):
256
+ log.info("Checkout already exists, continuing")
257
+ else:
258
+ raise
259
+
260
+ embeded_stage_name = (
261
+ f"snow://streamlit/{self.model.fqn.using_connection(self._conn).identifier}"
262
+ )
263
+
264
+ if GlobalFeatureFlag.ENABLE_STREAMLIT_VERSIONED_STAGE.is_enabled():
265
+ stage_root = f"{embeded_stage_name}/versions/live"
266
+ else:
267
+ stage_root = f"{embeded_stage_name}/default_checkout"
268
+
269
+ stage_resource = self._create_stage_if_not_exists(embeded_stage_name)
270
+
271
+ if prune:
272
+ sync_deploy_root_with_stage(
273
+ console=self._workspace_ctx.console,
274
+ deploy_root=bundle_map.deploy_root(),
275
+ bundle_map=bundle_map,
276
+ prune=prune,
277
+ recursive=True,
278
+ stage_path=StageManager().stage_path_parts_from_str(stage_root),
279
+ print_diff=True,
280
+ )
281
+ else:
282
+ self._upload_files_to_stage(stage_resource, bundle_map)
@@ -6,6 +6,7 @@ from snowflake.cli.api.console import cli_console
6
6
  from snowflake.cli.api.entities.utils import sync_deploy_root_with_stage
7
7
  from snowflake.cli.api.project.project_paths import ProjectPaths
8
8
  from snowflake.cli.api.project.schemas.entities.common import PathMapping
9
+ from snowflake.cli.api.secure_path import SecurePath
9
10
 
10
11
 
11
12
  def sync_artifacts_with_stage(
@@ -17,6 +18,9 @@ def sync_artifacts_with_stage(
17
18
  if artifacts is None:
18
19
  artifacts = []
19
20
 
21
+ project_paths.remove_up_bundle_root()
22
+ SecurePath(project_paths.bundle_root).mkdir(parents=True, exist_ok=True)
23
+
20
24
  bundle_map = bundle_artifacts(project_paths, artifacts)
21
25
  stage_path_parts = StageManager().stage_path_parts_from_str(stage_root)
22
26
  # We treat the bundle root as deploy root
@@ -29,6 +33,7 @@ def sync_artifacts_with_stage(
29
33
  stage_path=stage_path_parts,
30
34
  print_diff=True,
31
35
  )
36
+ project_paths.clean_up_output()
32
37
 
33
38
 
34
39
  def put_files(
@@ -68,8 +68,6 @@ def bundle_artifacts(project_paths: ProjectPaths, artifacts: Artifacts) -> Bundl
68
68
  for artifact in artifacts:
69
69
  bundle_map.add(artifact)
70
70
 
71
- project_paths.remove_up_bundle_root()
72
- SecurePath(project_paths.bundle_root).mkdir(parents=True, exist_ok=True)
73
71
  for absolute_src, absolute_dest in bundle_map.all_mappings(
74
72
  absolute=True, expand_directories=True
75
73
  ):
@@ -31,6 +31,7 @@ from snowflake.connector import SnowflakeConnection
31
31
  if TYPE_CHECKING:
32
32
  from snowflake.cli.api.project.definition_manager import DefinitionManager
33
33
  from snowflake.cli.api.project.schemas.project_definition import ProjectDefinition
34
+ from snowflake.core import Root
34
35
 
35
36
  _CONNECTION_CACHE = OpenConnectionCache()
36
37
 
@@ -47,6 +48,7 @@ class _CliGlobalContextManager:
47
48
  verbose: bool = False
48
49
  experimental: bool = False
49
50
  enable_tracebacks: bool = True
51
+ is_repl: bool = False
50
52
 
51
53
  metrics: CLIMetrics = field(default_factory=CLIMetrics)
52
54
 
@@ -200,9 +202,7 @@ class _CliGlobalContextAccess:
200
202
  @property
201
203
  def snow_api_root(
202
204
  self,
203
- ) -> Optional[
204
- object
205
- ]: # Should be Optional[Root], but we need local import for performance reasons
205
+ ) -> Optional[Root]:
206
206
  from snowflake.core import Root
207
207
 
208
208
  if self.connection:
@@ -214,6 +214,10 @@ class _CliGlobalContextAccess:
214
214
  def enhanced_exit_codes(self) -> bool:
215
215
  return self._manager.enhanced_exit_codes
216
216
 
217
+ @property
218
+ def is_repl(self) -> bool:
219
+ return self._manager.is_repl
220
+
217
221
 
218
222
  _CLI_CONTEXT_MANAGER: ContextVar[_CliGlobalContextManager | None] = ContextVar(
219
223
  "cli_context", default=None
@@ -23,6 +23,7 @@ from snowflake.cli.api.cli_global_context import get_cli_context
23
23
  from snowflake.cli.api.commands.flags import (
24
24
  AccountOption,
25
25
  AuthenticatorOption,
26
+ ClientStoreTemporaryCredentialOption,
26
27
  ConnectionOption,
27
28
  DatabaseOption,
28
29
  DebugOption,
@@ -33,6 +34,15 @@ from snowflake.cli.api.commands.flags import (
33
34
  HostOption,
34
35
  MasterTokenOption,
35
36
  MfaPasscodeOption,
37
+ OauthAuthorizationUrlOption,
38
+ OauthClientIdOption,
39
+ OauthClientSecretOption,
40
+ OauthDisablePkceOption,
41
+ OauthEnableRefreshTokensOption,
42
+ OauthEnableSingleUseRefreshTokensOption,
43
+ OauthRedirectUriOption,
44
+ OauthScopeOption,
45
+ OauthTokenRequestUrlOption,
36
46
  OutputFormatOption,
37
47
  PasswordOption,
38
48
  PortOption,
@@ -329,6 +339,66 @@ GLOBAL_CONNECTION_OPTIONS = [
329
339
  annotation=Optional[str],
330
340
  default=DiagAllowlistPathOption,
331
341
  ),
342
+ inspect.Parameter(
343
+ "oauth_client_id",
344
+ inspect.Parameter.KEYWORD_ONLY,
345
+ annotation=Optional[str],
346
+ default=OauthClientIdOption,
347
+ ),
348
+ inspect.Parameter(
349
+ "oauth_client_secret",
350
+ inspect.Parameter.KEYWORD_ONLY,
351
+ annotation=Optional[str],
352
+ default=OauthClientSecretOption,
353
+ ),
354
+ inspect.Parameter(
355
+ "oauth_authorization_url",
356
+ inspect.Parameter.KEYWORD_ONLY,
357
+ annotation=Optional[str],
358
+ default=OauthAuthorizationUrlOption,
359
+ ),
360
+ inspect.Parameter(
361
+ "oauth_token_request_url",
362
+ inspect.Parameter.KEYWORD_ONLY,
363
+ annotation=Optional[str],
364
+ default=OauthTokenRequestUrlOption,
365
+ ),
366
+ inspect.Parameter(
367
+ "oauth_redirect_uri",
368
+ inspect.Parameter.KEYWORD_ONLY,
369
+ annotation=Optional[str],
370
+ default=OauthRedirectUriOption,
371
+ ),
372
+ inspect.Parameter(
373
+ "oauth_scope",
374
+ inspect.Parameter.KEYWORD_ONLY,
375
+ annotation=Optional[str],
376
+ default=OauthScopeOption,
377
+ ),
378
+ inspect.Parameter(
379
+ "oauth_disable_pkce",
380
+ inspect.Parameter.KEYWORD_ONLY,
381
+ annotation=Optional[bool],
382
+ default=OauthDisablePkceOption,
383
+ ),
384
+ inspect.Parameter(
385
+ "oauth_enable_refresh_tokens",
386
+ inspect.Parameter.KEYWORD_ONLY,
387
+ annotation=Optional[bool],
388
+ default=OauthEnableRefreshTokensOption,
389
+ ),
390
+ inspect.Parameter(
391
+ "oauth_enable_single_use_refresh_tokens",
392
+ inspect.Parameter.KEYWORD_ONLY,
393
+ annotation=Optional[bool],
394
+ default=OauthEnableSingleUseRefreshTokensOption,
395
+ ),
396
+ inspect.Parameter(
397
+ "client_store_temporary_credential",
398
+ inspect.Parameter.KEYWORD_ONLY,
399
+ annotation=Optional[bool],
400
+ default=ClientStoreTemporaryCredentialOption,
401
+ ),
332
402
  ]
333
403
 
334
404
  GLOBAL_OPTIONS = [
@@ -284,6 +284,99 @@ EnableDiagOption = typer.Option(
284
284
  rich_help_panel=_CONNECTION_SECTION,
285
285
  )
286
286
 
287
+ OauthClientIdOption = typer.Option(
288
+ None,
289
+ "--oauth-client-id",
290
+ help="Value of client id provided by the Identity Provider for Snowflake integration.",
291
+ callback=_connection_callback("oauth_client_id"),
292
+ show_default=False,
293
+ rich_help_panel=_CONNECTION_SECTION,
294
+ )
295
+
296
+ OauthClientSecretOption = typer.Option(
297
+ None,
298
+ help="Value of the client secret provided by the Identity Provider for Snowflake integration.",
299
+ callback=_connection_callback("oauth_client_secret"),
300
+ show_default=False,
301
+ rich_help_panel=_CONNECTION_SECTION,
302
+ )
303
+
304
+ OauthAuthorizationUrlOption = typer.Option(
305
+ None,
306
+ "--oauth-authorization-url",
307
+ help="Identity Provider endpoint supplying the authorization code to the driver.",
308
+ callback=_connection_callback("oauth_authorization_url"),
309
+ show_default=False,
310
+ rich_help_panel=_CONNECTION_SECTION,
311
+ )
312
+
313
+ OauthTokenRequestUrlOption = typer.Option(
314
+ None,
315
+ "--oauth-token-request-url",
316
+ help="Identity Provider endpoint supplying the access tokens to the driver.",
317
+ callback=_connection_callback("oauth_token_request_url"),
318
+ show_default=False,
319
+ rich_help_panel=_CONNECTION_SECTION,
320
+ )
321
+
322
+ OauthRedirectUriOption = typer.Option(
323
+ None,
324
+ "--oauth-redirect-uri",
325
+ help="URI to use for authorization code redirection.",
326
+ callback=_connection_callback("oauth_redirect_uri"),
327
+ show_default=False,
328
+ rich_help_panel=_CONNECTION_SECTION,
329
+ )
330
+
331
+ OauthScopeOption = typer.Option(
332
+ None,
333
+ "--oauth-scope",
334
+ help="Scope requested in the Identity Provider authorization request.",
335
+ callback=_connection_callback("oauth_scope"),
336
+ show_default=False,
337
+ rich_help_panel=_CONNECTION_SECTION,
338
+ )
339
+
340
+ OauthDisablePkceOption = typer.Option(
341
+ None,
342
+ "--oauth-disable-pkce",
343
+ help="Disables Proof Key for Code Exchange (PKCE). Default: `False`.",
344
+ callback=_connection_callback("oauth_disable_pkce"),
345
+ show_default=False,
346
+ is_flag=True,
347
+ rich_help_panel=_CONNECTION_SECTION,
348
+ )
349
+
350
+ OauthEnableRefreshTokensOption = typer.Option(
351
+ None,
352
+ "--oauth-enable-refresh-tokens",
353
+ help="Enables a silent re-authentication when the actual access token becomes outdated. Default: `False`.",
354
+ callback=_connection_callback("oauth_enable_refresh_tokens"),
355
+ show_default=False,
356
+ is_flag=True,
357
+ rich_help_panel=_CONNECTION_SECTION,
358
+ )
359
+
360
+ OauthEnableSingleUseRefreshTokensOption = typer.Option(
361
+ None,
362
+ "--oauth-enable-single-use-refresh-tokens",
363
+ help="Whether to opt-in to single-use refresh token semantics. Default: `False`.",
364
+ callback=_connection_callback("oauth_enable_single_use_refresh_tokens"),
365
+ show_default=False,
366
+ is_flag=True,
367
+ rich_help_panel=_CONNECTION_SECTION,
368
+ )
369
+
370
+ ClientStoreTemporaryCredentialOption = typer.Option(
371
+ None,
372
+ "--client-store-temporary-credential",
373
+ help="Store the temporary credential.",
374
+ callback=_connection_callback("client_store_temporary_credential"),
375
+ is_flag=True,
376
+ show_default=False,
377
+ rich_help_panel=_CONNECTION_SECTION,
378
+ )
379
+
287
380
  # Set default via callback to avoid including tempdir path in generated docs (snow --docs).
288
381
  # Use constant instead of None, as None is removed from telemetry data.
289
382
  _DIAG_LOG_DEFAULT_VALUE = "<system_temporary_directory>"
@@ -382,7 +475,6 @@ EnhancedExitCodesOption = typer.Option(
382
475
  envvar="SNOWFLAKE_ENHANCED_EXIT_CODES",
383
476
  )
384
477
 
385
-
386
478
  # If IfExistsOption, IfNotExistsOption, or ReplaceOption are used with names other than those in CREATE_MODE_OPTION_NAMES,
387
479
  # you must also override mutually_exclusive if you want to retain the validation that at most one of these flags is
388
480
  # passed.
@@ -425,9 +517,9 @@ NoInteractiveOption = typer.Option(False, "--no-interactive", help="Disable prom
425
517
 
426
518
  PruneOption = OverrideableOption(
427
519
  False,
428
- "--prune",
520
+ "--prune/--no-prune",
429
521
  help=f"Delete files that exist in the stage, but not in the local filesystem.",
430
- show_default=False,
522
+ show_default=True,
431
523
  )
432
524
 
433
525
 
@@ -83,6 +83,16 @@ class ConnectionConfig:
83
83
  authenticator: Optional[str] = None
84
84
  private_key_file: Optional[str] = None
85
85
  token_file_path: Optional[str] = None
86
+ oauth_client_id: Optional[str] = None
87
+ oauth_client_secret: Optional[str] = None
88
+ oauth_authorization_url: Optional[str] = None
89
+ oauth_token_request_url: Optional[str] = None
90
+ oauth_redirect_uri: Optional[str] = None
91
+ oauth_scope: Optional[str] = None
92
+ oatuh_enable_pkce: Optional[bool] = None
93
+ oauth_enable_refresh_tokens: Optional[bool] = None
94
+ oauth_enable_single_use_refresh_tokens: Optional[bool] = None
95
+ client_store_temporary_credential: Optional[bool] = None
86
96
 
87
97
  _other_settings: dict = field(default_factory=lambda: {})
88
98
 
@@ -55,6 +55,16 @@ class ConnectionContext:
55
55
  session_token: Optional[str] = None
56
56
  master_token: Optional[str] = None
57
57
  token_file_path: Optional[Path] = None
58
+ oauth_client_id: Optional[str] = None
59
+ oauth_client_secret: Optional[str] = None
60
+ oauth_authorization_url: Optional[str] = None
61
+ oauth_token_request_url: Optional[str] = None
62
+ oauth_redirect_uri: Optional[str] = None
63
+ oauth_scope: Optional[str] = None
64
+ oauth_disable_pkce: Optional[bool] = None
65
+ oauth_enable_refresh_tokens: Optional[bool] = None
66
+ oauth_enable_single_use_refresh_tokens: Optional[bool] = None
67
+ client_store_temporary_credential: Optional[bool] = None
58
68
 
59
69
  VALIDATED_FIELD_NAMES = ["schema"]
60
70
 
@@ -19,7 +19,7 @@ from contextlib import contextmanager
19
19
  from typing import Callable, Iterator, Optional
20
20
 
21
21
  from rich import print as rich_print
22
- from rich.text import Text
22
+ from rich.jupyter import JupyterMixin
23
23
  from snowflake.cli.api.cli_global_context import (
24
24
  _CliGlobalContextAccess,
25
25
  get_cli_context,
@@ -57,7 +57,7 @@ class AbstractConsole(ABC):
57
57
  """Indicated whether output should be grouped."""
58
58
  return self._in_phase
59
59
 
60
- def _print(self, text: Text):
60
+ def _print(self, text: JupyterMixin):
61
61
  if self.is_silent:
62
62
  return
63
63
  rich_print(text)
@@ -92,3 +92,9 @@ class AbstractConsole(ABC):
92
92
  """Displays message in a style that makes it visually stand out from other output.
93
93
 
94
94
  Intended for displaying messages related to important messages."""
95
+
96
+ @abstractmethod
97
+ def panel(self, message: str):
98
+ """Displays message in a panel that makes it visually stand out from other output.
99
+
100
+ Intended for displaying visually separated messages."""
@@ -17,14 +17,23 @@ from __future__ import annotations
17
17
  from contextlib import contextmanager
18
18
  from typing import Optional
19
19
 
20
+ from rich import get_console
21
+ from rich.panel import Panel
20
22
  from rich.style import Style
21
23
  from rich.text import Text
22
24
  from snowflake.cli.api.console.abc import AbstractConsole
23
25
  from snowflake.cli.api.console.enum import Output
24
26
 
27
+ # ensure we do not break URLs that wrap lines
28
+ get_console().soft_wrap = True
29
+
30
+ # Disable markup to avoid escaping errors
31
+ get_console()._markup = False # noqa: SLF001
32
+
25
33
  PHASE_STYLE: Style = Style(bold=True)
26
34
  STEP_STYLE: Style = Style(italic=True)
27
35
  INFO_STYLE: Style = Style()
36
+ PANEL_STYLE: Style = Style()
28
37
  IMPORTANT_STYLE: Style = Style(bold=True, italic=True)
29
38
  INDENTATION_LEVEL: int = 2
30
39
 
@@ -47,6 +56,7 @@ class CliConsole(AbstractConsole):
47
56
  Output.STEP: STEP_STYLE,
48
57
  Output.INFO: None,
49
58
  Output.IMPORTANT: IMPORTANT_STYLE,
59
+ Output.PANEL: PANEL_STYLE,
50
60
  }
51
61
 
52
62
  def _format_message(self, message: str, output: Output) -> Text:
@@ -110,6 +120,12 @@ class CliConsole(AbstractConsole):
110
120
  text = self._format_message(message, Output.IMPORTANT)
111
121
  self._print(text)
112
122
 
123
+ def panel(self, message: str):
124
+ """Displays a message in a panel."""
125
+ style = self._styles.get(Output.PANEL, Style())
126
+ panel = Panel(message, style=style)
127
+ self._print(panel)
128
+
113
129
 
114
130
  def get_cli_console() -> AbstractConsole:
115
131
  console = CliConsole()
@@ -14,4 +14,4 @@
14
14
 
15
15
  from enum import Enum
16
16
 
17
- Output = Enum("Output", ("PHASE", "STEP", "INFO", "IMPORTANT"))
17
+ Output = Enum("Output", ("PHASE", "STEP", "INFO", "IMPORTANT", "PANEL"))