cumulusci-plus 5.0.21__py3-none-any.whl → 5.0.43__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 (135) hide show
  1. cumulusci/__about__.py +1 -1
  2. cumulusci/cli/logger.py +2 -2
  3. cumulusci/cli/service.py +20 -0
  4. cumulusci/cli/task.py +19 -3
  5. cumulusci/cli/tests/test_error.py +3 -1
  6. cumulusci/cli/tests/test_flow.py +279 -2
  7. cumulusci/cli/tests/test_org.py +5 -0
  8. cumulusci/cli/tests/test_service.py +15 -12
  9. cumulusci/cli/tests/test_task.py +122 -2
  10. cumulusci/cli/tests/utils.py +1 -4
  11. cumulusci/core/config/__init__.py +1 -0
  12. cumulusci/core/config/base_task_flow_config.py +26 -1
  13. cumulusci/core/config/org_config.py +2 -1
  14. cumulusci/core/config/project_config.py +14 -20
  15. cumulusci/core/config/scratch_org_config.py +12 -0
  16. cumulusci/core/config/tests/test_config.py +1 -0
  17. cumulusci/core/config/tests/test_config_expensive.py +9 -3
  18. cumulusci/core/config/universal_config.py +3 -4
  19. cumulusci/core/dependencies/base.py +5 -1
  20. cumulusci/core/dependencies/dependencies.py +1 -1
  21. cumulusci/core/dependencies/github.py +1 -2
  22. cumulusci/core/dependencies/resolvers.py +1 -1
  23. cumulusci/core/dependencies/tests/test_dependencies.py +1 -1
  24. cumulusci/core/dependencies/tests/test_resolvers.py +1 -1
  25. cumulusci/core/flowrunner.py +90 -6
  26. cumulusci/core/github.py +1 -1
  27. cumulusci/core/sfdx.py +3 -1
  28. cumulusci/core/source_transforms/tests/test_transforms.py +1 -1
  29. cumulusci/core/source_transforms/transforms.py +1 -1
  30. cumulusci/core/tasks.py +13 -2
  31. cumulusci/core/tests/test_flowrunner.py +100 -0
  32. cumulusci/core/tests/test_tasks.py +65 -0
  33. cumulusci/core/utils.py +3 -1
  34. cumulusci/core/versions.py +1 -1
  35. cumulusci/cumulusci.yml +73 -1
  36. cumulusci/oauth/client.py +1 -1
  37. cumulusci/plugins/plugin_base.py +5 -3
  38. cumulusci/robotframework/pageobjects/ObjectManagerPageObject.py +1 -1
  39. cumulusci/salesforce_api/rest_deploy.py +1 -1
  40. cumulusci/schema/cumulusci.jsonschema.json +69 -0
  41. cumulusci/tasks/apex/anon.py +1 -1
  42. cumulusci/tasks/apex/testrunner.py +421 -144
  43. cumulusci/tasks/apex/tests/test_apex_tasks.py +917 -1
  44. cumulusci/tasks/bulkdata/extract.py +0 -1
  45. cumulusci/tasks/bulkdata/extract_dataset_utils/extract_yml.py +1 -1
  46. cumulusci/tasks/bulkdata/extract_dataset_utils/synthesize_extract_declarations.py +1 -1
  47. cumulusci/tasks/bulkdata/extract_dataset_utils/tests/test_extract_yml.py +1 -1
  48. cumulusci/tasks/bulkdata/generate_and_load_data.py +136 -12
  49. cumulusci/tasks/bulkdata/mapping_parser.py +139 -44
  50. cumulusci/tasks/bulkdata/select_utils.py +1 -1
  51. cumulusci/tasks/bulkdata/snowfakery.py +100 -25
  52. cumulusci/tasks/bulkdata/tests/test_generate_and_load.py +159 -0
  53. cumulusci/tasks/bulkdata/tests/test_load.py +0 -2
  54. cumulusci/tasks/bulkdata/tests/test_mapping_parser.py +763 -1
  55. cumulusci/tasks/bulkdata/tests/test_select_utils.py +46 -0
  56. cumulusci/tasks/bulkdata/tests/test_snowfakery.py +133 -0
  57. cumulusci/tasks/create_package_version.py +190 -16
  58. cumulusci/tasks/datadictionary.py +1 -1
  59. cumulusci/tasks/metadata_etl/__init__.py +2 -0
  60. cumulusci/tasks/metadata_etl/applications.py +256 -0
  61. cumulusci/tasks/metadata_etl/base.py +7 -3
  62. cumulusci/tasks/metadata_etl/layouts.py +1 -1
  63. cumulusci/tasks/metadata_etl/permissions.py +1 -1
  64. cumulusci/tasks/metadata_etl/remote_site_settings.py +2 -2
  65. cumulusci/tasks/metadata_etl/tests/test_applications.py +710 -0
  66. cumulusci/tasks/push/README.md +15 -17
  67. cumulusci/tasks/release_notes/README.md +13 -13
  68. cumulusci/tasks/release_notes/generator.py +13 -8
  69. cumulusci/tasks/robotframework/tests/test_robotframework.py +6 -1
  70. cumulusci/tasks/salesforce/Deploy.py +53 -2
  71. cumulusci/tasks/salesforce/SfPackageCommands.py +363 -0
  72. cumulusci/tasks/salesforce/__init__.py +1 -0
  73. cumulusci/tasks/salesforce/assign_ps_psg.py +448 -0
  74. cumulusci/tasks/salesforce/composite.py +1 -1
  75. cumulusci/tasks/salesforce/custom_settings_wait.py +1 -1
  76. cumulusci/tasks/salesforce/enable_prediction.py +5 -1
  77. cumulusci/tasks/salesforce/getPackageVersion.py +89 -0
  78. cumulusci/tasks/salesforce/insert_record.py +18 -19
  79. cumulusci/tasks/salesforce/sourcetracking.py +1 -1
  80. cumulusci/tasks/salesforce/tests/test_Deploy.py +316 -1
  81. cumulusci/tasks/salesforce/tests/test_SfPackageCommands.py +554 -0
  82. cumulusci/tasks/salesforce/tests/test_assign_ps_psg.py +1055 -0
  83. cumulusci/tasks/salesforce/tests/test_enable_prediction.py +4 -2
  84. cumulusci/tasks/salesforce/tests/test_getPackageVersion.py +651 -0
  85. cumulusci/tasks/salesforce/tests/test_update_dependencies.py +1 -1
  86. cumulusci/tasks/salesforce/tests/test_update_external_auth_identity_provider.py +927 -0
  87. cumulusci/tasks/salesforce/tests/test_update_external_credential.py +1427 -0
  88. cumulusci/tasks/salesforce/tests/test_update_named_credential.py +1042 -0
  89. cumulusci/tasks/salesforce/tests/test_update_record.py +512 -0
  90. cumulusci/tasks/salesforce/update_dependencies.py +2 -2
  91. cumulusci/tasks/salesforce/update_external_auth_identity_provider.py +551 -0
  92. cumulusci/tasks/salesforce/update_external_credential.py +647 -0
  93. cumulusci/tasks/salesforce/update_named_credential.py +441 -0
  94. cumulusci/tasks/salesforce/update_profile.py +17 -13
  95. cumulusci/tasks/salesforce/update_record.py +217 -0
  96. cumulusci/tasks/salesforce/users/permsets.py +62 -5
  97. cumulusci/tasks/salesforce/users/tests/test_permsets.py +237 -11
  98. cumulusci/tasks/sfdmu/__init__.py +0 -0
  99. cumulusci/tasks/sfdmu/sfdmu.py +376 -0
  100. cumulusci/tasks/sfdmu/tests/__init__.py +1 -0
  101. cumulusci/tasks/sfdmu/tests/test_runner.py +212 -0
  102. cumulusci/tasks/sfdmu/tests/test_sfdmu.py +1012 -0
  103. cumulusci/tasks/tests/test_create_package_version.py +716 -1
  104. cumulusci/tasks/tests/test_util.py +42 -0
  105. cumulusci/tasks/util.py +37 -1
  106. cumulusci/tasks/utility/copyContents.py +402 -0
  107. cumulusci/tasks/utility/credentialManager.py +302 -0
  108. cumulusci/tasks/utility/directoryRecreator.py +30 -0
  109. cumulusci/tasks/utility/env_management.py +1 -1
  110. cumulusci/tasks/utility/secretsToEnv.py +135 -0
  111. cumulusci/tasks/utility/tests/test_copyContents.py +1719 -0
  112. cumulusci/tasks/utility/tests/test_credentialManager.py +1150 -0
  113. cumulusci/tasks/utility/tests/test_directoryRecreator.py +439 -0
  114. cumulusci/tasks/utility/tests/test_secretsToEnv.py +1118 -0
  115. cumulusci/tests/test_integration_infrastructure.py +3 -1
  116. cumulusci/tests/test_utils.py +70 -6
  117. cumulusci/utils/__init__.py +54 -9
  118. cumulusci/utils/classutils.py +5 -2
  119. cumulusci/utils/http/tests/cassettes/ManualEditTestCompositeParallelSalesforce.test_http_headers.yaml +31 -30
  120. cumulusci/utils/options.py +23 -1
  121. cumulusci/utils/parallel/task_worker_queues/parallel_worker.py +1 -1
  122. cumulusci/utils/yaml/cumulusci_yml.py +8 -3
  123. cumulusci/utils/yaml/model_parser.py +2 -2
  124. cumulusci/utils/yaml/tests/test_cumulusci_yml.py +1 -1
  125. cumulusci/utils/yaml/tests/test_model_parser.py +3 -3
  126. cumulusci/vcs/base.py +23 -15
  127. cumulusci/vcs/bootstrap.py +5 -4
  128. cumulusci/vcs/utils/list_modified_files.py +189 -0
  129. cumulusci/vcs/utils/tests/test_list_modified_files.py +588 -0
  130. {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.43.dist-info}/METADATA +11 -10
  131. {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.43.dist-info}/RECORD +135 -104
  132. {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.43.dist-info}/WHEEL +1 -1
  133. {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.43.dist-info}/entry_points.txt +0 -0
  134. {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.43.dist-info}/licenses/AUTHORS.rst +0 -0
  135. {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.43.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,256 @@
1
+ from typing import List, Optional, Set
2
+ from urllib.parse import quote
3
+
4
+ from pydantic.v1 import BaseModel
5
+
6
+ from cumulusci.tasks.metadata_etl import MetadataSingleEntityTransformTask
7
+ from cumulusci.utils import inject_namespace
8
+ from cumulusci.utils.xml.metadata_tree import MetadataElement
9
+
10
+
11
+ def _inject_namespace(text: str, options: dict) -> str:
12
+ return inject_namespace(
13
+ "",
14
+ text,
15
+ namespace=options["namespace_inject"]
16
+ if not options.get("namespaced_org")
17
+ else "",
18
+ managed=options.get("managed") or False,
19
+ namespaced_org=options.get("namespaced_org"),
20
+ )[1]
21
+
22
+
23
+ class ProfileActionOverrideOptions(BaseModel):
24
+ """Options for a single profileActionOverride"""
25
+
26
+ action_name: str
27
+ content: str
28
+ form_factor: str
29
+ page_or_sobject_type: str
30
+ record_type: Optional[str]
31
+ type: str
32
+ profile: str
33
+ options: dict = {}
34
+
35
+ def namespace_inject(self, field_name: str) -> None:
36
+ setattr(
37
+ self, field_name, _inject_namespace(getattr(self, field_name), self.options)
38
+ )
39
+
40
+
41
+ class AddProfileActionOverridesOptions(BaseModel):
42
+ """Options container for profile action overrides"""
43
+
44
+ name: str
45
+ overrides: List[ProfileActionOverrideOptions]
46
+
47
+
48
+ class AddProfileActionOverrides(MetadataSingleEntityTransformTask):
49
+ """
50
+ Inserts or updates profileActionOverrides in CustomApplication metadata.
51
+ If a profileActionOverride with the same actionName, pageOrSobjectType,
52
+ recordType, and profile already exists, it will be updated with a warning.
53
+ Otherwise, a new override will be added.
54
+ Task option details:
55
+ - applications: List of CustomApplications to modify
56
+ - name: API name of the CustomApplication to modify
57
+ - overrides: List of profile action overrides to add/update
58
+ - action_name: Action name (e.g., "View", "Edit", "New")
59
+ - content: Content reference (page/component API name)
60
+ - form_factor: Form factor (e.g., "Large", "Small")
61
+ - page_or_sobject_type: Page or SObject type
62
+ - record_type: Record type API name (optional)
63
+ - type: Override type (e.g., "Flexipage", "Visualforce", "LightningComponent")
64
+ - profile: Profile name or API name
65
+ Example Usage
66
+ -----------------------
67
+ .. code-block:: yaml
68
+ task: add_profile_action_overrides
69
+ options:
70
+ applications:
71
+ - name: "%%%NAMESPACE%%%CustomApplicationConsole"
72
+ overrides:
73
+ - action_name: View
74
+ content: "%%%NAMESPACED_ORG%%%AccountUserRecordPage"
75
+ form_factor: Large
76
+ page_or_sobject_type: Account
77
+ record_type: PersonAccount.User
78
+ type: Flexipage
79
+ profile: Admin
80
+ """
81
+
82
+ entity = "CustomApplication"
83
+ task_options = {
84
+ "applications": {
85
+ "description": "List of CustomApplications to modify. See task info for structure.",
86
+ "required": True,
87
+ },
88
+ **MetadataSingleEntityTransformTask.task_options,
89
+ }
90
+
91
+ def _init_options(self, kwargs):
92
+ super()._init_options(kwargs)
93
+
94
+ # Validate options using Pydantic
95
+ self._validated_options: List[AddProfileActionOverridesOptions] = []
96
+ for application in self.options.get("applications"):
97
+ validated_options = AddProfileActionOverridesOptions(
98
+ name=quote(_inject_namespace(application.get("name"), self.options)),
99
+ overrides=application.get("overrides"),
100
+ )
101
+
102
+ self._validated_options.append(validated_options)
103
+
104
+ self.api_names: Set[str] = set(
105
+ application.name for application in self._validated_options
106
+ )
107
+
108
+ def _transform_entity(
109
+ self, metadata: MetadataElement, api_name: str
110
+ ) -> Optional[MetadataElement]:
111
+
112
+ if not self._validated_options:
113
+ self.logger.warning("No applications to add profile action overrides for")
114
+ return None
115
+
116
+ for application in self._validated_options:
117
+ if application.name != api_name:
118
+ continue
119
+
120
+ for override_config in application.overrides:
121
+ self._add_or_update_override(
122
+ metadata, application.name, override_config
123
+ )
124
+
125
+ return metadata
126
+
127
+ def _add_or_update_override(self, metadata, api_name, override_config):
128
+ """Add or update a single profileActionOverride"""
129
+ override_config.options = self.options
130
+
131
+ # Inject namespace where needed
132
+ override_config.namespace_inject("content")
133
+ override_config.namespace_inject("page_or_sobject_type")
134
+ override_config.namespace_inject(
135
+ "record_type"
136
+ ) if override_config.record_type else None
137
+
138
+ # Find existing override with same key fields
139
+ existing_override = self._find_existing_override(metadata, override_config)
140
+
141
+ if existing_override:
142
+ self.logger.warning(
143
+ f"Updating existing profileActionOverride for {override_config.profile}/{override_config.page_or_sobject_type}/{override_config.action_name} in {api_name}"
144
+ )
145
+ # Update the existing override
146
+ self._update_override_element(
147
+ existing_override,
148
+ override_config,
149
+ )
150
+ else:
151
+ self.logger.info(
152
+ f"Adding profileActionOverride for {override_config.profile}/{override_config.page_or_sobject_type}/{override_config.action_name} to {api_name}"
153
+ )
154
+ # Create new override
155
+ self._create_new_override(metadata, override_config)
156
+
157
+ def _find_existing_override(self, metadata, override_config):
158
+ """
159
+ Find an existing profileActionOverride that matches the key fields:
160
+ actionName, pageOrSobjectType, recordType, and profile
161
+ """
162
+ for override_elem in metadata.findall("profileActionOverrides"):
163
+ elem_action = override_elem.find("actionName")
164
+ elem_page = override_elem.find("pageOrSobjectType")
165
+ elem_record_type = override_elem.find("recordType")
166
+ elem_profile = override_elem.find("profile")
167
+
168
+ # Match on all key fields
169
+ if (
170
+ elem_action
171
+ and elem_action.text == override_config.action_name
172
+ and elem_page
173
+ and elem_page.text == override_config.page_or_sobject_type
174
+ and elem_profile
175
+ and elem_profile.text == override_config.profile
176
+ ):
177
+ # Handle recordType - both must be None or both must match
178
+ if override_config.record_type is None and elem_record_type is None:
179
+ return override_elem
180
+ elif (
181
+ override_config.record_type is not None
182
+ and elem_record_type is not None
183
+ and elem_record_type.text == override_config.record_type
184
+ ):
185
+ return override_elem
186
+
187
+ return None
188
+
189
+ def _update_override_element(
190
+ self,
191
+ override_elem,
192
+ override_config,
193
+ ):
194
+ """Update an existing profileActionOverride element"""
195
+ # Update each child element
196
+ # actionName
197
+ elem = override_elem.find("actionName")
198
+ if elem is not None:
199
+ elem.text = override_config.action_name
200
+
201
+ # content
202
+ elem = override_elem.find("content")
203
+ if elem is not None:
204
+ elem.text = override_config.content
205
+
206
+ # formFactor
207
+ elem = override_elem.find("formFactor")
208
+ if elem is not None:
209
+ elem.text = override_config.form_factor
210
+
211
+ # pageOrSobjectType
212
+ elem = override_elem.find("pageOrSobjectType")
213
+ if elem is not None:
214
+ elem.text = override_config.page_or_sobject_type
215
+
216
+ # recordType (optional)
217
+ elem = override_elem.find("recordType")
218
+ if override_config.record_type:
219
+ if elem is not None:
220
+ elem.text = override_config.record_type
221
+ elif elem is not None:
222
+ # Remove recordType if it exists but shouldn't
223
+ override_elem.remove(elem)
224
+
225
+ # type
226
+ elem = override_elem.find("type")
227
+ if elem is not None:
228
+ elem.text = override_config.type
229
+
230
+ # profile
231
+ elem = override_elem.find("profile")
232
+ if elem is not None:
233
+ elem.text = override_config.profile
234
+
235
+ def _create_new_override(
236
+ self,
237
+ metadata,
238
+ override_config,
239
+ ):
240
+ """Create a new profileActionOverride element with proper ordering"""
241
+ override_elem = metadata.append("profileActionOverrides")
242
+
243
+ # Add elements in the correct order per Salesforce metadata API
244
+ override_elem.append("actionName", text=override_config.action_name)
245
+ override_elem.append("content", text=override_config.content)
246
+ override_elem.append("formFactor", text=override_config.form_factor)
247
+ override_elem.append(
248
+ "pageOrSobjectType", text=override_config.page_or_sobject_type
249
+ )
250
+
251
+ # recordType is optional
252
+ if override_config.record_type:
253
+ override_elem.append("recordType", text=override_config.record_type)
254
+
255
+ override_elem.append("type", text=override_config.type)
256
+ override_elem.append("profile", text=override_config.profile)
@@ -7,7 +7,11 @@ from cumulusci.core.config import TaskConfig
7
7
  from cumulusci.core.enums import StrEnum
8
8
  from cumulusci.core.exceptions import CumulusCIException, TaskOptionsError
9
9
  from cumulusci.core.tasks import BaseSalesforceTask
10
- from cumulusci.core.utils import process_bool_arg, process_list_arg, determine_managed_mode
10
+ from cumulusci.core.utils import (
11
+ determine_managed_mode,
12
+ process_bool_arg,
13
+ process_list_arg,
14
+ )
11
15
  from cumulusci.salesforce_api.metadata import ApiRetrieveUnpackaged
12
16
  from cumulusci.tasks.metadata.package import PackageXmlGenerator
13
17
  from cumulusci.utils import inject_namespace
@@ -74,8 +78,8 @@ class BaseMetadataETLTask(BaseSalesforceTask, metaclass=ABCMeta):
74
78
  self.options["namespaced_org"] or False
75
79
  )
76
80
  else:
77
- self.options["namespaced_org"] = (
78
- bool(namespace) and namespace == getattr(self.org_config, 'namespace', None)
81
+ self.options["namespaced_org"] = bool(namespace) and namespace == getattr(
82
+ self.org_config, "namespace", None
79
83
  )
80
84
 
81
85
  def _inject_namespace(self, text):
@@ -1,6 +1,6 @@
1
1
  from typing import List, Optional
2
2
 
3
- from pydantic import BaseModel, root_validator
3
+ from pydantic.v1 import BaseModel, root_validator
4
4
  from typing_extensions import Literal
5
5
 
6
6
  from cumulusci.core.exceptions import TaskOptionsError
@@ -1,6 +1,6 @@
1
1
  import typing as T
2
2
 
3
- import pydantic
3
+ import pydantic.v1 as pydantic
4
4
 
5
5
  from cumulusci.core.exceptions import TaskOptionsError
6
6
  from cumulusci.tasks.metadata_etl import MetadataSingleEntityTransformTask
@@ -1,7 +1,7 @@
1
1
  from typing import List, Optional
2
2
 
3
- import pydantic
4
- from pydantic import BaseModel
3
+ import pydantic.v1 as pydantic
4
+ from pydantic.v1 import BaseModel
5
5
 
6
6
  from cumulusci.core.exceptions import TaskOptionsError
7
7
  from cumulusci.tasks.metadata_etl.base import BaseMetadataSynthesisTask