snowflake-cli-labs 3.0.0rc1__py3-none-any.whl → 3.0.0rc3__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 (92) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/_app/cli_app.py +10 -1
  3. snowflake/cli/_app/commands_registration/builtin_plugins.py +2 -0
  4. snowflake/cli/_app/secret.py +9 -0
  5. snowflake/cli/_app/snow_connector.py +110 -51
  6. snowflake/cli/_app/telemetry.py +8 -4
  7. snowflake/cli/_app/version_check.py +74 -0
  8. snowflake/cli/_plugins/git/commands.py +55 -14
  9. snowflake/cli/_plugins/git/manager.py +53 -7
  10. snowflake/cli/_plugins/helpers/commands.py +57 -0
  11. snowflake/cli/{api/commands/typer_pre_execute.py → _plugins/helpers/plugin_spec.py} +14 -10
  12. snowflake/cli/_plugins/nativeapp/application_entity.py +651 -0
  13. snowflake/cli/{api/project/schemas/entities → _plugins/nativeapp}/application_entity_model.py +2 -2
  14. snowflake/cli/_plugins/nativeapp/application_package_entity.py +1107 -0
  15. snowflake/cli/{api/project/schemas/entities → _plugins/nativeapp}/application_package_entity_model.py +3 -3
  16. snowflake/cli/_plugins/nativeapp/artifacts.py +10 -9
  17. snowflake/cli/_plugins/nativeapp/bundle_context.py +1 -1
  18. snowflake/cli/_plugins/nativeapp/codegen/artifact_processor.py +1 -1
  19. snowflake/cli/_plugins/nativeapp/codegen/compiler.py +1 -1
  20. snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +1 -1
  21. snowflake/cli/_plugins/nativeapp/codegen/snowpark/extension_function_utils.py +1 -1
  22. snowflake/cli/_plugins/nativeapp/codegen/snowpark/models.py +1 -1
  23. snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +3 -6
  24. snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +50 -32
  25. snowflake/cli/_plugins/nativeapp/commands.py +84 -16
  26. snowflake/cli/_plugins/nativeapp/exceptions.py +0 -9
  27. snowflake/cli/_plugins/nativeapp/manager.py +56 -92
  28. snowflake/cli/_plugins/nativeapp/policy.py +3 -0
  29. snowflake/cli/_plugins/nativeapp/project_model.py +2 -2
  30. snowflake/cli/_plugins/nativeapp/run_processor.py +65 -272
  31. snowflake/cli/_plugins/nativeapp/same_account_install_method.py +70 -0
  32. snowflake/cli/_plugins/nativeapp/teardown_processor.py +11 -154
  33. snowflake/cli/_plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +150 -40
  34. snowflake/cli/_plugins/nativeapp/version/commands.py +6 -24
  35. snowflake/cli/_plugins/nativeapp/version/version_processor.py +35 -235
  36. snowflake/cli/_plugins/snowpark/commands.py +5 -5
  37. snowflake/cli/_plugins/snowpark/common.py +4 -4
  38. snowflake/cli/_plugins/snowpark/models.py +2 -1
  39. snowflake/cli/{api/entities → _plugins/snowpark}/snowpark_entity.py +2 -2
  40. snowflake/cli/{api/project/schemas/entities/snowpark_entity.py → _plugins/snowpark/snowpark_entity_model.py} +3 -6
  41. snowflake/cli/_plugins/snowpark/snowpark_project_paths.py +1 -1
  42. snowflake/cli/_plugins/stage/manager.py +9 -4
  43. snowflake/cli/_plugins/streamlit/commands.py +4 -4
  44. snowflake/cli/_plugins/streamlit/manager.py +17 -4
  45. snowflake/cli/{api/entities → _plugins/streamlit}/streamlit_entity.py +2 -2
  46. snowflake/cli/{api/project/schemas/entities → _plugins/streamlit}/streamlit_entity_model.py +5 -12
  47. snowflake/cli/_plugins/workspace/action_context.py +2 -1
  48. snowflake/cli/_plugins/workspace/commands.py +127 -48
  49. snowflake/cli/_plugins/workspace/manager.py +1 -0
  50. snowflake/cli/_plugins/workspace/plugin_spec.py +1 -1
  51. snowflake/cli/api/cli_global_context.py +136 -313
  52. snowflake/cli/api/commands/flags.py +76 -91
  53. snowflake/cli/api/commands/snow_typer.py +7 -5
  54. snowflake/cli/api/config.py +1 -1
  55. snowflake/cli/api/connections.py +214 -0
  56. snowflake/cli/api/console/abc.py +4 -2
  57. snowflake/cli/api/entities/common.py +4 -0
  58. snowflake/cli/api/entities/utils.py +41 -31
  59. snowflake/cli/api/errno.py +1 -0
  60. snowflake/cli/api/identifiers.py +7 -3
  61. snowflake/cli/api/project/definition.py +11 -0
  62. snowflake/cli/api/project/definition_conversion.py +175 -16
  63. snowflake/cli/api/project/schemas/entities/common.py +15 -14
  64. snowflake/cli/api/project/schemas/entities/entities.py +13 -10
  65. snowflake/cli/api/project/schemas/project_definition.py +107 -45
  66. snowflake/cli/api/project/schemas/v1/__init__.py +0 -0
  67. snowflake/cli/api/project/schemas/{identifier_model.py → v1/identifier_model.py} +0 -7
  68. snowflake/cli/api/project/schemas/v1/native_app/__init__.py +0 -0
  69. snowflake/cli/api/project/schemas/{native_app → v1/native_app}/native_app.py +4 -4
  70. snowflake/cli/api/project/schemas/v1/snowpark/__init__.py +0 -0
  71. snowflake/cli/api/project/schemas/{snowpark → v1/snowpark}/callable.py +2 -2
  72. snowflake/cli/api/project/schemas/{snowpark → v1/snowpark}/snowpark.py +2 -2
  73. snowflake/cli/api/project/schemas/v1/streamlit/__init__.py +0 -0
  74. snowflake/cli/api/project/schemas/{streamlit → v1/streamlit}/streamlit.py +2 -1
  75. snowflake/cli/api/rendering/project_definition_templates.py +4 -0
  76. snowflake/cli/api/rendering/sql_templates.py +7 -0
  77. snowflake/cli/api/sql_execution.py +6 -15
  78. snowflake/cli/api/utils/definition_rendering.py +3 -1
  79. {snowflake_cli_labs-3.0.0rc1.dist-info → snowflake_cli_labs-3.0.0rc3.dist-info}/METADATA +9 -9
  80. {snowflake_cli_labs-3.0.0rc1.dist-info → snowflake_cli_labs-3.0.0rc3.dist-info}/RECORD +88 -81
  81. snowflake/cli/api/entities/application_entity.py +0 -12
  82. snowflake/cli/api/entities/application_package_entity.py +0 -553
  83. snowflake/cli/api/project/schemas/snowpark/__init__.py +0 -13
  84. snowflake/cli/api/project/schemas/streamlit/__init__.py +0 -13
  85. /snowflake/cli/{api/project/schemas/native_app → _plugins/helpers}/__init__.py +0 -0
  86. /snowflake/cli/api/project/schemas/{native_app → v1/native_app}/application.py +0 -0
  87. /snowflake/cli/api/project/schemas/{native_app → v1/native_app}/package.py +0 -0
  88. /snowflake/cli/api/project/schemas/{native_app → v1/native_app}/path_mapping.py +0 -0
  89. /snowflake/cli/api/project/schemas/{snowpark → v1/snowpark}/argument.py +0 -0
  90. {snowflake_cli_labs-3.0.0rc1.dist-info → snowflake_cli_labs-3.0.0rc3.dist-info}/WHEEL +0 -0
  91. {snowflake_cli_labs-3.0.0rc1.dist-info → snowflake_cli_labs-3.0.0rc3.dist-info}/entry_points.txt +0 -0
  92. {snowflake_cli_labs-3.0.0rc1.dist-info → snowflake_cli_labs-3.0.0rc3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,70 @@
1
+ from typing import Optional
2
+
3
+ from snowflake.cli._plugins.nativeapp.constants import (
4
+ ALLOWED_SPECIAL_COMMENTS,
5
+ COMMENT_COL,
6
+ )
7
+ from snowflake.cli._plugins.nativeapp.exceptions import (
8
+ ApplicationCreatedExternallyError,
9
+ )
10
+
11
+ # from snowflake.cli._plugins.nativeapp.project_model import NativeAppProjectModel
12
+ from snowflake.cli._plugins.stage.manager import StageManager
13
+
14
+
15
+ class SameAccountInstallMethod:
16
+ _requires_created_by_cli: bool
17
+ _from_release_directive: bool
18
+ version: Optional[str]
19
+ patch: Optional[int]
20
+
21
+ def __init__(
22
+ self,
23
+ requires_created_by_cli: bool,
24
+ version: Optional[str] = None,
25
+ patch: Optional[int] = None,
26
+ from_release_directive: bool = False,
27
+ ):
28
+ self._requires_created_by_cli = requires_created_by_cli
29
+ self.version = version
30
+ self.patch = patch
31
+ self._from_release_directive = from_release_directive
32
+
33
+ @classmethod
34
+ def unversioned_dev(cls):
35
+ """aka. stage dev aka loose files"""
36
+ return cls(True)
37
+
38
+ @classmethod
39
+ def versioned_dev(cls, version: str, patch: Optional[int] = None):
40
+ return cls(False, version, patch)
41
+
42
+ @classmethod
43
+ def release_directive(cls):
44
+ return cls(False, from_release_directive=True)
45
+
46
+ @property
47
+ def is_dev_mode(self) -> bool:
48
+ return not self._from_release_directive
49
+
50
+ def using_clause(
51
+ self,
52
+ stage_fqn: str,
53
+ ) -> str:
54
+ if self._from_release_directive:
55
+ return ""
56
+
57
+ if self.version:
58
+ patch_clause = f"patch {self.patch}" if self.patch else ""
59
+ return f"using version {self.version} {patch_clause}"
60
+
61
+ stage_name = StageManager.quote_stage_name(stage_fqn)
62
+ return f"using {stage_name}"
63
+
64
+ def ensure_app_usable(self, app_name: str, app_role: str, show_app_row: dict):
65
+ """Raise an exception if we cannot proceed with install given the pre-existing application object"""
66
+
67
+ if self._requires_created_by_cli:
68
+ if show_app_row[COMMENT_COL] not in ALLOWED_SPECIAL_COMMENTS:
69
+ # this application object was not created by this tooling
70
+ raise ApplicationCreatedExternallyError(app_name)
@@ -15,179 +15,36 @@
15
15
  from __future__ import annotations
16
16
 
17
17
  from pathlib import Path
18
- from textwrap import dedent
19
18
  from typing import Dict, Optional
20
19
 
21
- import typer
22
- from snowflake.cli._plugins.nativeapp.constants import (
23
- ALLOWED_SPECIAL_COMMENTS,
24
- COMMENT_COL,
25
- OWNER_COL,
20
+ from snowflake.cli._plugins.nativeapp.application_entity import (
21
+ ApplicationEntity,
22
+ )
23
+ from snowflake.cli._plugins.nativeapp.application_package_entity import (
24
+ ApplicationPackageEntity,
26
25
  )
27
26
  from snowflake.cli._plugins.nativeapp.manager import (
28
27
  NativeAppCommandProcessor,
29
28
  NativeAppManager,
30
29
  )
31
- from snowflake.cli._plugins.nativeapp.utils import (
32
- needs_confirmation,
33
- )
34
30
  from snowflake.cli.api.console import cli_console as cc
35
- from snowflake.cli.api.entities.application_package_entity import (
36
- ApplicationPackageEntity,
37
- )
38
- from snowflake.cli.api.entities.utils import (
39
- drop_generic_object,
40
- ensure_correct_owner,
41
- )
42
- from snowflake.cli.api.errno import APPLICATION_NO_LONGER_AVAILABLE
43
- from snowflake.connector import ProgrammingError
44
31
 
45
32
 
46
33
  class NativeAppTeardownProcessor(NativeAppManager, NativeAppCommandProcessor):
47
34
  def __init__(self, project_definition: Dict, project_root: Path):
48
35
  super().__init__(project_definition, project_root)
49
36
 
50
- def drop_generic_object(
51
- self, object_type: str, object_name: str, role: str, cascade: bool = False
52
- ):
53
- return drop_generic_object(
54
- console=cc,
55
- object_type=object_type,
56
- object_name=object_name,
57
- role=role,
58
- cascade=cascade,
59
- )
60
-
61
37
  def drop_application(
62
38
  self, auto_yes: bool, interactive: bool = False, cascade: Optional[bool] = None
63
39
  ):
64
- """
65
- Attempts to drop the application object if all validations and user prompts allow so.
66
- """
67
-
68
- needs_confirm = True
69
-
70
- # 1. If existing application is not found, exit gracefully
71
- show_obj_row = self.get_existing_app_info()
72
- if show_obj_row is None:
73
- cc.warning(
74
- f"Role {self.app_role} does not own any application object with the name {self.app_name}, or the application object does not exist."
75
- )
76
- return
77
-
78
- # 2. Check for the right owner
79
- ensure_correct_owner(
80
- row=show_obj_row, role=self.app_role, obj_name=self.app_name
81
- )
82
-
83
- # 3. Check if created by the Snowflake CLI
84
- row_comment = show_obj_row[COMMENT_COL]
85
- if row_comment not in ALLOWED_SPECIAL_COMMENTS and needs_confirmation(
86
- needs_confirm, auto_yes
87
- ):
88
- should_drop_object = typer.confirm(
89
- dedent(
90
- f"""\
91
- Application object {self.app_name} was not created by Snowflake CLI.
92
- Application object details:
93
- Name: {self.app_name}
94
- Created on: {show_obj_row["created_on"]}
95
- Source: {show_obj_row["source"]}
96
- Owner: {show_obj_row[OWNER_COL]}
97
- Comment: {show_obj_row[COMMENT_COL]}
98
- Version: {show_obj_row["version"]}
99
- Patch: {show_obj_row["patch"]}
100
- Are you sure you want to drop it?
101
- """
102
- )
103
- )
104
- if not should_drop_object:
105
- cc.message(f"Did not drop application object {self.app_name}.")
106
- # The user desires to keep the app, therefore we can't proceed since it would
107
- # leave behind an orphan app when we get to dropping the package
108
- raise typer.Abort()
109
-
110
- # 4. Check for application objects owned by the application
111
- # This query will fail if the application package has already been dropped, so handle this case gracefully
112
- has_objects_to_drop = False
113
- message_prefix = ""
114
- cascade_true_message = ""
115
- cascade_false_message = ""
116
- interactive_prompt = ""
117
- non_interactive_abort = ""
118
- try:
119
- if application_objects := self.get_objects_owned_by_application():
120
- has_objects_to_drop = True
121
- message_prefix = (
122
- f"The following objects are owned by application {self.app_name}"
123
- )
124
- cascade_true_message = f"{message_prefix} and will be dropped:"
125
- cascade_false_message = f"{message_prefix} and will NOT be dropped:"
126
- interactive_prompt = "Would you like to drop these objects in addition to the application? [y/n/ABORT]"
127
- non_interactive_abort = "Re-run teardown again with --cascade or --no-cascade to specify whether these objects should be dropped along with the application"
128
- except ProgrammingError as e:
129
- if e.errno != APPLICATION_NO_LONGER_AVAILABLE:
130
- raise
131
- application_objects = []
132
- message_prefix = f"Could not determine which objects are owned by application {self.app_name}"
133
- has_objects_to_drop = True # potentially, but we don't know what they are
134
- cascade_true_message = (
135
- f"{message_prefix}, an unknown number of objects will be dropped."
136
- )
137
- cascade_false_message = f"{message_prefix}, they will NOT be dropped."
138
- interactive_prompt = f"Would you like to drop an unknown set of objects in addition to the application? [y/n/ABORT]"
139
- non_interactive_abort = f"Re-run teardown again with --cascade or --no-cascade to specify whether any objects should be dropped along with the application."
140
-
141
- if has_objects_to_drop:
142
- if cascade is True:
143
- # If the user explicitly passed the --cascade flag
144
- cc.message(cascade_true_message)
145
- with cc.indented():
146
- for obj in application_objects:
147
- cc.message(self._application_object_to_str(obj))
148
- elif cascade is False:
149
- # If the user explicitly passed the --no-cascade flag
150
- cc.message(cascade_false_message)
151
- with cc.indented():
152
- for obj in application_objects:
153
- cc.message(self._application_object_to_str(obj))
154
- elif interactive:
155
- # If the user didn't pass any cascade flag and the session is interactive
156
- cc.message(message_prefix)
157
- with cc.indented():
158
- for obj in application_objects:
159
- cc.message(self._application_object_to_str(obj))
160
- user_response = typer.prompt(
161
- interactive_prompt,
162
- show_default=False,
163
- default="ABORT",
164
- ).lower()
165
- if user_response in ["y", "yes"]:
166
- cascade = True
167
- elif user_response in ["n", "no"]:
168
- cascade = False
169
- else:
170
- raise typer.Abort()
171
- else:
172
- # Else abort since we don't know what to do and can't ask the user
173
- cc.message(message_prefix)
174
- with cc.indented():
175
- for obj in application_objects:
176
- cc.message(self._application_object_to_str(obj))
177
- cc.message(non_interactive_abort)
178
- raise typer.Abort()
179
- elif cascade is None:
180
- # If there's nothing to drop, set cascade to an explicit False value
181
- cascade = False
182
-
183
- # 5. All validations have passed, drop object
184
- self.drop_generic_object(
185
- object_type="application",
186
- object_name=self.app_name,
187
- role=self.app_role,
40
+ return ApplicationEntity.drop(
41
+ console=cc,
42
+ app_name=self.app_name,
43
+ app_role=self.app_role,
44
+ auto_yes=auto_yes,
45
+ interactive=interactive,
188
46
  cascade=cascade,
189
47
  )
190
- return # The application object was successfully dropped, therefore exit gracefully
191
48
 
192
49
  def drop_package(self, auto_yes: bool):
193
50
  return ApplicationPackageEntity.drop(
@@ -14,25 +14,29 @@
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
+ import inspect
17
18
  from functools import wraps
18
- from typing import Any, Dict, Optional, Union
19
+ from typing import Any, Dict, Optional, Type, TypeVar, Union
19
20
 
21
+ import typer
20
22
  from click import ClickException
21
- from snowflake.cli.api.cli_global_context import (
22
- get_cli_context,
23
- get_cli_context_manager,
24
- )
25
- from snowflake.cli.api.project.schemas.entities.application_entity_model import (
23
+ from snowflake.cli._plugins.nativeapp.application_entity_model import (
26
24
  ApplicationEntityModel,
27
25
  )
28
- from snowflake.cli.api.project.schemas.entities.application_package_entity_model import (
26
+ from snowflake.cli._plugins.nativeapp.application_package_entity_model import (
29
27
  ApplicationPackageEntityModel,
30
28
  )
31
- from snowflake.cli.api.project.schemas.native_app.path_mapping import PathMapping
29
+ from snowflake.cli.api.cli_global_context import (
30
+ get_cli_context,
31
+ get_cli_context_manager,
32
+ )
33
+ from snowflake.cli.api.commands.decorators import _options_decorator_factory
34
+ from snowflake.cli.api.project.schemas.entities.common import EntityModelBase
32
35
  from snowflake.cli.api.project.schemas.project_definition import (
33
36
  DefinitionV11,
34
37
  DefinitionV20,
35
38
  )
39
+ from snowflake.cli.api.project.schemas.v1.native_app.path_mapping import PathMapping
36
40
  from snowflake.cli.api.utils.definition_rendering import render_definition_template
37
41
 
38
42
 
@@ -48,29 +52,53 @@ def _convert_v2_artifact_to_v1_dict(
48
52
  return v2_artifact
49
53
 
50
54
 
51
- def _pdf_v2_to_v1(v2_definition: DefinitionV20) -> DefinitionV11:
55
+ def _pdf_v2_to_v1(
56
+ v2_definition: DefinitionV20,
57
+ package_entity_id: str = "",
58
+ app_entity_id: str = "",
59
+ app_required: bool = False,
60
+ ) -> DefinitionV11:
52
61
  pdfv1: Dict[str, Any] = {"definition_version": "1.1", "native_app": {}}
53
62
 
54
- app_package_definition: Optional[ApplicationPackageEntityModel] = None
55
- app_definition: Optional[ApplicationEntityModel] = None
63
+ # Determine the application entity to convert, there can be zero or one
64
+ app_definition = find_entity(
65
+ v2_definition,
66
+ ApplicationEntityModel,
67
+ app_entity_id,
68
+ disambiguation_option="--app-entity-id",
69
+ required=app_required,
70
+ )
56
71
 
57
- for key, entity in v2_definition.entities.items():
58
- if entity.get_type() == ApplicationPackageEntityModel.get_type():
59
- if app_package_definition:
60
- raise ClickException(
61
- "More than one application package entity exists in the project definition file."
62
- )
63
- app_package_definition = entity
64
- elif entity.get_type() == ApplicationEntityModel.get_type():
65
- if app_definition:
72
+ # Infer or verify the package if we have an app entity to convert
73
+ if app_definition:
74
+ target_package = app_definition.from_.target
75
+ if package_entity_id:
76
+ # If the user specified a package entity ID,
77
+ # check that the app entity targets the user-specified package entity
78
+ # if the app entity is used by the command being run
79
+ if target_package != package_entity_id and app_required:
66
80
  raise ClickException(
67
- "More than one application entity exists in the project definition file."
81
+ f"The application entity {app_definition.entity_id} does not "
82
+ f"target the application package entity {package_entity_id}. Either"
83
+ f"use --package-entity-id {target_package} to target the correct package entity, "
84
+ f"or omit the --package-entity-id flag to automatically use the package entity "
85
+ f"that the application entity targets."
68
86
  )
69
- app_definition = entity
70
- if not app_package_definition:
71
- raise ClickException(
72
- "Could not find an application package entity in the project definition file."
73
- )
87
+ elif target_package in v2_definition.get_entities_by_type(
88
+ ApplicationPackageEntityModel.get_type()
89
+ ):
90
+ # If the user didn't target a specific package entity, use the one the app entity targets
91
+ package_entity_id = target_package
92
+
93
+ # Determine the package entity to convert, there must be one
94
+ app_package_definition = find_entity(
95
+ v2_definition,
96
+ ApplicationPackageEntityModel,
97
+ package_entity_id,
98
+ disambiguation_option="--package-entity-id",
99
+ required=True,
100
+ )
101
+ assert app_package_definition is not None # satisfy mypy
74
102
 
75
103
  # NativeApp
76
104
  if app_definition and app_definition.fqn.identifier:
@@ -130,7 +158,60 @@ def _pdf_v2_to_v1(v2_definition: DefinitionV20) -> DefinitionV11:
130
158
  return result.project_definition
131
159
 
132
160
 
133
- def nativeapp_definition_v2_to_v1(func):
161
+ T = TypeVar("T", bound=EntityModelBase)
162
+
163
+
164
+ def find_entity(
165
+ project_definition: DefinitionV20,
166
+ entity_class: Type[T],
167
+ entity_id: str,
168
+ disambiguation_option: str,
169
+ required: bool,
170
+ ) -> T | None:
171
+ """
172
+ Find an entity of the specified type in the project definition file.
173
+
174
+ If an ID is passed, only that entity will be considered,
175
+ otherwise look for a single entity of the specified type.
176
+
177
+ If there are multiple entities of the specified type,
178
+ the user must specify which one to use using the CLI option
179
+ named in the disambiguation_option parameter.
180
+
181
+ If no entity is found, an error is raised if required is True,
182
+ otherwise None is returned.
183
+ """
184
+
185
+ entity_type = entity_class.get_type()
186
+ entities = project_definition.get_entities_by_type(entity_type)
187
+
188
+ entity: Optional[T] = None
189
+
190
+ if entity_id:
191
+ # If we're looking for a specific entity, use that one directly
192
+ entity = entities.get(entity_id)
193
+ elif len(entities) == 1:
194
+ # Otherwise, if there is only one entity, fall back to that one
195
+ entity = next(iter(entities.values()))
196
+ elif len(entities) > 1 and required:
197
+ # If there are multiple entities and it's required,
198
+ # the user must specify which one to use
199
+ raise ClickException(
200
+ f"More than one {entity_type} entity exists in the project definition file, "
201
+ f"specify {disambiguation_option} to choose which one to operate on."
202
+ )
203
+
204
+ # If we don't have a package entity to convert, error out if it's required
205
+ if not entity and required:
206
+ with_id = f'with ID "{entity_id}" ' if entity_id else ""
207
+ raise ClickException(
208
+ f"Could not find an {entity_type} entity {with_id}in the project definition file."
209
+ )
210
+
211
+ return entity
212
+
213
+
214
+ def nativeapp_definition_v2_to_v1(*, app_required: bool = False):
134
215
  """
135
216
  A command decorator that attempts to automatically convert a native app project from
136
217
  definition v2 to v1.1. Assumes with_project_definition() has already been called.
@@ -139,16 +220,45 @@ def nativeapp_definition_v2_to_v1(func):
139
220
  entity type is expected.
140
221
  """
141
222
 
142
- @wraps(func)
143
- def wrapper(*args, **kwargs):
144
- original_pdf: DefinitionV20 = get_cli_context().project_definition
145
- if not original_pdf:
146
- raise ValueError(
147
- "Project definition could not be found. The nativeapp_definition_v2_to_v1 command decorator assumes with_project_definition() was called before it."
148
- )
149
- if original_pdf.definition_version == "2":
150
- pdfv1 = _pdf_v2_to_v1(original_pdf)
151
- get_cli_context_manager().set_project_definition(pdfv1)
152
- return func(*args, **kwargs)
153
-
154
- return wrapper
223
+ def decorator(func):
224
+ @wraps(func)
225
+ def wrapper(*args, **kwargs):
226
+ original_pdf: Optional[DefinitionV20] = get_cli_context().project_definition
227
+ if not original_pdf:
228
+ raise ValueError(
229
+ "Project definition could not be found. The nativeapp_definition_v2_to_v1 command decorator assumes with_project_definition() was called before it."
230
+ )
231
+ if original_pdf.definition_version == "2":
232
+ package_entity_id = kwargs.get("package_entity_id", "")
233
+ app_entity_id = kwargs.get("app_entity_id", "")
234
+ pdfv1 = _pdf_v2_to_v1(
235
+ original_pdf, package_entity_id, app_entity_id, app_required
236
+ )
237
+ get_cli_context_manager().override_project_definition = pdfv1
238
+ return func(*args, **kwargs)
239
+
240
+ return _options_decorator_factory(
241
+ wrapper,
242
+ additional_options=[
243
+ inspect.Parameter(
244
+ "package_entity_id",
245
+ inspect.Parameter.KEYWORD_ONLY,
246
+ annotation=Optional[str],
247
+ default=typer.Option(
248
+ default="",
249
+ help="The ID of the package entity on which to operate when definition_version is 2 or higher.",
250
+ ),
251
+ ),
252
+ inspect.Parameter(
253
+ "app_entity_id",
254
+ inspect.Parameter.KEYWORD_ONLY,
255
+ annotation=Optional[str],
256
+ default=typer.Option(
257
+ default="",
258
+ help="The ID of the application entity on which to operate when definition_version is 2 or higher.",
259
+ ),
260
+ ),
261
+ ],
262
+ )
263
+
264
+ return decorator
@@ -51,7 +51,7 @@ log = logging.getLogger(__name__)
51
51
 
52
52
  @app.command(requires_connection=True)
53
53
  @with_project_definition()
54
- @nativeapp_definition_v2_to_v1
54
+ @nativeapp_definition_v2_to_v1()
55
55
  def create(
56
56
  version: Optional[str] = typer.Argument(
57
57
  None,
@@ -82,42 +82,24 @@ def create(
82
82
  if version is None and patch is not None:
83
83
  raise MissingParameter("Cannot provide a patch without version!")
84
84
 
85
- is_interactive = False
86
- if force:
87
- policy = AllowAlwaysPolicy()
88
- elif interactive:
89
- is_interactive = True
90
- policy = AskAlwaysPolicy()
91
- else:
92
- policy = DenyAlwaysPolicy()
93
-
94
- if skip_git_check:
95
- git_policy = DenyAlwaysPolicy()
96
- else:
97
- git_policy = AllowAlwaysPolicy()
98
-
99
85
  cli_context = get_cli_context()
100
86
  processor = NativeAppVersionCreateProcessor(
101
87
  project_definition=cli_context.project_definition.native_app,
102
88
  project_root=cli_context.project_root,
103
89
  )
104
-
105
- # We need build_bundle() to (optionally) find version in manifest.yml and create an application package
106
- bundle_map = processor.build_bundle()
107
90
  processor.process(
108
- bundle_map=bundle_map,
109
91
  version=version,
110
92
  patch=patch,
111
- policy=policy,
112
- git_policy=git_policy,
113
- is_interactive=is_interactive,
93
+ force=force,
94
+ interactive=interactive,
95
+ skip_git_check=skip_git_check,
114
96
  )
115
97
  return MessageResult(f"Version create is now complete.")
116
98
 
117
99
 
118
100
  @app.command("list", requires_connection=True)
119
101
  @with_project_definition()
120
- @nativeapp_definition_v2_to_v1
102
+ @nativeapp_definition_v2_to_v1()
121
103
  def version_list(
122
104
  **options,
123
105
  ) -> CommandResult:
@@ -138,7 +120,7 @@ def version_list(
138
120
 
139
121
  @app.command(requires_connection=True)
140
122
  @with_project_definition()
141
- @nativeapp_definition_v2_to_v1
123
+ @nativeapp_definition_v2_to_v1()
142
124
  def drop(
143
125
  version: Optional[str] = typer.Argument(
144
126
  None,