snowflake-cli-labs 3.0.0rc3__py3-none-any.whl → 3.0.0rc5__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 (44) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/_app/telemetry.py +28 -0
  3. snowflake/cli/_plugins/connection/commands.py +9 -4
  4. snowflake/cli/_plugins/helpers/commands.py +34 -1
  5. snowflake/cli/_plugins/nativeapp/codegen/compiler.py +5 -0
  6. snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +4 -0
  7. snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +3 -0
  8. snowflake/cli/_plugins/nativeapp/commands.py +9 -86
  9. snowflake/cli/_plugins/nativeapp/entities/__init__.py +0 -0
  10. snowflake/cli/_plugins/nativeapp/{application_entity.py → entities/application.py} +266 -39
  11. snowflake/cli/_plugins/nativeapp/{application_package_entity.py → entities/application_package.py} +357 -72
  12. snowflake/cli/_plugins/nativeapp/manager.py +62 -183
  13. snowflake/cli/_plugins/nativeapp/run_processor.py +6 -6
  14. snowflake/cli/_plugins/nativeapp/teardown_processor.py +2 -4
  15. snowflake/cli/_plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +2 -4
  16. snowflake/cli/_plugins/nativeapp/version/commands.py +1 -15
  17. snowflake/cli/_plugins/nativeapp/version/version_processor.py +16 -82
  18. snowflake/cli/_plugins/object/manager.py +36 -15
  19. snowflake/cli/_plugins/streamlit/commands.py +12 -0
  20. snowflake/cli/_plugins/streamlit/manager.py +4 -0
  21. snowflake/cli/_plugins/workspace/commands.py +33 -0
  22. snowflake/cli/api/cli_global_context.py +7 -0
  23. snowflake/cli/api/commands/decorators.py +14 -0
  24. snowflake/cli/api/commands/flags.py +18 -0
  25. snowflake/cli/api/config.py +25 -6
  26. snowflake/cli/api/connections.py +3 -1
  27. snowflake/cli/api/entities/common.py +1 -0
  28. snowflake/cli/api/entities/utils.py +3 -0
  29. snowflake/cli/api/metrics.py +92 -0
  30. snowflake/cli/api/project/definition_conversion.py +69 -22
  31. snowflake/cli/api/project/definition_manager.py +5 -5
  32. snowflake/cli/api/project/schemas/entities/entities.py +3 -5
  33. snowflake/cli/api/project/schemas/project_definition.py +1 -3
  34. snowflake/cli/api/rendering/sql_templates.py +6 -0
  35. snowflake/cli/api/rest_api.py +11 -5
  36. snowflake/cli/api/utils/definition_rendering.py +24 -4
  37. {snowflake_cli_labs-3.0.0rc3.dist-info → snowflake_cli_labs-3.0.0rc5.dist-info}/METADATA +4 -2
  38. {snowflake_cli_labs-3.0.0rc3.dist-info → snowflake_cli_labs-3.0.0rc5.dist-info}/RECORD +41 -42
  39. snowflake/cli/_plugins/nativeapp/application_entity_model.py +0 -56
  40. snowflake/cli/_plugins/nativeapp/application_package_entity_model.py +0 -94
  41. snowflake/cli/_plugins/nativeapp/init.py +0 -345
  42. {snowflake_cli_labs-3.0.0rc3.dist-info → snowflake_cli_labs-3.0.0rc5.dist-info}/WHEEL +0 -0
  43. {snowflake_cli_labs-3.0.0rc3.dist-info → snowflake_cli_labs-3.0.0rc5.dist-info}/entry_points.txt +0 -0
  44. {snowflake_cli_labs-3.0.0rc3.dist-info → snowflake_cli_labs-3.0.0rc5.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",
@@ -130,12 +130,21 @@ def config_init(config_file: Optional[Path]):
130
130
  create_initial_loggers()
131
131
 
132
132
 
133
- def add_connection(name: str, connection_config: ConnectionConfig):
134
- set_config_value(
135
- section=CONNECTIONS_SECTION,
136
- key=name,
137
- value=connection_config.to_dict_of_all_non_empty_values(),
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))
@@ -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 (key, value) in updates.items():
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__}")
@@ -13,6 +13,7 @@ class EntityActions(str, Enum):
13
13
 
14
14
  VERSION_LIST = "action_version_list"
15
15
  VERSION_CREATE = "action_version_create"
16
+ VERSION_DROP = "action_version_drop"
16
17
 
17
18
 
18
19
  T = TypeVar("T")
@@ -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()
@@ -6,7 +6,7 @@ from typing import Any, Dict, Literal, Optional
6
6
 
7
7
  from click import ClickException
8
8
  from snowflake.cli._plugins.nativeapp.artifacts import (
9
- build_bundle,
9
+ BundleMap,
10
10
  )
11
11
  from snowflake.cli._plugins.snowpark.common import is_name_a_templated_one
12
12
  from snowflake.cli.api.constants import (
@@ -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, pd: ProjectDefinition, accept_templates: bool = False
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, native_app: NativeApp
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 = {}
@@ -188,17 +216,22 @@ def convert_native_app_to_v2_data(
188
216
  # glob patterns. The simplest solution is to bundle the app and find the
189
217
  # manifest file from the resultant BundleMap, since the bundle process ensures
190
218
  # that only a single source path can map to the corresponding destination path
191
- try:
192
- bundle_map = build_bundle(
193
- project_root, Path(native_app.deploy_root), native_app.artifacts
194
- )
195
- except Exception as e:
196
- # The manifest field is required, so we can't gracefully handle bundle failures
197
- raise ClickException(
198
- f"{e}\nCould not bundle Native App artifacts, unable to perform migration"
199
- ) from e
200
-
201
- manifest_path = bundle_map.to_project_path(Path("manifest.yml"))
219
+ bundle_map = BundleMap(
220
+ project_root=project_root, deploy_root=Path(native_app.deploy_root)
221
+ )
222
+ for artifact in native_app.artifacts:
223
+ bundle_map.add(artifact)
224
+
225
+ manifest_path = next(
226
+ (
227
+ src
228
+ for src, dest in bundle_map.all_mappings(
229
+ absolute=True, expand_directories=True
230
+ )
231
+ if dest.name == "manifest.yml"
232
+ ),
233
+ None,
234
+ )
202
235
  if not manifest_path:
203
236
  # The manifest field is required, so we can't gracefully handle it being missing
204
237
  raise ClickException(
@@ -208,7 +241,7 @@ def convert_native_app_to_v2_data(
208
241
 
209
242
  # Use a POSIX path to be consistent with other migrated fields
210
243
  # which use POSIX paths as default values
211
- return manifest_path.as_posix()
244
+ return manifest_path.relative_to(project_root).as_posix()
212
245
 
213
246
  def _make_template(template: str) -> str:
214
247
  return f"{PROJECT_TEMPLATE_VARIABLE_OPENING} {template} {PROJECT_TEMPLATE_VARIABLE_CLOSING}"
@@ -250,14 +283,24 @@ def convert_native_app_to_v2_data(
250
283
  "identifier": package_identifier,
251
284
  "manifest": _find_manifest(),
252
285
  "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
286
  }
287
+
288
+ if _is_field_defined(template_context, "ctx", "native_app", "bundle_root"):
289
+ package["bundle_root"] = native_app.bundle_root
290
+ if _is_field_defined(template_context, "ctx", "native_app", "generated_root"):
291
+ package["generated_root"] = native_app.generated_root
292
+ if _is_field_defined(template_context, "ctx", "native_app", "deploy_root"):
293
+ package["deploy_root"] = native_app.deploy_root
294
+ if _is_field_defined(template_context, "ctx", "native_app", "source_stage"):
295
+ package["stage"] = native_app.source_stage
296
+ if _is_field_defined(template_context, "ctx", "native_app", "scratch_stage"):
297
+ package["scratch_stage"] = native_app.scratch_stage
298
+
259
299
  if native_app.package:
260
- package["distribution"] = native_app.package.distribution
300
+ if _is_field_defined(
301
+ template_context, "ctx", "native_app", "package", "distribution"
302
+ ):
303
+ package["distribution"] = native_app.package.distribution
261
304
  package_meta = _make_meta(native_app.package)
262
305
  if native_app.package.scripts:
263
306
  converted_post_deploy_hooks = _convert_package_script_files(
@@ -289,6 +332,10 @@ def convert_native_app_to_v2_data(
289
332
  if native_app.application:
290
333
  if app_meta := _make_meta(native_app.application):
291
334
  app["meta"] = app_meta
335
+ if _is_field_defined(
336
+ template_context, "ctx", "native_app", "application", "debug"
337
+ ):
338
+ app["debug"] = native_app.application.debug
292
339
 
293
340
  return {
294
341
  "entities": {
@@ -41,7 +41,7 @@ class DefinitionManager:
41
41
  USER_DEFINITION_FILENAME = "snowflake.local.yml"
42
42
 
43
43
  project_root: Path
44
- _project_config_paths: List[Path]
44
+ project_config_paths: List[Path]
45
45
 
46
46
  def __init__(
47
47
  self,
@@ -53,12 +53,12 @@ class DefinitionManager:
53
53
  )
54
54
 
55
55
  self.project_root = project_root
56
- self._project_config_paths = self._find_definition_files(self.project_root)
56
+ self.project_config_paths = self._find_definition_files(self.project_root)
57
57
  self._context_overrides = context_overrides
58
58
 
59
59
  @functools.cached_property
60
60
  def has_definition_file(self):
61
- return len(self._project_config_paths) > 0
61
+ return len(self.project_config_paths) > 0
62
62
 
63
63
  @staticmethod
64
64
  def _find_definition_files(project_root: Path) -> List[Path]:
@@ -126,11 +126,11 @@ class DefinitionManager:
126
126
 
127
127
  @functools.cached_property
128
128
  def _project_properties(self) -> ProjectProperties:
129
- return load_project(self._project_config_paths, self._context_overrides)
129
+ return load_project(self.project_config_paths, self._context_overrides)
130
130
 
131
131
  @functools.cached_property
132
132
  def _raw_project_data(self) -> ProjectProperties:
133
- return load_project(self._project_config_paths, {}, False)
133
+ return load_project(self.project_config_paths, {}, False)
134
134
 
135
135
  @functools.cached_property
136
136
  def project_definition(self) -> ProjectDefinitionV1:
@@ -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.application_entity import ApplicationEntity
20
- from snowflake.cli._plugins.nativeapp.application_entity_model import (
19
+ from snowflake.cli._plugins.nativeapp.entities.application import (
20
+ ApplicationEntity,
21
21
  ApplicationEntityModel,
22
22
  )
23
- from snowflake.cli._plugins.nativeapp.application_package_entity import (
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.application_entity_model import (
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)
@@ -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, InterfaceError
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
- result = self.send_rest_request(url, method="get")
51
- return bool(result) or result == []
52
- except InterfaceError as err:
53
- if "404 Not Found" in str(err):
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
- referenced_vars = _get_referenced_vars_in_definition(
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.0rc3
3
+ Version: 3.0.0rc5
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.1
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
  <!--