snowflake-cli-labs 2.5.0rc3__py3-none-any.whl → 2.6.0__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.
- snowflake/cli/__about__.py +1 -1
- snowflake/cli/api/cli_global_context.py +31 -3
- snowflake/cli/api/commands/decorators.py +21 -6
- snowflake/cli/api/commands/flags.py +60 -51
- snowflake/cli/api/commands/snow_typer.py +24 -0
- snowflake/cli/api/commands/typer_pre_execute.py +26 -0
- snowflake/cli/api/console/abc.py +8 -0
- snowflake/cli/api/console/console.py +29 -4
- snowflake/cli/api/constants.py +3 -0
- snowflake/cli/api/project/definition.py +17 -35
- snowflake/cli/api/project/definition_manager.py +22 -19
- snowflake/cli/api/project/errors.py +9 -6
- snowflake/cli/api/project/schemas/identifier_model.py +1 -1
- snowflake/cli/api/project/schemas/native_app/application.py +15 -3
- snowflake/cli/api/project/schemas/native_app/native_app.py +5 -1
- snowflake/cli/api/project/schemas/native_app/path_mapping.py +14 -3
- snowflake/cli/api/project/schemas/project_definition.py +37 -6
- snowflake/cli/api/project/schemas/streamlit/streamlit.py +3 -0
- snowflake/cli/api/project/schemas/updatable_model.py +2 -6
- snowflake/cli/api/rest_api.py +113 -0
- snowflake/cli/api/sanitizers.py +43 -0
- snowflake/cli/api/sql_execution.py +7 -0
- snowflake/cli/api/utils/definition_rendering.py +95 -25
- snowflake/cli/api/utils/models.py +31 -26
- snowflake/cli/api/utils/rendering.py +24 -3
- snowflake/cli/app/cli_app.py +2 -0
- snowflake/cli/app/commands_registration/command_plugins_loader.py +8 -0
- snowflake/cli/app/dev/docs/commands_docs_generator.py +100 -0
- snowflake/cli/app/dev/docs/generator.py +8 -67
- snowflake/cli/app/dev/docs/project_definition_docs_generator.py +58 -0
- snowflake/cli/app/dev/docs/project_definition_generate_json_schema.py +227 -0
- snowflake/cli/app/dev/docs/template_utils.py +23 -0
- snowflake/cli/app/dev/docs/templates/definition_description.rst.jinja2 +38 -0
- snowflake/cli/app/dev/docs/templates/usage.rst.jinja2 +6 -1
- snowflake/cli/app/loggers.py +25 -0
- snowflake/cli/app/printing.py +7 -5
- snowflake/cli/app/telemetry.py +11 -0
- snowflake/cli/plugins/nativeapp/artifacts.py +78 -9
- snowflake/cli/plugins/nativeapp/codegen/artifact_processor.py +3 -11
- snowflake/cli/plugins/nativeapp/codegen/compiler.py +6 -24
- snowflake/cli/plugins/nativeapp/codegen/snowpark/python_processor.py +27 -27
- snowflake/cli/plugins/nativeapp/commands.py +23 -12
- snowflake/cli/plugins/nativeapp/constants.py +2 -0
- snowflake/cli/plugins/nativeapp/errno.py +15 -0
- snowflake/cli/plugins/nativeapp/feature_flags.py +24 -0
- snowflake/cli/plugins/nativeapp/init.py +5 -0
- snowflake/cli/plugins/nativeapp/manager.py +101 -103
- snowflake/cli/plugins/nativeapp/project_model.py +181 -0
- snowflake/cli/plugins/nativeapp/run_processor.py +178 -110
- snowflake/cli/plugins/nativeapp/teardown_processor.py +89 -64
- snowflake/cli/plugins/nativeapp/utils.py +2 -2
- snowflake/cli/plugins/nativeapp/version/commands.py +3 -3
- snowflake/cli/plugins/object/commands.py +70 -4
- snowflake/cli/plugins/object/manager.py +44 -3
- snowflake/cli/plugins/snowpark/commands.py +2 -2
- snowflake/cli/plugins/sql/commands.py +2 -10
- snowflake/cli/plugins/sql/manager.py +4 -2
- snowflake/cli/plugins/stage/commands.py +23 -4
- snowflake/cli/plugins/stage/diff.py +81 -51
- snowflake/cli/plugins/stage/manager.py +2 -1
- snowflake/cli/plugins/streamlit/commands.py +2 -1
- snowflake/cli/plugins/streamlit/manager.py +6 -0
- {snowflake_cli_labs-2.5.0rc3.dist-info → snowflake_cli_labs-2.6.0.dist-info}/METADATA +15 -9
- {snowflake_cli_labs-2.5.0rc3.dist-info → snowflake_cli_labs-2.6.0.dist-info}/RECORD +67 -56
- {snowflake_cli_labs-2.5.0rc3.dist-info → snowflake_cli_labs-2.6.0.dist-info}/WHEEL +1 -1
- {snowflake_cli_labs-2.5.0rc3.dist-info → snowflake_cli_labs-2.6.0.dist-info}/entry_points.txt +0 -0
- {snowflake_cli_labs-2.5.0rc3.dist-info → snowflake_cli_labs-2.6.0.dist-info}/licenses/LICENSE +0 -0
snowflake/cli/__about__.py
CHANGED
|
@@ -16,7 +16,7 @@ from __future__ import annotations
|
|
|
16
16
|
|
|
17
17
|
import re
|
|
18
18
|
from pathlib import Path
|
|
19
|
-
from typing import
|
|
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[
|
|
267
|
+
def project_definition(self) -> Optional[ProjectDefinition]:
|
|
265
268
|
return self._project_definition
|
|
266
269
|
|
|
267
|
-
def set_project_definition(self, value:
|
|
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
|
-
|
|
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(
|
|
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=
|
|
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
|
|
503
|
-
|
|
504
|
-
|
|
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
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
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
|
-
|
|
512
|
-
|
|
513
|
-
|
|
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
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
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
|
-
|
|
562
|
-
"
|
|
563
|
-
"
|
|
564
|
-
|
|
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()
|
snowflake/cli/api/console/abc.py
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
snowflake/cli/api/constants.py
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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__(
|
|
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
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
return {CONTEXT_KEY: definition}
|
|
134
|
+
return self._project_properties.project_context
|