snowflake-cli-labs 3.0.0rc3__py3-none-any.whl → 3.0.0rc4__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/_app/telemetry.py +28 -0
- snowflake/cli/_plugins/connection/commands.py +9 -4
- snowflake/cli/_plugins/helpers/commands.py +5 -1
- snowflake/cli/_plugins/nativeapp/codegen/compiler.py +5 -0
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +4 -0
- snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +3 -0
- snowflake/cli/_plugins/nativeapp/commands.py +9 -86
- snowflake/cli/_plugins/nativeapp/entities/__init__.py +0 -0
- snowflake/cli/_plugins/nativeapp/{application_entity.py → entities/application.py} +266 -39
- snowflake/cli/_plugins/nativeapp/{application_package_entity.py → entities/application_package.py} +357 -72
- snowflake/cli/_plugins/nativeapp/manager.py +62 -183
- snowflake/cli/_plugins/nativeapp/run_processor.py +6 -6
- snowflake/cli/_plugins/nativeapp/teardown_processor.py +2 -4
- snowflake/cli/_plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +2 -4
- snowflake/cli/_plugins/nativeapp/version/commands.py +1 -15
- snowflake/cli/_plugins/nativeapp/version/version_processor.py +16 -82
- snowflake/cli/_plugins/object/manager.py +36 -15
- snowflake/cli/_plugins/streamlit/commands.py +12 -0
- snowflake/cli/_plugins/streamlit/manager.py +4 -0
- snowflake/cli/_plugins/workspace/commands.py +33 -0
- snowflake/cli/api/cli_global_context.py +7 -0
- snowflake/cli/api/commands/decorators.py +14 -0
- snowflake/cli/api/commands/flags.py +18 -0
- snowflake/cli/api/config.py +25 -6
- snowflake/cli/api/connections.py +3 -1
- snowflake/cli/api/entities/common.py +1 -0
- snowflake/cli/api/entities/utils.py +3 -0
- snowflake/cli/api/metrics.py +92 -0
- snowflake/cli/api/project/definition_conversion.py +51 -9
- snowflake/cli/api/project/schemas/entities/entities.py +3 -5
- snowflake/cli/api/project/schemas/project_definition.py +1 -3
- snowflake/cli/api/rendering/sql_templates.py +6 -0
- snowflake/cli/api/rest_api.py +11 -5
- snowflake/cli/api/utils/definition_rendering.py +24 -4
- {snowflake_cli_labs-3.0.0rc3.dist-info → snowflake_cli_labs-3.0.0rc4.dist-info}/METADATA +4 -2
- {snowflake_cli_labs-3.0.0rc3.dist-info → snowflake_cli_labs-3.0.0rc4.dist-info}/RECORD +40 -41
- snowflake/cli/_plugins/nativeapp/application_entity_model.py +0 -56
- snowflake/cli/_plugins/nativeapp/application_package_entity_model.py +0 -94
- snowflake/cli/_plugins/nativeapp/init.py +0 -345
- {snowflake_cli_labs-3.0.0rc3.dist-info → snowflake_cli_labs-3.0.0rc4.dist-info}/WHEEL +0 -0
- {snowflake_cli_labs-3.0.0rc3.dist-info → snowflake_cli_labs-3.0.0rc4.dist-info}/entry_points.txt +0 -0
- {snowflake_cli_labs-3.0.0rc3.dist-info → snowflake_cli_labs-3.0.0rc4.dist-info}/licenses/LICENSE +0 -0
|
@@ -271,3 +271,36 @@ def version_create(
|
|
|
271
271
|
interactive=interactive,
|
|
272
272
|
force=force,
|
|
273
273
|
)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
@version.command(name="drop", requires_connection=True, hidden=True)
|
|
277
|
+
@with_project_definition()
|
|
278
|
+
def version_drop(
|
|
279
|
+
entity_id: str = typer.Option(
|
|
280
|
+
help="The ID of the entity you want to create a version for.",
|
|
281
|
+
),
|
|
282
|
+
version: Optional[str] = typer.Argument(
|
|
283
|
+
None,
|
|
284
|
+
help=f"""Version to define in your application package. If the version already exists, an auto-incremented patch is added to the version instead. Defaults to the version specified in the `manifest.yml` file.""",
|
|
285
|
+
),
|
|
286
|
+
interactive: bool = InteractiveOption,
|
|
287
|
+
force: Optional[bool] = ForceOption,
|
|
288
|
+
**options,
|
|
289
|
+
):
|
|
290
|
+
"""
|
|
291
|
+
Drops a version defined for your entity. Versions can either be passed in as an argument to the command or read from the `manifest.yml` file.
|
|
292
|
+
Dropping patches is not allowed.
|
|
293
|
+
"""
|
|
294
|
+
|
|
295
|
+
cli_context = get_cli_context()
|
|
296
|
+
ws = WorkspaceManager(
|
|
297
|
+
project_definition=cli_context.project_definition,
|
|
298
|
+
project_root=cli_context.project_root,
|
|
299
|
+
)
|
|
300
|
+
ws.perform_action(
|
|
301
|
+
entity_id,
|
|
302
|
+
EntityActions.VERSION_CREATE,
|
|
303
|
+
version=version,
|
|
304
|
+
interactive=interactive,
|
|
305
|
+
force=force,
|
|
306
|
+
)
|
|
@@ -22,6 +22,7 @@ from typing import TYPE_CHECKING, Iterator
|
|
|
22
22
|
|
|
23
23
|
from snowflake.cli.api.connections import ConnectionContext, OpenConnectionCache
|
|
24
24
|
from snowflake.cli.api.exceptions import MissingConfiguration
|
|
25
|
+
from snowflake.cli.api.metrics import CLIMetrics
|
|
25
26
|
from snowflake.cli.api.output.formats import OutputFormat
|
|
26
27
|
from snowflake.cli.api.rendering.jinja import CONTEXT_KEY
|
|
27
28
|
from snowflake.connector import SnowflakeConnection
|
|
@@ -46,6 +47,8 @@ class _CliGlobalContextManager:
|
|
|
46
47
|
experimental: bool = False
|
|
47
48
|
enable_tracebacks: bool = True
|
|
48
49
|
|
|
50
|
+
metrics: CLIMetrics = field(default_factory=CLIMetrics)
|
|
51
|
+
|
|
49
52
|
project_path_arg: str | None = None
|
|
50
53
|
project_is_optional: bool = True
|
|
51
54
|
project_env_overrides_args: dict[str, str] = field(default_factory=dict)
|
|
@@ -152,6 +155,10 @@ class _CliGlobalContextAccess:
|
|
|
152
155
|
def enable_tracebacks(self) -> bool:
|
|
153
156
|
return self._manager.enable_tracebacks
|
|
154
157
|
|
|
158
|
+
@property
|
|
159
|
+
def metrics(self):
|
|
160
|
+
return self._manager.metrics
|
|
161
|
+
|
|
155
162
|
@property
|
|
156
163
|
def output_format(self) -> OutputFormat:
|
|
157
164
|
return self._manager.output_format
|
|
@@ -29,10 +29,12 @@ from snowflake.cli.api.commands.flags import (
|
|
|
29
29
|
DiagAllowlistPathOption,
|
|
30
30
|
DiagLogPathOption,
|
|
31
31
|
EnableDiagOption,
|
|
32
|
+
HostOption,
|
|
32
33
|
MasterTokenOption,
|
|
33
34
|
MfaPasscodeOption,
|
|
34
35
|
OutputFormatOption,
|
|
35
36
|
PasswordOption,
|
|
37
|
+
PortOption,
|
|
36
38
|
PrivateKeyPathOption,
|
|
37
39
|
RoleOption,
|
|
38
40
|
SchemaOption,
|
|
@@ -210,6 +212,18 @@ GLOBAL_CONNECTION_OPTIONS = [
|
|
|
210
212
|
annotation=Optional[str],
|
|
211
213
|
default=ConnectionOption,
|
|
212
214
|
),
|
|
215
|
+
inspect.Parameter(
|
|
216
|
+
"host",
|
|
217
|
+
inspect.Parameter.KEYWORD_ONLY,
|
|
218
|
+
annotation=Optional[str],
|
|
219
|
+
default=HostOption,
|
|
220
|
+
),
|
|
221
|
+
inspect.Parameter(
|
|
222
|
+
"port",
|
|
223
|
+
inspect.Parameter.KEYWORD_ONLY,
|
|
224
|
+
annotation=Optional[int],
|
|
225
|
+
default=PortOption,
|
|
226
|
+
),
|
|
213
227
|
inspect.Parameter(
|
|
214
228
|
"account",
|
|
215
229
|
inspect.Parameter.KEYWORD_ONLY,
|
|
@@ -100,6 +100,24 @@ TemporaryConnectionOption = typer.Option(
|
|
|
100
100
|
rich_help_panel=_CONNECTION_SECTION,
|
|
101
101
|
)
|
|
102
102
|
|
|
103
|
+
HostOption = typer.Option(
|
|
104
|
+
None,
|
|
105
|
+
"--host",
|
|
106
|
+
help="Host address for the connection. Overrides the value specified for the connection.",
|
|
107
|
+
callback=_connection_callback("host"),
|
|
108
|
+
show_default=False,
|
|
109
|
+
rich_help_panel=_CONNECTION_SECTION,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
PortOption = typer.Option(
|
|
113
|
+
None,
|
|
114
|
+
"--port",
|
|
115
|
+
help="Port for the connection. Overrides the value specified for the connection.",
|
|
116
|
+
callback=_connection_callback("port"),
|
|
117
|
+
show_default=False,
|
|
118
|
+
rich_help_panel=_CONNECTION_SECTION,
|
|
119
|
+
)
|
|
120
|
+
|
|
103
121
|
AccountOption = typer.Option(
|
|
104
122
|
None,
|
|
105
123
|
"--account",
|
snowflake/cli/api/config.py
CHANGED
|
@@ -130,12 +130,21 @@ def config_init(config_file: Optional[Path]):
|
|
|
130
130
|
create_initial_loggers()
|
|
131
131
|
|
|
132
132
|
|
|
133
|
-
def
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
133
|
+
def add_connection_to_proper_file(name: str, connection_config: ConnectionConfig):
|
|
134
|
+
if CONNECTIONS_FILE.exists():
|
|
135
|
+
existing_connections = _read_connections_toml()
|
|
136
|
+
existing_connections.update(
|
|
137
|
+
{name: connection_config.to_dict_of_all_non_empty_values()}
|
|
138
|
+
)
|
|
139
|
+
_update_connections_toml(existing_connections)
|
|
140
|
+
return CONNECTIONS_FILE
|
|
141
|
+
else:
|
|
142
|
+
set_config_value(
|
|
143
|
+
section=CONNECTIONS_SECTION,
|
|
144
|
+
key=name,
|
|
145
|
+
value=connection_config.to_dict_of_all_non_empty_values(),
|
|
146
|
+
)
|
|
147
|
+
return CONFIG_MANAGER.file_path
|
|
139
148
|
|
|
140
149
|
|
|
141
150
|
_DEFAULT_LOGS_CONFIG = {
|
|
@@ -359,3 +368,13 @@ def get_feature_flags_section() -> Dict[str, bool | Literal["UNKNOWN"]]:
|
|
|
359
368
|
return "UNKNOWN"
|
|
360
369
|
|
|
361
370
|
return {k: _bool_or_unknown(v) for k, v in flags.items()}
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def _read_connections_toml() -> dict:
|
|
374
|
+
with open(CONNECTIONS_FILE, "r") as f:
|
|
375
|
+
return tomlkit.loads(f.read()).unwrap()
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def _update_connections_toml(connections: dict):
|
|
379
|
+
with open(CONNECTIONS_FILE, "w") as f:
|
|
380
|
+
f.write(tomlkit.dumps(connections))
|
snowflake/cli/api/connections.py
CHANGED
|
@@ -36,6 +36,8 @@ schema_pattern = re.compile(r".+\..+")
|
|
|
36
36
|
class ConnectionContext:
|
|
37
37
|
# FIXME: can reduce duplication using config.ConnectionConfig
|
|
38
38
|
connection_name: Optional[str] = None
|
|
39
|
+
host: Optional[str] = None
|
|
40
|
+
port: Optional[int] = None
|
|
39
41
|
account: Optional[str] = None
|
|
40
42
|
database: Optional[str] = None
|
|
41
43
|
role: Optional[str] = None
|
|
@@ -71,7 +73,7 @@ class ConnectionContext:
|
|
|
71
73
|
Raises KeyError if a non-property is specified as a key.
|
|
72
74
|
"""
|
|
73
75
|
field_map = {field.name: field for field in fields(self)}
|
|
74
|
-
for
|
|
76
|
+
for key, value in updates.items():
|
|
75
77
|
# ensure key represents a property
|
|
76
78
|
if key not in field_map:
|
|
77
79
|
raise KeyError(f"{key} is not a field of {self.__class__.__name__}")
|
|
@@ -31,6 +31,7 @@ from snowflake.cli.api.errno import (
|
|
|
31
31
|
NO_WAREHOUSE_SELECTED_IN_SESSION,
|
|
32
32
|
)
|
|
33
33
|
from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
|
|
34
|
+
from snowflake.cli.api.metrics import CLICounterField
|
|
34
35
|
from snowflake.cli.api.project.schemas.entities.common import PostDeployHook
|
|
35
36
|
from snowflake.cli.api.rendering.sql_templates import (
|
|
36
37
|
choose_sql_jinja_env_based_on_template_syntax,
|
|
@@ -249,6 +250,8 @@ def execute_post_deploy_hooks(
|
|
|
249
250
|
if not post_deploy_hooks:
|
|
250
251
|
return
|
|
251
252
|
|
|
253
|
+
get_cli_context().metrics.set_counter(CLICounterField.POST_DEPLOY_SCRIPTS, 1)
|
|
254
|
+
|
|
252
255
|
with console.phase(f"Executing {deployed_object_type} post-deploy actions"):
|
|
253
256
|
sql_scripts_paths = []
|
|
254
257
|
for hook in post_deploy_hooks:
|
|
@@ -0,0 +1,92 @@
|
|
|
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 Dict, Optional
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class _TypePrefix:
|
|
19
|
+
FEATURES = "features"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class _DomainPrefix:
|
|
23
|
+
GLOBAL = "global"
|
|
24
|
+
APP = "app"
|
|
25
|
+
SQL = "sql"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class CLICounterField:
|
|
29
|
+
"""
|
|
30
|
+
for each counter field we're adopting a convention of
|
|
31
|
+
<type>.<domain>.<name>
|
|
32
|
+
for example, if we're tracking a global feature, then the field name would be
|
|
33
|
+
features.global.feature_name
|
|
34
|
+
|
|
35
|
+
The metrics API is implemented to be generic, but we are adopting a convention
|
|
36
|
+
for feature tracking with the following model for a given command execution:
|
|
37
|
+
* counter not present -> feature is not available
|
|
38
|
+
* counter == 0 -> feature is available, but not used
|
|
39
|
+
* counter == 1 -> feature is used
|
|
40
|
+
this makes it easy to compute percentages for feature dashboards in Snowsight
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
TEMPLATES_PROCESSOR = (
|
|
44
|
+
f"{_TypePrefix.FEATURES}.{_DomainPrefix.GLOBAL}.templates_processor"
|
|
45
|
+
)
|
|
46
|
+
SQL_TEMPLATES = f"{_TypePrefix.FEATURES}.{_DomainPrefix.SQL}.sql_templates"
|
|
47
|
+
PDF_TEMPLATES = f"{_TypePrefix.FEATURES}.{_DomainPrefix.GLOBAL}.pdf_templates"
|
|
48
|
+
SNOWPARK_PROCESSOR = (
|
|
49
|
+
f"{_TypePrefix.FEATURES}.{_DomainPrefix.APP}.snowpark_processor"
|
|
50
|
+
)
|
|
51
|
+
POST_DEPLOY_SCRIPTS = (
|
|
52
|
+
f"{_TypePrefix.FEATURES}.{_DomainPrefix.APP}.post_deploy_scripts"
|
|
53
|
+
)
|
|
54
|
+
PACKAGE_SCRIPTS = f"{_TypePrefix.FEATURES}.{_DomainPrefix.APP}.package_scripts"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class CLIMetrics:
|
|
58
|
+
"""
|
|
59
|
+
Class to track various metrics across the execution of a command
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self):
|
|
63
|
+
self._counters: Dict[str, int] = {}
|
|
64
|
+
|
|
65
|
+
def __eq__(self, other):
|
|
66
|
+
if isinstance(other, CLIMetrics):
|
|
67
|
+
return self._counters == other._counters
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
def get_counter(self, name: str) -> Optional[int]:
|
|
71
|
+
return self._counters.get(name)
|
|
72
|
+
|
|
73
|
+
def set_counter(self, name: str, value: int) -> None:
|
|
74
|
+
self._counters[name] = value
|
|
75
|
+
|
|
76
|
+
def set_counter_default(self, name: str, value: int) -> None:
|
|
77
|
+
"""
|
|
78
|
+
sets the counter if it does not already exist
|
|
79
|
+
"""
|
|
80
|
+
if name not in self._counters:
|
|
81
|
+
self.set_counter(name, value)
|
|
82
|
+
|
|
83
|
+
def increment_counter(self, name: str, value: int = 1) -> None:
|
|
84
|
+
if name not in self._counters:
|
|
85
|
+
self.set_counter(name, value)
|
|
86
|
+
else:
|
|
87
|
+
self._counters[name] += value
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def counters(self) -> Dict[str, int]:
|
|
91
|
+
# return a copy of the original dict to avoid mutating the original
|
|
92
|
+
return self._counters.copy()
|
|
@@ -41,15 +41,41 @@ from snowflake.cli.api.rendering.jinja import get_basic_jinja_env
|
|
|
41
41
|
log = logging.getLogger(__name__)
|
|
42
42
|
|
|
43
43
|
|
|
44
|
+
def _is_field_defined(template_context: Optional[Dict[str, Any]], *path: str) -> bool:
|
|
45
|
+
"""
|
|
46
|
+
Determines if a field is defined in the provided template context. For example,
|
|
47
|
+
|
|
48
|
+
_is_field_defined({"ctx": {"native_app": {"bundle_root": "my_root"}}}, "ctx", "native_app", "bundle_root")
|
|
49
|
+
|
|
50
|
+
returns True. If the provided template context is None, this function returns True for all paths.
|
|
51
|
+
|
|
52
|
+
"""
|
|
53
|
+
if template_context is None:
|
|
54
|
+
return True # No context, so assume that all variables are defined
|
|
55
|
+
|
|
56
|
+
current_dict = template_context
|
|
57
|
+
for key in path:
|
|
58
|
+
if not isinstance(current_dict, dict):
|
|
59
|
+
return False
|
|
60
|
+
if key not in current_dict:
|
|
61
|
+
return False
|
|
62
|
+
current_dict = current_dict[key]
|
|
63
|
+
|
|
64
|
+
return True
|
|
65
|
+
|
|
66
|
+
|
|
44
67
|
def convert_project_definition_to_v2(
|
|
45
|
-
project_root: Path,
|
|
68
|
+
project_root: Path,
|
|
69
|
+
pd: ProjectDefinition,
|
|
70
|
+
accept_templates: bool = False,
|
|
71
|
+
template_context: Optional[Dict[str, Any]] = None,
|
|
46
72
|
) -> ProjectDefinitionV2:
|
|
47
73
|
_check_if_project_definition_meets_requirements(pd, accept_templates)
|
|
48
74
|
|
|
49
75
|
snowpark_data = convert_snowpark_to_v2_data(pd.snowpark) if pd.snowpark else {}
|
|
50
76
|
streamlit_data = convert_streamlit_to_v2_data(pd.streamlit) if pd.streamlit else {}
|
|
51
77
|
native_app_data = (
|
|
52
|
-
convert_native_app_to_v2_data(project_root, pd.native_app)
|
|
78
|
+
convert_native_app_to_v2_data(project_root, pd.native_app, template_context)
|
|
53
79
|
if pd.native_app
|
|
54
80
|
else {}
|
|
55
81
|
)
|
|
@@ -170,7 +196,9 @@ def convert_streamlit_to_v2_data(streamlit: Streamlit) -> Dict[str, Any]:
|
|
|
170
196
|
|
|
171
197
|
|
|
172
198
|
def convert_native_app_to_v2_data(
|
|
173
|
-
project_root,
|
|
199
|
+
project_root,
|
|
200
|
+
native_app: NativeApp,
|
|
201
|
+
template_context: Optional[Dict[str, Any]] = None,
|
|
174
202
|
) -> Dict[str, Any]:
|
|
175
203
|
def _make_meta(obj: Application | Package):
|
|
176
204
|
meta = {}
|
|
@@ -250,14 +278,24 @@ def convert_native_app_to_v2_data(
|
|
|
250
278
|
"identifier": package_identifier,
|
|
251
279
|
"manifest": _find_manifest(),
|
|
252
280
|
"artifacts": native_app.artifacts,
|
|
253
|
-
"bundle_root": native_app.bundle_root,
|
|
254
|
-
"generated_root": native_app.generated_root,
|
|
255
|
-
"deploy_root": native_app.deploy_root,
|
|
256
|
-
"stage": native_app.source_stage,
|
|
257
|
-
"scratch_stage": native_app.scratch_stage,
|
|
258
281
|
}
|
|
282
|
+
|
|
283
|
+
if _is_field_defined(template_context, "ctx", "native_app", "bundle_root"):
|
|
284
|
+
package["bundle_root"] = native_app.bundle_root
|
|
285
|
+
if _is_field_defined(template_context, "ctx", "native_app", "generated_root"):
|
|
286
|
+
package["generated_root"] = native_app.generated_root
|
|
287
|
+
if _is_field_defined(template_context, "ctx", "native_app", "deploy_root"):
|
|
288
|
+
package["deploy_root"] = native_app.deploy_root
|
|
289
|
+
if _is_field_defined(template_context, "ctx", "native_app", "source_stage"):
|
|
290
|
+
package["stage"] = native_app.source_stage
|
|
291
|
+
if _is_field_defined(template_context, "ctx", "native_app", "scratch_stage"):
|
|
292
|
+
package["scratch_stage"] = native_app.scratch_stage
|
|
293
|
+
|
|
259
294
|
if native_app.package:
|
|
260
|
-
|
|
295
|
+
if _is_field_defined(
|
|
296
|
+
template_context, "ctx", "native_app", "package", "distribution"
|
|
297
|
+
):
|
|
298
|
+
package["distribution"] = native_app.package.distribution
|
|
261
299
|
package_meta = _make_meta(native_app.package)
|
|
262
300
|
if native_app.package.scripts:
|
|
263
301
|
converted_post_deploy_hooks = _convert_package_script_files(
|
|
@@ -289,6 +327,10 @@ def convert_native_app_to_v2_data(
|
|
|
289
327
|
if native_app.application:
|
|
290
328
|
if app_meta := _make_meta(native_app.application):
|
|
291
329
|
app["meta"] = app_meta
|
|
330
|
+
if _is_field_defined(
|
|
331
|
+
template_context, "ctx", "native_app", "application", "debug"
|
|
332
|
+
):
|
|
333
|
+
app["debug"] = native_app.application.debug
|
|
292
334
|
|
|
293
335
|
return {
|
|
294
336
|
"entities": {
|
|
@@ -16,14 +16,12 @@ from __future__ import annotations
|
|
|
16
16
|
|
|
17
17
|
from typing import Dict, List, Union, get_args
|
|
18
18
|
|
|
19
|
-
from snowflake.cli._plugins.nativeapp.
|
|
20
|
-
|
|
19
|
+
from snowflake.cli._plugins.nativeapp.entities.application import (
|
|
20
|
+
ApplicationEntity,
|
|
21
21
|
ApplicationEntityModel,
|
|
22
22
|
)
|
|
23
|
-
from snowflake.cli._plugins.nativeapp.
|
|
23
|
+
from snowflake.cli._plugins.nativeapp.entities.application_package import (
|
|
24
24
|
ApplicationPackageEntity,
|
|
25
|
-
)
|
|
26
|
-
from snowflake.cli._plugins.nativeapp.application_package_entity_model import (
|
|
27
25
|
ApplicationPackageEntityModel,
|
|
28
26
|
)
|
|
29
27
|
from snowflake.cli._plugins.snowpark.snowpark_entity import (
|
|
@@ -19,9 +19,7 @@ from typing import Any, Dict, List, Optional, Union
|
|
|
19
19
|
|
|
20
20
|
from packaging.version import Version
|
|
21
21
|
from pydantic import Field, ValidationError, field_validator, model_validator
|
|
22
|
-
from snowflake.cli._plugins.nativeapp.
|
|
23
|
-
ApplicationEntityModel,
|
|
24
|
-
)
|
|
22
|
+
from snowflake.cli._plugins.nativeapp.entities.application import ApplicationEntityModel
|
|
25
23
|
from snowflake.cli.api.project.errors import SchemaValidationError
|
|
26
24
|
from snowflake.cli.api.project.schemas.entities.common import (
|
|
27
25
|
TargetField,
|
|
@@ -21,6 +21,7 @@ from jinja2 import Environment, StrictUndefined, loaders, meta
|
|
|
21
21
|
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
22
22
|
from snowflake.cli.api.console.console import cli_console
|
|
23
23
|
from snowflake.cli.api.exceptions import InvalidTemplate
|
|
24
|
+
from snowflake.cli.api.metrics import CLICounterField
|
|
24
25
|
from snowflake.cli.api.rendering.jinja import (
|
|
25
26
|
CONTEXT_KEY,
|
|
26
27
|
FUNCTION_KEY,
|
|
@@ -96,4 +97,9 @@ def snowflake_sql_jinja_render(content: str, data: Dict | None = None) -> str:
|
|
|
96
97
|
context_data = get_cli_context().template_context
|
|
97
98
|
context_data.update(data)
|
|
98
99
|
env = choose_sql_jinja_env_based_on_template_syntax(content)
|
|
100
|
+
|
|
101
|
+
get_cli_context().metrics.set_counter(
|
|
102
|
+
CLICounterField.SQL_TEMPLATES, int(has_sql_templates(content))
|
|
103
|
+
)
|
|
104
|
+
|
|
99
105
|
return env.from_string(content).render(context_data)
|
snowflake/cli/api/rest_api.py
CHANGED
|
@@ -21,8 +21,9 @@ from typing import Any, Dict, Optional
|
|
|
21
21
|
from click import ClickException
|
|
22
22
|
from snowflake.cli.api.constants import SF_REST_API_URL_PREFIX
|
|
23
23
|
from snowflake.connector.connection import SnowflakeConnection
|
|
24
|
-
from snowflake.connector.errors import BadRequest
|
|
24
|
+
from snowflake.connector.errors import BadRequest
|
|
25
25
|
from snowflake.connector.network import SnowflakeRestful
|
|
26
|
+
from snowflake.connector.vendored.requests.exceptions import HTTPError
|
|
26
27
|
|
|
27
28
|
log = logging.getLogger(__name__)
|
|
28
29
|
|
|
@@ -47,10 +48,10 @@ class RestApi:
|
|
|
47
48
|
Check whether [get] endpoint exists under given URL.
|
|
48
49
|
"""
|
|
49
50
|
try:
|
|
50
|
-
|
|
51
|
-
return
|
|
52
|
-
except
|
|
53
|
-
if
|
|
51
|
+
self.send_rest_request(url, method="get")
|
|
52
|
+
return True
|
|
53
|
+
except HTTPError as err:
|
|
54
|
+
if err.response.status_code == 404:
|
|
54
55
|
return False
|
|
55
56
|
raise err
|
|
56
57
|
|
|
@@ -60,6 +61,10 @@ class RestApi:
|
|
|
60
61
|
return bool(result)
|
|
61
62
|
except BadRequest:
|
|
62
63
|
return False
|
|
64
|
+
except HTTPError as err:
|
|
65
|
+
if err.response.status_code == 404:
|
|
66
|
+
return False
|
|
67
|
+
raise err
|
|
63
68
|
|
|
64
69
|
def send_rest_request(
|
|
65
70
|
self, url: str, method: str, data: Optional[Dict[str, Any]] = None
|
|
@@ -91,6 +96,7 @@ class RestApi:
|
|
|
91
96
|
token=self.rest.token,
|
|
92
97
|
data=json.dumps(data if data else {}),
|
|
93
98
|
no_retry=True,
|
|
99
|
+
raise_raw_http_failure=True,
|
|
94
100
|
)
|
|
95
101
|
|
|
96
102
|
def _database_exists(self, db_name: str) -> bool:
|
|
@@ -19,8 +19,10 @@ from typing import Any, Optional
|
|
|
19
19
|
|
|
20
20
|
from jinja2 import Environment, TemplateSyntaxError, nodes
|
|
21
21
|
from packaging.version import Version
|
|
22
|
+
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
22
23
|
from snowflake.cli.api.console import cli_console as cc
|
|
23
24
|
from snowflake.cli.api.exceptions import CycleDetectedError, InvalidTemplate
|
|
25
|
+
from snowflake.cli.api.metrics import CLICounterField
|
|
24
26
|
from snowflake.cli.api.project.schemas.project_definition import (
|
|
25
27
|
ProjectProperties,
|
|
26
28
|
build_project_definition,
|
|
@@ -266,6 +268,12 @@ def _get_referenced_vars_in_definition(
|
|
|
266
268
|
return referenced_vars
|
|
267
269
|
|
|
268
270
|
|
|
271
|
+
def _has_referenced_vars_in_definition(
|
|
272
|
+
template_env: TemplatedEnvironment, definition: Definition
|
|
273
|
+
) -> bool:
|
|
274
|
+
return len(_get_referenced_vars_in_definition(template_env, definition)) > 0
|
|
275
|
+
|
|
276
|
+
|
|
269
277
|
def _template_version_warning():
|
|
270
278
|
cc.warning(
|
|
271
279
|
"Ignoring template pattern in project definition file. "
|
|
@@ -291,6 +299,17 @@ def _add_defaults_to_definition(original_definition: Definition) -> Definition:
|
|
|
291
299
|
return definition_with_defaults
|
|
292
300
|
|
|
293
301
|
|
|
302
|
+
def _update_metrics(template_env: TemplatedEnvironment, definition: Definition):
|
|
303
|
+
metrics = get_cli_context().metrics
|
|
304
|
+
|
|
305
|
+
# render_definition_template is invoked multiple times both by the user
|
|
306
|
+
# and by us so we should make sure we don't overwrite a 1 with a 0 here
|
|
307
|
+
metrics.set_counter_default(CLICounterField.PDF_TEMPLATES, 0)
|
|
308
|
+
|
|
309
|
+
if _has_referenced_vars_in_definition(template_env, definition):
|
|
310
|
+
metrics.set_counter(CLICounterField.PDF_TEMPLATES, 1)
|
|
311
|
+
|
|
312
|
+
|
|
294
313
|
def render_definition_template(
|
|
295
314
|
original_definition: Optional[Definition], context_overrides: Context
|
|
296
315
|
) -> ProjectProperties:
|
|
@@ -326,10 +345,7 @@ def render_definition_template(
|
|
|
326
345
|
definition["definition_version"]
|
|
327
346
|
) < Version("1.1"):
|
|
328
347
|
try:
|
|
329
|
-
|
|
330
|
-
template_env, definition
|
|
331
|
-
)
|
|
332
|
-
if referenced_vars:
|
|
348
|
+
if _has_referenced_vars_in_definition(template_env, definition):
|
|
333
349
|
_template_version_warning()
|
|
334
350
|
except Exception:
|
|
335
351
|
# also warn on Exception, as it means the user is incorrectly attempting to use templating
|
|
@@ -340,6 +356,10 @@ def render_definition_template(
|
|
|
340
356
|
project_context[CONTEXT_KEY]["env"] = environment_overrides
|
|
341
357
|
return ProjectProperties(project_definition, project_context)
|
|
342
358
|
|
|
359
|
+
# need to have the metrics added here since we add defaults to the
|
|
360
|
+
# definition that the user might not have added themselves later
|
|
361
|
+
_update_metrics(template_env, definition)
|
|
362
|
+
|
|
343
363
|
definition = _add_defaults_to_definition(definition)
|
|
344
364
|
project_context = {CONTEXT_KEY: definition}
|
|
345
365
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: snowflake-cli-labs
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.0rc4
|
|
4
4
|
Summary: Snowflake CLI
|
|
5
5
|
Project-URL: Source code, https://github.com/snowflakedb/snowflake-cli
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/snowflakedb/snowflake-cli/issues
|
|
@@ -222,7 +222,7 @@ Requires-Dist: jinja2==3.1.4
|
|
|
222
222
|
Requires-Dist: packaging
|
|
223
223
|
Requires-Dist: pip
|
|
224
224
|
Requires-Dist: pluggy==1.5.0
|
|
225
|
-
Requires-Dist: pydantic==2.9.
|
|
225
|
+
Requires-Dist: pydantic==2.9.2
|
|
226
226
|
Requires-Dist: pyyaml==6.0.2
|
|
227
227
|
Requires-Dist: requests==2.32.3
|
|
228
228
|
Requires-Dist: requirements-parser==0.11.0
|
|
@@ -240,6 +240,8 @@ Requires-Dist: pre-commit>=3.5.0; extra == 'development'
|
|
|
240
240
|
Requires-Dist: pytest-randomly==3.15.0; extra == 'development'
|
|
241
241
|
Requires-Dist: pytest==8.3.3; extra == 'development'
|
|
242
242
|
Requires-Dist: syrupy==4.7.1; extra == 'development'
|
|
243
|
+
Provides-Extra: packaging
|
|
244
|
+
Requires-Dist: pyinstaller~=6.10; extra == 'packaging'
|
|
243
245
|
Description-Content-Type: text/markdown
|
|
244
246
|
|
|
245
247
|
<!--
|