snowflake-cli-labs 3.0.0rc0__py3-none-any.whl → 3.0.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 (51) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/_app/snow_connector.py +18 -11
  3. snowflake/cli/_plugins/connection/commands.py +3 -2
  4. snowflake/cli/_plugins/git/manager.py +14 -6
  5. snowflake/cli/_plugins/nativeapp/codegen/compiler.py +18 -2
  6. snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +123 -42
  7. snowflake/cli/_plugins/nativeapp/codegen/setup/setup_driver.py.source +5 -2
  8. snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +4 -6
  9. snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +93 -0
  10. snowflake/cli/_plugins/nativeapp/exceptions.py +3 -3
  11. snowflake/cli/_plugins/nativeapp/manager.py +29 -58
  12. snowflake/cli/_plugins/nativeapp/project_model.py +2 -9
  13. snowflake/cli/_plugins/nativeapp/teardown_processor.py +19 -105
  14. snowflake/cli/_plugins/snowpark/commands.py +5 -65
  15. snowflake/cli/_plugins/snowpark/common.py +17 -1
  16. snowflake/cli/_plugins/snowpark/package/anaconda_packages.py +1 -35
  17. snowflake/cli/_plugins/sql/commands.py +1 -2
  18. snowflake/cli/_plugins/stage/commands.py +2 -2
  19. snowflake/cli/_plugins/stage/manager.py +46 -15
  20. snowflake/cli/_plugins/streamlit/commands.py +4 -63
  21. snowflake/cli/_plugins/streamlit/manager.py +4 -0
  22. snowflake/cli/_plugins/workspace/action_context.py +6 -0
  23. snowflake/cli/_plugins/workspace/commands.py +103 -22
  24. snowflake/cli/_plugins/workspace/manager.py +20 -4
  25. snowflake/cli/api/cli_global_context.py +6 -6
  26. snowflake/cli/api/commands/decorators.py +1 -1
  27. snowflake/cli/api/commands/flags.py +31 -12
  28. snowflake/cli/api/commands/snow_typer.py +9 -2
  29. snowflake/cli/api/config.py +17 -4
  30. snowflake/cli/api/constants.py +11 -0
  31. snowflake/cli/api/entities/application_package_entity.py +296 -3
  32. snowflake/cli/api/entities/common.py +6 -2
  33. snowflake/cli/api/entities/utils.py +46 -10
  34. snowflake/cli/api/exceptions.py +12 -2
  35. snowflake/cli/api/feature_flags.py +0 -2
  36. snowflake/cli/api/project/definition.py +24 -1
  37. snowflake/cli/api/project/definition_conversion.py +194 -0
  38. snowflake/cli/api/project/schemas/entities/application_package_entity_model.py +17 -0
  39. snowflake/cli/api/project/schemas/project_definition.py +1 -4
  40. snowflake/cli/api/rendering/jinja.py +2 -16
  41. snowflake/cli/api/rendering/project_definition_templates.py +1 -1
  42. snowflake/cli/api/rendering/sql_templates.py +7 -4
  43. snowflake/cli/api/secure_path.py +13 -18
  44. snowflake/cli/api/secure_utils.py +90 -1
  45. snowflake/cli/api/sql_execution.py +13 -0
  46. snowflake/cli/api/utils/definition_rendering.py +4 -6
  47. {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc1.dist-info}/METADATA +5 -5
  48. {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc1.dist-info}/RECORD +51 -49
  49. {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc1.dist-info}/WHEEL +0 -0
  50. {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc1.dist-info}/entry_points.txt +0 -0
  51. {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc1.dist-info}/licenses/LICENSE +0 -0
@@ -46,13 +46,12 @@ from snowflake.cli.api.output.types import (
46
46
  MessageResult,
47
47
  SingleQueryResult,
48
48
  )
49
+ from snowflake.cli.api.project.definition_conversion import (
50
+ convert_project_definition_to_v2,
51
+ )
49
52
  from snowflake.cli.api.project.schemas.entities.streamlit_entity_model import (
50
53
  StreamlitEntityModel,
51
54
  )
52
- from snowflake.cli.api.project.schemas.project_definition import (
53
- ProjectDefinition,
54
- ProjectDefinitionV2,
55
- )
56
55
 
57
56
  app = SnowTyperFactory(
58
57
  name="streamlit",
@@ -139,7 +138,7 @@ def streamlit_deploy(
139
138
  raise NoProjectDefinitionError(
140
139
  project_type="streamlit", project_root=cli_context.project_root
141
140
  )
142
- pd = migrate_v1_streamlit_to_v2(pd)
141
+ pd = convert_project_definition_to_v2(pd)
143
142
 
144
143
  streamlits: Dict[str, StreamlitEntityModel] = pd.get_entities_by_type(
145
144
  entity_type="streamlit"
@@ -171,64 +170,6 @@ def streamlit_deploy(
171
170
  return MessageResult(f"Streamlit successfully deployed and available under {url}")
172
171
 
173
172
 
174
- def migrate_v1_streamlit_to_v2(pd: ProjectDefinition):
175
- default_env_file = "environment.yml"
176
- default_pages_dir = "pages"
177
-
178
- # Process env file
179
- environment_file = pd.streamlit.env_file
180
- if environment_file and not Path(environment_file).exists():
181
- raise ClickException(f"Provided file {environment_file} does not exist")
182
- elif environment_file is None and Path(default_env_file).exists():
183
- environment_file = default_env_file
184
- # Process pages dir
185
- pages_dir = pd.streamlit.pages_dir
186
- if pages_dir and not Path(pages_dir).exists():
187
- raise ClickException(f"Provided file {pages_dir} does not exist")
188
- elif pages_dir is None and Path(default_pages_dir).exists():
189
- pages_dir = default_pages_dir
190
-
191
- # Build V2 definition
192
- artifacts = [
193
- pd.streamlit.main_file,
194
- environment_file,
195
- pages_dir,
196
- ]
197
- artifacts = [a for a in artifacts if a is not None]
198
- if pd.streamlit.additional_source_files:
199
- artifacts.extend(pd.streamlit.additional_source_files)
200
-
201
- identifier = {"name": pd.streamlit.name}
202
- if pd.streamlit.schema_name:
203
- identifier["schema"] = pd.streamlit.schema_name
204
- if pd.streamlit.database:
205
- identifier["database"] = pd.streamlit.database
206
-
207
- if pd.streamlit.name.startswith("<%") and pd.streamlit.name.endswith("%>"):
208
- streamlit_name = "streamlit_entity_1"
209
- else:
210
- streamlit_name = pd.streamlit.name
211
-
212
- data = {
213
- "definition_version": "2",
214
- "entities": {
215
- streamlit_name: {
216
- "type": "streamlit",
217
- "identifier": identifier,
218
- "title": pd.streamlit.title,
219
- "query_warehouse": pd.streamlit.query_warehouse,
220
- "main_file": str(pd.streamlit.main_file),
221
- "pages_dir": str(pd.streamlit.pages_dir),
222
- "stage": pd.streamlit.stage,
223
- "artifacts": artifacts,
224
- }
225
- },
226
- }
227
- if hasattr(pd, "env") and pd.env:
228
- data["env"] = {k: v for k, v in pd.env.items()}
229
- return ProjectDefinitionV2(**data)
230
-
231
-
232
173
  @app.command("get-url", requires_connection=True)
233
174
  def get_url(
234
175
  name: FQN = StreamlitNameArgument,
@@ -57,6 +57,10 @@ class StreamlitManager(SqlExecutionMixin):
57
57
  stage_manager = StageManager()
58
58
  for file in artifacts:
59
59
  if file.is_dir():
60
+ if not any(file.iterdir()):
61
+ cli_console.warning(f"Skipping empty directory: {file}")
62
+ continue
63
+
60
64
  stage_manager.put(
61
65
  f"{file.joinpath('*')}", f"{root_location}/{file}", 4, True
62
66
  )
@@ -1,5 +1,8 @@
1
1
  from dataclasses import dataclass
2
2
  from pathlib import Path
3
+ from typing import Optional
4
+
5
+ from snowflake.cli.api.console.abc import AbstractConsole
3
6
 
4
7
 
5
8
  @dataclass
@@ -8,4 +11,7 @@ class ActionContext:
8
11
  An object that is passed to each action when called by WorkspaceManager
9
12
  """
10
13
 
14
+ console: AbstractConsole
11
15
  project_root: Path
16
+ default_role: str
17
+ default_warehouse: Optional[str]
@@ -15,19 +15,27 @@
15
15
  from __future__ import annotations
16
16
 
17
17
  import logging
18
+ from pathlib import Path
19
+ from textwrap import dedent
20
+ from typing import List, Optional
18
21
 
19
22
  import typer
20
23
  import yaml
21
- from click import ClickException
22
24
  from snowflake.cli._plugins.nativeapp.artifacts import BundleMap
23
- from snowflake.cli._plugins.snowpark.commands import migrate_v1_snowpark_to_v2
24
- from snowflake.cli._plugins.streamlit.commands import migrate_v1_streamlit_to_v2
25
+ from snowflake.cli._plugins.nativeapp.common_flags import (
26
+ ForceOption,
27
+ ValidateOption,
28
+ )
25
29
  from snowflake.cli._plugins.workspace.manager import WorkspaceManager
26
30
  from snowflake.cli.api.cli_global_context import get_cli_context
27
31
  from snowflake.cli.api.commands.decorators import with_project_definition
28
32
  from snowflake.cli.api.commands.snow_typer import SnowTyper
29
33
  from snowflake.cli.api.entities.common import EntityActions
34
+ from snowflake.cli.api.exceptions import IncompatibleParametersError
30
35
  from snowflake.cli.api.output.types import MessageResult
36
+ from snowflake.cli.api.project.definition_conversion import (
37
+ convert_project_definition_to_v2,
38
+ )
31
39
  from snowflake.cli.api.project.definition_manager import DefinitionManager
32
40
  from snowflake.cli.api.secure_path import SecurePath
33
41
 
@@ -45,30 +53,13 @@ def migrate(
45
53
  ),
46
54
  **options,
47
55
  ):
48
- """Migrates the Snowpark and Streamlit project definition files form V1 to V2."""
56
+ """Migrates the Snowpark and Streamlit project definition files from V1 to V2."""
49
57
  pd = DefinitionManager().unrendered_project_definition
50
58
 
51
59
  if pd.meets_version_requirement("2"):
52
60
  return MessageResult("Project definition is already at version 2.")
53
61
 
54
- if "<% ctx." in str(pd):
55
- if not accept_templates:
56
- raise ClickException(
57
- "Project definition contains templates. They may not be migrated correctly, and require manual migration."
58
- "You can try again with --accept-templates option, to attempt automatic migration."
59
- )
60
- log.warning(
61
- "Your V1 definition contains templates. We cannot guarantee the correctness of the migration."
62
- )
63
-
64
- if pd.streamlit:
65
- pd_v2 = migrate_v1_streamlit_to_v2(pd)
66
- elif pd.snowpark:
67
- pd_v2 = migrate_v1_snowpark_to_v2(pd)
68
- else:
69
- raise ValueError(
70
- "Only Snowpark and Streamlit entities are supported for migration."
71
- )
62
+ pd_v2 = convert_project_definition_to_v2(pd, accept_templates)
72
63
 
73
64
  SecurePath("snowflake.yml").rename("snowflake_V1.yml")
74
65
  with open("snowflake.yml", "w") as file:
@@ -111,3 +102,93 @@ def bundle(
111
102
 
112
103
  bundle_map: BundleMap = ws.perform_action(entity_id, EntityActions.BUNDLE)
113
104
  return MessageResult(f"Bundle generated at {bundle_map.deploy_root()}")
105
+
106
+
107
+ @ws.command(requires_connection=True)
108
+ @with_project_definition()
109
+ def deploy(
110
+ entity_id: str = typer.Option(
111
+ help=f"""The ID of the entity you want to deploy.""",
112
+ ),
113
+ # TODO The following options should be generated automatically, depending on the specified entity type
114
+ prune: Optional[bool] = typer.Option(
115
+ default=None,
116
+ help=f"""Whether to delete specified files from the stage if they don't exist locally. If set, the command deletes files that exist in the stage, but not in the local filesystem. This option cannot be used when paths are specified.""",
117
+ ),
118
+ recursive: Optional[bool] = typer.Option(
119
+ None,
120
+ "--recursive/--no-recursive",
121
+ "-r",
122
+ help=f"""Whether to traverse and deploy files from subdirectories. If set, the command deploys all files and subdirectories; otherwise, only files in the current directory are deployed.""",
123
+ ),
124
+ paths: Optional[List[Path]] = typer.Argument(
125
+ default=None,
126
+ show_default=False,
127
+ help=dedent(
128
+ f"""
129
+ Paths, relative to the the project root, of files or directories you want to upload to a stage. If a file is
130
+ specified, it must match one of the artifacts src pattern entries in snowflake.yml. If a directory is
131
+ specified, it will be searched for subfolders or files to deploy based on artifacts src pattern entries. If
132
+ unspecified, the command syncs all local changes to the stage."""
133
+ ).strip(),
134
+ ),
135
+ validate: bool = ValidateOption,
136
+ **options,
137
+ ):
138
+ """
139
+ Deploys the specified entity.
140
+ """
141
+ if prune is None and recursive is None and not paths:
142
+ prune = True
143
+ recursive = True
144
+ else:
145
+ if prune is None:
146
+ prune = False
147
+ if recursive is None:
148
+ recursive = False
149
+
150
+ if paths and prune:
151
+ raise IncompatibleParametersError(["paths", "--prune"])
152
+
153
+ cli_context = get_cli_context()
154
+ ws = WorkspaceManager(
155
+ project_definition=cli_context.project_definition,
156
+ project_root=cli_context.project_root,
157
+ )
158
+
159
+ ws.perform_action(
160
+ entity_id,
161
+ EntityActions.DEPLOY,
162
+ prune=prune,
163
+ recursive=recursive,
164
+ paths=paths,
165
+ validate=validate,
166
+ )
167
+ return MessageResult("Deployed successfully.")
168
+
169
+
170
+ @ws.command(requires_connection=True)
171
+ @with_project_definition()
172
+ def drop(
173
+ entity_id: str = typer.Option(
174
+ help=f"""The ID of the entity you want to drop.""",
175
+ ),
176
+ # TODO The following options should be generated automatically, depending on the specified entity type
177
+ force: Optional[bool] = ForceOption,
178
+ **options,
179
+ ):
180
+ """
181
+ Drops the specified entity.
182
+ """
183
+
184
+ cli_context = get_cli_context()
185
+ ws = WorkspaceManager(
186
+ project_definition=cli_context.project_definition,
187
+ project_root=cli_context.project_root,
188
+ )
189
+
190
+ ws.perform_action(
191
+ entity_id,
192
+ EntityActions.DROP,
193
+ force_drop=force,
194
+ )
@@ -2,8 +2,11 @@ from pathlib import Path
2
2
  from typing import Dict
3
3
 
4
4
  from snowflake.cli._plugins.workspace.action_context import ActionContext
5
- from snowflake.cli.api.entities.common import EntityActions
5
+ from snowflake.cli.api.cli_global_context import get_cli_context
6
+ from snowflake.cli.api.console import cli_console as cc
7
+ from snowflake.cli.api.entities.common import EntityActions, get_sql_executor
6
8
  from snowflake.cli.api.exceptions import InvalidProjectDefinitionVersionError
9
+ from snowflake.cli.api.project.definition import default_role
7
10
  from snowflake.cli.api.project.schemas.entities.entities import (
8
11
  Entity,
9
12
  v2_entity_model_to_entity_map,
@@ -12,6 +15,7 @@ from snowflake.cli.api.project.schemas.project_definition import (
12
15
  DefinitionV20,
13
16
  ProjectDefinition,
14
17
  )
18
+ from snowflake.cli.api.project.util import to_identifier
15
19
 
16
20
 
17
21
  class WorkspaceManager:
@@ -27,6 +31,13 @@ class WorkspaceManager:
27
31
  self._entities_cache: Dict[str, Entity] = {}
28
32
  self._project_definition: DefinitionV20 = project_definition
29
33
  self._project_root = project_root
34
+ self._default_role = default_role()
35
+ if self._default_role is None:
36
+ self._default_role = get_sql_executor().current_role()
37
+ self.default_warehouse = None
38
+ cli_context = get_cli_context()
39
+ if cli_context.connection.warehouse:
40
+ self.default_warehouse = to_identifier(cli_context.connection.warehouse)
30
41
 
31
42
  def get_entity(self, entity_id: str):
32
43
  """
@@ -42,14 +53,19 @@ class WorkspaceManager:
42
53
  self._entities_cache[entity_id] = entity_cls(entity_model)
43
54
  return self._entities_cache[entity_id]
44
55
 
45
- def perform_action(self, entity_id: str, action: EntityActions):
56
+ def perform_action(self, entity_id: str, action: EntityActions, *args, **kwargs):
46
57
  """
47
58
  Instantiates an entity of the given ID and calls the given action on it.
48
59
  """
49
60
  entity = self.get_entity(entity_id)
50
61
  if entity.supports(action):
51
- action_ctx = ActionContext(project_root=self.project_root())
52
- return entity.perform(action, action_ctx)
62
+ action_ctx = ActionContext(
63
+ console=cc,
64
+ project_root=self.project_root(),
65
+ default_role=self._default_role,
66
+ default_warehouse=self.default_warehouse,
67
+ )
68
+ return entity.perform(action, action_ctx, *args, **kwargs)
53
69
  else:
54
70
  raise ValueError(f'This entity type does not support "{action.value}"')
55
71
 
@@ -42,7 +42,7 @@ class _ConnectionContext:
42
42
  self._user: Optional[str] = None
43
43
  self._password: Optional[str] = None
44
44
  self._authenticator: Optional[str] = None
45
- self._private_key_path: Optional[str] = None
45
+ self._private_key_file: Optional[str] = None
46
46
  self._warehouse: Optional[str] = None
47
47
  self._mfa_passcode: Optional[str] = None
48
48
  self._enable_diag: Optional[bool] = False
@@ -125,11 +125,11 @@ class _ConnectionContext:
125
125
  self._authenticator = value
126
126
 
127
127
  @property
128
- def private_key_path(self) -> Optional[str]:
129
- return self._private_key_path
128
+ def private_key_file(self) -> Optional[str]:
129
+ return self._private_key_file
130
130
 
131
- def set_private_key_path(self, value: Optional[str]):
132
- self._private_key_path = value
131
+ def set_private_key_file(self, value: Optional[str]):
132
+ self._private_key_file = value
133
133
 
134
134
  @property
135
135
  def warehouse(self) -> Optional[str]:
@@ -206,7 +206,7 @@ class _ConnectionContext:
206
206
  "user": self.user,
207
207
  "password": self.password,
208
208
  "authenticator": self.authenticator,
209
- "private_key_path": self.private_key_path,
209
+ "private_key_file": self.private_key_file,
210
210
  "database": self.database,
211
211
  "schema": self.schema,
212
212
  "role": self.role,
@@ -235,7 +235,7 @@ GLOBAL_CONNECTION_OPTIONS = [
235
235
  default=AuthenticatorOption,
236
236
  ),
237
237
  inspect.Parameter(
238
- "private_key_path",
238
+ "private_key_file",
239
239
  inspect.Parameter.KEYWORD_ONLY,
240
240
  annotation=Optional[str],
241
241
  default=PrivateKeyPathOption,
@@ -134,11 +134,12 @@ AuthenticatorOption = typer.Option(
134
134
 
135
135
  PrivateKeyPathOption = typer.Option(
136
136
  None,
137
+ "--private-key-file",
137
138
  "--private-key-path",
138
- help="Snowflake private key path. Overrides the value specified for the connection.",
139
+ help="Snowflake private key file path. Overrides the value specified for the connection.",
139
140
  hide_input=True,
140
141
  callback=_callback(
141
- lambda: get_cli_context_manager().connection_context.set_private_key_path
142
+ lambda: get_cli_context_manager().connection_context.set_private_key_file
142
143
  ),
143
144
  show_default=False,
144
145
  rich_help_panel=_CONNECTION_SECTION,
@@ -440,18 +441,43 @@ def experimental_option(
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
  ...,
@@ -506,7 +532,7 @@ def project_env_overrides_option():
506
532
  return typer.Option(
507
533
  [],
508
534
  "--env",
509
- help="String in format of key=value. Overrides variables from env section used for templating.",
535
+ help="String in format of key=value. Overrides variables from env section used for templates.",
510
536
  callback=_callback(lambda: project_env_overrides_callback),
511
537
  show_default=False,
512
538
  )
@@ -530,10 +556,3 @@ def deprecated_flag_callback_enum(msg: str):
530
556
  return value.value
531
557
 
532
558
  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,
@@ -33,6 +34,7 @@ from snowflake.cli.api.commands.typer_pre_execute import run_pre_execute_command
33
34
  from snowflake.cli.api.exceptions import CommandReturnTypeError
34
35
  from snowflake.cli.api.output.types import CommandResult
35
36
  from snowflake.cli.api.sanitizers import sanitize_for_terminal
37
+ from snowflake.cli.api.sql_execution import SqlExecutionMixin
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,7 +100,7 @@ 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)
@@ -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
@@ -127,6 +130,10 @@ class SnowTyper(typer.Typer):
127
130
  log.debug("Executing command pre execution callback")
128
131
  run_pre_execute_commands()
129
132
  log_command_usage(execution)
133
+ if require_warehouse and not SqlExecutionMixin().session_has_warehouse():
134
+ raise ClickException(
135
+ "The command requires warehouse. No warehouse found in current connection."
136
+ )
130
137
 
131
138
  @staticmethod
132
139
  def process_result(result):
@@ -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:
@@ -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):
@@ -77,3 +77,14 @@ VALID_SCOPES = ["database", "schema", "compute-pool"]
77
77
  DEFAULT_SIZE_LIMIT_MB = 128
78
78
 
79
79
  SF_REST_API_URL_PREFIX = "/api/v2"
80
+
81
+ PROJECT_TEMPLATE_VARIABLE_OPENING = "<%"
82
+ PROJECT_TEMPLATE_VARIABLE_CLOSING = "%>"
83
+
84
+ INIT_TEMPLATE_VARIABLE_OPENING = "<!"
85
+ INIT_TEMPLATE_VARIABLE_CLOSING = "!>"
86
+
87
+ SNOWPARK_SHARED_MIXIN = "snowpark_shared"
88
+
89
+ DEFAULT_ENV_FILE = "environment.yml"
90
+ DEFAULT_PAGES_DIR = "pages"