snowflake-cli-labs 2.5.0rc3__py3-none-any.whl → 2.6.0.dev0__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 (67) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/api/cli_global_context.py +31 -3
  3. snowflake/cli/api/commands/decorators.py +21 -6
  4. snowflake/cli/api/commands/flags.py +60 -51
  5. snowflake/cli/api/commands/snow_typer.py +24 -0
  6. snowflake/cli/api/commands/typer_pre_execute.py +26 -0
  7. snowflake/cli/api/console/abc.py +8 -0
  8. snowflake/cli/api/console/console.py +29 -4
  9. snowflake/cli/api/constants.py +3 -0
  10. snowflake/cli/api/project/definition.py +17 -35
  11. snowflake/cli/api/project/definition_manager.py +22 -19
  12. snowflake/cli/api/project/errors.py +9 -6
  13. snowflake/cli/api/project/schemas/identifier_model.py +1 -1
  14. snowflake/cli/api/project/schemas/native_app/application.py +15 -3
  15. snowflake/cli/api/project/schemas/native_app/native_app.py +5 -1
  16. snowflake/cli/api/project/schemas/native_app/path_mapping.py +14 -3
  17. snowflake/cli/api/project/schemas/project_definition.py +37 -6
  18. snowflake/cli/api/project/schemas/streamlit/streamlit.py +3 -0
  19. snowflake/cli/api/project/schemas/updatable_model.py +2 -6
  20. snowflake/cli/api/rest_api.py +113 -0
  21. snowflake/cli/api/sanitizers.py +43 -0
  22. snowflake/cli/api/sql_execution.py +7 -0
  23. snowflake/cli/api/utils/definition_rendering.py +95 -25
  24. snowflake/cli/api/utils/models.py +31 -26
  25. snowflake/cli/api/utils/rendering.py +24 -3
  26. snowflake/cli/app/cli_app.py +2 -0
  27. snowflake/cli/app/commands_registration/command_plugins_loader.py +8 -0
  28. snowflake/cli/app/dev/docs/commands_docs_generator.py +100 -0
  29. snowflake/cli/app/dev/docs/generator.py +8 -67
  30. snowflake/cli/app/dev/docs/project_definition_docs_generator.py +58 -0
  31. snowflake/cli/app/dev/docs/project_definition_generate_json_schema.py +227 -0
  32. snowflake/cli/app/dev/docs/template_utils.py +23 -0
  33. snowflake/cli/app/dev/docs/templates/definition_description.rst.jinja2 +38 -0
  34. snowflake/cli/app/dev/docs/templates/usage.rst.jinja2 +6 -1
  35. snowflake/cli/app/loggers.py +25 -0
  36. snowflake/cli/app/printing.py +7 -5
  37. snowflake/cli/app/telemetry.py +11 -0
  38. snowflake/cli/plugins/nativeapp/artifacts.py +78 -9
  39. snowflake/cli/plugins/nativeapp/codegen/artifact_processor.py +3 -11
  40. snowflake/cli/plugins/nativeapp/codegen/compiler.py +6 -24
  41. snowflake/cli/plugins/nativeapp/codegen/snowpark/python_processor.py +27 -27
  42. snowflake/cli/plugins/nativeapp/commands.py +23 -12
  43. snowflake/cli/plugins/nativeapp/constants.py +2 -0
  44. snowflake/cli/plugins/nativeapp/errno.py +15 -0
  45. snowflake/cli/plugins/nativeapp/feature_flags.py +24 -0
  46. snowflake/cli/plugins/nativeapp/init.py +5 -0
  47. snowflake/cli/plugins/nativeapp/manager.py +101 -103
  48. snowflake/cli/plugins/nativeapp/project_model.py +181 -0
  49. snowflake/cli/plugins/nativeapp/run_processor.py +178 -110
  50. snowflake/cli/plugins/nativeapp/teardown_processor.py +89 -64
  51. snowflake/cli/plugins/nativeapp/utils.py +2 -2
  52. snowflake/cli/plugins/nativeapp/version/commands.py +3 -3
  53. snowflake/cli/plugins/object/commands.py +69 -3
  54. snowflake/cli/plugins/object/manager.py +44 -3
  55. snowflake/cli/plugins/snowpark/commands.py +2 -2
  56. snowflake/cli/plugins/sql/commands.py +2 -10
  57. snowflake/cli/plugins/sql/manager.py +4 -2
  58. snowflake/cli/plugins/stage/commands.py +23 -4
  59. snowflake/cli/plugins/stage/diff.py +81 -51
  60. snowflake/cli/plugins/stage/manager.py +2 -1
  61. snowflake/cli/plugins/streamlit/commands.py +2 -1
  62. snowflake/cli/plugins/streamlit/manager.py +6 -0
  63. {snowflake_cli_labs-2.5.0rc3.dist-info → snowflake_cli_labs-2.6.0.dev0.dist-info}/METADATA +15 -9
  64. {snowflake_cli_labs-2.5.0rc3.dist-info → snowflake_cli_labs-2.6.0.dev0.dist-info}/RECORD +67 -56
  65. {snowflake_cli_labs-2.5.0rc3.dist-info → snowflake_cli_labs-2.6.0.dev0.dist-info}/WHEEL +1 -1
  66. {snowflake_cli_labs-2.5.0rc3.dist-info → snowflake_cli_labs-2.6.0.dev0.dist-info}/entry_points.txt +0 -0
  67. {snowflake_cli_labs-2.5.0rc3.dist-info → snowflake_cli_labs-2.6.0.dev0.dist-info}/licenses/LICENSE +0 -0
@@ -14,4 +14,4 @@
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
- VERSION = "2.5.0rc3"
17
+ VERSION = "2.6.0.dev0"
@@ -16,7 +16,7 @@ from __future__ import annotations
16
16
 
17
17
  import re
18
18
  from pathlib import Path
19
- from typing import Dict, Optional
19
+ from typing import Callable, Optional
20
20
 
21
21
  from snowflake.cli.api.exceptions import InvalidSchemaError
22
22
  from snowflake.cli.api.output.formats import OutputFormat
@@ -226,6 +226,9 @@ class _CliGlobalContextManager:
226
226
  self._experimental = False
227
227
  self._project_definition = None
228
228
  self._project_root = None
229
+ self._project_path_arg = None
230
+ self._project_env_overrides_args = {}
231
+ self._typer_pre_execute_commands = []
229
232
  self._template_context = None
230
233
  self._silent: bool = False
231
234
 
@@ -261,10 +264,10 @@ class _CliGlobalContextManager:
261
264
  self._experimental = value
262
265
 
263
266
  @property
264
- def project_definition(self) -> Optional[Dict]:
267
+ def project_definition(self) -> Optional[ProjectDefinition]:
265
268
  return self._project_definition
266
269
 
267
- def set_project_definition(self, value: Dict):
270
+ def set_project_definition(self, value: ProjectDefinition):
268
271
  self._project_definition = value
269
272
 
270
273
  @property
@@ -274,6 +277,22 @@ class _CliGlobalContextManager:
274
277
  def set_project_root(self, project_root: Path):
275
278
  self._project_root = project_root
276
279
 
280
+ @property
281
+ def project_path_arg(self) -> Optional[str]:
282
+ return self._project_path_arg
283
+
284
+ def set_project_path_arg(self, project_path_arg: str):
285
+ self._project_path_arg = project_path_arg
286
+
287
+ @property
288
+ def project_env_overrides_args(self) -> dict[str, str]:
289
+ return self._project_env_overrides_args
290
+
291
+ def set_project_env_overrides_args(
292
+ self, project_env_overrides_args: dict[str, str]
293
+ ):
294
+ self._project_env_overrides_args = project_env_overrides_args
295
+
277
296
  @property
278
297
  def template_context(self) -> dict:
279
298
  return self._template_context
@@ -281,6 +300,15 @@ class _CliGlobalContextManager:
281
300
  def set_template_context(self, template_context: dict):
282
301
  self._template_context = template_context
283
302
 
303
+ @property
304
+ def typer_pre_execute_commands(self) -> list[Callable[[], None]]:
305
+ return self._typer_pre_execute_commands
306
+
307
+ def add_typer_pre_execute_commands(
308
+ self, typer_pre_execute_command: Callable[[], None]
309
+ ):
310
+ self._typer_pre_execute_commands.append(typer_pre_execute_command)
311
+
284
312
  @property
285
313
  def connection_context(self) -> _ConnectionContext:
286
314
  return self._connection_context
@@ -43,7 +43,8 @@ from snowflake.cli.api.commands.flags import (
43
43
  VerboseOption,
44
44
  WarehouseOption,
45
45
  experimental_option,
46
- project_type_option,
46
+ project_definition_option,
47
+ project_env_overrides_option,
47
48
  )
48
49
  from snowflake.cli.api.exceptions import CommandReturnTypeError
49
50
  from snowflake.cli.api.output.formats import OutputFormat
@@ -72,8 +73,11 @@ def global_options_with_connection(func: Callable):
72
73
  )
73
74
 
74
75
 
75
- def with_project_definition(project_name: str):
76
+ def with_project_definition(
77
+ project_name: Optional[str] = None, is_optional: bool = False
78
+ ):
76
79
  def _decorator(func: Callable):
80
+
77
81
  return _options_decorator_factory(
78
82
  func,
79
83
  additional_options=[
@@ -81,8 +85,14 @@ def with_project_definition(project_name: str):
81
85
  "project_definition",
82
86
  inspect.Parameter.KEYWORD_ONLY,
83
87
  annotation=Optional[str],
84
- default=project_type_option(project_name),
85
- )
88
+ default=project_definition_option(project_name, is_optional),
89
+ ),
90
+ inspect.Parameter(
91
+ "env_overrides",
92
+ inspect.Parameter.KEYWORD_ONLY,
93
+ annotation=List[str],
94
+ default=project_env_overrides_option(),
95
+ ),
86
96
  ],
87
97
  )
88
98
 
@@ -115,7 +125,7 @@ def with_experimental_behaviour(
115
125
  return decorator
116
126
 
117
127
 
118
- def _execute_before_command_using_global_options():
128
+ def _execute_before_command_using_global_options(**options):
119
129
  from snowflake.cli.app.loggers import create_loggers
120
130
 
121
131
  create_loggers(cli_context.verbose, cli_context.enable_tracebacks)
@@ -136,10 +146,15 @@ def _options_decorator_factory(
136
146
  additional_options: List[inspect.Parameter],
137
147
  execute_before_command_using_new_options: Optional[Callable] = None,
138
148
  ):
149
+ """
150
+ execute_before_command_using_new_options executes before command telemetry has been emitted,
151
+ but after command line options have been populated.
152
+ """
153
+
139
154
  @wraps(func)
140
155
  def wrapper(**options):
141
156
  if execute_before_command_using_new_options:
142
- execute_before_command_using_new_options()
157
+ execute_before_command_using_new_options(**options)
143
158
  return func(**options)
144
159
 
145
160
  wrapper.__signature__ = _extend_signature_with_additional_options(func, additional_options) # type: ignore
@@ -14,7 +14,6 @@
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
- import os
18
17
  import tempfile
19
18
  from dataclasses import dataclass
20
19
  from enum import Enum
@@ -26,9 +25,11 @@ import click
26
25
  import typer
27
26
  from click import ClickException
28
27
  from snowflake.cli.api.cli_global_context import cli_context_manager
28
+ from snowflake.cli.api.commands.typer_pre_execute import register_pre_execute_command
29
29
  from snowflake.cli.api.console import cli_console
30
- from snowflake.cli.api.exceptions import MissingConfiguration
30
+ from snowflake.cli.api.exceptions import MissingConfiguration, NoProjectDefinitionError
31
31
  from snowflake.cli.api.output.formats import OutputFormat
32
+ from snowflake.cli.api.project.definition_manager import DefinitionManager
32
33
  from snowflake.cli.api.utils.rendering import CONTEXT_KEY
33
34
 
34
35
  DEFAULT_CONTEXT_SETTINGS = {"help_option_names": ["--help", "-h"]}
@@ -499,70 +500,74 @@ def execution_identifier_argument(sf_object: str, example: str) -> typer.Argumen
499
500
  )
500
501
 
501
502
 
502
- def project_type_option(project_name: str):
503
- from snowflake.cli.api.exceptions import NoProjectDefinitionError
504
- from snowflake.cli.api.project.definition_manager import DefinitionManager
503
+ def register_project_definition(project_name: Optional[str], is_optional: bool) -> None:
504
+ project_path = cli_context_manager.project_path_arg
505
+ env_overrides_args = cli_context_manager.project_env_overrides_args
505
506
 
506
- def _callback(project_path: Optional[str]):
507
- dm = DefinitionManager(project_path)
508
- project_definition = getattr(dm.project_definition, project_name, None)
509
- project_root = dm.project_root
507
+ dm = DefinitionManager(project_path, {CONTEXT_KEY: {"env": env_overrides_args}})
508
+ project_definition = dm.project_definition
509
+ project_root = dm.project_root
510
+ template_context = dm.template_context
510
511
 
511
- if not project_definition:
512
- raise NoProjectDefinitionError(
513
- project_type=project_name, project_file=project_path
514
- )
512
+ if not dm.has_definition_file and not is_optional:
513
+ raise MissingConfiguration(
514
+ "Cannot find project definition (snowflake.yml). Please provide a path to the project or run this command in a valid project directory."
515
+ )
516
+
517
+ if project_name is not None and not getattr(project_definition, project_name, None):
518
+ raise NoProjectDefinitionError(
519
+ project_type=project_name, project_file=project_path
520
+ )
521
+
522
+ cli_context_manager.set_project_definition(project_definition)
523
+ cli_context_manager.set_project_root(project_root)
524
+ cli_context_manager.set_template_context(template_context)
515
525
 
516
- cli_context_manager.set_project_definition(project_definition)
517
- cli_context_manager.set_project_root(project_root)
518
- cli_context_manager.set_template_context(dm.template_context)
519
- return project_definition
520
526
 
521
- if project_name == "native_app":
522
- project_name_help = "Snowflake Native App"
523
- elif project_name == "streamlit":
524
- project_name_help = "Streamlit app"
527
+ def _get_project_long_name(project_short_name: Optional[str]) -> str:
528
+ if project_short_name is None:
529
+ return "Snowflake"
530
+
531
+ if project_short_name == "native_app":
532
+ project_long_name = "Snowflake Native App"
533
+ elif project_short_name == "streamlit":
534
+ project_long_name = "Streamlit app"
525
535
  else:
526
- project_name_help = project_name.replace("_", " ").capitalize()
536
+ project_long_name = project_short_name.replace("_", " ").capitalize()
537
+
538
+ return f"the {project_long_name}"
539
+
540
+
541
+ def project_definition_option(project_name: Optional[str], is_optional: bool):
542
+ def project_definition_callback(project_path: str) -> None:
543
+ cli_context_manager.set_project_path_arg(project_path)
544
+ register_pre_execute_command(
545
+ lambda: register_project_definition(project_name, is_optional)
546
+ )
527
547
 
528
548
  return typer.Option(
529
549
  None,
530
550
  "-p",
531
551
  "--project",
532
- help=f"Path where the {project_name_help} project resides. "
552
+ help=f"Path where {_get_project_long_name(project_name)} project resides. "
533
553
  f"Defaults to current working directory.",
534
- callback=_callback,
554
+ callback=_callback(lambda: project_definition_callback),
535
555
  show_default=False,
536
556
  )
537
557
 
538
558
 
539
- def project_definition_option(optional: bool = False):
540
- from snowflake.cli.api.project.definition_manager import DefinitionManager
541
-
542
- def _callback(project_path: Optional[str]):
543
- try:
544
- dm = DefinitionManager(project_path)
545
- project_definition = dm.project_definition
546
- project_root = dm.project_root
547
- template_context = dm.template_context
548
- except MissingConfiguration:
549
- if optional:
550
- project_definition = None
551
- project_root = None
552
- template_context = {CONTEXT_KEY: {"env": os.environ}}
553
- else:
554
- raise
555
- cli_context_manager.set_project_definition(project_definition)
556
- cli_context_manager.set_project_root(project_root)
557
- cli_context_manager.set_template_context(template_context)
558
- return project_definition
559
+ def project_env_overrides_option():
560
+ def project_env_overrides_callback(env_overrides_args_list: list[str]) -> None:
561
+ env_overrides_args_map = {
562
+ v.key: v.value for v in parse_key_value_variables(env_overrides_args_list)
563
+ }
564
+ cli_context_manager.set_project_env_overrides_args(env_overrides_args_map)
559
565
 
560
566
  return typer.Option(
561
- None,
562
- "-p",
563
- "--project",
564
- help=f"Path where Snowflake project resides. Defaults to current working directory.",
565
- callback=_callback,
567
+ [],
568
+ "--env",
569
+ help="String in format of key=value. Overrides variables from env section used for templating.",
570
+ callback=_callback(lambda: project_env_overrides_callback),
566
571
  show_default=False,
567
572
  )
568
573
 
@@ -610,9 +615,13 @@ class Variable:
610
615
  self.value = value
611
616
 
612
617
 
613
- def parse_key_value_variables(variables: List[str]) -> List[Variable]:
618
+ def parse_key_value_variables(variables: Optional[List[str]]) -> List[Variable]:
614
619
  """Util for parsing key=value input. Useful for commands accepting multiple input options."""
615
- result = []
620
+ result: List[Variable] = []
621
+
622
+ if variables is None:
623
+ return result
624
+
616
625
  for p in variables:
617
626
  if "=" not in p:
618
627
  raise ClickException(f"Invalid variable: '{p}'")
@@ -25,14 +25,17 @@ from snowflake.cli.api.commands.decorators import (
25
25
  global_options_with_connection,
26
26
  )
27
27
  from snowflake.cli.api.commands.flags import DEFAULT_CONTEXT_SETTINGS
28
+ from snowflake.cli.api.commands.typer_pre_execute import run_pre_execute_commands
28
29
  from snowflake.cli.api.exceptions import CommandReturnTypeError
29
30
  from snowflake.cli.api.output.types import CommandResult
31
+ from snowflake.cli.api.sanitizers import sanitize_for_terminal
30
32
 
31
33
  log = logging.getLogger(__name__)
32
34
 
33
35
 
34
36
  class SnowTyper(typer.Typer):
35
37
  def __init__(self, /, **kwargs):
38
+ self._sanitize_kwargs(kwargs)
36
39
  super().__init__(
37
40
  **kwargs,
38
41
  context_settings=DEFAULT_CONTEXT_SETTINGS,
@@ -41,6 +44,21 @@ class SnowTyper(typer.Typer):
41
44
  add_completion=True,
42
45
  )
43
46
 
47
+ @staticmethod
48
+ def _sanitize_kwargs(kwargs: Dict):
49
+ # Sanitize all string options that are visible in terminal output
50
+ known_keywords = [
51
+ "help",
52
+ "short_help",
53
+ "options_metavar",
54
+ "rich_help_panel",
55
+ "epilog",
56
+ ]
57
+ for kw in known_keywords:
58
+ if kw in kwargs:
59
+ kwargs[kw] = sanitize_for_terminal(kwargs[kw])
60
+ return kwargs
61
+
44
62
  @wraps(typer.Typer.command)
45
63
  def command(
46
64
  self,
@@ -55,11 +73,16 @@ class SnowTyper(typer.Typer):
55
73
  logic before and after execution as well as process the result and act on possible
56
74
  errors.
57
75
  """
76
+ name = sanitize_for_terminal(name)
77
+ self._sanitize_kwargs(kwargs)
58
78
  if is_enabled is not None and not is_enabled():
59
79
  return lambda func: func
60
80
 
61
81
  def custom_command(command_callable):
62
82
  """Custom command wrapper similar to Typer.command."""
83
+ # Sanitize doc string which is used to create help in terminal
84
+ command_callable.__doc__ = sanitize_for_terminal(command_callable.__doc__)
85
+
63
86
  if requires_connection:
64
87
  command_callable = global_options_with_connection(command_callable)
65
88
  elif requires_global_options:
@@ -94,6 +117,7 @@ class SnowTyper(typer.Typer):
94
117
  from snowflake.cli.app.telemetry import log_command_usage
95
118
 
96
119
  log.debug("Executing command pre execution callback")
120
+ run_pre_execute_commands()
97
121
  log_command_usage()
98
122
 
99
123
  @staticmethod
@@ -0,0 +1,26 @@
1
+ # Copyright (c) 2024 Snowflake Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from typing import Callable
16
+
17
+ from snowflake.cli.api.cli_global_context import cli_context_manager
18
+
19
+
20
+ def register_pre_execute_command(command: Callable[[], None]) -> None:
21
+ cli_context_manager.add_typer_pre_execute_commands(command)
22
+
23
+
24
+ def run_pre_execute_commands() -> None:
25
+ for command in cli_context_manager.typer_pre_execute_commands:
26
+ command()
@@ -66,6 +66,14 @@ class AbstractConsole(ABC):
66
66
  ) -> Iterator[Callable[[str], None]]:
67
67
  """A context manager for organising steps into logical group."""
68
68
 
69
+ @contextmanager
70
+ @abstractmethod
71
+ def indented(self):
72
+ """
73
+ A context manager for temporarily indenting messages and warnings. Phases and steps cannot be used in indented blocks,
74
+ but multiple indented blocks can be nested (use sparingly).
75
+ """
76
+
69
77
  @abstractmethod
70
78
  def step(self, message: str):
71
79
  """Displays a message to output."""
@@ -44,22 +44,27 @@ class CliConsole(AbstractConsole):
44
44
  """
45
45
 
46
46
  _indentation_level: int = INDENTATION_LEVEL
47
+ _extra_indent: int = 0
47
48
  _styles: dict = {
48
49
  "default": "",
49
50
  Output.PHASE: PHASE_STYLE,
50
51
  Output.STEP: STEP_STYLE,
51
- Output.INFO: INFO_STYLE,
52
+ Output.INFO: None,
52
53
  Output.IMPORTANT: IMPORTANT_STYLE,
53
54
  }
54
55
 
55
56
  def _format_message(self, message: str, output: Output) -> Text:
56
57
  """Wraps message in rich Text object and applies formatting."""
57
58
  style = self._styles.get(output, "default")
58
- text = Text(message, style=style)
59
+ if style is not None:
60
+ text = Text(message, style=style)
61
+ else:
62
+ text = Text.from_markup(message)
59
63
 
64
+ current_indent = self._extra_indent
60
65
  if self.in_phase and output in {Output.STEP, Output.INFO, Output.IMPORTANT}:
61
- text.pad_left(self._indentation_level)
62
-
66
+ current_indent += 1
67
+ text.pad_left(current_indent * self._indentation_level)
63
68
  return text
64
69
 
65
70
  @contextmanager
@@ -67,6 +72,10 @@ class CliConsole(AbstractConsole):
67
72
  """A context manager for organising steps into logical group."""
68
73
  if self.in_phase:
69
74
  raise CliConsoleNestingProhibitedError("Only one phase allowed at a time.")
75
+ if self._extra_indent > 0:
76
+ raise CliConsoleNestingProhibitedError(
77
+ "Phase cannot be used in an indented block."
78
+ )
70
79
 
71
80
  self._print(self._format_message(enter_message, Output.PHASE))
72
81
  self._in_phase = True
@@ -78,11 +87,27 @@ class CliConsole(AbstractConsole):
78
87
  if exit_message:
79
88
  self._print(self._format_message(exit_message, Output.PHASE))
80
89
 
90
+ @contextmanager
91
+ def indented(self):
92
+ """
93
+ A context manager for temporarily indenting messages and warnings. Phases and steps cannot be used in indented blocks,
94
+ but multiple indented blocks can be nested (use sparingly).
95
+ """
96
+ self._extra_indent += 1
97
+ try:
98
+ yield
99
+ finally:
100
+ self._extra_indent -= 1
101
+
81
102
  def step(self, message: str):
82
103
  """Displays a message to output.
83
104
 
84
105
  If called within a phase, the output will be indented.
85
106
  """
107
+ if self._extra_indent > 0:
108
+ raise CliConsoleNestingProhibitedError(
109
+ "Step cannot be used in an indented block."
110
+ )
86
111
  text = self._format_message(message, Output.STEP)
87
112
  self._print(text)
88
113
 
@@ -20,6 +20,7 @@ from pathlib import Path
20
20
 
21
21
  TEMPLATES_PATH = Path(__file__).parent.parent / "templates"
22
22
  DEPLOYMENT_STAGE = "deployments"
23
+ PYTHON_3_12 = (3, 12)
23
24
 
24
25
 
25
26
  @dataclass(frozen=True)
@@ -74,3 +75,5 @@ SUPPORTED_OBJECTS = sorted(OBJECT_TO_NAMES.keys())
74
75
  VALID_SCOPES = ["database", "schema", "compute-pool"]
75
76
 
76
77
  DEFAULT_SIZE_LIMIT_MB = 128
78
+
79
+ SF_REST_API_URL_PREFIX = "/api/v2"
@@ -14,14 +14,16 @@
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
- from dataclasses import dataclass
18
17
  from pathlib import Path
19
- from typing import List
18
+ from typing import List, Optional
20
19
 
21
- import yaml.loader
20
+ import yaml
22
21
  from snowflake.cli.api.cli_global_context import cli_context
23
22
  from snowflake.cli.api.constants import DEFAULT_SIZE_LIMIT_MB
24
- from snowflake.cli.api.project.schemas.project_definition import ProjectDefinition
23
+ from snowflake.cli.api.project.schemas.project_definition import (
24
+ ProjectDefinition,
25
+ ProjectProperties,
26
+ )
25
27
  from snowflake.cli.api.project.util import (
26
28
  append_to_identifier,
27
29
  clean_identifier,
@@ -31,61 +33,41 @@ from snowflake.cli.api.project.util import (
31
33
  from snowflake.cli.api.secure_path import SecurePath
32
34
  from snowflake.cli.api.utils.definition_rendering import render_definition_template
33
35
  from snowflake.cli.api.utils.dict_utils import deep_merge_dicts
34
- from snowflake.cli.api.utils.types import Definition
35
- from yaml import load
36
+ from snowflake.cli.api.utils.types import Context, Definition
36
37
 
37
38
  DEFAULT_USERNAME = "unknown_user"
38
39
 
39
40
 
40
- @dataclass
41
- class ProjectProperties:
42
- """
43
- This class stores 2 objects representing the project definition:
44
-
45
- The raw_project_definition object:
46
- - Only contains data structures like dict, list, and scalars.
47
- - The purpose of the raw_project_defintion object is to represent the same structure as the yaml project definition file.
48
- - This can be used as a templating context when users reference variables in the project definition file.
49
-
50
- The project_definition object:
51
- - This is a transformed object type through Pydantic, which has been normalized.
52
- - This object could have slightly different structure than what the users see in their yaml project definition files.
53
- - This should be used for the business logic of snow CLI modules.
54
- """
55
-
56
- project_definition: ProjectDefinition
57
- raw_project_definition: Definition
58
-
59
-
60
- def _get_merged_definitions(paths: List[Path]) -> Definition:
41
+ def _get_merged_definitions(paths: List[Path]) -> Optional[Definition]:
61
42
  spaths: List[SecurePath] = [SecurePath(p) for p in paths]
62
43
  if len(spaths) == 0:
63
- raise ValueError("Need at least one definition file.")
44
+ return None
64
45
 
65
46
  with spaths[0].open("r", read_file_limit_mb=DEFAULT_SIZE_LIMIT_MB) as base_yml:
66
- definition = load(base_yml.read(), Loader=yaml.loader.BaseLoader) or {}
47
+ definition = yaml.load(base_yml.read(), Loader=yaml.loader.BaseLoader) or {}
67
48
 
68
49
  for override_path in spaths[1:]:
69
50
  with override_path.open(
70
51
  "r", read_file_limit_mb=DEFAULT_SIZE_LIMIT_MB
71
52
  ) as override_yml:
72
- overrides = load(override_yml.read(), Loader=yaml.loader.BaseLoader) or {}
53
+ overrides = (
54
+ yaml.load(override_yml.read(), Loader=yaml.loader.BaseLoader) or {}
55
+ )
73
56
  deep_merge_dicts(definition, overrides)
74
57
 
75
58
  return definition
76
59
 
77
60
 
78
- def load_project(paths: List[Path]) -> ProjectProperties:
61
+ def load_project(
62
+ paths: List[Path], context_overrides: Optional[Context] = None
63
+ ) -> ProjectProperties:
79
64
  """
80
65
  Loads project definition, optionally overriding values. Definition values
81
66
  are merged in left-to-right order (increasing precedence).
82
67
  Templating is also applied after the merging process.
83
68
  """
84
69
  merged_definitions = _get_merged_definitions(paths)
85
- rendered_definition = render_definition_template(merged_definitions)
86
- return ProjectProperties(
87
- ProjectDefinition(**rendered_definition), rendered_definition
88
- )
70
+ return render_definition_template(merged_definitions, context_overrides or {})
89
71
 
90
72
 
91
73
  def generate_local_override_yml(
@@ -19,10 +19,8 @@ import os
19
19
  from pathlib import Path
20
20
  from typing import List, Optional
21
21
 
22
- from snowflake.cli.api.exceptions import MissingConfiguration
23
22
  from snowflake.cli.api.project.definition import ProjectProperties, load_project
24
23
  from snowflake.cli.api.project.schemas.project_definition import ProjectDefinition
25
- from snowflake.cli.api.utils.rendering import CONTEXT_KEY
26
24
  from snowflake.cli.api.utils.types import Context
27
25
 
28
26
 
@@ -42,32 +40,39 @@ class DefinitionManager:
42
40
  project_root: Path
43
41
  _project_config_paths: List[Path]
44
42
 
45
- def __init__(self, project_arg: Optional[str] = None) -> None:
43
+ def __init__(
44
+ self,
45
+ project_arg: Optional[str] = None,
46
+ context_overrides: Optional[Context] = None,
47
+ ) -> None:
46
48
  project_root = Path(
47
49
  os.path.abspath(project_arg) if project_arg else os.getcwd()
48
50
  )
49
- if not self._base_definition_file_if_available(project_root):
50
- raise MissingConfiguration(
51
- f"Cannot find project definition (snowflake.yml). Please provide a path to the project or run this command in a valid project directory."
52
- )
53
51
 
54
52
  self.project_root = project_root
55
53
  self._project_config_paths = self._find_definition_files(self.project_root)
54
+ self._context_overrides = context_overrides
55
+
56
+ @functools.cached_property
57
+ def has_definition_file(self):
58
+ return len(self._project_config_paths) > 0
56
59
 
57
60
  @staticmethod
58
61
  def _find_definition_files(project_root: Path) -> List[Path]:
59
- base_config_file_path = (
60
- project_root / DefinitionManager.BASE_DEFINITION_FILENAME
62
+ base_config_file_path = DefinitionManager._base_definition_file_if_available(
63
+ project_root
61
64
  )
62
65
  user_config_file_path = DefinitionManager._user_definition_file_if_available(
63
66
  project_root
64
67
  )
65
- if user_config_file_path:
66
- return [
67
- base_config_file_path,
68
- user_config_file_path,
69
- ]
70
- return [base_config_file_path]
68
+
69
+ definition_files: List[Path] = []
70
+ if base_config_file_path:
71
+ definition_files.append(base_config_file_path)
72
+ if user_config_file_path:
73
+ definition_files.append(user_config_file_path)
74
+
75
+ return definition_files
71
76
 
72
77
  @staticmethod
73
78
  def find_project_root(search_path: Path) -> Optional[Path]:
@@ -118,7 +123,7 @@ class DefinitionManager:
118
123
 
119
124
  @functools.cached_property
120
125
  def _project_properties(self) -> ProjectProperties:
121
- return load_project(self._project_config_paths)
126
+ return load_project(self._project_config_paths, self._context_overrides)
122
127
 
123
128
  @functools.cached_property
124
129
  def project_definition(self) -> ProjectDefinition:
@@ -126,6 +131,4 @@ class DefinitionManager:
126
131
 
127
132
  @functools.cached_property
128
133
  def template_context(self) -> Context:
129
- definition = self._project_properties.raw_project_definition
130
-
131
- return {CONTEXT_KEY: definition}
134
+ return self._project_properties.project_context