snowflake-cli-labs 3.0.0rc0__py3-none-any.whl → 3.0.0rc2__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 (66) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/_app/cli_app.py +10 -1
  3. snowflake/cli/_app/snow_connector.py +91 -37
  4. snowflake/cli/_app/telemetry.py +8 -4
  5. snowflake/cli/_app/version_check.py +74 -0
  6. snowflake/cli/_plugins/connection/commands.py +3 -2
  7. snowflake/cli/_plugins/git/commands.py +55 -14
  8. snowflake/cli/_plugins/git/manager.py +14 -6
  9. snowflake/cli/_plugins/nativeapp/codegen/compiler.py +18 -2
  10. snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +123 -42
  11. snowflake/cli/_plugins/nativeapp/codegen/setup/setup_driver.py.source +5 -2
  12. snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +6 -11
  13. snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +111 -0
  14. snowflake/cli/_plugins/nativeapp/exceptions.py +3 -3
  15. snowflake/cli/_plugins/nativeapp/manager.py +74 -144
  16. snowflake/cli/_plugins/nativeapp/project_model.py +2 -9
  17. snowflake/cli/_plugins/nativeapp/run_processor.py +56 -260
  18. snowflake/cli/_plugins/nativeapp/same_account_install_method.py +74 -0
  19. snowflake/cli/_plugins/nativeapp/teardown_processor.py +17 -246
  20. snowflake/cli/_plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +91 -17
  21. snowflake/cli/_plugins/snowpark/commands.py +5 -65
  22. snowflake/cli/_plugins/snowpark/common.py +17 -1
  23. snowflake/cli/_plugins/snowpark/models.py +2 -1
  24. snowflake/cli/_plugins/snowpark/package/anaconda_packages.py +1 -35
  25. snowflake/cli/_plugins/sql/commands.py +1 -2
  26. snowflake/cli/_plugins/stage/commands.py +2 -2
  27. snowflake/cli/_plugins/stage/manager.py +46 -15
  28. snowflake/cli/_plugins/streamlit/commands.py +4 -63
  29. snowflake/cli/_plugins/streamlit/manager.py +13 -0
  30. snowflake/cli/_plugins/workspace/action_context.py +7 -0
  31. snowflake/cli/_plugins/workspace/commands.py +145 -32
  32. snowflake/cli/_plugins/workspace/manager.py +21 -4
  33. snowflake/cli/api/cli_global_context.py +136 -313
  34. snowflake/cli/api/commands/decorators.py +1 -1
  35. snowflake/cli/api/commands/flags.py +106 -102
  36. snowflake/cli/api/commands/snow_typer.py +15 -6
  37. snowflake/cli/api/config.py +18 -5
  38. snowflake/cli/api/connections.py +214 -0
  39. snowflake/cli/api/console/abc.py +4 -2
  40. snowflake/cli/api/constants.py +11 -0
  41. snowflake/cli/api/entities/application_entity.py +687 -2
  42. snowflake/cli/api/entities/application_package_entity.py +407 -9
  43. snowflake/cli/api/entities/common.py +7 -2
  44. snowflake/cli/api/entities/utils.py +80 -20
  45. snowflake/cli/api/exceptions.py +12 -2
  46. snowflake/cli/api/feature_flags.py +0 -2
  47. snowflake/cli/api/identifiers.py +3 -0
  48. snowflake/cli/api/project/definition.py +35 -1
  49. snowflake/cli/api/project/definition_conversion.py +352 -0
  50. snowflake/cli/api/project/schemas/entities/application_package_entity_model.py +17 -0
  51. snowflake/cli/api/project/schemas/entities/common.py +0 -12
  52. snowflake/cli/api/project/schemas/identifier_model.py +2 -2
  53. snowflake/cli/api/project/schemas/project_definition.py +102 -43
  54. snowflake/cli/api/rendering/jinja.py +2 -16
  55. snowflake/cli/api/rendering/project_definition_templates.py +5 -1
  56. snowflake/cli/api/rendering/sql_templates.py +14 -4
  57. snowflake/cli/api/secure_path.py +13 -18
  58. snowflake/cli/api/secure_utils.py +90 -1
  59. snowflake/cli/api/sql_execution.py +13 -0
  60. snowflake/cli/api/utils/definition_rendering.py +7 -7
  61. {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/METADATA +9 -9
  62. {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/RECORD +65 -61
  63. snowflake/cli/api/commands/typer_pre_execute.py +0 -26
  64. {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/WHEEL +0 -0
  65. {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/entry_points.txt +0 -0
  66. {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/licenses/LICENSE +0 -0
@@ -21,18 +21,18 @@ from typing import Any, Callable, Optional
21
21
  import click
22
22
  import typer
23
23
  from click import ClickException
24
- from snowflake.cli.api.cli_global_context import get_cli_context_manager
24
+ from snowflake.cli.api.cli_global_context import (
25
+ _CliGlobalContextManager,
26
+ get_cli_context_manager,
27
+ )
25
28
  from snowflake.cli.api.commands.common import OnErrorType
26
29
  from snowflake.cli.api.commands.overrideable_parameter import OverrideableOption
27
- from snowflake.cli.api.commands.typer_pre_execute import register_pre_execute_command
28
30
  from snowflake.cli.api.commands.utils import parse_key_value_variables
29
31
  from snowflake.cli.api.config import get_all_connections
32
+ from snowflake.cli.api.connections import ConnectionContext
30
33
  from snowflake.cli.api.console import cli_console
31
- from snowflake.cli.api.exceptions import MissingConfiguration
32
34
  from snowflake.cli.api.identifiers import FQN
33
35
  from snowflake.cli.api.output.formats import OutputFormat
34
- from snowflake.cli.api.project.definition_manager import DefinitionManager
35
- from snowflake.cli.api.rendering.jinja import CONTEXT_KEY
36
36
 
37
37
  DEFAULT_CONTEXT_SETTINGS = {"help_option_names": ["--help", "-h"]}
38
38
 
@@ -40,10 +40,39 @@ _CONNECTION_SECTION = "Connection configuration"
40
40
  _CLI_BEHAVIOUR = "Global configuration"
41
41
 
42
42
 
43
- def _callback(provide_setter: Callable[[], Callable[[Any], Any]]):
43
+ def _connection_callback(prop: str):
44
+ """Generates a setter for a field on the current context manager's connection context."""
45
+ if prop not in ConnectionContext.__dataclass_fields__:
46
+ raise KeyError(
47
+ f"Cannot generate setter for non-existent connection attr {prop}"
48
+ )
49
+
44
50
  def callback(value):
45
- set_value = provide_setter()
46
- set_value(value)
51
+ try:
52
+ if click.get_current_context().resilient_parsing:
53
+ return
54
+ except RuntimeError:
55
+ pass
56
+
57
+ setattr(get_cli_context_manager().connection_context, prop, value)
58
+ return value
59
+
60
+ return callback
61
+
62
+
63
+ def _context_callback(prop: str):
64
+ """Generates a setter for a field on the current context manager."""
65
+ if prop not in _CliGlobalContextManager.__dataclass_fields__:
66
+ raise KeyError(f"Cannot generate setter for non-existent context attr {prop}")
67
+
68
+ def callback(value):
69
+ try:
70
+ if click.get_current_context().resilient_parsing:
71
+ return
72
+ except RuntimeError:
73
+ pass
74
+
75
+ setattr(get_cli_context_manager(), prop, value)
47
76
  return value
48
77
 
49
78
  return callback
@@ -55,9 +84,7 @@ ConnectionOption = typer.Option(
55
84
  "-c",
56
85
  "--environment",
57
86
  help=f"Name of the connection, as defined in your `config.toml`. Default: `default`.",
58
- callback=_callback(
59
- lambda: get_cli_context_manager().connection_context.set_connection_name
60
- ),
87
+ callback=_connection_callback("connection_name"),
61
88
  show_default=False,
62
89
  rich_help_panel=_CONNECTION_SECTION,
63
90
  shell_complete=lambda _, __, ___: list(get_all_connections()),
@@ -68,9 +95,7 @@ TemporaryConnectionOption = typer.Option(
68
95
  "--temporary-connection",
69
96
  "-x",
70
97
  help="Uses connection defined with command line parameters, instead of one defined in config",
71
- callback=_callback(
72
- lambda: get_cli_context_manager().connection_context.set_temporary_connection
73
- ),
98
+ callback=_connection_callback("temporary_connection"),
74
99
  is_flag=True,
75
100
  rich_help_panel=_CONNECTION_SECTION,
76
101
  )
@@ -80,9 +105,7 @@ AccountOption = typer.Option(
80
105
  "--account",
81
106
  "--accountname",
82
107
  help="Name assigned to your Snowflake account. Overrides the value specified for the connection.",
83
- callback=_callback(
84
- lambda: get_cli_context_manager().connection_context.set_account
85
- ),
108
+ callback=_connection_callback("account"),
86
109
  show_default=False,
87
110
  rich_help_panel=_CONNECTION_SECTION,
88
111
  )
@@ -92,7 +115,7 @@ UserOption = typer.Option(
92
115
  "--user",
93
116
  "--username",
94
117
  help="Username to connect to Snowflake. Overrides the value specified for the connection.",
95
- callback=_callback(lambda: get_cli_context_manager().connection_context.set_user),
118
+ callback=_connection_callback("user"),
96
119
  show_default=False,
97
120
  rich_help_panel=_CONNECTION_SECTION,
98
121
  )
@@ -105,9 +128,7 @@ def _password_callback(value: str):
105
128
  if value:
106
129
  cli_console.message(PLAIN_PASSWORD_MSG)
107
130
 
108
- return _callback(lambda: get_cli_context_manager().connection_context.set_password)(
109
- value
110
- )
131
+ return _connection_callback("password")(value)
111
132
 
112
133
 
113
134
  PasswordOption = typer.Option(
@@ -125,21 +146,18 @@ AuthenticatorOption = typer.Option(
125
146
  "--authenticator",
126
147
  help="Snowflake authenticator. Overrides the value specified for the connection.",
127
148
  hide_input=True,
128
- callback=_callback(
129
- lambda: get_cli_context_manager().connection_context.set_authenticator
130
- ),
149
+ callback=_connection_callback("authenticator"),
131
150
  show_default=False,
132
151
  rich_help_panel=_CONNECTION_SECTION,
133
152
  )
134
153
 
135
154
  PrivateKeyPathOption = typer.Option(
136
155
  None,
156
+ "--private-key-file",
137
157
  "--private-key-path",
138
- help="Snowflake private key path. Overrides the value specified for the connection.",
158
+ help="Snowflake private key file path. Overrides the value specified for the connection.",
139
159
  hide_input=True,
140
- callback=_callback(
141
- lambda: get_cli_context_manager().connection_context.set_private_key_path
142
- ),
160
+ callback=_connection_callback("private_key_file"),
143
161
  show_default=False,
144
162
  rich_help_panel=_CONNECTION_SECTION,
145
163
  exists=True,
@@ -152,9 +170,7 @@ SessionTokenOption = typer.Option(
152
170
  "--session-token",
153
171
  help="Snowflake session token. Can be used only in conjunction with --master-token. Overrides the value specified for the connection.",
154
172
  hide_input=True,
155
- callback=_callback(
156
- lambda: get_cli_context_manager().connection_context.set_session_token
157
- ),
173
+ callback=_connection_callback("session_token"),
158
174
  show_default=False,
159
175
  rich_help_panel=_CONNECTION_SECTION,
160
176
  exists=True,
@@ -168,9 +184,7 @@ MasterTokenOption = typer.Option(
168
184
  "--master-token",
169
185
  help="Snowflake master token. Can be used only in conjunction with --session-token. Overrides the value specified for the connection.",
170
186
  hide_input=True,
171
- callback=_callback(
172
- lambda: get_cli_context_manager().connection_context.set_master_token
173
- ),
187
+ callback=_connection_callback("master_token"),
174
188
  show_default=False,
175
189
  rich_help_panel=_CONNECTION_SECTION,
176
190
  exists=True,
@@ -183,9 +197,7 @@ TokenFilePathOption = typer.Option(
183
197
  None,
184
198
  "--token-file-path",
185
199
  help="Path to file with an OAuth token that should be used when connecting to Snowflake",
186
- callback=_callback(
187
- lambda: get_cli_context_manager().connection_context.set_token_file_path
188
- ),
200
+ callback=_connection_callback("token_file_path"),
189
201
  show_default=False,
190
202
  rich_help_panel=_CONNECTION_SECTION,
191
203
  exists=True,
@@ -198,9 +210,7 @@ DatabaseOption = typer.Option(
198
210
  "--database",
199
211
  "--dbname",
200
212
  help="Database to use. Overrides the value specified for the connection.",
201
- callback=_callback(
202
- lambda: get_cli_context_manager().connection_context.set_database
203
- ),
213
+ callback=_connection_callback("database"),
204
214
  show_default=False,
205
215
  rich_help_panel=_CONNECTION_SECTION,
206
216
  )
@@ -210,7 +220,7 @@ SchemaOption = typer.Option(
210
220
  "--schema",
211
221
  "--schemaname",
212
222
  help="Database schema to use. Overrides the value specified for the connection.",
213
- callback=_callback(lambda: get_cli_context_manager().connection_context.set_schema),
223
+ callback=_connection_callback("schema"),
214
224
  show_default=False,
215
225
  rich_help_panel=_CONNECTION_SECTION,
216
226
  )
@@ -220,7 +230,7 @@ RoleOption = typer.Option(
220
230
  "--role",
221
231
  "--rolename",
222
232
  help="Role to use. Overrides the value specified for the connection.",
223
- callback=_callback(lambda: get_cli_context_manager().connection_context.set_role),
233
+ callback=_connection_callback("role"),
224
234
  show_default=False,
225
235
  rich_help_panel=_CONNECTION_SECTION,
226
236
  )
@@ -229,9 +239,7 @@ WarehouseOption = typer.Option(
229
239
  None,
230
240
  "--warehouse",
231
241
  help="Warehouse to use. Overrides the value specified for the connection.",
232
- callback=_callback(
233
- lambda: get_cli_context_manager().connection_context.set_warehouse
234
- ),
242
+ callback=_connection_callback("warehouse"),
235
243
  show_default=False,
236
244
  rich_help_panel=_CONNECTION_SECTION,
237
245
  )
@@ -240,9 +248,7 @@ MfaPasscodeOption = typer.Option(
240
248
  None,
241
249
  "--mfa-passcode",
242
250
  help="Token to use for multi-factor authentication (MFA)",
243
- callback=_callback(
244
- lambda: get_cli_context_manager().connection_context.set_mfa_passcode
245
- ),
251
+ callback=_connection_callback("mfa_passcode"),
246
252
  prompt="MFA passcode",
247
253
  prompt_required=False,
248
254
  show_default=False,
@@ -253,9 +259,7 @@ EnableDiagOption = typer.Option(
253
259
  False,
254
260
  "--enable-diag",
255
261
  help="Run python connector diagnostic test",
256
- callback=_callback(
257
- lambda: get_cli_context_manager().connection_context.set_enable_diag
258
- ),
262
+ callback=_connection_callback("enable_diag"),
259
263
  show_default=False,
260
264
  is_flag=True,
261
265
  rich_help_panel=_CONNECTION_SECTION,
@@ -267,18 +271,17 @@ _DIAG_LOG_DEFAULT_VALUE = "<temporary_directory>"
267
271
 
268
272
 
269
273
  def _diag_log_path_callback(path: str):
270
- if path != _DIAG_LOG_DEFAULT_VALUE:
271
- return path
272
- return tempfile.gettempdir()
274
+ if path == _DIAG_LOG_DEFAULT_VALUE:
275
+ path = tempfile.gettempdir()
276
+ get_cli_context_manager().connection_context.diag_log_path = Path(path)
277
+ return path
273
278
 
274
279
 
275
280
  DiagLogPathOption: Path = typer.Option(
276
281
  _DIAG_LOG_DEFAULT_VALUE,
277
282
  "--diag-log-path",
278
283
  help="Diagnostic report path",
279
- callback=_callback(
280
- lambda: get_cli_context_manager().connection_context.set_diag_log_path
281
- ),
284
+ callback=_diag_log_path_callback,
282
285
  show_default=False,
283
286
  rich_help_panel=_CONNECTION_SECTION,
284
287
  exists=True,
@@ -289,9 +292,7 @@ DiagAllowlistPathOption: Path = typer.Option(
289
292
  None,
290
293
  "--diag-allowlist-path",
291
294
  help="Diagnostic report path to optional allowlist",
292
- callback=_callback(
293
- lambda: get_cli_context_manager().connection_context.set_diag_allowlist_path
294
- ),
295
+ callback=_connection_callback("diag_allowlist_path"),
295
296
  show_default=False,
296
297
  rich_help_panel=_CONNECTION_SECTION,
297
298
  exists=True,
@@ -303,7 +304,7 @@ OutputFormatOption = typer.Option(
303
304
  "--format",
304
305
  help="Specifies the output format.",
305
306
  case_sensitive=False,
306
- callback=_callback(lambda: get_cli_context_manager().set_output_format),
307
+ callback=_context_callback("output_format"),
307
308
  rich_help_panel=_CLI_BEHAVIOUR,
308
309
  )
309
310
 
@@ -311,7 +312,7 @@ SilentOption = typer.Option(
311
312
  False,
312
313
  "--silent",
313
314
  help="Turns off intermediate output to console.",
314
- callback=_callback(lambda: get_cli_context_manager().set_silent),
315
+ callback=_context_callback("silent"),
315
316
  is_flag=True,
316
317
  rich_help_panel=_CLI_BEHAVIOUR,
317
318
  is_eager=True,
@@ -322,7 +323,7 @@ VerboseOption = typer.Option(
322
323
  "--verbose",
323
324
  "-v",
324
325
  help="Displays log entries for log levels `info` and higher.",
325
- callback=_callback(lambda: get_cli_context_manager().set_verbose),
326
+ callback=_context_callback("verbose"),
326
327
  is_flag=True,
327
328
  rich_help_panel=_CLI_BEHAVIOUR,
328
329
  )
@@ -331,7 +332,7 @@ DebugOption = typer.Option(
331
332
  False,
332
333
  "--debug",
333
334
  help="Displays log entries for log levels `debug` and higher; debug logs contains additional information.",
334
- callback=_callback(lambda: get_cli_context_manager().set_enable_tracebacks),
335
+ callback=_context_callback("enable_tracebacks"),
335
336
  is_flag=True,
336
337
  rich_help_panel=_CLI_BEHAVIOUR,
337
338
  )
@@ -434,24 +435,49 @@ def experimental_option(
434
435
  "--experimental",
435
436
  help=help_text,
436
437
  hidden=True,
437
- callback=_callback(lambda: get_cli_context_manager().set_experimental),
438
+ callback=_context_callback("experimental"),
438
439
  is_flag=True,
439
440
  rich_help_panel=_CLI_BEHAVIOUR,
440
441
  )
441
442
 
442
443
 
444
+ class IdentifierType(click.ParamType):
445
+ name = "TEXT"
446
+
447
+ def convert(self, value, param, ctx):
448
+ return FQN.from_string(value)
449
+
450
+
451
+ class IdentifierStageType(click.ParamType):
452
+ name = "TEXT"
453
+
454
+ def convert(self, value, param, ctx):
455
+ return FQN.from_stage(value)
456
+
457
+
443
458
  def identifier_argument(
444
- sf_object: str, example: str, callback: Callable | None = None
459
+ sf_object: str,
460
+ example: str,
461
+ click_type: click.ParamType = IdentifierType(),
462
+ callback: Callable | None = None,
445
463
  ) -> typer.Argument:
446
464
  return typer.Argument(
447
465
  ...,
448
466
  help=f"Identifier of the {sf_object}. For example: {example}",
449
467
  show_default=False,
450
- click_type=IdentifierType(),
468
+ click_type=click_type,
451
469
  callback=callback,
452
470
  )
453
471
 
454
472
 
473
+ def identifier_stage_argument(
474
+ sf_object: str, example: str, callback: Callable | None = None
475
+ ) -> typer.Argument:
476
+ return identifier_argument(
477
+ sf_object, example, click_type=IdentifierStageType(), callback=callback
478
+ )
479
+
480
+
455
481
  def execution_identifier_argument(sf_object: str, example: str) -> typer.Argument:
456
482
  return typer.Argument(
457
483
  ...,
@@ -460,30 +486,12 @@ def execution_identifier_argument(sf_object: str, example: str) -> typer.Argumen
460
486
  )
461
487
 
462
488
 
463
- def register_project_definition(is_optional: bool) -> None:
464
- cli_context_manager = get_cli_context_manager()
465
- project_path = cli_context_manager.project_path_arg
466
- env_overrides_args = cli_context_manager.project_env_overrides_args
467
-
468
- dm = DefinitionManager(project_path, {CONTEXT_KEY: {"env": env_overrides_args}})
469
- project_definition = dm.project_definition
470
- project_root = dm.project_root
471
- template_context = dm.template_context
472
-
473
- if not dm.has_definition_file and not is_optional:
474
- raise MissingConfiguration(
475
- "Cannot find project definition (snowflake.yml). Please provide a path to the project or run this command in a valid project directory."
476
- )
477
-
478
- cli_context_manager.set_project_definition(project_definition)
479
- cli_context_manager.set_project_root(project_root)
480
- cli_context_manager.set_template_context(template_context)
481
-
482
-
483
489
  def project_definition_option(is_optional: bool):
484
- def project_definition_callback(project_path: str) -> None:
485
- get_cli_context_manager().set_project_path_arg(project_path)
486
- register_pre_execute_command(lambda: register_project_definition(is_optional))
490
+ def project_path_callback(project_path: str) -> str:
491
+ ctx_mgr = get_cli_context_manager()
492
+ ctx_mgr.project_path_arg = project_path
493
+ ctx_mgr.project_is_optional = is_optional
494
+ return project_path
487
495
 
488
496
  return typer.Option(
489
497
  None,
@@ -491,23 +499,26 @@ def project_definition_option(is_optional: bool):
491
499
  "--project",
492
500
  help=f"Path where Snowflake project resides. "
493
501
  f"Defaults to current working directory.",
494
- callback=_callback(lambda: project_definition_callback),
502
+ callback=project_path_callback,
495
503
  show_default=False,
496
504
  )
497
505
 
498
506
 
499
507
  def project_env_overrides_option():
500
- def project_env_overrides_callback(env_overrides_args_list: list[str]) -> None:
508
+ def project_env_overrides_callback(
509
+ env_overrides_args_list: list[str],
510
+ ) -> dict[str, str]:
501
511
  env_overrides_args_map = {
502
512
  v.key: v.value for v in parse_key_value_variables(env_overrides_args_list)
503
513
  }
504
- get_cli_context_manager().set_project_env_overrides_args(env_overrides_args_map)
514
+ get_cli_context_manager().project_env_overrides_args = env_overrides_args_map
515
+ return env_overrides_args_map
505
516
 
506
517
  return typer.Option(
507
518
  [],
508
519
  "--env",
509
- help="String in format of key=value. Overrides variables from env section used for templating.",
510
- callback=_callback(lambda: project_env_overrides_callback),
520
+ help="String in format of key=value. Overrides variables from env section used for templates.",
521
+ callback=project_env_overrides_callback,
511
522
  show_default=False,
512
523
  )
513
524
 
@@ -530,10 +541,3 @@ def deprecated_flag_callback_enum(msg: str):
530
541
  return value.value
531
542
 
532
543
  return _warning_callback
533
-
534
-
535
- class IdentifierType(click.ParamType):
536
- name = "TEXT"
537
-
538
- def convert(self, value, param, ctx):
539
- return FQN.from_string(value)
@@ -20,6 +20,7 @@ from functools import wraps
20
20
  from typing import Any, Callable, Dict, List, Optional, Tuple
21
21
 
22
22
  import typer
23
+ from click import ClickException
23
24
  from snowflake.cli.api.commands.decorators import (
24
25
  global_options,
25
26
  global_options_with_connection,
@@ -29,10 +30,11 @@ from snowflake.cli.api.commands.execution_metadata import (
29
30
  ExecutionStatus,
30
31
  )
31
32
  from snowflake.cli.api.commands.flags import DEFAULT_CONTEXT_SETTINGS
32
- from snowflake.cli.api.commands.typer_pre_execute import run_pre_execute_commands
33
33
  from snowflake.cli.api.exceptions import CommandReturnTypeError
34
34
  from snowflake.cli.api.output.types import CommandResult
35
35
  from snowflake.cli.api.sanitizers import sanitize_for_terminal
36
+ from snowflake.cli.api.sql_execution import SqlExecutionMixin
37
+ from snowflake.connector import DatabaseError
36
38
 
37
39
  log = logging.getLogger(__name__)
38
40
 
@@ -71,6 +73,7 @@ class SnowTyper(typer.Typer):
71
73
  requires_global_options: bool = True,
72
74
  requires_connection: bool = False,
73
75
  is_enabled: Callable[[], bool] | None = None,
76
+ require_warehouse: bool = False,
74
77
  **kwargs,
75
78
  ):
76
79
  """
@@ -97,15 +100,15 @@ class SnowTyper(typer.Typer):
97
100
  def command_callable_decorator(*args, **kw):
98
101
  """Wrapper around command callable. This is what happens at "runtime"."""
99
102
  execution = ExecutionMetadata()
100
- self.pre_execute(execution)
103
+ self.pre_execute(execution, require_warehouse=require_warehouse)
101
104
  try:
102
105
  result = command_callable(*args, **kw)
103
106
  self.process_result(result)
104
107
  execution.complete(ExecutionStatus.SUCCESS)
105
108
  except Exception as err:
106
109
  execution.complete(ExecutionStatus.FAILURE)
107
- self.exception_handler(err, execution)
108
- raise
110
+ exception = self.exception_handler(err, execution)
111
+ raise exception
109
112
  finally:
110
113
  self.post_execute(execution)
111
114
 
@@ -116,7 +119,7 @@ class SnowTyper(typer.Typer):
116
119
  return custom_command
117
120
 
118
121
  @staticmethod
119
- def pre_execute(execution: ExecutionMetadata):
122
+ def pre_execute(execution: ExecutionMetadata, require_warehouse: bool = False):
120
123
  """
121
124
  Callback executed before running any command callable (after context execution).
122
125
  Pay attention to make this method safe to use if performed operations are not necessary
@@ -125,8 +128,11 @@ class SnowTyper(typer.Typer):
125
128
  from snowflake.cli._app.telemetry import log_command_usage
126
129
 
127
130
  log.debug("Executing command pre execution callback")
128
- run_pre_execute_commands()
129
131
  log_command_usage(execution)
132
+ if require_warehouse and not SqlExecutionMixin().session_has_warehouse():
133
+ raise ClickException(
134
+ "The command requires warehouse. No warehouse found in current connection."
135
+ )
130
136
 
131
137
  @staticmethod
132
138
  def process_result(result):
@@ -150,6 +156,9 @@ class SnowTyper(typer.Typer):
150
156
 
151
157
  log.debug("Executing command exception callback")
152
158
  log_command_execution_error(exception, execution)
159
+ if isinstance(exception, DatabaseError):
160
+ return ClickException(exception.msg)
161
+ return exception
153
162
 
154
163
  @staticmethod
155
164
  def post_execute(execution: ExecutionMetadata):
@@ -30,7 +30,10 @@ from snowflake.cli.api.exceptions import (
30
30
  UnsupportedConfigSectionTypeError,
31
31
  )
32
32
  from snowflake.cli.api.secure_path import SecurePath
33
- from snowflake.cli.api.secure_utils import file_permissions_are_strict
33
+ from snowflake.cli.api.secure_utils import (
34
+ file_permissions_are_strict,
35
+ windows_get_not_whitelisted_users_with_access,
36
+ )
34
37
  from snowflake.cli.api.utils.types import try_cast_to_bool
35
38
  from snowflake.connector.compat import IS_WINDOWS
36
39
  from snowflake.connector.config_manager import CONFIG_MANAGER
@@ -77,7 +80,7 @@ class ConnectionConfig:
77
80
  warehouse: Optional[str] = None
78
81
  role: Optional[str] = None
79
82
  authenticator: Optional[str] = None
80
- private_key_path: Optional[str] = None
83
+ private_key_file: Optional[str] = None
81
84
  token_file_path: Optional[str] = None
82
85
 
83
86
  _other_settings: dict = field(default_factory=lambda: {})
@@ -160,6 +163,18 @@ def _read_config_file():
160
163
  message="Bad owner or permissions.*",
161
164
  module="snowflake.connector.config_manager",
162
165
  )
166
+
167
+ if not file_permissions_are_strict(CONFIG_MANAGER.file_path):
168
+ users = ", ".join(
169
+ windows_get_not_whitelisted_users_with_access(
170
+ CONFIG_MANAGER.file_path
171
+ )
172
+ )
173
+ warnings.warn(
174
+ f"Unauthorized users ({users}) have access to configuration file {CONFIG_MANAGER.file_path}.\n"
175
+ f'Run `icacls "{CONFIG_MANAGER.file_path}" /deny <USER_ID>:F` on those users to restrict permissions.'
176
+ )
177
+
163
178
  try:
164
179
  CONFIG_MANAGER.read_config()
165
180
  except ConfigSourceError as exception:
@@ -281,7 +296,7 @@ def _initialise_config(config_file: Path) -> None:
281
296
 
282
297
 
283
298
  def get_env_variable_name(*path, key: str) -> str:
284
- return "SNOWFLAKE_" + "_".join(p.upper() for p in path) + f"_{key.upper()}"
299
+ return ("_".join(["snowflake", *path, key])).upper()
285
300
 
286
301
 
287
302
  def get_env_value(*path, key: str) -> str | None:
@@ -322,8 +337,6 @@ def _dump_config(conf_file_cache: Dict):
322
337
 
323
338
 
324
339
  def _check_default_config_files_permissions() -> None:
325
- if IS_WINDOWS:
326
- return
327
340
  if CONNECTIONS_FILE.exists() and not file_permissions_are_strict(CONNECTIONS_FILE):
328
341
  raise ConfigFileTooWidePermissionsError(CONNECTIONS_FILE)
329
342
  if CONFIG_FILE.exists() and not file_permissions_are_strict(CONFIG_FILE):