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,753 @@
1
+ import shutil
2
+ import time
3
+ import typing as T
4
+ from collections import defaultdict
5
+ from contextlib import contextmanager
6
+ from datetime import timedelta
7
+ from math import ceil
8
+ from pathlib import Path
9
+ from queue import Empty
10
+ from tempfile import TemporaryDirectory, mkdtemp
11
+
12
+ import psutil
13
+ from snowfakery.api import COUNT_REPS, infer_load_file_path
14
+ from snowfakery.cci_mapping_files.declaration_parser import (
15
+ ChannelDeclaration,
16
+ SObjectRuleDeclarationFile,
17
+ )
18
+
19
+ import cumulusci.core.exceptions as exc
20
+ from cumulusci.core.config import OrgConfig, TaskConfig
21
+ from cumulusci.core.debug import get_debug_mode
22
+ from cumulusci.core.exceptions import TaskOptionsError
23
+ from cumulusci.core.keychain import BaseProjectKeychain
24
+ from cumulusci.core.utils import (
25
+ format_duration,
26
+ process_bool_arg,
27
+ process_list_arg,
28
+ process_list_of_pairs_dict_arg,
29
+ )
30
+ from cumulusci.tasks.bulkdata.generate_and_load_data_from_yaml import (
31
+ GenerateAndLoadDataFromYaml,
32
+ )
33
+ from cumulusci.tasks.salesforce import BaseSalesforceApiTask
34
+
35
+ from .snowfakery_utils.queue_manager import (
36
+ SnowfakeryChannelManager,
37
+ data_loader_new_directory_name,
38
+ )
39
+ from .snowfakery_utils.snowfakery_run_until import PortionGenerator, determine_run_until
40
+ from .snowfakery_utils.snowfakery_working_directory import SnowfakeryWorkingDirectory
41
+ from .snowfakery_utils.subtask_configurator import SubtaskConfigurator
42
+
43
+ # A portion serves the same process in this system as a "batch" in
44
+ # other systems. The term "batch" is not used to avoid confusion with
45
+ # Salesforce Bulk API 1.0 batches. For example, a portion of 250_000
46
+ # Account records would be broken into roughly 25 Salesforce upload
47
+ # batches.
48
+
49
+ # The system starts at the MIN_PORTION_SIZE and grows towards the
50
+ # MAX_PORTION_SIZE. This is to prevent the org wasting time waiting
51
+ # for the first portions.
52
+ MIN_PORTION_SIZE = 2_000
53
+ MAX_PORTION_SIZE = 250_000
54
+ ERROR_THRESHOLD = (
55
+ 0 # TODO v2.1: Allow this to be a percentage of recent records instead
56
+ )
57
+
58
+ # time between "ticks" where the task re-evaluates its progress
59
+ # relatively arbitrary trade-off between busy-waiting and adding latency.
60
+ WAIT_TIME = 3
61
+
62
+
63
+ class Snowfakery(BaseSalesforceApiTask):
64
+
65
+ task_docs = """
66
+ Do a data load with Snowfakery.
67
+
68
+ All options are optional.
69
+
70
+ The most commonly supplied options are `recipe` and one of the three
71
+ `run_until_...` options.
72
+ """
73
+
74
+ task_options = {
75
+ "recipe": {
76
+ "required": True,
77
+ "description": "Path to a Snowfakery recipe file determining what data to generate and load.",
78
+ },
79
+ "run_until_records_in_org": {
80
+ "description": """<sobject>:<count>
81
+
82
+ Run the recipe repeatedly until the count of <sobject>
83
+ in the org matches the given <count>.
84
+
85
+ For example, `--run_until_records_in_org Account:50_000` means:
86
+
87
+ Count the Account records in the org. Let’s say the number
88
+ is 20,000. Thus, we must run the recipe over and
89
+ over again until we generate 30,000 new Account records.
90
+ If the recipe also generates e.g.Contacts, Opportunities or whatever
91
+ else, it generates the appropriate number of them to match.
92
+
93
+ Underscores are allowed but optional in big numbers: 2000000
94
+ is the same as 2_000_000.
95
+ """
96
+ },
97
+ "run_until_records_loaded": {
98
+ "description": """<sobject>:<count>
99
+
100
+ Run the recipe repeatedly until the number of records of
101
+ <sobject> uploaded in this task execution matches <count>.
102
+
103
+ For example, `--run_until_records_loaded Account:50_000` means:
104
+
105
+ Run the recipe over and over again
106
+ until we generate 50_000 new Account records. If the recipe
107
+ also generates e.g. Contacts, Opportunities or whatever else, it
108
+ generates the appropriate number of them to match.
109
+ """
110
+ },
111
+ "run_until_recipe_repeated": {
112
+ "description": """Run the recipe <count> times,
113
+ no matter what data is already in the org.
114
+
115
+ For example, `--run_until_recipe_repeated 50_000` means
116
+ run the recipe 50_000 times."""
117
+ },
118
+ "working_directory": {"description": "Path for temporary / working files"},
119
+ "loading_rules": {
120
+ "description": "Path to .load.yml file containing rules to use to "
121
+ "load the file. Defaults to `<recipename>.load.yml`. "
122
+ "Multiple files can be comma separated."
123
+ },
124
+ "recipe_options": {
125
+ "required": False,
126
+ "description": """Pass values to override options in the format VAR1:foo,VAR2:bar
127
+
128
+ Example: --recipe_options weight:10,color:purple""",
129
+ },
130
+ "bulk_mode": {
131
+ "description": "Set to Serial to serialize everything: data generation, data loading, data ingestion through bulk API. Parallel is the default."
132
+ },
133
+ "drop_missing_schema": {
134
+ "description": "Set to True to skip any missing objects or fields instead of stopping with an error."
135
+ },
136
+ "num_processes": {
137
+ "description": "Number of data generating processes. Defaults to matching the number of CPUs."
138
+ },
139
+ "ignore_row_errors": {
140
+ "description": "Boolean: should we continue loading even after running into row errors? "
141
+ "Defaults to False."
142
+ },
143
+ }
144
+
145
+ def _validate_options(self):
146
+ "Validate options before executing the task or before freezing it"
147
+ super()._validate_options()
148
+ # Do not store recipe due to MetaDeploy options freezing
149
+ recipe = self.options.get("recipe")
150
+ recipe = Path(recipe)
151
+ if not recipe.exists():
152
+ raise exc.TaskOptionsError(f"Cannot find recipe `{recipe}`")
153
+
154
+ self.num_generator_workers = self.options.get("num_processes", None)
155
+ if self.num_generator_workers is not None:
156
+ self.num_generator_workers = int(self.num_generator_workers)
157
+ self.ignore_row_errors = process_bool_arg(
158
+ self.options.get("ignore_row_errors", False)
159
+ )
160
+ self.drop_missing_schema = process_bool_arg(
161
+ self.options.get("drop_missing_schema", False)
162
+ )
163
+
164
+ loading_rules = process_list_arg(self.options.get("loading_rules")) or []
165
+ self.loading_rules = [Path(path) for path in loading_rules if path]
166
+ self.recipe_options = process_list_of_pairs_dict_arg(
167
+ self.options.get("recipe_options") or {}
168
+ )
169
+ self.bulk_mode = self.options.get("bulk_mode", "Parallel").title()
170
+ if self.bulk_mode and self.bulk_mode not in ["Serial", "Parallel"]:
171
+ raise TaskOptionsError("bulk_mode must be either Serial or Parallel")
172
+
173
+ def _init_channel_configs(self, recipe):
174
+ """The channels describe the 'shape' of the communication
175
+
176
+ The normal case is a single, parallelized, bulk channel,
177
+ multi-threaded on client and server, using a single user
178
+ account.
179
+
180
+ Using .load.yml you can add more channels, utilizing
181
+ more user accounts which can speed up throughput in
182
+ a few cases.
183
+
184
+ This method reads files and options to determine
185
+ what channels should be created later.
186
+ """
187
+ channel_decls = read_channel_declarations(recipe, self.loading_rules)
188
+
189
+ if channel_decls:
190
+ self.channel_configs = channel_configs_from_decls(
191
+ channel_decls, self.project_config.keychain
192
+ )
193
+ elif self.bulk_mode == "Serial":
194
+ self.channel_configs = [
195
+ standard_channel_config(
196
+ self.org_config,
197
+ self.recipe_options,
198
+ 1,
199
+ 1,
200
+ )
201
+ ]
202
+ else:
203
+ self.channel_configs = [
204
+ standard_channel_config(
205
+ self.org_config,
206
+ self.recipe_options,
207
+ self.num_generator_workers,
208
+ None,
209
+ )
210
+ ]
211
+
212
+ def setup(self):
213
+ """Setup for loading."""
214
+ self.debug_mode = get_debug_mode()
215
+ if not self.num_generator_workers:
216
+ # logical CPUs do not really improve performance of CPU-bound
217
+ # code, so we ignore them.
218
+ self.num_generator_workers = psutil.cpu_count(logical=False)
219
+ if self.debug_mode:
220
+ self.logger.info(f"Using {self.num_generator_workers} workers")
221
+
222
+ self.run_until = determine_run_until(self.options, self.sf)
223
+ self.start_time = time.time()
224
+ self.recipe = Path(self.options.get("recipe"))
225
+ self.sobject_counts = defaultdict(RunningTotals)
226
+ self._init_channel_configs(self.recipe)
227
+
228
+ ## Todo: Consider when this process runs longer than 2 Hours,
229
+ # what will happen to my sf connection?
230
+ def _run_task(self):
231
+ self.setup()
232
+
233
+ portions = PortionGenerator(
234
+ self.run_until.gap,
235
+ MIN_PORTION_SIZE,
236
+ MAX_PORTION_SIZE,
237
+ )
238
+
239
+ working_directory = self.options.get("working_directory")
240
+ with self.workingdir_or_tempdir(working_directory) as working_directory:
241
+ self._setup_channels_and_queues(working_directory)
242
+ self.logger.info(f"Working directory is {working_directory}")
243
+
244
+ if self.run_until.nothing_to_do:
245
+ self.logger.info(
246
+ f"Dataload is finished before it started! {self.run_until.nothing_to_do_because}"
247
+ )
248
+ return
249
+
250
+ template_path, relevant_sobjects = self._generate_and_load_initial_batch(
251
+ working_directory
252
+ )
253
+
254
+ # disable OrgReordCounts for now until it's reliability can be better
255
+ # tested and documented.
256
+
257
+ # Retrieve OrgRecordCounts code from
258
+ # https://github.com/SFDO-Tooling/CumulusCI/commit/7d703c44b94e8b21f165e5538c2249a65da0a9eb#diff-54676811961455410c30d9c9405a8f3b9d12a6222a58db9d55580a2da3cfb870R147
259
+
260
+ self._loop(
261
+ template_path,
262
+ working_directory,
263
+ None,
264
+ portions,
265
+ )
266
+ self.finish()
267
+
268
+ def _setup_channels_and_queues(self, working_directory):
269
+ """Set up all of the channels and queues.
270
+
271
+ In particular their directories and the in-memory
272
+ runtime datastructures.
273
+
274
+ Each channel can hold multiple queues.
275
+ """
276
+ additional_load_options = {
277
+ "ignore_row_errors": self.ignore_row_errors,
278
+ "drop_missing_schema": self.drop_missing_schema,
279
+ }
280
+ subtask_configurator = SubtaskConfigurator(
281
+ self.recipe, self.run_until, self.bulk_mode, additional_load_options
282
+ )
283
+ self.queue_manager = SnowfakeryChannelManager(
284
+ project_config=self.project_config,
285
+ logger=self.logger,
286
+ subtask_configurator=subtask_configurator,
287
+ )
288
+ if len(self.channel_configs) == 1:
289
+ channel = self.channel_configs[0]
290
+ self.queue_manager.add_channel(
291
+ org_config=channel.org_config,
292
+ num_generator_workers=channel.declaration.num_generators,
293
+ num_loader_workers=channel.declaration.num_loaders,
294
+ working_directory=working_directory,
295
+ recipe_options=channel.declaration.recipe_options,
296
+ )
297
+ else:
298
+ self.configure_multiple_channels(working_directory)
299
+
300
+ def configure_multiple_channels(self, working_directory):
301
+ """If there is more than one channel (=user account),
302
+ pre-allocate work among them.
303
+ """
304
+ allocated_generator_workers = sum(
305
+ (channel.declaration.num_generators or 0)
306
+ for channel in self.channel_configs
307
+ )
308
+ channels_without_workers = len(
309
+ [
310
+ channel.declaration.num_generators
311
+ for channel in self.channel_configs
312
+ if not channel.declaration.num_generators
313
+ ]
314
+ )
315
+ remaining_generator_workers = (
316
+ self.num_generator_workers - allocated_generator_workers
317
+ )
318
+ num_generators_per_channel = ceil(
319
+ remaining_generator_workers / channels_without_workers
320
+ )
321
+ for idx, channel in enumerate(self.channel_configs):
322
+ if self.debug_mode:
323
+ self.logger.info("Initializing %s", channel)
324
+ channel_wd = working_directory / f"channel_{idx}"
325
+ channel_wd.mkdir()
326
+ recipe_options = channel.merge_recipe_options(self.recipe_options)
327
+ generator_workers = (
328
+ channel.declaration.num_generators or num_generators_per_channel
329
+ )
330
+
331
+ self.queue_manager.add_channel(
332
+ org_config=channel.org_config,
333
+ num_generator_workers=generator_workers,
334
+ num_loader_workers=channel.declaration.num_loaders,
335
+ working_directory=channel_wd,
336
+ recipe_options=recipe_options,
337
+ )
338
+
339
+ def _loop(
340
+ self,
341
+ template_path,
342
+ tempdir,
343
+ org_record_counts_thread,
344
+ portions: PortionGenerator,
345
+ ):
346
+ """The inner loop that controls when data is generated and when we are done."""
347
+ upload_status = self.get_upload_status(
348
+ portions.next_batch_size,
349
+ )
350
+
351
+ while not portions.done(upload_status.total_sets_working_on_or_uploaded):
352
+ if self.debug_mode:
353
+ self.logger.info(f"Working Directory: {tempdir}")
354
+
355
+ self.queue_manager.tick(
356
+ upload_status,
357
+ template_path,
358
+ tempdir,
359
+ portions,
360
+ self.get_upload_status,
361
+ )
362
+ self.update_running_totals()
363
+ self.print_running_totals()
364
+
365
+ time.sleep(WAIT_TIME)
366
+
367
+ upload_status = self._report_status(
368
+ portions.batch_size,
369
+ org_record_counts_thread,
370
+ template_path,
371
+ )
372
+
373
+ return upload_status
374
+
375
+ def _report_status(
376
+ self,
377
+ batch_size,
378
+ org_record_counts_thread,
379
+ template_path,
380
+ ):
381
+ """Let the user know what is going on."""
382
+ self.logger.info(
383
+ "\n********** PROGRESS *********",
384
+ )
385
+
386
+ upload_status = self.get_upload_status(
387
+ batch_size or 0,
388
+ )
389
+
390
+ self.logger.info(upload_status._display(detailed=self.debug_mode))
391
+
392
+ if upload_status.sets_failed:
393
+ # TODO: this is not sufficiently tested.
394
+ # commenting it out doesn't break tests
395
+ self.log_failures()
396
+
397
+ if upload_status.sets_failed > ERROR_THRESHOLD:
398
+ raise exc.BulkDataException(
399
+ f"Errors exceeded threshold: {upload_status.sets_failed} vs {ERROR_THRESHOLD}"
400
+ )
401
+
402
+ # TODO: Retrieve OrgRecordCounts code from
403
+ # https://github.com/SFDO-Tooling/CumulusCI/commit/7d703c44b94e8b21f165e5538c2249a65da0a9eb#diff-54676811961455410c30d9c9405a8f3b9d12a6222a58db9d55580a2da3cfb870R147
404
+
405
+ return upload_status
406
+
407
+ def update_running_totals(self) -> None:
408
+ """Read and collate result reports from sub-processes/sub-threads
409
+
410
+ This is a realtime reporting channel which could, in theory, be updated
411
+ before sub-tasks finish. Currently no sub-tasks are coded to do that.
412
+
413
+ The logical next step is to allow LoadData to monitor steps one by
414
+ one or even batches one by one.
415
+
416
+ Note that until we implement that, we are paying the complexity
417
+ cost of a real-time channel but not getting the benefits of it.
418
+ """
419
+ while True:
420
+ try:
421
+ results = self.queue_manager.get_results_report()
422
+ except Empty:
423
+ break
424
+ if "results" in results and "step_results" in results["results"]:
425
+ self.update_running_totals_from_load_step_results(results["results"])
426
+ elif "error" in results:
427
+ self.logger.warning(f"Error in load: {results}")
428
+ else: # pragma: no cover
429
+ self.logger.warning(f"Unexpected message from subtask: {results}")
430
+
431
+ def update_running_totals_from_load_step_results(self, results: dict) -> None:
432
+ """'Parse' the results from a load step, to keep track of row errors."""
433
+ for result in results["step_results"].values():
434
+ sobject_name = result["sobject"]
435
+ totals = self.sobject_counts[sobject_name]
436
+ totals.errors += result["total_row_errors"]
437
+ totals.successes += result["records_processed"] - result["total_row_errors"]
438
+
439
+ def print_running_totals(self):
440
+ for name, result in self.sobject_counts.items():
441
+ self.logger.info(
442
+ f" {name}: {result.successes:,} successes, {result.errors:,} errors"
443
+ )
444
+
445
+ def finish(self):
446
+ """Wait for jobs to finish"""
447
+ old_message = None
448
+ cooldown = 5
449
+ while not self.queue_manager.check_finished():
450
+ status = self.get_upload_status(0)
451
+ datagen_workers = f"{status.sets_being_generated} data generators, "
452
+ msg = f"Waiting for {datagen_workers}{status.sets_being_loaded} uploads to finish"
453
+ if old_message != msg or cooldown < 1:
454
+ old_message = msg
455
+ self.logger.info(msg)
456
+ self.update_running_totals()
457
+ self.print_running_totals()
458
+ cooldown = 5
459
+ else:
460
+ cooldown -= 1
461
+ time.sleep(WAIT_TIME)
462
+
463
+ self.log_failures()
464
+
465
+ self.logger.info("")
466
+ self.logger.info(" == Results == ")
467
+ self.update_running_totals()
468
+ self.print_running_totals()
469
+ elapsed = format_duration(timedelta(seconds=time.time() - self.start_time))
470
+
471
+ if self.run_until.sobject_name:
472
+ result_msg = f"{self.sobject_counts[self.run_until.sobject_name].successes} {self.run_until.sobject_name} records and associated records"
473
+ else:
474
+ result_msg = f"{self.run_until.target:,} iterations"
475
+
476
+ self.logger.info(f"☃ Snowfakery created {result_msg} in {elapsed}.")
477
+
478
+ def log_failures(self):
479
+ """Log failures from sub-processes to main process"""
480
+ for exception in self.queue_manager.failure_descriptions():
481
+ self.logger.info(exception)
482
+
483
+ # TODO: This method is actually based on the number generated,
484
+ # because it is called before the load.
485
+ # If there are row errors, it will drift out of correctness
486
+ # Code needs to be updated to rename again after load.
487
+ # Or move away from using directory names for math altogether.
488
+ def data_loader_new_directory_name(self, working_dir: Path):
489
+ """Change the directory name to reflect the true number of sets created."""
490
+
491
+ wd = SnowfakeryWorkingDirectory(working_dir)
492
+ key = wd.index
493
+ if key not in self.cached_counts:
494
+ self.cached_counts[key] = wd.get_record_counts()
495
+
496
+ if not self.run_until.sobject_name:
497
+ return working_dir
498
+
499
+ count = self.cached_counts[key][self.run_until.sobject_name]
500
+
501
+ path, _ = str(working_dir).rsplit("_", 1)
502
+ new_working_dir = Path(path + "_" + str(count))
503
+ return new_working_dir
504
+
505
+ def generator_data_dir(self, idx, template_path, batch_size, parent_dir):
506
+ """Create a new generator directory with a name based on index and batch_size"""
507
+ assert batch_size > 0
508
+ data_dir = parent_dir / (str(idx) + "_" + str(batch_size))
509
+ shutil.copytree(template_path, data_dir)
510
+ return data_dir
511
+
512
+ def get_upload_status(
513
+ self,
514
+ batch_size,
515
+ ):
516
+ """Combine information from the different data sources into a single "report".
517
+
518
+ Useful for debugging but also for making decisions about what to do next."""
519
+
520
+ return self.queue_manager.get_upload_status(
521
+ batch_size, self.sets_finished_while_generating_template
522
+ )
523
+
524
+ @contextmanager
525
+ def workingdir_or_tempdir(self, working_directory: T.Optional[T.Union[Path, str]]):
526
+ """Make a working directory or a temporary directory, as needed"""
527
+ if working_directory:
528
+ working_directory = Path(working_directory)
529
+ working_directory.mkdir()
530
+ self.logger.info(f"Working Directory {working_directory}")
531
+ yield working_directory
532
+ elif self.debug_mode:
533
+ working_directory = Path(mkdtemp())
534
+ self.logger.info(
535
+ f"Due to debug mode, Working Directory {working_directory} will not be removed"
536
+ )
537
+ yield working_directory
538
+ else:
539
+ with TemporaryDirectory() as tempdir:
540
+ yield Path(tempdir)
541
+
542
+ def _generate_and_load_initial_batch(self, working_directory: Path):
543
+ """Generate a single batch to set up all just_once (singleton) objects"""
544
+
545
+ template_dir = Path(working_directory) / "template_1"
546
+ template_dir.mkdir()
547
+ # changes here should often be reflected in
548
+ # data_generator_opts and data_loader_opts
549
+
550
+ channel_decl = self.channel_configs[0]
551
+
552
+ plugin_options = {
553
+ "pid": "0",
554
+ "big_ids": "True",
555
+ }
556
+ # if it's efficient to do the whole load in one go, let's just do that.
557
+ if self.run_until.gap < MIN_PORTION_SIZE:
558
+ num_records = self.run_until.gap
559
+ else:
560
+ num_records = 1 # smallest possible batch to get to parallelizing fast
561
+ results = self._generate_and_load_batch(
562
+ template_dir,
563
+ channel_decl.org_config,
564
+ {
565
+ "generator_yaml": self.options.get("recipe"),
566
+ "num_records": num_records,
567
+ "num_records_tablename": self.run_until.sobject_name or COUNT_REPS,
568
+ "loading_rules": self.loading_rules,
569
+ "vars": channel_decl.merge_recipe_options(self.recipe_options),
570
+ "plugin_options": plugin_options,
571
+ "bulk_mode": self.bulk_mode,
572
+ },
573
+ )
574
+ self.update_running_totals_from_load_step_results(results)
575
+
576
+ # rename directory to reflect real number of sets created.
577
+ wd = SnowfakeryWorkingDirectory(template_dir)
578
+ if self.run_until.sobject_name:
579
+ self.sets_finished_while_generating_template = wd.get_record_counts()[
580
+ self.run_until.sobject_name
581
+ ]
582
+ else:
583
+ self.sets_finished_while_generating_template = num_records
584
+
585
+ new_template_dir = data_loader_new_directory_name(template_dir, self.run_until)
586
+ # rename only if new_template_dir does not match template_dir
587
+ if template_dir.resolve() != new_template_dir.resolve():
588
+ shutil.move(template_dir, new_template_dir)
589
+ template_dir = new_template_dir
590
+
591
+ # don't send data tables to child processes. All they
592
+ # care about are ID->OID mappings
593
+ wd = SnowfakeryWorkingDirectory(template_dir)
594
+ self._cleanup_object_tables(*wd.setup_engine())
595
+
596
+ return template_dir, wd.relevant_sobjects()
597
+
598
+ def _generate_and_load_batch(self, tempdir, org_config, options) -> dict:
599
+ """Before the "full" dataload starts we do a single batch to
600
+ load singletons.
601
+ """
602
+ options = {
603
+ **options,
604
+ "working_directory": tempdir,
605
+ "set_recently_viewed": False,
606
+ "ignore_row_errors": self.ignore_row_errors,
607
+ "drop_missing_schema": self.drop_missing_schema,
608
+ }
609
+ subtask_config = TaskConfig({"options": options})
610
+ subtask = GenerateAndLoadDataFromYaml(
611
+ project_config=self.project_config,
612
+ task_config=subtask_config,
613
+ org_config=org_config,
614
+ flow=self.flow,
615
+ name=self.name,
616
+ stepnum=self.stepnum,
617
+ )
618
+ subtask()
619
+ return subtask.return_values["load_results"][0]
620
+
621
+ def _cleanup_object_tables(self, engine, metadata):
622
+ """Delete all tables that do not relate to id->OID mapping"""
623
+ tables = metadata.tables
624
+ tables_to_drop = [
625
+ table
626
+ for tablename, table in tables.items()
627
+ if not tablename.endswith("sf_ids")
628
+ ]
629
+ if tables_to_drop:
630
+ metadata.drop_all(tables=tables_to_drop)
631
+
632
+
633
+ class RunningTotals:
634
+ """Keep track of # of row errors and successess"""
635
+
636
+ errors: int = 0
637
+ successes: int = 0
638
+
639
+ def __repr__(self):
640
+ return f"<{self.__class__.__name__} {self.__dict__}>"
641
+
642
+
643
+ class RulesFileAndRules(T.NamedTuple):
644
+ file: Path
645
+ rules: T.List[ChannelDeclaration]
646
+
647
+
648
+ def _read_channel_declarations_from_file(
649
+ loading_rules_file: Path,
650
+ ) -> RulesFileAndRules:
651
+ """Look in a .load.yml file for the channel declarations"""
652
+ with loading_rules_file.open() as f:
653
+ decls = SObjectRuleDeclarationFile.parse_from_yaml(f)
654
+ channel_decls = decls.channel_declarations or []
655
+ assert isinstance(channel_decls, list)
656
+ return RulesFileAndRules(loading_rules_file, channel_decls)
657
+
658
+
659
+ def read_channel_declarations(
660
+ recipe: Path, loading_rules_files: T.List[Path]
661
+ ) -> T.List[ChannelDeclaration]:
662
+ """Find all appropriate channel declarations.
663
+
664
+ Some discovered through naming conventions, others
665
+ through command lines options or YML config."""
666
+ implicit_rules_file = infer_load_file_path(recipe)
667
+ if implicit_rules_file and implicit_rules_file.exists():
668
+ loading_rules_files = loading_rules_files + [implicit_rules_file]
669
+ # uniqify without losing order
670
+ loading_rules_files = list(dict.fromkeys(loading_rules_files))
671
+
672
+ rules_lists = [
673
+ _read_channel_declarations_from_file(file) for file in loading_rules_files
674
+ ]
675
+ rules_lists = [rules_list for rules_list in rules_lists if rules_list.rules]
676
+
677
+ if len(rules_lists) > 1:
678
+ files = ", ".join(
679
+ [f"{rules_list.file}: {rules_list.rules}" for rules_list in rules_lists]
680
+ )
681
+ msg = f"Multiple channel declarations: {files}"
682
+ raise TaskOptionsError(msg)
683
+ elif len(rules_lists) == 1:
684
+ return rules_lists[0].rules
685
+ else:
686
+ return []
687
+
688
+
689
+ class ChannelConfig(T.NamedTuple):
690
+ """A channel represents a connection to Salesforce via a username.
691
+
692
+ It can also have recipe options and other documented properties.
693
+ https://github.com/SFDO-Tooling/Snowfakery/search?q=ChannelDeclaration
694
+ """
695
+
696
+ org_config: OrgConfig
697
+ declaration: ChannelDeclaration = None
698
+
699
+ def merge_recipe_options(self, task_recipe_options):
700
+ """Merge the recipe options from the channel declaration with those from the task config"""
701
+ channel_options = self.declaration.recipe_options or {}
702
+ task_recipe_options = task_recipe_options or {}
703
+ self.check_conflicting_options(channel_options, task_recipe_options)
704
+ recipe_options = {
705
+ **task_recipe_options,
706
+ **channel_options,
707
+ }
708
+ return recipe_options
709
+
710
+ @staticmethod
711
+ def check_conflicting_options(channel_options, task_recipe_options):
712
+ """Check that options do not conflict"""
713
+ double_specified_options = set(task_recipe_options.keys()).intersection(
714
+ set(channel_options.keys())
715
+ )
716
+ conflicting_options = [
717
+ optname
718
+ for optname in double_specified_options
719
+ if task_recipe_options[optname] != channel_options[optname]
720
+ ]
721
+ if conflicting_options:
722
+ raise TaskOptionsError(
723
+ f"Recipe options cannot conflict: {conflicting_options}"
724
+ )
725
+
726
+
727
+ def standard_channel_config(
728
+ org_config: OrgConfig,
729
+ recipe_options: dict,
730
+ num_generators: int,
731
+ num_loaders: int = None,
732
+ ):
733
+ """Default configuration for a single-channel data-load"""
734
+ channel = ChannelDeclaration(
735
+ user="Username not used in this context",
736
+ recipe_options=recipe_options,
737
+ num_generators=num_generators,
738
+ num_loaders=num_loaders,
739
+ )
740
+
741
+ return ChannelConfig(org_config, channel)
742
+
743
+
744
+ def channel_configs_from_decls(
745
+ channel_decls: T.List[ChannelDeclaration],
746
+ keychain: BaseProjectKeychain,
747
+ ):
748
+ """Reify channel configs and look up orgconfig"""
749
+
750
+ def config_from_decl(decl):
751
+ return ChannelConfig(keychain.get_org(decl.user), decl)
752
+
753
+ return [config_from_decl(decl) for decl in channel_decls]