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
@@ -0,0 +1,24 @@
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 enum import unique
16
+
17
+ from snowflake.cli.api.feature_flags import BooleanFlag, FeatureFlagMixin
18
+
19
+
20
+ @unique
21
+ class FeatureFlag(FeatureFlagMixin):
22
+ ENABLE_NATIVE_APP_PYTHON_SETUP = BooleanFlag(
23
+ "ENABLE_NATIVE_APP_PYTHON_SETUP", False
24
+ )
@@ -21,6 +21,7 @@ from typing import Optional
21
21
 
22
22
  from click.exceptions import ClickException
23
23
  from snowflake.cli.api.constants import DEFAULT_SIZE_LIMIT_MB
24
+ from snowflake.cli.api.exceptions import MissingConfiguration
24
25
  from snowflake.cli.api.project.definition_manager import DefinitionManager
25
26
  from snowflake.cli.api.project.util import (
26
27
  is_valid_identifier,
@@ -197,6 +198,10 @@ def _validate_and_update_snowflake_yml(target_directory: Path, project_identifie
197
198
  """
198
199
  # 1. Determine if a snowflake.yml file exists, at the very least
199
200
  definition_manager = DefinitionManager(target_directory)
201
+ if not definition_manager.has_definition_file:
202
+ raise MissingConfiguration(
203
+ "Cannot find project definition (snowflake.yml). Please provide a path to the project or run this command in a valid project directory."
204
+ )
200
205
 
201
206
  # 2. Change the project name in snowflake.yml if necessary
202
207
  _replace_snowflake_yml_name_with_project(
@@ -26,17 +26,13 @@ import jinja2
26
26
  from click import ClickException
27
27
  from snowflake.cli.api.console import cli_console as cc
28
28
  from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
29
- from snowflake.cli.api.project.definition import (
30
- default_app_package,
31
- default_application,
32
- default_role,
29
+ from snowflake.cli.api.project.schemas.native_app.application import (
30
+ ApplicationPostDeployHook,
33
31
  )
34
32
  from snowflake.cli.api.project.schemas.native_app.native_app import NativeApp
35
33
  from snowflake.cli.api.project.schemas.native_app.path_mapping import PathMapping
36
34
  from snowflake.cli.api.project.util import (
37
- extract_schema,
38
35
  identifier_for_url,
39
- to_identifier,
40
36
  unquote_identifier,
41
37
  )
42
38
  from snowflake.cli.api.sql_execution import SqlExecutionMixin
@@ -68,18 +64,21 @@ from snowflake.cli.plugins.nativeapp.exceptions import (
68
64
  SetupScriptFailedValidation,
69
65
  UnexpectedOwnerError,
70
66
  )
67
+ from snowflake.cli.plugins.nativeapp.project_model import (
68
+ NativeAppProjectModel,
69
+ )
71
70
  from snowflake.cli.plugins.nativeapp.utils import verify_exists, verify_no_directories
72
71
  from snowflake.cli.plugins.stage.diff import (
73
72
  DiffResult,
74
73
  StagePath,
75
74
  compute_stage_diff,
76
75
  preserve_from_diff,
76
+ print_diff_to_console,
77
77
  sync_local_diff_with_stage,
78
78
  to_stage_path,
79
79
  )
80
80
  from snowflake.cli.plugins.stage.manager import StageManager
81
81
  from snowflake.connector import ProgrammingError
82
- from snowflake.connector.cursor import DictCursor
83
82
 
84
83
  ApplicationOwnedObject = TypedDict("ApplicationOwnedObject", {"name": str, "type": str})
85
84
 
@@ -164,125 +163,94 @@ class NativeAppManager(SqlExecutionMixin):
164
163
 
165
164
  def __init__(self, project_definition: NativeApp, project_root: Path):
166
165
  super().__init__()
167
- self._project_root = project_root
168
- self._project_definition = project_definition
166
+ self._na_project = NativeAppProjectModel(
167
+ project_definition=project_definition,
168
+ project_root=project_root,
169
+ )
170
+
171
+ @property
172
+ def na_project(self) -> NativeAppProjectModel:
173
+ return self._na_project
169
174
 
170
175
  @property
171
176
  def project_root(self) -> Path:
172
- return self._project_root
177
+ return self.na_project.project_root
173
178
 
174
179
  @property
175
180
  def definition(self) -> NativeApp:
176
- return self._project_definition
181
+ return self.na_project.definition
177
182
 
178
- @cached_property
183
+ @property
179
184
  def artifacts(self) -> List[PathMapping]:
180
- return self.definition.artifacts
185
+ return self.na_project.artifacts
181
186
 
182
- @cached_property
187
+ @property
188
+ def bundle_root(self) -> Path:
189
+ return self.na_project.bundle_root
190
+
191
+ @property
183
192
  def deploy_root(self) -> Path:
184
- return Path(self.project_root, self.definition.deploy_root)
193
+ return self.na_project.deploy_root
185
194
 
186
- @cached_property
195
+ @property
187
196
  def generated_root(self) -> Path:
188
- return Path(self.deploy_root, self.definition.generated_root)
197
+ return self.na_project.generated_root
189
198
 
190
- @cached_property
199
+ @property
191
200
  def package_scripts(self) -> List[str]:
192
- """
193
- Relative paths to package scripts from the project root.
194
- """
195
- if self.definition.package and self.definition.package.scripts:
196
- return self.definition.package.scripts
197
- else:
198
- return []
201
+ return self.na_project.package_scripts
199
202
 
200
- @cached_property
203
+ @property
201
204
  def stage_fqn(self) -> str:
202
- return f"{self.package_name}.{self.definition.source_stage}"
205
+ return self.na_project.stage_fqn
203
206
 
204
- @cached_property
207
+ @property
205
208
  def scratch_stage_fqn(self) -> str:
206
- return f"{self.package_name}.{self.definition.scratch_stage}"
209
+ return self.na_project.scratch_stage_fqn
207
210
 
208
- @cached_property
211
+ @property
209
212
  def stage_schema(self) -> Optional[str]:
210
- return extract_schema(self.stage_fqn)
213
+ return self.na_project.stage_schema
211
214
 
212
- @cached_property
215
+ @property
213
216
  def package_warehouse(self) -> Optional[str]:
214
- if self.definition.package and self.definition.package.warehouse:
215
- return self.definition.package.warehouse
216
- else:
217
- return self._conn.warehouse
217
+ return self.na_project.package_warehouse
218
218
 
219
- @cached_property
219
+ @property
220
220
  def application_warehouse(self) -> Optional[str]:
221
- if self.definition.application and self.definition.application.warehouse:
222
- return self.definition.application.warehouse
223
- else:
224
- return self._conn.warehouse
221
+ return self.na_project.application_warehouse
225
222
 
226
- @cached_property
223
+ @property
227
224
  def project_identifier(self) -> str:
228
- # name is expected to be a valid Snowflake identifier, but PyYAML
229
- # will sometimes strip out double quotes so we try to get them back here.
230
- return to_identifier(self.definition.name)
225
+ return self.na_project.project_identifier
231
226
 
232
- @cached_property
227
+ @property
233
228
  def package_name(self) -> str:
234
- if self.definition.package and self.definition.package.name:
235
- return to_identifier(self.definition.package.name)
236
- else:
237
- return to_identifier(default_app_package(self.project_identifier))
229
+ return self.na_project.package_name
238
230
 
239
- @cached_property
231
+ @property
240
232
  def package_role(self) -> str:
241
- if self.definition.package and self.definition.package.role:
242
- return self.definition.package.role
243
- else:
244
- return self._default_role
233
+ return self.na_project.package_role
245
234
 
246
- @cached_property
235
+ @property
247
236
  def package_distribution(self) -> str:
248
- if self.definition.package and self.definition.package.distribution:
249
- return self.definition.package.distribution.lower()
250
- else:
251
- return "internal"
237
+ return self.na_project.package_distribution
252
238
 
253
- @cached_property
239
+ @property
254
240
  def app_name(self) -> str:
255
- if self.definition.application and self.definition.application.name:
256
- return to_identifier(self.definition.application.name)
257
- else:
258
- return to_identifier(default_application(self.project_identifier))
241
+ return self.na_project.app_name
259
242
 
260
- @cached_property
243
+ @property
261
244
  def app_role(self) -> str:
262
- if self.definition.application and self.definition.application.role:
263
- return self.definition.application.role
264
- else:
265
- return self._default_role
245
+ return self.na_project.app_role
266
246
 
267
- @cached_property
268
- def _default_role(self) -> str:
269
- role = default_role()
270
- if role is None:
271
- role = self._get_current_role()
272
- return role
273
-
274
- def _get_current_role(self) -> str:
275
- role_result = self._execute_query(
276
- "select current_role()", cursor_class=DictCursor
277
- ).fetchone()
278
- return role_result["CURRENT_ROLE()"]
247
+ @property
248
+ def app_post_deploy_hooks(self) -> Optional[List[ApplicationPostDeployHook]]:
249
+ return self.na_project.app_post_deploy_hooks
279
250
 
280
- @cached_property
251
+ @property
281
252
  def debug_mode(self) -> bool:
282
- if self.definition.application:
283
- return self.definition.application.debug
284
- else:
285
- return True
253
+ return self.na_project.debug_mode
286
254
 
287
255
  @cached_property
288
256
  def get_app_pkg_distribution_in_snowflake(self) -> str:
@@ -341,15 +309,12 @@ class NativeAppManager(SqlExecutionMixin):
341
309
  """
342
310
  Populates the local deploy root from artifact sources.
343
311
  """
344
- mapped_files = build_bundle(self.project_root, self.deploy_root, self.artifacts)
312
+ bundle_map = build_bundle(self.project_root, self.deploy_root, self.artifacts)
345
313
  compiler = NativeAppCompiler(
346
- project_definition=self._project_definition,
347
- project_root=self.project_root,
348
- deploy_root=self.deploy_root,
349
- generated_root=self.generated_root,
314
+ na_project=self.na_project,
350
315
  )
351
316
  compiler.compile_artifacts()
352
- return mapped_files
317
+ return bundle_map
353
318
 
354
319
  def sync_deploy_root_with_stage(
355
320
  self,
@@ -359,6 +324,7 @@ class NativeAppManager(SqlExecutionMixin):
359
324
  recursive: bool,
360
325
  stage_fqn: str,
361
326
  local_paths_to_sync: List[Path] | None = None,
327
+ print_diff: bool = True,
362
328
  ) -> DiffResult:
363
329
  """
364
330
  Ensures that the files on our remote stage match the artifacts we have in
@@ -372,6 +338,7 @@ class NativeAppManager(SqlExecutionMixin):
372
338
  stage_fqn (str): The name of the stage to diff against and upload to.
373
339
  local_paths_to_sync (List[Path], optional): List of local paths to sync. Defaults to None to sync all
374
340
  local paths. Note that providing an empty list here is equivalent to None.
341
+ print_diff (bool): Whether to print the diff between the local files and the remote stage. Defaults to True
375
342
 
376
343
  Returns:
377
344
  A `DiffResult` instance describing the changes that were performed.
@@ -394,13 +361,13 @@ class NativeAppManager(SqlExecutionMixin):
394
361
  )
395
362
 
396
363
  # Perform a diff operation and display results to the user for informational purposes
397
- cc.step(
398
- "Performing a diff between the Snowflake stage and your local deploy_root ('%s') directory."
399
- % self.deploy_root
400
- )
364
+ if print_diff:
365
+ cc.step(
366
+ "Performing a diff between the Snowflake stage and your local deploy_root ('%s') directory."
367
+ % self.deploy_root.resolve()
368
+ )
401
369
  diff: DiffResult = compute_stage_diff(self.deploy_root, stage_fqn)
402
370
 
403
- files_not_removed = []
404
371
  if local_paths_to_sync:
405
372
  # Deploying specific files/directories
406
373
  resolved_paths_to_sync = [
@@ -413,6 +380,17 @@ class NativeAppManager(SqlExecutionMixin):
413
380
  for resolved_path in resolved_paths_to_sync:
414
381
  verify_exists(resolved_path)
415
382
  deploy_paths = bundle_map.to_deploy_paths(resolved_path)
383
+ if not deploy_paths:
384
+ if resolved_path.is_dir() and recursive:
385
+ # No direct artifact mapping found for this path. Check to see
386
+ # if there are subpaths of this directory that are matches. We
387
+ # loop over sources because it's likely a much smaller list
388
+ # than the project directory.
389
+ for src in bundle_map.all_sources(absolute=True):
390
+ if resolved_path in src.parents:
391
+ # There is a source that contains this path, get its dest path(s)
392
+ deploy_paths.extend(bundle_map.to_deploy_paths(src))
393
+
416
394
  if not deploy_paths:
417
395
  raise ClickException(f"No artifact found for {resolved_path}")
418
396
  deploy_paths_to_sync.extend(deploy_paths)
@@ -424,8 +402,7 @@ class NativeAppManager(SqlExecutionMixin):
424
402
  else:
425
403
  # Full deploy
426
404
  if not recursive:
427
- deploy_files = [p for p in self.deploy_root.resolve().iterdir()]
428
- verify_no_directories(deploy_files)
405
+ verify_no_directories(self.deploy_root.resolve().iterdir())
429
406
 
430
407
  if not prune:
431
408
  files_not_removed = [str(path) for path in diff.only_on_stage]
@@ -437,13 +414,14 @@ class NativeAppManager(SqlExecutionMixin):
437
414
  f"The following files exist only on the stage:\n{files_not_removed_str}\n\nUse the --prune flag to delete them from the stage."
438
415
  )
439
416
 
440
- cc.message(str(diff))
417
+ if print_diff:
418
+ print_diff_to_console(diff, bundle_map)
441
419
 
442
420
  # Upload diff-ed files to application package stage
443
421
  if diff.has_changes():
444
422
  cc.step(
445
- "Uploading diff-ed files from your local %s directory to the Snowflake stage."
446
- % self.deploy_root,
423
+ "Updating the Snowflake stage from your local %s directory."
424
+ % self.deploy_root.resolve(),
447
425
  )
448
426
  sync_local_diff_with_stage(
449
427
  role=role,
@@ -484,6 +462,23 @@ class NativeAppManager(SqlExecutionMixin):
484
462
  ).fetchall()
485
463
  return [{"name": row[1], "type": row[2]} for row in results]
486
464
 
465
+ def _application_objects_to_str(
466
+ self, application_objects: list[ApplicationOwnedObject]
467
+ ) -> str:
468
+ """
469
+ Returns a list in an "(Object Type) Object Name" format. Database-level and schema-level object names are fully qualified:
470
+ (COMPUTE_POOL) POOL_NAME
471
+ (DATABASE) DB_NAME
472
+ (SCHEMA) DB_NAME.PUBLIC
473
+ ...
474
+ """
475
+ return "\n".join(
476
+ [self._application_object_to_str(obj) for obj in application_objects]
477
+ )
478
+
479
+ def _application_object_to_str(self, obj: ApplicationOwnedObject) -> str:
480
+ return f"({obj['type']}) {obj['name']}"
481
+
487
482
  def get_snowsight_url(self) -> str:
488
483
  """Returns the URL that can be used to visit this app via Snowsight."""
489
484
  name = identifier_for_url(self.app_name)
@@ -580,6 +575,7 @@ class NativeAppManager(SqlExecutionMixin):
580
575
  stage_fqn: Optional[str] = None,
581
576
  local_paths_to_sync: List[Path] | None = None,
582
577
  validate: bool = True,
578
+ print_diff: bool = True,
583
579
  ) -> DiffResult:
584
580
  """app deploy process"""
585
581
 
@@ -599,6 +595,7 @@ class NativeAppManager(SqlExecutionMixin):
599
595
  recursive=recursive,
600
596
  stage_fqn=stage_fqn,
601
597
  local_paths_to_sync=local_paths_to_sync,
598
+ print_diff=print_diff,
602
599
  )
603
600
 
604
601
  if validate:
@@ -637,6 +634,7 @@ class NativeAppManager(SqlExecutionMixin):
637
634
  recursive=True,
638
635
  stage_fqn=stage_fqn,
639
636
  validate=False,
637
+ print_diff=False,
640
638
  )
641
639
  prefixed_stage_fqn = StageManager.get_standard_stage_prefix(stage_fqn)
642
640
  try:
@@ -0,0 +1,181 @@
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 __future__ import annotations
16
+
17
+ from functools import cached_property
18
+ from pathlib import Path
19
+ from typing import List, Optional
20
+
21
+ from snowflake.cli.api.cli_global_context import cli_context
22
+ from snowflake.cli.api.project.definition import (
23
+ default_app_package,
24
+ default_application,
25
+ default_role,
26
+ )
27
+ from snowflake.cli.api.project.schemas.native_app.application import (
28
+ ApplicationPostDeployHook,
29
+ )
30
+ from snowflake.cli.api.project.schemas.native_app.native_app import NativeApp
31
+ from snowflake.cli.api.project.schemas.native_app.path_mapping import PathMapping
32
+ from snowflake.cli.api.project.util import extract_schema, to_identifier
33
+ from snowflake.cli.plugins.nativeapp.artifacts import resolve_without_follow
34
+ from snowflake.connector import DictCursor
35
+
36
+
37
+ def current_role() -> str:
38
+ conn = cli_context.connection
39
+ *_, cursor = conn.execute_string("select current_role()", cursor_class=DictCursor)
40
+ role_result = cursor.fetchone()
41
+ return role_result["CURRENT_ROLE()"]
42
+
43
+
44
+ class NativeAppProjectModel:
45
+ """
46
+ Exposes properties of a native app project defined in a Snowflake Project Definition file. Whenever
47
+ appropriate, APIs defined in this class provide suitable defaults or fallback logic to access properties
48
+ of the project.
49
+ """
50
+
51
+ def __init__(
52
+ self,
53
+ project_definition: NativeApp,
54
+ project_root: Path,
55
+ ):
56
+ self._project_definition = project_definition
57
+ self._project_root = resolve_without_follow(project_root)
58
+
59
+ @property
60
+ def project_root(self) -> Path:
61
+ return self._project_root
62
+
63
+ @property
64
+ def definition(self) -> NativeApp:
65
+ return self._project_definition
66
+
67
+ @cached_property
68
+ def artifacts(self) -> List[PathMapping]:
69
+ return self.definition.artifacts
70
+
71
+ @cached_property
72
+ def bundle_root(self) -> Path:
73
+ return self.project_root / self.definition.bundle_root
74
+
75
+ @cached_property
76
+ def deploy_root(self) -> Path:
77
+ return self.project_root / self.definition.deploy_root
78
+
79
+ @cached_property
80
+ def generated_root(self) -> Path:
81
+ return self.deploy_root / self.definition.generated_root
82
+
83
+ @cached_property
84
+ def package_scripts(self) -> List[str]:
85
+ """
86
+ Relative paths to package scripts from the project root.
87
+ """
88
+ if self.definition.package and self.definition.package.scripts:
89
+ return self.definition.package.scripts
90
+ else:
91
+ return []
92
+
93
+ @cached_property
94
+ def stage_fqn(self) -> str:
95
+ return f"{self.package_name}.{self.definition.source_stage}"
96
+
97
+ @cached_property
98
+ def scratch_stage_fqn(self) -> str:
99
+ return f"{self.package_name}.{self.definition.scratch_stage}"
100
+
101
+ @cached_property
102
+ def stage_schema(self) -> Optional[str]:
103
+ return extract_schema(self.stage_fqn)
104
+
105
+ @cached_property
106
+ def package_warehouse(self) -> Optional[str]:
107
+ if self.definition.package and self.definition.package.warehouse:
108
+ return self.definition.package.warehouse
109
+ else:
110
+ return cli_context.connection.warehouse
111
+
112
+ @cached_property
113
+ def application_warehouse(self) -> Optional[str]:
114
+ if self.definition.application and self.definition.application.warehouse:
115
+ return self.definition.application.warehouse
116
+ else:
117
+ return cli_context.connection.warehouse
118
+
119
+ @cached_property
120
+ def project_identifier(self) -> str:
121
+ # name is expected to be a valid Snowflake identifier, but YAML parsers will
122
+ # sometimes strip out double quotes, so we try to get them back here.
123
+ return to_identifier(self.definition.name)
124
+
125
+ @cached_property
126
+ def package_name(self) -> str:
127
+ if self.definition.package and self.definition.package.name:
128
+ return to_identifier(self.definition.package.name)
129
+ else:
130
+ return to_identifier(default_app_package(self.project_identifier))
131
+
132
+ @cached_property
133
+ def package_role(self) -> str:
134
+ if self.definition.package and self.definition.package.role:
135
+ return self.definition.package.role
136
+ else:
137
+ return self._default_role
138
+
139
+ @cached_property
140
+ def package_distribution(self) -> str:
141
+ if self.definition.package and self.definition.package.distribution:
142
+ return self.definition.package.distribution.lower()
143
+ else:
144
+ return "internal"
145
+
146
+ @cached_property
147
+ def app_name(self) -> str:
148
+ if self.definition.application and self.definition.application.name:
149
+ return to_identifier(self.definition.application.name)
150
+ else:
151
+ return to_identifier(default_application(self.project_identifier))
152
+
153
+ @cached_property
154
+ def app_role(self) -> str:
155
+ if self.definition.application and self.definition.application.role:
156
+ return self.definition.application.role
157
+ else:
158
+ return self._default_role
159
+
160
+ @cached_property
161
+ def app_post_deploy_hooks(self) -> Optional[List[ApplicationPostDeployHook]]:
162
+ """
163
+ List of application post deploy hooks.
164
+ """
165
+ if self.definition.application and self.definition.application.post_deploy:
166
+ return self.definition.application.post_deploy
167
+ else:
168
+ return None
169
+
170
+ @cached_property
171
+ def _default_role(self) -> str:
172
+ role = default_role()
173
+ if role is None:
174
+ role = current_role()
175
+ return role
176
+
177
+ @cached_property
178
+ def debug_mode(self) -> Optional[bool]:
179
+ if self.definition.application:
180
+ return self.definition.application.debug
181
+ return None