cumulusci-plus 5.0.0__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.

Potentially problematic release.


This version of cumulusci-plus might be problematic. Click here for more details.

Files changed (744) hide show
  1. cumulusci/__about__.py +1 -0
  2. cumulusci/__init__.py +22 -0
  3. cumulusci/__main__.py +3 -0
  4. cumulusci/cli/__init__.py +0 -0
  5. cumulusci/cli/cci.py +244 -0
  6. cumulusci/cli/error.py +125 -0
  7. cumulusci/cli/flow.py +185 -0
  8. cumulusci/cli/logger.py +72 -0
  9. cumulusci/cli/org.py +692 -0
  10. cumulusci/cli/plan.py +181 -0
  11. cumulusci/cli/project.py +391 -0
  12. cumulusci/cli/robot.py +116 -0
  13. cumulusci/cli/runtime.py +190 -0
  14. cumulusci/cli/service.py +521 -0
  15. cumulusci/cli/task.py +295 -0
  16. cumulusci/cli/tests/__init__.py +0 -0
  17. cumulusci/cli/tests/test_cci.py +545 -0
  18. cumulusci/cli/tests/test_error.py +170 -0
  19. cumulusci/cli/tests/test_flow.py +276 -0
  20. cumulusci/cli/tests/test_logger.py +25 -0
  21. cumulusci/cli/tests/test_org.py +1438 -0
  22. cumulusci/cli/tests/test_plan.py +245 -0
  23. cumulusci/cli/tests/test_project.py +235 -0
  24. cumulusci/cli/tests/test_robot.py +177 -0
  25. cumulusci/cli/tests/test_runtime.py +197 -0
  26. cumulusci/cli/tests/test_service.py +853 -0
  27. cumulusci/cli/tests/test_task.py +266 -0
  28. cumulusci/cli/tests/test_ui.py +310 -0
  29. cumulusci/cli/tests/test_utils.py +122 -0
  30. cumulusci/cli/tests/utils.py +52 -0
  31. cumulusci/cli/ui.py +234 -0
  32. cumulusci/cli/utils.py +150 -0
  33. cumulusci/conftest.py +181 -0
  34. cumulusci/core/__init__.py +0 -0
  35. cumulusci/core/config/BaseConfig.py +5 -0
  36. cumulusci/core/config/BaseTaskFlowConfig.py +5 -0
  37. cumulusci/core/config/OrgConfig.py +5 -0
  38. cumulusci/core/config/ScratchOrgConfig.py +5 -0
  39. cumulusci/core/config/__init__.py +125 -0
  40. cumulusci/core/config/base_config.py +111 -0
  41. cumulusci/core/config/base_task_flow_config.py +82 -0
  42. cumulusci/core/config/marketing_cloud_service_config.py +83 -0
  43. cumulusci/core/config/oauth2_service_config.py +17 -0
  44. cumulusci/core/config/org_config.py +604 -0
  45. cumulusci/core/config/project_config.py +782 -0
  46. cumulusci/core/config/scratch_org_config.py +251 -0
  47. cumulusci/core/config/sfdx_org_config.py +220 -0
  48. cumulusci/core/config/tests/_test_config_backwards_compatibility.py +33 -0
  49. cumulusci/core/config/tests/test_config.py +1895 -0
  50. cumulusci/core/config/tests/test_config_expensive.py +839 -0
  51. cumulusci/core/config/tests/test_config_util.py +91 -0
  52. cumulusci/core/config/universal_config.py +88 -0
  53. cumulusci/core/config/util.py +18 -0
  54. cumulusci/core/datasets.py +303 -0
  55. cumulusci/core/debug.py +33 -0
  56. cumulusci/core/dependencies/__init__.py +55 -0
  57. cumulusci/core/dependencies/base.py +561 -0
  58. cumulusci/core/dependencies/dependencies.py +273 -0
  59. cumulusci/core/dependencies/github.py +177 -0
  60. cumulusci/core/dependencies/github_resolvers.py +244 -0
  61. cumulusci/core/dependencies/resolvers.py +580 -0
  62. cumulusci/core/dependencies/tests/__init__.py +0 -0
  63. cumulusci/core/dependencies/tests/conftest.py +385 -0
  64. cumulusci/core/dependencies/tests/test_dependencies.py +950 -0
  65. cumulusci/core/dependencies/tests/test_github.py +83 -0
  66. cumulusci/core/dependencies/tests/test_resolvers.py +1027 -0
  67. cumulusci/core/dependencies/utils.py +13 -0
  68. cumulusci/core/enums.py +11 -0
  69. cumulusci/core/exceptions.py +311 -0
  70. cumulusci/core/flowrunner.py +888 -0
  71. cumulusci/core/github.py +665 -0
  72. cumulusci/core/keychain/__init__.py +24 -0
  73. cumulusci/core/keychain/base_project_keychain.py +441 -0
  74. cumulusci/core/keychain/encrypted_file_project_keychain.py +945 -0
  75. cumulusci/core/keychain/environment_project_keychain.py +7 -0
  76. cumulusci/core/keychain/serialization.py +152 -0
  77. cumulusci/core/keychain/subprocess_keychain.py +24 -0
  78. cumulusci/core/keychain/tests/conftest.py +50 -0
  79. cumulusci/core/keychain/tests/test_base_project_keychain.py +299 -0
  80. cumulusci/core/keychain/tests/test_encrypted_file_project_keychain.py +1228 -0
  81. cumulusci/core/metadeploy/__init__.py +0 -0
  82. cumulusci/core/metadeploy/api.py +88 -0
  83. cumulusci/core/metadeploy/plans.py +25 -0
  84. cumulusci/core/metadeploy/tests/test_api.py +276 -0
  85. cumulusci/core/runtime.py +115 -0
  86. cumulusci/core/sfdx.py +162 -0
  87. cumulusci/core/source/__init__.py +16 -0
  88. cumulusci/core/source/github.py +50 -0
  89. cumulusci/core/source/local_folder.py +35 -0
  90. cumulusci/core/source_transforms/__init__.py +0 -0
  91. cumulusci/core/source_transforms/tests/test_transforms.py +1091 -0
  92. cumulusci/core/source_transforms/transforms.py +532 -0
  93. cumulusci/core/tasks.py +404 -0
  94. cumulusci/core/template_utils.py +59 -0
  95. cumulusci/core/tests/__init__.py +0 -0
  96. cumulusci/core/tests/cassettes/TestDatasetsE2E.test_datasets_e2e.yaml +215 -0
  97. cumulusci/core/tests/cassettes/TestDatasetsE2E.test_datasets_extract_standard_objects.yaml +199 -0
  98. cumulusci/core/tests/cassettes/TestDatasetsE2E.test_datasets_read_explicit_extract_declaration.yaml +3 -0
  99. cumulusci/core/tests/fake_remote_repo/cumulusci.yml +32 -0
  100. cumulusci/core/tests/fake_remote_repo/tasks/directory/example_2.py +6 -0
  101. cumulusci/core/tests/fake_remote_repo/tasks/example.py +43 -0
  102. cumulusci/core/tests/fake_remote_repo_2/cumulusci.yml +11 -0
  103. cumulusci/core/tests/fake_remote_repo_2/tasks/example_3.py +6 -0
  104. cumulusci/core/tests/test_datasets_e2e.py +386 -0
  105. cumulusci/core/tests/test_exceptions.py +11 -0
  106. cumulusci/core/tests/test_flowrunner.py +836 -0
  107. cumulusci/core/tests/test_github.py +942 -0
  108. cumulusci/core/tests/test_sfdx.py +138 -0
  109. cumulusci/core/tests/test_source.py +678 -0
  110. cumulusci/core/tests/test_tasks.py +262 -0
  111. cumulusci/core/tests/test_utils.py +141 -0
  112. cumulusci/core/tests/test_utils_merge_config.py +276 -0
  113. cumulusci/core/tests/test_versions.py +76 -0
  114. cumulusci/core/tests/untrusted_repo_child/cumulusci.yml +7 -0
  115. cumulusci/core/tests/untrusted_repo_child/tasks/untrusted_child.py +6 -0
  116. cumulusci/core/tests/untrusted_repo_parent/cumulusci.yml +26 -0
  117. cumulusci/core/tests/untrusted_repo_parent/tasks/untrusted_parent.py +6 -0
  118. cumulusci/core/tests/utils.py +116 -0
  119. cumulusci/core/tests/yaml/global.yaml +0 -0
  120. cumulusci/core/utils.py +402 -0
  121. cumulusci/core/versions.py +149 -0
  122. cumulusci/cumulusci.yml +1621 -0
  123. cumulusci/files/admin_profile.xml +20 -0
  124. cumulusci/files/delete_excludes.txt +424 -0
  125. cumulusci/files/templates/project/README.md +12 -0
  126. cumulusci/files/templates/project/cumulusci.yml +63 -0
  127. cumulusci/files/templates/project/dot-gitignore +60 -0
  128. cumulusci/files/templates/project/mapping.yml +45 -0
  129. cumulusci/files/templates/project/scratch_def.json +25 -0
  130. cumulusci/oauth/__init__.py +0 -0
  131. cumulusci/oauth/client.py +400 -0
  132. cumulusci/oauth/exceptions.py +9 -0
  133. cumulusci/oauth/salesforce.py +95 -0
  134. cumulusci/oauth/tests/__init__.py +0 -0
  135. cumulusci/oauth/tests/cassettes/test_get_device_code.yaml +22 -0
  136. cumulusci/oauth/tests/cassettes/test_get_device_oauth_token.yaml +74 -0
  137. cumulusci/oauth/tests/test_client.py +308 -0
  138. cumulusci/oauth/tests/test_salesforce.py +46 -0
  139. cumulusci/plugins/__init__.py +3 -0
  140. cumulusci/plugins/plugin_base.py +93 -0
  141. cumulusci/plugins/plugin_loader.py +59 -0
  142. cumulusci/robotframework/CumulusCI.py +340 -0
  143. cumulusci/robotframework/CumulusCI.robot +7 -0
  144. cumulusci/robotframework/Performance.py +165 -0
  145. cumulusci/robotframework/Salesforce.py +936 -0
  146. cumulusci/robotframework/Salesforce.robot +192 -0
  147. cumulusci/robotframework/SalesforceAPI.py +416 -0
  148. cumulusci/robotframework/SalesforcePlaywright.py +220 -0
  149. cumulusci/robotframework/SalesforcePlaywright.robot +40 -0
  150. cumulusci/robotframework/__init__.py +2 -0
  151. cumulusci/robotframework/base_library.py +39 -0
  152. cumulusci/robotframework/faker_mixin.py +89 -0
  153. cumulusci/robotframework/form_handlers.py +222 -0
  154. cumulusci/robotframework/javascript/cci_init.js +34 -0
  155. cumulusci/robotframework/javascript/cumulusci.js +4 -0
  156. cumulusci/robotframework/locator_manager.py +197 -0
  157. cumulusci/robotframework/locators_56.py +88 -0
  158. cumulusci/robotframework/locators_57.py +5 -0
  159. cumulusci/robotframework/pageobjects/BasePageObjects.py +433 -0
  160. cumulusci/robotframework/pageobjects/ObjectManagerPageObject.py +246 -0
  161. cumulusci/robotframework/pageobjects/PageObjectLibrary.py +45 -0
  162. cumulusci/robotframework/pageobjects/PageObjects.py +351 -0
  163. cumulusci/robotframework/pageobjects/__init__.py +12 -0
  164. cumulusci/robotframework/pageobjects/baseobjects.py +120 -0
  165. cumulusci/robotframework/perftests/short/collection_perf.robot +105 -0
  166. cumulusci/robotframework/tests/CustomObjectTestPage.py +10 -0
  167. cumulusci/robotframework/tests/FooTestPage.py +8 -0
  168. cumulusci/robotframework/tests/cumulusci/base.robot +40 -0
  169. cumulusci/robotframework/tests/cumulusci/bulkdata.robot +38 -0
  170. cumulusci/robotframework/tests/cumulusci/communities.robot +57 -0
  171. cumulusci/robotframework/tests/cumulusci/datagen.robot +84 -0
  172. cumulusci/robotframework/tests/salesforce/TestLibraryA.py +24 -0
  173. cumulusci/robotframework/tests/salesforce/TestLibraryB.py +20 -0
  174. cumulusci/robotframework/tests/salesforce/TestListener.py +93 -0
  175. cumulusci/robotframework/tests/salesforce/api.robot +178 -0
  176. cumulusci/robotframework/tests/salesforce/browsers.robot +143 -0
  177. cumulusci/robotframework/tests/salesforce/classic.robot +51 -0
  178. cumulusci/robotframework/tests/salesforce/create_contact.robot +59 -0
  179. cumulusci/robotframework/tests/salesforce/faker.robot +68 -0
  180. cumulusci/robotframework/tests/salesforce/forms.robot +172 -0
  181. cumulusci/robotframework/tests/salesforce/label_locator.robot +244 -0
  182. cumulusci/robotframework/tests/salesforce/labels.html +33 -0
  183. cumulusci/robotframework/tests/salesforce/locators.robot +149 -0
  184. cumulusci/robotframework/tests/salesforce/pageobjects/base_pageobjects.robot +100 -0
  185. cumulusci/robotframework/tests/salesforce/pageobjects/example_page_object.py +25 -0
  186. cumulusci/robotframework/tests/salesforce/pageobjects/listing_page.robot +115 -0
  187. cumulusci/robotframework/tests/salesforce/pageobjects/objectmanager.robot +74 -0
  188. cumulusci/robotframework/tests/salesforce/pageobjects/pageobjects.robot +171 -0
  189. cumulusci/robotframework/tests/salesforce/performance.robot +109 -0
  190. cumulusci/robotframework/tests/salesforce/playwright/javascript_keywords.robot +33 -0
  191. cumulusci/robotframework/tests/salesforce/playwright/open_test_browser.robot +48 -0
  192. cumulusci/robotframework/tests/salesforce/playwright/playwright.robot +24 -0
  193. cumulusci/robotframework/tests/salesforce/playwright/ui.robot +32 -0
  194. cumulusci/robotframework/tests/salesforce/populate.robot +89 -0
  195. cumulusci/robotframework/tests/salesforce/test_testlistener.py +37 -0
  196. cumulusci/robotframework/tests/salesforce/ui.robot +361 -0
  197. cumulusci/robotframework/tests/test_cumulusci_library.py +304 -0
  198. cumulusci/robotframework/tests/test_locator_manager.py +158 -0
  199. cumulusci/robotframework/tests/test_pageobjects.py +291 -0
  200. cumulusci/robotframework/tests/test_performance.py +38 -0
  201. cumulusci/robotframework/tests/test_salesforce.py +79 -0
  202. cumulusci/robotframework/tests/test_salesforce_locators.py +73 -0
  203. cumulusci/robotframework/tests/test_template_util.py +53 -0
  204. cumulusci/robotframework/tests/test_utils.py +106 -0
  205. cumulusci/robotframework/utils.py +283 -0
  206. cumulusci/salesforce_api/__init__.py +0 -0
  207. cumulusci/salesforce_api/exceptions.py +23 -0
  208. cumulusci/salesforce_api/filterable_objects.py +96 -0
  209. cumulusci/salesforce_api/mc_soap_envelopes.py +89 -0
  210. cumulusci/salesforce_api/metadata.py +721 -0
  211. cumulusci/salesforce_api/org_schema.py +571 -0
  212. cumulusci/salesforce_api/org_schema_models.py +226 -0
  213. cumulusci/salesforce_api/package_install.py +265 -0
  214. cumulusci/salesforce_api/package_zip.py +301 -0
  215. cumulusci/salesforce_api/rest_deploy.py +148 -0
  216. cumulusci/salesforce_api/retrieve_profile_api.py +301 -0
  217. cumulusci/salesforce_api/soap_envelopes.py +177 -0
  218. cumulusci/salesforce_api/tests/__init__.py +0 -0
  219. cumulusci/salesforce_api/tests/metadata_test_strings.py +24 -0
  220. cumulusci/salesforce_api/tests/test_metadata.py +1015 -0
  221. cumulusci/salesforce_api/tests/test_package_install.py +219 -0
  222. cumulusci/salesforce_api/tests/test_package_zip.py +380 -0
  223. cumulusci/salesforce_api/tests/test_rest_deploy.py +264 -0
  224. cumulusci/salesforce_api/tests/test_retrieve_profile_api.py +337 -0
  225. cumulusci/salesforce_api/tests/test_utils.py +124 -0
  226. cumulusci/salesforce_api/utils.py +51 -0
  227. cumulusci/schema/cumulusci.jsonschema.json +782 -0
  228. cumulusci/tasks/__init__.py +0 -0
  229. cumulusci/tasks/apex/__init__.py +0 -0
  230. cumulusci/tasks/apex/anon.py +157 -0
  231. cumulusci/tasks/apex/batch.py +180 -0
  232. cumulusci/tasks/apex/testrunner.py +835 -0
  233. cumulusci/tasks/apex/tests/cassettes/ManualEditTestApexIntegrationTests.test_run_tests__integration_test.yaml +703 -0
  234. cumulusci/tasks/apex/tests/test_apex_tasks.py +1558 -0
  235. cumulusci/tasks/base_source_control_task.py +17 -0
  236. cumulusci/tasks/bulkdata/__init__.py +15 -0
  237. cumulusci/tasks/bulkdata/base_generate_data_task.py +96 -0
  238. cumulusci/tasks/bulkdata/dates.py +97 -0
  239. cumulusci/tasks/bulkdata/delete.py +156 -0
  240. cumulusci/tasks/bulkdata/extract.py +441 -0
  241. cumulusci/tasks/bulkdata/extract_dataset_utils/calculate_dependencies.py +117 -0
  242. cumulusci/tasks/bulkdata/extract_dataset_utils/extract_yml.py +123 -0
  243. cumulusci/tasks/bulkdata/extract_dataset_utils/hardcoded_default_declarations.py +49 -0
  244. cumulusci/tasks/bulkdata/extract_dataset_utils/synthesize_extract_declarations.py +283 -0
  245. cumulusci/tasks/bulkdata/extract_dataset_utils/tests/test_extract_yml.py +142 -0
  246. cumulusci/tasks/bulkdata/extract_dataset_utils/tests/test_synthesize_extract_declarations.py +575 -0
  247. cumulusci/tasks/bulkdata/factory_utils.py +134 -0
  248. cumulusci/tasks/bulkdata/generate.py +4 -0
  249. cumulusci/tasks/bulkdata/generate_and_load_data.py +232 -0
  250. cumulusci/tasks/bulkdata/generate_and_load_data_from_yaml.py +19 -0
  251. cumulusci/tasks/bulkdata/generate_from_yaml.py +183 -0
  252. cumulusci/tasks/bulkdata/generate_mapping.py +434 -0
  253. cumulusci/tasks/bulkdata/generate_mapping_utils/dependency_map.py +169 -0
  254. cumulusci/tasks/bulkdata/generate_mapping_utils/extract_mapping_file_generator.py +45 -0
  255. cumulusci/tasks/bulkdata/generate_mapping_utils/generate_mapping_from_declarations.py +121 -0
  256. cumulusci/tasks/bulkdata/generate_mapping_utils/load_mapping_file_generator.py +127 -0
  257. cumulusci/tasks/bulkdata/generate_mapping_utils/mapping_generator_post_processes.py +53 -0
  258. cumulusci/tasks/bulkdata/generate_mapping_utils/mapping_transforms.py +139 -0
  259. cumulusci/tasks/bulkdata/generate_mapping_utils/tests/test_generate_extract_mapping_from_declarations.py +135 -0
  260. cumulusci/tasks/bulkdata/generate_mapping_utils/tests/test_generate_load_mapping_from_declarations.py +330 -0
  261. cumulusci/tasks/bulkdata/generate_mapping_utils/tests/test_mapping_generator_post_processes.py +60 -0
  262. cumulusci/tasks/bulkdata/generate_mapping_utils/tests/test_mapping_transforms.py +188 -0
  263. cumulusci/tasks/bulkdata/load.py +1196 -0
  264. cumulusci/tasks/bulkdata/mapping_parser.py +811 -0
  265. cumulusci/tasks/bulkdata/query_transformers.py +264 -0
  266. cumulusci/tasks/bulkdata/select_utils.py +792 -0
  267. cumulusci/tasks/bulkdata/snowfakery.py +753 -0
  268. cumulusci/tasks/bulkdata/snowfakery_utils/queue_manager.py +478 -0
  269. cumulusci/tasks/bulkdata/snowfakery_utils/snowfakery_run_until.py +141 -0
  270. cumulusci/tasks/bulkdata/snowfakery_utils/snowfakery_working_directory.py +53 -0
  271. cumulusci/tasks/bulkdata/snowfakery_utils/subtask_configurator.py +64 -0
  272. cumulusci/tasks/bulkdata/step.py +1242 -0
  273. cumulusci/tasks/bulkdata/tests/__init__.py +0 -0
  274. cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_random_strategy.yaml +147 -0
  275. cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_similarity_annoy_strategy.yaml +123 -0
  276. cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_similarity_select_and_insert_strategy.yaml +313 -0
  277. cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_similarity_select_and_insert_strategy_bulk.yaml +550 -0
  278. cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_similarity_strategy.yaml +175 -0
  279. cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_standard_strategy.yaml +147 -0
  280. cumulusci/tasks/bulkdata/tests/cassettes/TestSnowfakery.test_run_until_records_in_org__multiple_needed.yaml +69 -0
  281. cumulusci/tasks/bulkdata/tests/cassettes/TestSnowfakery.test_run_until_records_in_org__none_needed.yaml +22 -0
  282. cumulusci/tasks/bulkdata/tests/cassettes/TestSnowfakery.test_run_until_records_in_org__one_needed.yaml +24 -0
  283. cumulusci/tasks/bulkdata/tests/cassettes/TestSnowfakery.test_snowfakery_query_salesforce.yaml +25 -0
  284. cumulusci/tasks/bulkdata/tests/cassettes/TestUpdatesIntegrationTests.test_updates_task.yaml +80 -0
  285. cumulusci/tasks/bulkdata/tests/cassettes/TestUpsert.test_simple_upsert__rest.yaml +270 -0
  286. cumulusci/tasks/bulkdata/tests/cassettes/TestUpsert.test_upsert__rest.yaml +267 -0
  287. cumulusci/tasks/bulkdata/tests/cassettes/TestUpsert.test_upsert_complex_external_id_field__rest.yaml +369 -0
  288. cumulusci/tasks/bulkdata/tests/cassettes/TestUpsert.test_upsert_complex_external_id_field_rest__duplicate_error.yaml +204 -0
  289. cumulusci/tasks/bulkdata/tests/cassettes/TestUpsert.test_upsert_complex_fields__bulk.yaml +675 -0
  290. cumulusci/tasks/bulkdata/tests/dummy_data_factory.py +36 -0
  291. cumulusci/tasks/bulkdata/tests/integration_test_utils.py +49 -0
  292. cumulusci/tasks/bulkdata/tests/mapping-oid.yml +87 -0
  293. cumulusci/tasks/bulkdata/tests/mapping_after.yml +38 -0
  294. cumulusci/tasks/bulkdata/tests/mapping_poly.yml +34 -0
  295. cumulusci/tasks/bulkdata/tests/mapping_poly_incomplete.yml +20 -0
  296. cumulusci/tasks/bulkdata/tests/mapping_poly_wrong.yml +21 -0
  297. cumulusci/tasks/bulkdata/tests/mapping_select.yml +20 -0
  298. cumulusci/tasks/bulkdata/tests/mapping_select_invalid_strategy.yml +20 -0
  299. cumulusci/tasks/bulkdata/tests/mapping_select_invalid_threshold__invalid_number.yml +21 -0
  300. cumulusci/tasks/bulkdata/tests/mapping_select_invalid_threshold__invalid_strategy.yml +21 -0
  301. cumulusci/tasks/bulkdata/tests/mapping_select_invalid_threshold__non_float.yml +21 -0
  302. cumulusci/tasks/bulkdata/tests/mapping_select_missing_priority_fields.yml +22 -0
  303. cumulusci/tasks/bulkdata/tests/mapping_select_no_priority_fields.yml +18 -0
  304. cumulusci/tasks/bulkdata/tests/mapping_simple.yml +27 -0
  305. cumulusci/tasks/bulkdata/tests/mapping_v1.yml +28 -0
  306. cumulusci/tasks/bulkdata/tests/mapping_v2.yml +21 -0
  307. cumulusci/tasks/bulkdata/tests/mapping_v3.yml +32 -0
  308. cumulusci/tasks/bulkdata/tests/mapping_vanilla_sf.yml +69 -0
  309. cumulusci/tasks/bulkdata/tests/mock_data_factory_without_mapping.py +12 -0
  310. cumulusci/tasks/bulkdata/tests/person_accounts.yml +23 -0
  311. cumulusci/tasks/bulkdata/tests/person_accounts_minimal.yml +15 -0
  312. cumulusci/tasks/bulkdata/tests/recordtypes.yml +8 -0
  313. cumulusci/tasks/bulkdata/tests/recordtypes_2.yml +6 -0
  314. cumulusci/tasks/bulkdata/tests/recordtypes_with_ispersontype.yml +8 -0
  315. cumulusci/tasks/bulkdata/tests/snowfakery/child/child2.yml +3 -0
  316. cumulusci/tasks/bulkdata/tests/snowfakery/child.yml +4 -0
  317. cumulusci/tasks/bulkdata/tests/snowfakery/gen_npsp_standard_objects.recipe.yml +89 -0
  318. cumulusci/tasks/bulkdata/tests/snowfakery/include_parent.yml +3 -0
  319. cumulusci/tasks/bulkdata/tests/snowfakery/npsp_standard_objects_macros.yml +34 -0
  320. cumulusci/tasks/bulkdata/tests/snowfakery/options.recipe.yml +6 -0
  321. cumulusci/tasks/bulkdata/tests/snowfakery/query_snowfakery.recipe.yml +16 -0
  322. cumulusci/tasks/bulkdata/tests/snowfakery/sf_standard_object_macros.yml +83 -0
  323. cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery.load.yml +2 -0
  324. cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery.recipe.yml +13 -0
  325. cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery_2.load.yml +5 -0
  326. cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery_channels.load.yml +13 -0
  327. cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery_channels.recipe.yml +12 -0
  328. cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery_channels_2.load.yml +13 -0
  329. cumulusci/tasks/bulkdata/tests/snowfakery/unique_values.recipe.yml +4 -0
  330. cumulusci/tasks/bulkdata/tests/snowfakery/upsert.recipe.yml +23 -0
  331. cumulusci/tasks/bulkdata/tests/snowfakery/upsert_2.recipe.yml +29 -0
  332. cumulusci/tasks/bulkdata/tests/snowfakery/upsert_before.yml +10 -0
  333. cumulusci/tasks/bulkdata/tests/test_base_generate_data_tasks.py +61 -0
  334. cumulusci/tasks/bulkdata/tests/test_dates.py +99 -0
  335. cumulusci/tasks/bulkdata/tests/test_delete.py +404 -0
  336. cumulusci/tasks/bulkdata/tests/test_extract.py +1311 -0
  337. cumulusci/tasks/bulkdata/tests/test_factory_utils.py +55 -0
  338. cumulusci/tasks/bulkdata/tests/test_generate_and_load.py +252 -0
  339. cumulusci/tasks/bulkdata/tests/test_generate_from_snowfakery_task.py +343 -0
  340. cumulusci/tasks/bulkdata/tests/test_generatemapping.py +1039 -0
  341. cumulusci/tasks/bulkdata/tests/test_load.py +3175 -0
  342. cumulusci/tasks/bulkdata/tests/test_mapping_parser.py +1658 -0
  343. cumulusci/tasks/bulkdata/tests/test_query_db__joins_self_lookups.yml +12 -0
  344. cumulusci/tasks/bulkdata/tests/test_query_db_joins_lookups.yml +26 -0
  345. cumulusci/tasks/bulkdata/tests/test_query_db_joins_lookups_select.yml +48 -0
  346. cumulusci/tasks/bulkdata/tests/test_select.py +171 -0
  347. cumulusci/tasks/bulkdata/tests/test_select_utils.py +1057 -0
  348. cumulusci/tasks/bulkdata/tests/test_snowfakery.py +1153 -0
  349. cumulusci/tasks/bulkdata/tests/test_step.py +3957 -0
  350. cumulusci/tasks/bulkdata/tests/test_updates.py +513 -0
  351. cumulusci/tasks/bulkdata/tests/test_upsert.py +1015 -0
  352. cumulusci/tasks/bulkdata/tests/test_utils.py +158 -0
  353. cumulusci/tasks/bulkdata/tests/testdata.db +0 -0
  354. cumulusci/tasks/bulkdata/tests/update_describe.py +50 -0
  355. cumulusci/tasks/bulkdata/tests/update_person_accounts.yml +23 -0
  356. cumulusci/tasks/bulkdata/tests/utils.py +114 -0
  357. cumulusci/tasks/bulkdata/update_data.py +260 -0
  358. cumulusci/tasks/bulkdata/upsert_utils.py +130 -0
  359. cumulusci/tasks/bulkdata/utils.py +249 -0
  360. cumulusci/tasks/command.py +178 -0
  361. cumulusci/tasks/connectedapp.py +186 -0
  362. cumulusci/tasks/create_package_version.py +778 -0
  363. cumulusci/tasks/datadictionary.py +745 -0
  364. cumulusci/tasks/dx_convert_from.py +26 -0
  365. cumulusci/tasks/github/__init__.py +17 -0
  366. cumulusci/tasks/github/base.py +16 -0
  367. cumulusci/tasks/github/commit_status.py +13 -0
  368. cumulusci/tasks/github/merge.py +11 -0
  369. cumulusci/tasks/github/publish.py +11 -0
  370. cumulusci/tasks/github/pull_request.py +11 -0
  371. cumulusci/tasks/github/release.py +11 -0
  372. cumulusci/tasks/github/release_report.py +11 -0
  373. cumulusci/tasks/github/tag.py +11 -0
  374. cumulusci/tasks/github/tests/__init__.py +0 -0
  375. cumulusci/tasks/github/tests/test_util.py +202 -0
  376. cumulusci/tasks/github/tests/test_vcs_migration.py +44 -0
  377. cumulusci/tasks/github/tests/util_github_api.py +666 -0
  378. cumulusci/tasks/github/util.py +252 -0
  379. cumulusci/tasks/marketing_cloud/__init__.py +0 -0
  380. cumulusci/tasks/marketing_cloud/api.py +188 -0
  381. cumulusci/tasks/marketing_cloud/base.py +38 -0
  382. cumulusci/tasks/marketing_cloud/deploy.py +345 -0
  383. cumulusci/tasks/marketing_cloud/get_user_info.py +40 -0
  384. cumulusci/tasks/marketing_cloud/mc_constants.py +1 -0
  385. cumulusci/tasks/marketing_cloud/tests/__init__.py +0 -0
  386. cumulusci/tasks/marketing_cloud/tests/conftest.py +46 -0
  387. cumulusci/tasks/marketing_cloud/tests/expected-payload.json +110 -0
  388. cumulusci/tasks/marketing_cloud/tests/test_api.py +97 -0
  389. cumulusci/tasks/marketing_cloud/tests/test_api_soap_envelopes.py +145 -0
  390. cumulusci/tasks/marketing_cloud/tests/test_base.py +14 -0
  391. cumulusci/tasks/marketing_cloud/tests/test_deploy.py +400 -0
  392. cumulusci/tasks/marketing_cloud/tests/test_get_user_info.py +141 -0
  393. cumulusci/tasks/marketing_cloud/tests/validation-response.json +39 -0
  394. cumulusci/tasks/metadata/__init__.py +0 -0
  395. cumulusci/tasks/metadata/ee_src.py +94 -0
  396. cumulusci/tasks/metadata/managed_src.py +100 -0
  397. cumulusci/tasks/metadata/metadata_map.yml +868 -0
  398. cumulusci/tasks/metadata/modify.py +99 -0
  399. cumulusci/tasks/metadata/package.py +684 -0
  400. cumulusci/tasks/metadata/tests/__init__.py +0 -0
  401. cumulusci/tasks/metadata/tests/package_metadata/namespaced_report_folder/.hidden/.keep +0 -0
  402. cumulusci/tasks/metadata/tests/package_metadata/namespaced_report_folder/destructiveChanges.xml +9 -0
  403. cumulusci/tasks/metadata/tests/package_metadata/namespaced_report_folder/package.xml +9 -0
  404. cumulusci/tasks/metadata/tests/package_metadata/namespaced_report_folder/package_install_uninstall.xml +11 -0
  405. cumulusci/tasks/metadata/tests/package_metadata/namespaced_report_folder/reports/namespace__TestFolder/TestReport.report +3 -0
  406. cumulusci/tasks/metadata/tests/sample_package.xml +9 -0
  407. cumulusci/tasks/metadata/tests/test_ee_src.py +112 -0
  408. cumulusci/tasks/metadata/tests/test_managed_src.py +111 -0
  409. cumulusci/tasks/metadata/tests/test_modify.py +123 -0
  410. cumulusci/tasks/metadata/tests/test_package.py +476 -0
  411. cumulusci/tasks/metadata_etl/__init__.py +29 -0
  412. cumulusci/tasks/metadata_etl/base.py +436 -0
  413. cumulusci/tasks/metadata_etl/duplicate_rules.py +24 -0
  414. cumulusci/tasks/metadata_etl/field_sets.py +70 -0
  415. cumulusci/tasks/metadata_etl/help_text.py +92 -0
  416. cumulusci/tasks/metadata_etl/layouts.py +550 -0
  417. cumulusci/tasks/metadata_etl/objects.py +68 -0
  418. cumulusci/tasks/metadata_etl/permissions.py +167 -0
  419. cumulusci/tasks/metadata_etl/picklists.py +221 -0
  420. cumulusci/tasks/metadata_etl/remote_site_settings.py +99 -0
  421. cumulusci/tasks/metadata_etl/sharing.py +138 -0
  422. cumulusci/tasks/metadata_etl/tests/test_base.py +512 -0
  423. cumulusci/tasks/metadata_etl/tests/test_duplicate_rules.py +22 -0
  424. cumulusci/tasks/metadata_etl/tests/test_field_sets.py +156 -0
  425. cumulusci/tasks/metadata_etl/tests/test_help_text.py +387 -0
  426. cumulusci/tasks/metadata_etl/tests/test_ip_ranges.py +85 -0
  427. cumulusci/tasks/metadata_etl/tests/test_layouts.py +858 -0
  428. cumulusci/tasks/metadata_etl/tests/test_objects.py +236 -0
  429. cumulusci/tasks/metadata_etl/tests/test_permissions.py +223 -0
  430. cumulusci/tasks/metadata_etl/tests/test_picklists.py +547 -0
  431. cumulusci/tasks/metadata_etl/tests/test_remote_site_settings.py +46 -0
  432. cumulusci/tasks/metadata_etl/tests/test_sharing.py +333 -0
  433. cumulusci/tasks/metadata_etl/tests/test_value_sets.py +298 -0
  434. cumulusci/tasks/metadata_etl/value_sets.py +106 -0
  435. cumulusci/tasks/metadeploy.py +393 -0
  436. cumulusci/tasks/metaxml.py +88 -0
  437. cumulusci/tasks/preflight/__init__.py +0 -0
  438. cumulusci/tasks/preflight/dataset_load.py +49 -0
  439. cumulusci/tasks/preflight/licenses.py +86 -0
  440. cumulusci/tasks/preflight/packages.py +14 -0
  441. cumulusci/tasks/preflight/permsets.py +23 -0
  442. cumulusci/tasks/preflight/recordtypes.py +16 -0
  443. cumulusci/tasks/preflight/retrieve_tasks.py +30 -0
  444. cumulusci/tasks/preflight/settings.py +77 -0
  445. cumulusci/tasks/preflight/sobjects.py +202 -0
  446. cumulusci/tasks/preflight/tests/test_dataset_load.py +85 -0
  447. cumulusci/tasks/preflight/tests/test_licenses.py +174 -0
  448. cumulusci/tasks/preflight/tests/test_packages.py +14 -0
  449. cumulusci/tasks/preflight/tests/test_permset_preflights.py +51 -0
  450. cumulusci/tasks/preflight/tests/test_recordtypes.py +30 -0
  451. cumulusci/tasks/preflight/tests/test_retrieve_tasks.py +62 -0
  452. cumulusci/tasks/preflight/tests/test_settings.py +130 -0
  453. cumulusci/tasks/preflight/tests/test_sobjects.py +231 -0
  454. cumulusci/tasks/push/README.md +59 -0
  455. cumulusci/tasks/push/__init__.py +0 -0
  456. cumulusci/tasks/push/push_api.py +659 -0
  457. cumulusci/tasks/push/pushfails.py +136 -0
  458. cumulusci/tasks/push/tasks.py +476 -0
  459. cumulusci/tasks/push/tests/conftest.py +263 -0
  460. cumulusci/tasks/push/tests/test_push_api.py +951 -0
  461. cumulusci/tasks/push/tests/test_push_tasks.py +659 -0
  462. cumulusci/tasks/release_notes/README.md +63 -0
  463. cumulusci/tasks/release_notes/__init__.py +0 -0
  464. cumulusci/tasks/release_notes/exceptions.py +5 -0
  465. cumulusci/tasks/release_notes/generator.py +137 -0
  466. cumulusci/tasks/release_notes/parser.py +232 -0
  467. cumulusci/tasks/release_notes/provider.py +44 -0
  468. cumulusci/tasks/release_notes/task.py +300 -0
  469. cumulusci/tasks/release_notes/tests/__init__.py +0 -0
  470. cumulusci/tasks/release_notes/tests/change_notes/full/example1.md +17 -0
  471. cumulusci/tasks/release_notes/tests/change_notes/multi/1.txt +1 -0
  472. cumulusci/tasks/release_notes/tests/change_notes/multi/2.txt +1 -0
  473. cumulusci/tasks/release_notes/tests/change_notes/multi/3.txt +1 -0
  474. cumulusci/tasks/release_notes/tests/change_notes/single/1.txt +1 -0
  475. cumulusci/tasks/release_notes/tests/test_generator.py +582 -0
  476. cumulusci/tasks/release_notes/tests/test_parser.py +867 -0
  477. cumulusci/tasks/release_notes/tests/test_provider.py +512 -0
  478. cumulusci/tasks/release_notes/tests/test_task.py +461 -0
  479. cumulusci/tasks/release_notes/tests/utils.py +153 -0
  480. cumulusci/tasks/robotframework/__init__.py +3 -0
  481. cumulusci/tasks/robotframework/debugger/DebugListener.py +100 -0
  482. cumulusci/tasks/robotframework/debugger/__init__.py +10 -0
  483. cumulusci/tasks/robotframework/debugger/model.py +87 -0
  484. cumulusci/tasks/robotframework/debugger/ui.py +259 -0
  485. cumulusci/tasks/robotframework/libdoc.py +269 -0
  486. cumulusci/tasks/robotframework/robotframework.py +392 -0
  487. cumulusci/tasks/robotframework/stylesheet.css +130 -0
  488. cumulusci/tasks/robotframework/template.html +109 -0
  489. cumulusci/tasks/robotframework/tests/TestLibrary.py +18 -0
  490. cumulusci/tasks/robotframework/tests/TestPageObjects.py +31 -0
  491. cumulusci/tasks/robotframework/tests/TestResource.robot +8 -0
  492. cumulusci/tasks/robotframework/tests/failing_tests.robot +16 -0
  493. cumulusci/tasks/robotframework/tests/performance.robot +23 -0
  494. cumulusci/tasks/robotframework/tests/test_browser_proxies.py +137 -0
  495. cumulusci/tasks/robotframework/tests/test_debugger.py +360 -0
  496. cumulusci/tasks/robotframework/tests/test_robot_parallel.py +141 -0
  497. cumulusci/tasks/robotframework/tests/test_robotframework.py +860 -0
  498. cumulusci/tasks/salesforce/BaseRetrieveMetadata.py +58 -0
  499. cumulusci/tasks/salesforce/BaseSalesforceApiTask.py +45 -0
  500. cumulusci/tasks/salesforce/BaseSalesforceMetadataApiTask.py +18 -0
  501. cumulusci/tasks/salesforce/BaseSalesforceTask.py +4 -0
  502. cumulusci/tasks/salesforce/BaseUninstallMetadata.py +41 -0
  503. cumulusci/tasks/salesforce/CreateCommunity.py +124 -0
  504. cumulusci/tasks/salesforce/CreatePackage.py +29 -0
  505. cumulusci/tasks/salesforce/Deploy.py +240 -0
  506. cumulusci/tasks/salesforce/DeployBundles.py +88 -0
  507. cumulusci/tasks/salesforce/DescribeMetadataTypes.py +26 -0
  508. cumulusci/tasks/salesforce/EnsureRecordTypes.py +202 -0
  509. cumulusci/tasks/salesforce/GetInstalledPackages.py +8 -0
  510. cumulusci/tasks/salesforce/ListCommunities.py +40 -0
  511. cumulusci/tasks/salesforce/ListCommunityTemplates.py +19 -0
  512. cumulusci/tasks/salesforce/PublishCommunity.py +62 -0
  513. cumulusci/tasks/salesforce/RetrievePackaged.py +41 -0
  514. cumulusci/tasks/salesforce/RetrieveReportsAndDashboards.py +82 -0
  515. cumulusci/tasks/salesforce/RetrieveUnpackaged.py +36 -0
  516. cumulusci/tasks/salesforce/SOQLQuery.py +39 -0
  517. cumulusci/tasks/salesforce/UninstallLocal.py +15 -0
  518. cumulusci/tasks/salesforce/UninstallLocalBundles.py +28 -0
  519. cumulusci/tasks/salesforce/UninstallLocalNamespacedBundles.py +58 -0
  520. cumulusci/tasks/salesforce/UninstallPackage.py +32 -0
  521. cumulusci/tasks/salesforce/UninstallPackaged.py +56 -0
  522. cumulusci/tasks/salesforce/UpdateAdminProfile.py +8 -0
  523. cumulusci/tasks/salesforce/__init__.py +79 -0
  524. cumulusci/tasks/salesforce/activate_flow.py +74 -0
  525. cumulusci/tasks/salesforce/check_components.py +324 -0
  526. cumulusci/tasks/salesforce/composite.py +142 -0
  527. cumulusci/tasks/salesforce/create_permission_sets.py +35 -0
  528. cumulusci/tasks/salesforce/custom_settings.py +134 -0
  529. cumulusci/tasks/salesforce/custom_settings_wait.py +132 -0
  530. cumulusci/tasks/salesforce/enable_prediction.py +107 -0
  531. cumulusci/tasks/salesforce/insert_record.py +40 -0
  532. cumulusci/tasks/salesforce/install_package_version.py +242 -0
  533. cumulusci/tasks/salesforce/license_preflights.py +8 -0
  534. cumulusci/tasks/salesforce/network_member_group.py +178 -0
  535. cumulusci/tasks/salesforce/nonsourcetracking.py +228 -0
  536. cumulusci/tasks/salesforce/org_settings.py +193 -0
  537. cumulusci/tasks/salesforce/package_upload.py +328 -0
  538. cumulusci/tasks/salesforce/profiles.py +74 -0
  539. cumulusci/tasks/salesforce/promote_package_version.py +376 -0
  540. cumulusci/tasks/salesforce/retrieve_profile.py +195 -0
  541. cumulusci/tasks/salesforce/salesforce_files.py +244 -0
  542. cumulusci/tasks/salesforce/sourcetracking.py +507 -0
  543. cumulusci/tasks/salesforce/tests/__init__.py +3 -0
  544. cumulusci/tasks/salesforce/tests/test_CreateCommunity.py +278 -0
  545. cumulusci/tasks/salesforce/tests/test_CreatePackage.py +22 -0
  546. cumulusci/tasks/salesforce/tests/test_Deploy.py +470 -0
  547. cumulusci/tasks/salesforce/tests/test_DeployBundles.py +76 -0
  548. cumulusci/tasks/salesforce/tests/test_EnsureRecordTypes.py +345 -0
  549. cumulusci/tasks/salesforce/tests/test_ListCommunities.py +84 -0
  550. cumulusci/tasks/salesforce/tests/test_ListCommunityTemplates.py +49 -0
  551. cumulusci/tasks/salesforce/tests/test_PackageUpload.py +547 -0
  552. cumulusci/tasks/salesforce/tests/test_ProfileGrantAllAccess.py +699 -0
  553. cumulusci/tasks/salesforce/tests/test_PublishCommunity.py +181 -0
  554. cumulusci/tasks/salesforce/tests/test_RetrievePackaged.py +24 -0
  555. cumulusci/tasks/salesforce/tests/test_RetrieveReportsAndDashboards.py +56 -0
  556. cumulusci/tasks/salesforce/tests/test_RetrieveUnpackaged.py +21 -0
  557. cumulusci/tasks/salesforce/tests/test_SOQLQuery.py +30 -0
  558. cumulusci/tasks/salesforce/tests/test_UninstallLocal.py +15 -0
  559. cumulusci/tasks/salesforce/tests/test_UninstallLocalBundles.py +19 -0
  560. cumulusci/tasks/salesforce/tests/test_UninstallLocalNamespacedBundles.py +22 -0
  561. cumulusci/tasks/salesforce/tests/test_UninstallPackage.py +19 -0
  562. cumulusci/tasks/salesforce/tests/test_UninstallPackaged.py +66 -0
  563. cumulusci/tasks/salesforce/tests/test_UninstallPackagedIncremental.py +127 -0
  564. cumulusci/tasks/salesforce/tests/test_activate_flow.py +132 -0
  565. cumulusci/tasks/salesforce/tests/test_base_tasks.py +110 -0
  566. cumulusci/tasks/salesforce/tests/test_check_components.py +445 -0
  567. cumulusci/tasks/salesforce/tests/test_composite.py +250 -0
  568. cumulusci/tasks/salesforce/tests/test_create_permission_sets.py +41 -0
  569. cumulusci/tasks/salesforce/tests/test_custom_settings.py +227 -0
  570. cumulusci/tasks/salesforce/tests/test_custom_settings_wait.py +174 -0
  571. cumulusci/tasks/salesforce/tests/test_describemetadatatypes.py +18 -0
  572. cumulusci/tasks/salesforce/tests/test_enable_prediction.py +240 -0
  573. cumulusci/tasks/salesforce/tests/test_insert_record.py +110 -0
  574. cumulusci/tasks/salesforce/tests/test_install_package_version.py +464 -0
  575. cumulusci/tasks/salesforce/tests/test_network_member_group.py +444 -0
  576. cumulusci/tasks/salesforce/tests/test_nonsourcetracking.py +235 -0
  577. cumulusci/tasks/salesforce/tests/test_org_settings.py +407 -0
  578. cumulusci/tasks/salesforce/tests/test_profiles.py +202 -0
  579. cumulusci/tasks/salesforce/tests/test_retrieve_profile.py +287 -0
  580. cumulusci/tasks/salesforce/tests/test_salesforce_files.py +228 -0
  581. cumulusci/tasks/salesforce/tests/test_sourcetracking.py +350 -0
  582. cumulusci/tasks/salesforce/tests/test_trigger_handlers.py +300 -0
  583. cumulusci/tasks/salesforce/tests/test_update_dependencies.py +509 -0
  584. cumulusci/tasks/salesforce/tests/util.py +79 -0
  585. cumulusci/tasks/salesforce/trigger_handlers.py +119 -0
  586. cumulusci/tasks/salesforce/uninstall_packaged_incremental.py +136 -0
  587. cumulusci/tasks/salesforce/update_dependencies.py +290 -0
  588. cumulusci/tasks/salesforce/update_profile.py +339 -0
  589. cumulusci/tasks/salesforce/users/permsets.py +227 -0
  590. cumulusci/tasks/salesforce/users/photos.py +162 -0
  591. cumulusci/tasks/salesforce/users/tests/photo.mock.txt +1 -0
  592. cumulusci/tasks/salesforce/users/tests/test_permsets.py +950 -0
  593. cumulusci/tasks/salesforce/users/tests/test_photos.py +373 -0
  594. cumulusci/tasks/sample_data/capture_sample_data.py +77 -0
  595. cumulusci/tasks/sample_data/load_sample_data.py +85 -0
  596. cumulusci/tasks/sample_data/test_capture_sample_data.py +117 -0
  597. cumulusci/tasks/sample_data/test_load_sample_data.py +121 -0
  598. cumulusci/tasks/sfdx.py +83 -0
  599. cumulusci/tasks/tests/__init__.py +1 -0
  600. cumulusci/tasks/tests/conftest.py +30 -0
  601. cumulusci/tasks/tests/test_command.py +129 -0
  602. cumulusci/tasks/tests/test_connectedapp.py +236 -0
  603. cumulusci/tasks/tests/test_create_package_version.py +847 -0
  604. cumulusci/tasks/tests/test_datadictionary.py +1575 -0
  605. cumulusci/tasks/tests/test_dx_convert_from.py +60 -0
  606. cumulusci/tasks/tests/test_metadeploy.py +624 -0
  607. cumulusci/tasks/tests/test_metaxml.py +99 -0
  608. cumulusci/tasks/tests/test_promote_package_version.py +488 -0
  609. cumulusci/tasks/tests/test_pushfails.py +96 -0
  610. cumulusci/tasks/tests/test_salesforce.py +72 -0
  611. cumulusci/tasks/tests/test_sfdx.py +105 -0
  612. cumulusci/tasks/tests/test_util.py +207 -0
  613. cumulusci/tasks/util.py +261 -0
  614. cumulusci/tasks/vcs/__init__.py +19 -0
  615. cumulusci/tasks/vcs/commit_status.py +58 -0
  616. cumulusci/tasks/vcs/create_commit_status.py +37 -0
  617. cumulusci/tasks/vcs/download_extract.py +199 -0
  618. cumulusci/tasks/vcs/merge.py +298 -0
  619. cumulusci/tasks/vcs/publish.py +207 -0
  620. cumulusci/tasks/vcs/pull_request.py +9 -0
  621. cumulusci/tasks/vcs/release.py +134 -0
  622. cumulusci/tasks/vcs/release_report.py +105 -0
  623. cumulusci/tasks/vcs/tag.py +31 -0
  624. cumulusci/tasks/vcs/tests/github/test_commit_status.py +196 -0
  625. cumulusci/tasks/vcs/tests/github/test_download_extract.py +896 -0
  626. cumulusci/tasks/vcs/tests/github/test_merge.py +1118 -0
  627. cumulusci/tasks/vcs/tests/github/test_publish.py +823 -0
  628. cumulusci/tasks/vcs/tests/github/test_pull_request.py +29 -0
  629. cumulusci/tasks/vcs/tests/github/test_release.py +390 -0
  630. cumulusci/tasks/vcs/tests/github/test_release_report.py +109 -0
  631. cumulusci/tasks/vcs/tests/github/test_tag.py +90 -0
  632. cumulusci/tasks/vlocity/exceptions.py +2 -0
  633. cumulusci/tasks/vlocity/tests/test_vlocity.py +283 -0
  634. cumulusci/tasks/vlocity/vlocity.py +342 -0
  635. cumulusci/tests/__init__.py +1 -0
  636. cumulusci/tests/cassettes/GET_sobjects_Account_PersonAccount_describe.yaml +18 -0
  637. cumulusci/tests/cassettes/TestIntegrationInfrastructure.test_integration_tests.yaml +19 -0
  638. cumulusci/tests/pytest_plugins/pytest_sf_orgconnect.py +307 -0
  639. cumulusci/tests/pytest_plugins/pytest_sf_vcr.py +275 -0
  640. cumulusci/tests/pytest_plugins/pytest_sf_vcr_serializer.py +160 -0
  641. cumulusci/tests/pytest_plugins/pytest_typeguard.py +5 -0
  642. cumulusci/tests/pytest_plugins/test_vcr_string_compressor.py +49 -0
  643. cumulusci/tests/pytest_plugins/vcr_string_compressor.py +97 -0
  644. cumulusci/tests/shared_cassettes/GET_sobjects_Account_describe.yaml +18 -0
  645. cumulusci/tests/shared_cassettes/GET_sobjects_Case_describe.yaml +18 -0
  646. cumulusci/tests/shared_cassettes/GET_sobjects_Contact_describe.yaml +4838 -0
  647. cumulusci/tests/shared_cassettes/GET_sobjects_Custom__c_describe.yaml +242 -0
  648. cumulusci/tests/shared_cassettes/GET_sobjects_Event_describe.yaml +19 -0
  649. cumulusci/tests/shared_cassettes/GET_sobjects_Global_describe.yaml +1338 -0
  650. cumulusci/tests/shared_cassettes/GET_sobjects_Lead_describe.yaml +18 -0
  651. cumulusci/tests/shared_cassettes/GET_sobjects_OpportunityContactRole_describe.yaml +34 -0
  652. cumulusci/tests/shared_cassettes/GET_sobjects_Opportunity_describe.yaml +1261 -0
  653. cumulusci/tests/shared_cassettes/GET_sobjects_Organization.yaml +49 -0
  654. cumulusci/tests/shared_cassettes/vcr_string_templates/batchInfoList_xml.tpl +15 -0
  655. cumulusci/tests/shared_cassettes/vcr_string_templates/batchInfo_xml.tpl +13 -0
  656. cumulusci/tests/shared_cassettes/vcr_string_templates/jobInfo_insert_xml.tpl +24 -0
  657. cumulusci/tests/shared_cassettes/vcr_string_templates/jobInfo_upsert_xml.tpl +25 -0
  658. cumulusci/tests/test_entry_points.py +20 -0
  659. cumulusci/tests/test_integration_infrastructure.py +131 -0
  660. cumulusci/tests/test_main.py +9 -0
  661. cumulusci/tests/test_schema.py +32 -0
  662. cumulusci/tests/test_utils.py +657 -0
  663. cumulusci/tests/test_vcr_serializer.py +134 -0
  664. cumulusci/tests/uncompressed_cassette.yaml +83 -0
  665. cumulusci/tests/util.py +344 -0
  666. cumulusci/utils/__init__.py +731 -0
  667. cumulusci/utils/classutils.py +9 -0
  668. cumulusci/utils/collections.py +32 -0
  669. cumulusci/utils/deprecation.py +11 -0
  670. cumulusci/utils/encryption.py +31 -0
  671. cumulusci/utils/fileutils.py +295 -0
  672. cumulusci/utils/git.py +142 -0
  673. cumulusci/utils/http/multi_request.py +214 -0
  674. cumulusci/utils/http/requests_utils.py +103 -0
  675. cumulusci/utils/http/tests/cassettes/ManualEditTestCompositeParallelSalesforce.test_http_headers.yaml +32 -0
  676. cumulusci/utils/http/tests/cassettes/TestCompositeParallelSalesforce.test_composite_parallel_salesforce.yaml +65 -0
  677. cumulusci/utils/http/tests/cassettes/TestCompositeParallelSalesforce.test_errors.yaml +24 -0
  678. cumulusci/utils/http/tests/cassettes/TestCompositeParallelSalesforce.test_reference_ids.yaml +49 -0
  679. cumulusci/utils/http/tests/test_multi_request.py +255 -0
  680. cumulusci/utils/iterators.py +21 -0
  681. cumulusci/utils/logging.py +128 -0
  682. cumulusci/utils/metaprogramming.py +10 -0
  683. cumulusci/utils/options.py +138 -0
  684. cumulusci/utils/parallel/queries_in_parallel/run_queries_in_parallel.py +29 -0
  685. cumulusci/utils/parallel/queries_in_parallel/tests/test_run_queries_in_parallel.py +50 -0
  686. cumulusci/utils/parallel/task_worker_queues/parallel_worker.py +238 -0
  687. cumulusci/utils/parallel/task_worker_queues/parallel_worker_queue.py +243 -0
  688. cumulusci/utils/parallel/task_worker_queues/tests/test_parallel_worker.py +353 -0
  689. cumulusci/utils/salesforce/count_sobjects.py +46 -0
  690. cumulusci/utils/salesforce/soql.py +17 -0
  691. cumulusci/utils/salesforce/tests/cassettes/ManualEdit_TestCountSObjects.test_count_sobjects__network_errors.yaml +23 -0
  692. cumulusci/utils/salesforce/tests/cassettes/TestCountSObjects.test_count_sobjects__errors.yaml +33 -0
  693. cumulusci/utils/salesforce/tests/cassettes/TestCountSObjects.test_count_sobjects_simple.yaml +29 -0
  694. cumulusci/utils/salesforce/tests/test_count_sobjects.py +29 -0
  695. cumulusci/utils/salesforce/tests/test_soql.py +30 -0
  696. cumulusci/utils/tests/cassettes/ManualEditTestDescribeOrg.test_minimal_schema.yaml +36 -0
  697. cumulusci/utils/tests/cassettes/ManualEdit_test_describe_to_sql.yaml +191 -0
  698. cumulusci/utils/tests/test_fileutils.py +284 -0
  699. cumulusci/utils/tests/test_git.py +85 -0
  700. cumulusci/utils/tests/test_logging.py +70 -0
  701. cumulusci/utils/tests/test_option_parsing.py +188 -0
  702. cumulusci/utils/tests/test_org_schema.py +691 -0
  703. cumulusci/utils/tests/test_org_schema_models.py +79 -0
  704. cumulusci/utils/tests/test_waiting.py +25 -0
  705. cumulusci/utils/version_strings.py +391 -0
  706. cumulusci/utils/waiting.py +42 -0
  707. cumulusci/utils/xml/__init__.py +91 -0
  708. cumulusci/utils/xml/metadata_tree.py +299 -0
  709. cumulusci/utils/xml/robot_xml.py +114 -0
  710. cumulusci/utils/xml/salesforce_encoding.py +100 -0
  711. cumulusci/utils/xml/test/test_metadata_tree.py +251 -0
  712. cumulusci/utils/xml/test/test_salesforce_encoding.py +173 -0
  713. cumulusci/utils/yaml/cumulusci_yml.py +401 -0
  714. cumulusci/utils/yaml/model_parser.py +156 -0
  715. cumulusci/utils/yaml/safer_loader.py +74 -0
  716. cumulusci/utils/yaml/tests/bad_cci.yml +5 -0
  717. cumulusci/utils/yaml/tests/cassettes/TestCumulusciYml.test_validate_url__with_errors.yaml +20 -0
  718. cumulusci/utils/yaml/tests/test_cumulusci_yml.py +286 -0
  719. cumulusci/utils/yaml/tests/test_model_parser.py +175 -0
  720. cumulusci/utils/yaml/tests/test_safer_loader.py +88 -0
  721. cumulusci/utils/ziputils.py +61 -0
  722. cumulusci/vcs/base.py +143 -0
  723. cumulusci/vcs/bootstrap.py +272 -0
  724. cumulusci/vcs/github/__init__.py +24 -0
  725. cumulusci/vcs/github/adapter.py +689 -0
  726. cumulusci/vcs/github/release_notes/generator.py +219 -0
  727. cumulusci/vcs/github/release_notes/parser.py +151 -0
  728. cumulusci/vcs/github/release_notes/provider.py +143 -0
  729. cumulusci/vcs/github/service.py +569 -0
  730. cumulusci/vcs/github/tests/test_adapter.py +138 -0
  731. cumulusci/vcs/github/tests/test_service.py +408 -0
  732. cumulusci/vcs/models.py +586 -0
  733. cumulusci/vcs/tests/conftest.py +41 -0
  734. cumulusci/vcs/tests/dummy_service.py +241 -0
  735. cumulusci/vcs/tests/test_vcs_base.py +687 -0
  736. cumulusci/vcs/tests/test_vcs_bootstrap.py +727 -0
  737. cumulusci/vcs/utils/__init__.py +31 -0
  738. cumulusci/vcs/vcs_source.py +287 -0
  739. cumulusci_plus-5.0.0.dist-info/METADATA +145 -0
  740. cumulusci_plus-5.0.0.dist-info/RECORD +744 -0
  741. cumulusci_plus-5.0.0.dist-info/WHEEL +4 -0
  742. cumulusci_plus-5.0.0.dist-info/entry_points.txt +3 -0
  743. cumulusci_plus-5.0.0.dist-info/licenses/AUTHORS.rst +41 -0
  744. cumulusci_plus-5.0.0.dist-info/licenses/LICENSE +30 -0
@@ -0,0 +1,792 @@
1
+ import logging
2
+ import random
3
+ import re
4
+ import typing as T
5
+ from enum import Enum
6
+
7
+ from pydantic import Field, root_validator, validator
8
+
9
+ from cumulusci.core.enums import StrEnum
10
+ from cumulusci.tasks.bulkdata.utils import CaseInsensitiveDict
11
+ from cumulusci.utils import get_cci_upgrade_command
12
+ from cumulusci.utils.yaml.model_parser import CCIDictModel
13
+
14
+ logger = logging.getLogger(__name__)
15
+ try:
16
+ import numpy as np
17
+ import pandas as pd
18
+ from annoy import AnnoyIndex
19
+ from sklearn.feature_extraction.text import HashingVectorizer
20
+ from sklearn.preprocessing import StandardScaler
21
+
22
+ OPTIONAL_DEPENDENCIES_AVAILABLE = True
23
+ except ImportError:
24
+ logger.warning(
25
+ f"Optional dependencies are missing. "
26
+ "Handling high volumes of records for the 'select' functionality will be significantly slower, "
27
+ "as optimizations for this feature are currently disabled. "
28
+ f"To enable optimized performance, install all required dependencies using: {get_cci_upgrade_command()}[select]\n"
29
+ )
30
+ OPTIONAL_DEPENDENCIES_AVAILABLE = False
31
+
32
+
33
+ class SelectStrategy(StrEnum):
34
+ """Enum defining the different selection strategies requested."""
35
+
36
+ STANDARD = "standard"
37
+ SIMILARITY = "similarity"
38
+ RANDOM = "random"
39
+
40
+
41
+ class SelectRecordRetrievalMode(StrEnum):
42
+ """Enum defining whether you need all records or match the
43
+ number of records of the local sql file"""
44
+
45
+ ALL = "all"
46
+ MATCH = "match"
47
+
48
+
49
+ ENUM_VALUES = {
50
+ v.value.lower(): v.value
51
+ for enum in [SelectStrategy]
52
+ for v in enum.__members__.values()
53
+ }
54
+
55
+
56
+ class SelectOptions(CCIDictModel):
57
+ filter: T.Optional[str] = None # Optional filter for selection
58
+ strategy: SelectStrategy = SelectStrategy.STANDARD # Strategy for selection
59
+ priority_fields: T.Dict[str, str] = Field({})
60
+ threshold: T.Optional[float] = None
61
+
62
+ @validator("strategy", pre=True)
63
+ def validate_strategy(cls, value):
64
+ if isinstance(value, Enum):
65
+ return value
66
+
67
+ if value:
68
+ matched_strategy = ENUM_VALUES.get(value.lower())
69
+ if matched_strategy:
70
+ return matched_strategy
71
+
72
+ raise ValueError(f"Invalid strategy value: {value}")
73
+
74
+ @validator("priority_fields", pre=True)
75
+ def standardize_fields_to_dict(cls, values):
76
+ if values is None:
77
+ values = {}
78
+ if type(values) is list:
79
+ values = {elem: elem for elem in values}
80
+ return CaseInsensitiveDict(values)
81
+
82
+ @root_validator
83
+ def validate_threshold_and_strategy(cls, values):
84
+ threshold = values.get("threshold")
85
+ strategy = values.get("strategy")
86
+
87
+ if threshold is not None:
88
+ values["threshold"] = float(threshold) # Convert to float
89
+
90
+ if not (0 <= values["threshold"] <= 1):
91
+ raise ValueError(
92
+ f"Threshold must be between 0 and 1, got {values['threshold']}."
93
+ )
94
+
95
+ if strategy != SelectStrategy.SIMILARITY:
96
+ raise ValueError(
97
+ "If a threshold is specified, the strategy must be set to 'similarity'."
98
+ )
99
+
100
+ return values
101
+
102
+
103
+ class SelectOperationExecutor:
104
+ def __init__(self, strategy: SelectStrategy):
105
+ self.strategy = strategy
106
+ self.retrieval_mode = (
107
+ SelectRecordRetrievalMode.ALL
108
+ if strategy == SelectStrategy.SIMILARITY
109
+ else SelectRecordRetrievalMode.MATCH
110
+ )
111
+
112
+ def select_generate_query(
113
+ self,
114
+ sobject: str,
115
+ fields: T.List[str],
116
+ user_filter: str,
117
+ limit: T.Union[int, None],
118
+ offset: T.Union[int, None],
119
+ ):
120
+ _, select_fields = split_and_filter_fields(fields=fields)
121
+ # For STANDARD strategy
122
+ if self.strategy == SelectStrategy.STANDARD:
123
+ return standard_generate_query(
124
+ sobject=sobject, user_filter=user_filter, limit=limit, offset=offset
125
+ )
126
+ # For SIMILARITY strategy
127
+ elif self.strategy == SelectStrategy.SIMILARITY:
128
+ return similarity_generate_query(
129
+ sobject=sobject,
130
+ fields=select_fields,
131
+ user_filter=user_filter,
132
+ limit=limit,
133
+ offset=offset,
134
+ )
135
+ # For RANDOM strategy
136
+ elif self.strategy == SelectStrategy.RANDOM:
137
+ return standard_generate_query(
138
+ sobject=sobject, user_filter=user_filter, limit=limit, offset=offset
139
+ )
140
+
141
+ def select_post_process(
142
+ self,
143
+ load_records,
144
+ query_records: list,
145
+ fields: list,
146
+ num_records: int,
147
+ sobject: str,
148
+ weights: list,
149
+ threshold: T.Union[float, None],
150
+ ):
151
+ # For STANDARD strategy
152
+ if self.strategy == SelectStrategy.STANDARD:
153
+ return standard_post_process(
154
+ query_records=query_records, num_records=num_records, sobject=sobject
155
+ )
156
+ # For SIMILARITY strategy
157
+ elif self.strategy == SelectStrategy.SIMILARITY:
158
+ return similarity_post_process(
159
+ load_records=load_records,
160
+ query_records=query_records,
161
+ fields=fields,
162
+ sobject=sobject,
163
+ weights=weights,
164
+ threshold=threshold,
165
+ )
166
+ # For RANDOM strategy
167
+ elif self.strategy == SelectStrategy.RANDOM:
168
+ return random_post_process(
169
+ query_records=query_records, num_records=num_records, sobject=sobject
170
+ )
171
+
172
+
173
+ def standard_generate_query(
174
+ sobject: str,
175
+ user_filter: str,
176
+ limit: T.Union[int, None],
177
+ offset: T.Union[int, None],
178
+ ) -> T.Tuple[str, T.List[str]]:
179
+ """Generates the SOQL query for the standard (as well as random) selection strategy"""
180
+
181
+ query = f"SELECT Id FROM {sobject}"
182
+ # If user specifies user_filter
183
+ if user_filter:
184
+ query += add_limit_offset_to_user_filter(
185
+ filter_clause=user_filter, limit_clause=limit, offset_clause=offset
186
+ )
187
+ else:
188
+ query += f" LIMIT {limit}" if limit else ""
189
+ query += f" OFFSET {offset}" if offset else ""
190
+ return query, ["Id"]
191
+
192
+
193
+ def standard_post_process(
194
+ query_records: list, num_records: int, sobject: str
195
+ ) -> T.Tuple[T.List[dict], None, T.Union[str, None]]:
196
+ """Processes the query results for the standard selection strategy"""
197
+ # Handle case where query returns 0 records
198
+ if not query_records:
199
+ error_message = f"No records found for {sobject} in the target org."
200
+ return [], None, error_message
201
+
202
+ # Add 'success: True' to each record to emulate records have been inserted
203
+ selected_records = [
204
+ {"id": record[0], "success": True, "created": False} for record in query_records
205
+ ]
206
+
207
+ # If fewer records than requested, repeat existing records to match num_records
208
+ if len(selected_records) < num_records:
209
+ original_records = selected_records.copy()
210
+ while len(selected_records) < num_records:
211
+ selected_records.extend(original_records)
212
+ selected_records = selected_records[:num_records]
213
+
214
+ return selected_records, None, None # Return selected records and None for error
215
+
216
+
217
+ def similarity_generate_query(
218
+ sobject: str,
219
+ fields: T.List[str],
220
+ user_filter: str,
221
+ limit: T.Union[int, None],
222
+ offset: T.Union[int, None],
223
+ ) -> T.Tuple[str, T.List[str]]:
224
+ """Generates the SOQL query for the similarity selection strategy, with support for TYPEOF on polymorphic fields."""
225
+
226
+ # Pre-process the new fields format to create a nested dict structure for TYPEOF clauses
227
+ nested_fields = {}
228
+ regular_fields = []
229
+
230
+ for field in fields:
231
+ components = field.split(".")
232
+ if len(components) >= 3:
233
+ # Handle polymorphic fields (format: {relationship_name}.{ref_obj}.{ref_field})
234
+ relationship, ref_obj, ref_field = (
235
+ components[0],
236
+ components[1],
237
+ components[2],
238
+ )
239
+ if relationship not in nested_fields:
240
+ nested_fields[relationship] = {}
241
+ if ref_obj not in nested_fields[relationship]:
242
+ nested_fields[relationship][ref_obj] = []
243
+ nested_fields[relationship][ref_obj].append(ref_field)
244
+ else:
245
+ # Handle regular fields (format: {field})
246
+ regular_fields.append(field)
247
+
248
+ # Construct the query fields
249
+ query_fields = []
250
+
251
+ # Build TYPEOF clauses for polymorphic fields
252
+ for relationship, references in nested_fields.items():
253
+ type_clauses = []
254
+ for ref_obj, ref_fields in references.items():
255
+ fields_clause = ", ".join(ref_fields)
256
+ type_clauses.append(f"WHEN {ref_obj} THEN {fields_clause}")
257
+ type_clause = f"TYPEOF {relationship} {' '.join(type_clauses)} ELSE Id END"
258
+ query_fields.append(type_clause)
259
+
260
+ # Add regular fields to the query
261
+ query_fields.extend(regular_fields)
262
+
263
+ # Ensure "Id" is included in the fields list for identification
264
+ if "Id" not in query_fields:
265
+ query_fields.insert(0, "Id")
266
+
267
+ # Build the main SOQL query
268
+ fields_to_query = ", ".join(query_fields)
269
+ query = f"SELECT {fields_to_query} FROM {sobject}"
270
+
271
+ # Add the user-defined filter clause or default clause
272
+ if user_filter:
273
+ query += add_limit_offset_to_user_filter(
274
+ filter_clause=user_filter, limit_clause=limit, offset_clause=offset
275
+ )
276
+ else:
277
+ query += f" LIMIT {limit}" if limit else ""
278
+ query += f" OFFSET {offset}" if offset else ""
279
+
280
+ # Return the original input fields with "Id" added if needed
281
+ if "Id" not in fields:
282
+ fields.insert(0, "Id")
283
+
284
+ return query, fields
285
+
286
+
287
+ def similarity_post_process(
288
+ load_records,
289
+ query_records: list,
290
+ fields: list,
291
+ sobject: str,
292
+ weights: list,
293
+ threshold: T.Union[float, None],
294
+ ) -> T.Tuple[
295
+ T.List[T.Union[dict, None]], T.List[T.Union[list, None]], T.Union[str, None]
296
+ ]:
297
+ """Processes the query results for the similarity selection strategy"""
298
+ # Handle case where query returns 0 records
299
+ if not query_records and threshold is None:
300
+ error_message = f"No records found for {sobject} in the target org."
301
+ return [], [], error_message
302
+
303
+ load_records = list(load_records)
304
+ # Replace None values in each row with empty strings
305
+ for idx, row in enumerate(load_records):
306
+ row = [value if value is not None else "" for value in row]
307
+ load_records[idx] = row
308
+ load_record_count, query_record_count = len(load_records), len(query_records)
309
+
310
+ complexity_constant = load_record_count * query_record_count
311
+
312
+ select_records = []
313
+ insert_records = []
314
+
315
+ if complexity_constant < 1000 or not OPTIONAL_DEPENDENCIES_AVAILABLE:
316
+ select_records, insert_records = levenshtein_post_process(
317
+ load_records, query_records, fields, weights, threshold
318
+ )
319
+ else:
320
+ select_records, insert_records = annoy_post_process(
321
+ load_records, query_records, fields, weights, threshold
322
+ )
323
+
324
+ return select_records, insert_records, None
325
+
326
+
327
+ def annoy_post_process(
328
+ load_records: list,
329
+ query_records: list,
330
+ all_fields: list,
331
+ similarity_weights: list,
332
+ threshold: T.Union[float, None],
333
+ ) -> T.Tuple[T.List[dict], list]:
334
+ """Processes the query results for the similarity selection strategy using Annoy algorithm for large number of records"""
335
+ # Add warning when threshold is 0
336
+ if threshold is not None and threshold == 0:
337
+ logger.warning(
338
+ "Warning: A threshold of 0 may miss exact matches in high volumes. Use a small value like 0.1 for better accuracy."
339
+ )
340
+
341
+ selected_records = []
342
+ insertion_candidates = []
343
+
344
+ # Split fields into load and select categories
345
+ load_field_list, select_field_list = split_and_filter_fields(fields=all_fields)
346
+ # Only select those weights for select field list
347
+ similarity_weights = [
348
+ similarity_weights[idx]
349
+ for idx, field in enumerate(all_fields)
350
+ if field in select_field_list
351
+ ]
352
+ load_shaped_records = reorder_records(
353
+ records=load_records, original_fields=all_fields, new_fields=load_field_list
354
+ )
355
+ select_shaped_records = reorder_records(
356
+ records=load_records, original_fields=all_fields, new_fields=select_field_list
357
+ )
358
+
359
+ if not query_records:
360
+ # Directly append to load record for insertion if target_records is empty
361
+ selected_records = [None for _ in load_records]
362
+ insertion_candidates = load_shaped_records
363
+ return selected_records, insertion_candidates
364
+
365
+ hash_features = 100
366
+ # Adjust number of trees for small datasets to avoid issues
367
+ num_trees = max(1, min(10, len(query_records)))
368
+
369
+ query_record_ids = [record[0] for record in query_records]
370
+ query_record_data = [record[1:] for record in query_records]
371
+
372
+ record_to_id_map = {
373
+ tuple(query_record_data[i]): query_record_ids[i]
374
+ for i in range(len(query_records))
375
+ }
376
+
377
+ final_load_vectors, final_query_vectors = vectorize_records(
378
+ select_shaped_records,
379
+ query_record_data,
380
+ hash_features=hash_features,
381
+ weights=similarity_weights,
382
+ )
383
+
384
+ # Create Annoy index for nearest neighbor search
385
+ vector_dimension = final_query_vectors.shape[1]
386
+ annoy_index = AnnoyIndex(vector_dimension, "euclidean")
387
+
388
+ for i in range(len(final_query_vectors)):
389
+ annoy_index.add_item(i, final_query_vectors[i])
390
+
391
+ # Build the index
392
+ annoy_index.build(num_trees)
393
+
394
+ # Find nearest neighbors for each query vector
395
+ # For small datasets, search more neighbors to ensure we find the best match
396
+ n_neighbors = min(len(query_records), 2)
397
+
398
+ for i, load_vector in enumerate(final_load_vectors):
399
+ # Get nearest neighbors' indices and distances
400
+ nearest_neighbors = annoy_index.get_nns_by_vector(
401
+ load_vector, n_neighbors, include_distances=True
402
+ )
403
+ neighbor_indices = nearest_neighbors[0] # Indices of nearest neighbors
404
+ neighbor_distances = [
405
+ distance / 2 for distance in nearest_neighbors[1]
406
+ ] # Distances sqrt(2(1-cos(u,v)))/2 lies between [0,1]
407
+
408
+ # Find the best match (minimum distance)
409
+ best_distance = neighbor_distances[0]
410
+ best_neighbor_index = neighbor_indices[0]
411
+
412
+ for idx in range(1, len(neighbor_indices)):
413
+ if neighbor_distances[idx] < best_distance:
414
+ best_distance = neighbor_distances[idx]
415
+ best_neighbor_index = neighbor_indices[idx]
416
+
417
+ # Use the best match
418
+ record = query_record_data[best_neighbor_index]
419
+ closest_record_id = record_to_id_map[tuple(record)]
420
+ if threshold is not None and (best_distance >= threshold):
421
+ selected_records.append(None)
422
+ insertion_candidates.append(load_shaped_records[i])
423
+ else:
424
+ selected_records.append(
425
+ {"id": closest_record_id, "success": True, "created": False}
426
+ )
427
+
428
+ return selected_records, insertion_candidates
429
+
430
+
431
+ def levenshtein_post_process(
432
+ source_records: list,
433
+ target_records: list,
434
+ all_fields: list,
435
+ similarity_weights: list,
436
+ distance_threshold: T.Union[float, None],
437
+ ) -> T.Tuple[T.List[T.Optional[dict]], T.List[T.Optional[list]]]:
438
+ """Processes query results using Levenshtein algorithm for similarity selection with a small number of records."""
439
+ selected_records = []
440
+ insertion_candidates = []
441
+
442
+ # Split fields into load and select categories
443
+ load_field_list, select_field_list = split_and_filter_fields(fields=all_fields)
444
+ # Only select those weights for select field list
445
+ similarity_weights = [
446
+ similarity_weights[idx]
447
+ for idx, field in enumerate(all_fields)
448
+ if field in select_field_list
449
+ ]
450
+ load_shaped_records = reorder_records(
451
+ records=source_records, original_fields=all_fields, new_fields=load_field_list
452
+ )
453
+ select_shaped_records = reorder_records(
454
+ records=source_records, original_fields=all_fields, new_fields=select_field_list
455
+ )
456
+
457
+ if not target_records:
458
+ # Directly append to load record for insertion if target_records is empty
459
+ selected_records = [None for _ in source_records]
460
+ insertion_candidates = load_shaped_records
461
+ return selected_records, insertion_candidates
462
+
463
+ for select_record, load_record in zip(select_shaped_records, load_shaped_records):
464
+ closest_match, match_distance = find_closest_record(
465
+ select_record, target_records, similarity_weights
466
+ )
467
+
468
+ if distance_threshold is not None and match_distance > distance_threshold:
469
+ # Append load record for insertion if distance exceeds threshold
470
+ insertion_candidates.append(load_record)
471
+ selected_records.append(None)
472
+ elif closest_match:
473
+ # Append match details if distance is within threshold
474
+ selected_records.append(
475
+ {"id": closest_match[0], "success": True, "created": False}
476
+ )
477
+
478
+ return selected_records, insertion_candidates
479
+
480
+
481
+ def random_post_process(
482
+ query_records: list, num_records: int, sobject: str
483
+ ) -> T.Tuple[T.List[dict], None, T.Union[str, None]]:
484
+ """Processes the query results for the random selection strategy"""
485
+
486
+ if not query_records:
487
+ error_message = f"No records found for {sobject} in the target org."
488
+ return [], None, error_message
489
+
490
+ selected_records = []
491
+ for _ in range(num_records): # Loop 'num_records' times
492
+ # Randomly select one record from query_records
493
+ random_record = random.choice(query_records)
494
+ selected_records.append(
495
+ {"id": random_record[0], "success": True, "created": False}
496
+ )
497
+
498
+ return selected_records, None, None
499
+
500
+
501
+ def find_closest_record(load_record: list, query_records: list, weights: list):
502
+ closest_distance = float("inf")
503
+ closest_record = query_records[0]
504
+
505
+ for record in query_records:
506
+ distance = calculate_levenshtein_distance(load_record, record[1:], weights)
507
+ if distance < closest_distance:
508
+ closest_distance = distance
509
+ closest_record = record
510
+
511
+ return closest_record, closest_distance
512
+
513
+
514
+ def levenshtein_distance(str1: str, str2: str):
515
+ """Calculate the Levenshtein distance between two strings"""
516
+ len_str1 = len(str1) + 1
517
+ len_str2 = len(str2) + 1
518
+
519
+ dp = [[0 for _ in range(len_str2)] for _ in range(len_str1)]
520
+
521
+ for i in range(len_str1):
522
+ dp[i][0] = i
523
+ for j in range(len_str2):
524
+ dp[0][j] = j
525
+
526
+ for i in range(1, len_str1):
527
+ for j in range(1, len_str2):
528
+ cost = 0 if str1[i - 1] == str2[j - 1] else 1
529
+ dp[i][j] = min(
530
+ dp[i - 1][j] + 1, # Deletion
531
+ dp[i][j - 1] + 1, # Insertion
532
+ dp[i - 1][j - 1] + cost,
533
+ ) # Substitution
534
+
535
+ return dp[-1][-1]
536
+
537
+
538
+ def calculate_levenshtein_distance(record1: list, record2: list, weights: list):
539
+ if len(record1) != len(record2):
540
+ raise ValueError("Records must have the same number of fields.")
541
+ elif len(record1) != len(weights):
542
+ raise ValueError("Records must be same size as fields (weights).")
543
+
544
+ total_distance = 0
545
+
546
+ for field1, field2, weight in zip(record1, record2, weights):
547
+ field1 = field1.lower()
548
+ field2 = field2.lower()
549
+
550
+ if len(field1) == 0 and len(field2) == 0:
551
+ # If both fields are blank, distance is 0
552
+ distance = 0
553
+ else:
554
+ # Average distance per character
555
+ distance = levenshtein_distance(field1, field2) / max(
556
+ len(field1), len(field2)
557
+ )
558
+ if len(field1) == 0 or len(field2) == 0:
559
+ # If one field is blank, reduce the impact of the distance
560
+ distance = distance * 0.05 # Fixed value for blank vs non-blank
561
+
562
+ # Multiply the distance by the corresponding weight
563
+ total_distance += distance * weight
564
+
565
+ # Average distance per character with weights
566
+ return total_distance / sum(weights) if len(weights) else 0
567
+
568
+
569
+ def add_limit_offset_to_user_filter(
570
+ filter_clause: str,
571
+ limit_clause: T.Union[float, None] = None,
572
+ offset_clause: T.Union[float, None] = None,
573
+ ) -> str:
574
+
575
+ # Extract existing LIMIT and OFFSET from filter_clause if present
576
+ existing_limit_match = re.search(r"LIMIT\s+(\d+)", filter_clause, re.IGNORECASE)
577
+ existing_offset_match = re.search(r"OFFSET\s+(\d+)", filter_clause, re.IGNORECASE)
578
+
579
+ if existing_limit_match:
580
+ existing_limit = int(existing_limit_match.group(1))
581
+ if limit_clause is not None: # Only apply limit_clause if it's provided
582
+ limit_clause = min(existing_limit, limit_clause)
583
+ else:
584
+ limit_clause = existing_limit
585
+
586
+ if existing_offset_match:
587
+ existing_offset = int(existing_offset_match.group(1))
588
+ if offset_clause is not None:
589
+ offset_clause = existing_offset + offset_clause
590
+ else:
591
+ offset_clause = existing_offset
592
+
593
+ # Remove existing LIMIT and OFFSET from filter_clause, handling potential extra spaces
594
+ filter_clause = re.sub(
595
+ r"\s+OFFSET\s+\d+\s*", " ", filter_clause, flags=re.IGNORECASE
596
+ ).strip()
597
+ filter_clause = re.sub(
598
+ r"\s+LIMIT\s+\d+\s*", " ", filter_clause, flags=re.IGNORECASE
599
+ ).strip()
600
+
601
+ if limit_clause is not None:
602
+ filter_clause += f" LIMIT {limit_clause}"
603
+ if offset_clause is not None:
604
+ filter_clause += f" OFFSET {offset_clause}"
605
+
606
+ return f" {filter_clause}"
607
+
608
+
609
+ def determine_field_types(df_db, df_query, weights):
610
+ numerical_features = []
611
+ boolean_features = []
612
+ categorical_features = []
613
+
614
+ numerical_weights = []
615
+ boolean_weights = []
616
+ categorical_weights = []
617
+
618
+ for col, weight in zip(df_db.columns, weights):
619
+ # Check if the column can be converted to numeric
620
+ try:
621
+ temp_df_db = pd.to_numeric(df_db[col], errors="raise")
622
+ temp_df_query = pd.to_numeric(df_query[col], errors="raise")
623
+ # Replace empty values with 0 for numerical features
624
+ df_db[col] = temp_df_db.fillna(0).replace("", 0)
625
+ df_query[col] = temp_df_query.fillna(0).replace("", 0)
626
+ numerical_features.append(col)
627
+ numerical_weights.append(weight)
628
+ except ValueError:
629
+ # Check for boolean values
630
+ if (
631
+ df_db[col].str.lower().isin(["true", "false"]).all()
632
+ and df_query[col].str.lower().isin(["true", "false"]).all()
633
+ ):
634
+ # Map to actual boolean values
635
+ df_db[col] = df_db[col].str.lower().map({"true": True, "false": False})
636
+ df_query[col] = (
637
+ df_query[col].str.lower().map({"true": True, "false": False})
638
+ )
639
+ boolean_features.append(col)
640
+ boolean_weights.append(weight)
641
+ else:
642
+ categorical_features.append(col)
643
+ categorical_weights.append(weight)
644
+ # Replace empty values with 'missing' for categorical features
645
+ df_db[col] = df_db[col].replace("", "missing")
646
+ df_query[col] = df_query[col].replace("", "missing")
647
+
648
+ return (
649
+ numerical_features,
650
+ boolean_features,
651
+ categorical_features,
652
+ numerical_weights,
653
+ boolean_weights,
654
+ categorical_weights,
655
+ )
656
+
657
+
658
+ def vectorize_records(db_records, query_records, hash_features, weights):
659
+ # Convert database records and query records to DataFrames
660
+ df_db = pd.DataFrame(db_records)
661
+ df_query = pd.DataFrame(query_records)
662
+
663
+ # Determine field types and corresponding weights
664
+ # Modifies boolean columns to True or False
665
+ (
666
+ numerical_features,
667
+ boolean_features,
668
+ categorical_features,
669
+ numerical_weights,
670
+ boolean_weights,
671
+ categorical_weights,
672
+ ) = determine_field_types(df_db, df_query, weights)
673
+
674
+ # Fit StandardScaler on the numerical features of the database records
675
+ scaler = StandardScaler()
676
+ if numerical_features:
677
+ df_db[numerical_features] = scaler.fit_transform(df_db[numerical_features])
678
+ df_query[numerical_features] = scaler.transform(df_query[numerical_features])
679
+
680
+ # For db_records
681
+ hashed_categorical_data_db = []
682
+ # For query_records
683
+ hashed_categorical_data_query = []
684
+
685
+ # Process each categorical column separately with its own HashingVectorizer
686
+ for idx, col in enumerate(categorical_features):
687
+ # Create a separate HashingVectorizer for each column to avoid hash collisions
688
+ hashing_vectorizer = HashingVectorizer(
689
+ n_features=hash_features, alternate_sign=False
690
+ )
691
+
692
+ # Combine all unique values from both db and query for this column to fit the vectorizer
693
+ all_values_for_col = pd.concat([df_db[col], df_query[col]]).unique()
694
+
695
+ # Fit the vectorizer on all unique values to ensure consistency
696
+ hashing_vectorizer.fit(all_values_for_col)
697
+
698
+ # Transform db and query data for this column
699
+ hashed_db = hashing_vectorizer.transform(df_db[col]).toarray()
700
+ hashed_query = hashing_vectorizer.transform(df_query[col]).toarray()
701
+
702
+ # Apply weight to the hashed vectors
703
+ hashed_db_weighted = hashed_db * categorical_weights[idx]
704
+ hashed_query_weighted = hashed_query * categorical_weights[idx]
705
+
706
+ hashed_categorical_data_db.append(hashed_db_weighted)
707
+ hashed_categorical_data_query.append(hashed_query_weighted)
708
+
709
+ # Combine all feature types into a single vector for the database records
710
+ db_vectors = []
711
+ if numerical_features:
712
+ db_vectors.append(df_db[numerical_features].values * numerical_weights)
713
+ if boolean_features:
714
+ db_vectors.append(df_db[boolean_features].astype(int).values * boolean_weights)
715
+ if hashed_categorical_data_db:
716
+ db_vectors.append(np.hstack(hashed_categorical_data_db))
717
+
718
+ # Concatenate database vectors
719
+ final_db_vectors = np.hstack(db_vectors)
720
+
721
+ # Combine all feature types into a single vector for the query records
722
+ query_vectors = []
723
+ if numerical_features:
724
+ query_vectors.append(df_query[numerical_features].values * numerical_weights)
725
+ if boolean_features:
726
+ query_vectors.append(
727
+ df_query[boolean_features].astype(int).values * boolean_weights
728
+ )
729
+ if hashed_categorical_data_query:
730
+ query_vectors.append(np.hstack(hashed_categorical_data_query))
731
+
732
+ # Concatenate query vectors
733
+ final_query_vectors = np.hstack(query_vectors)
734
+
735
+ return final_db_vectors, final_query_vectors
736
+
737
+
738
+ def split_and_filter_fields(fields: T.List[str]) -> T.Tuple[T.List[str], T.List[str]]:
739
+ # List to store non-lookup fields (load fields)
740
+ load_fields = []
741
+
742
+ # Set to store unique first components of select fields
743
+ unique_components = set()
744
+ # Keep track of last flattened lookup index
745
+ last_flat_lookup_index = -1
746
+
747
+ # Iterate through the fields
748
+ for idx, field in enumerate(fields):
749
+ if "." in field:
750
+ # Split the field by '.' and add the first component to the set
751
+ first_component = field.split(".")[0]
752
+ unique_components.add(first_component)
753
+ last_flat_lookup_index = max(last_flat_lookup_index, idx)
754
+ else:
755
+ # Add the field to the load_fields list
756
+ load_fields.append(field)
757
+
758
+ # Number of unique components
759
+ num_unique_components = len(unique_components)
760
+
761
+ # Adjust select_fields by removing only the field at last_flat_lookup_index + 1
762
+ if last_flat_lookup_index + 1 < len(
763
+ fields
764
+ ) and last_flat_lookup_index + num_unique_components < len(fields):
765
+ select_fields = (
766
+ fields[: last_flat_lookup_index + 1]
767
+ + fields[last_flat_lookup_index + num_unique_components + 1 :]
768
+ )
769
+ else:
770
+ select_fields = fields
771
+
772
+ return load_fields, select_fields
773
+
774
+
775
+ # Function to reorder records based on the new field list
776
+ def reorder_records(records, original_fields, new_fields):
777
+ if not original_fields:
778
+ raise KeyError("original_fields should not be empty")
779
+ # Map the original field indices
780
+ field_index_map = {field: i for i, field in enumerate(original_fields)}
781
+ reordered_records = []
782
+
783
+ for record in records:
784
+ reordered_records.append(
785
+ [
786
+ record[field_index_map[field]]
787
+ for field in new_fields
788
+ if field in field_index_map
789
+ ]
790
+ )
791
+
792
+ return reordered_records