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.
- cumulusci/__about__.py +1 -0
- cumulusci/__init__.py +22 -0
- cumulusci/__main__.py +3 -0
- cumulusci/cli/__init__.py +0 -0
- cumulusci/cli/cci.py +244 -0
- cumulusci/cli/error.py +125 -0
- cumulusci/cli/flow.py +185 -0
- cumulusci/cli/logger.py +72 -0
- cumulusci/cli/org.py +692 -0
- cumulusci/cli/plan.py +181 -0
- cumulusci/cli/project.py +391 -0
- cumulusci/cli/robot.py +116 -0
- cumulusci/cli/runtime.py +190 -0
- cumulusci/cli/service.py +521 -0
- cumulusci/cli/task.py +295 -0
- cumulusci/cli/tests/__init__.py +0 -0
- cumulusci/cli/tests/test_cci.py +545 -0
- cumulusci/cli/tests/test_error.py +170 -0
- cumulusci/cli/tests/test_flow.py +276 -0
- cumulusci/cli/tests/test_logger.py +25 -0
- cumulusci/cli/tests/test_org.py +1438 -0
- cumulusci/cli/tests/test_plan.py +245 -0
- cumulusci/cli/tests/test_project.py +235 -0
- cumulusci/cli/tests/test_robot.py +177 -0
- cumulusci/cli/tests/test_runtime.py +197 -0
- cumulusci/cli/tests/test_service.py +853 -0
- cumulusci/cli/tests/test_task.py +266 -0
- cumulusci/cli/tests/test_ui.py +310 -0
- cumulusci/cli/tests/test_utils.py +122 -0
- cumulusci/cli/tests/utils.py +52 -0
- cumulusci/cli/ui.py +234 -0
- cumulusci/cli/utils.py +150 -0
- cumulusci/conftest.py +181 -0
- cumulusci/core/__init__.py +0 -0
- cumulusci/core/config/BaseConfig.py +5 -0
- cumulusci/core/config/BaseTaskFlowConfig.py +5 -0
- cumulusci/core/config/OrgConfig.py +5 -0
- cumulusci/core/config/ScratchOrgConfig.py +5 -0
- cumulusci/core/config/__init__.py +125 -0
- cumulusci/core/config/base_config.py +111 -0
- cumulusci/core/config/base_task_flow_config.py +82 -0
- cumulusci/core/config/marketing_cloud_service_config.py +83 -0
- cumulusci/core/config/oauth2_service_config.py +17 -0
- cumulusci/core/config/org_config.py +604 -0
- cumulusci/core/config/project_config.py +782 -0
- cumulusci/core/config/scratch_org_config.py +251 -0
- cumulusci/core/config/sfdx_org_config.py +220 -0
- cumulusci/core/config/tests/_test_config_backwards_compatibility.py +33 -0
- cumulusci/core/config/tests/test_config.py +1895 -0
- cumulusci/core/config/tests/test_config_expensive.py +839 -0
- cumulusci/core/config/tests/test_config_util.py +91 -0
- cumulusci/core/config/universal_config.py +88 -0
- cumulusci/core/config/util.py +18 -0
- cumulusci/core/datasets.py +303 -0
- cumulusci/core/debug.py +33 -0
- cumulusci/core/dependencies/__init__.py +55 -0
- cumulusci/core/dependencies/base.py +561 -0
- cumulusci/core/dependencies/dependencies.py +273 -0
- cumulusci/core/dependencies/github.py +177 -0
- cumulusci/core/dependencies/github_resolvers.py +244 -0
- cumulusci/core/dependencies/resolvers.py +580 -0
- cumulusci/core/dependencies/tests/__init__.py +0 -0
- cumulusci/core/dependencies/tests/conftest.py +385 -0
- cumulusci/core/dependencies/tests/test_dependencies.py +950 -0
- cumulusci/core/dependencies/tests/test_github.py +83 -0
- cumulusci/core/dependencies/tests/test_resolvers.py +1027 -0
- cumulusci/core/dependencies/utils.py +13 -0
- cumulusci/core/enums.py +11 -0
- cumulusci/core/exceptions.py +311 -0
- cumulusci/core/flowrunner.py +888 -0
- cumulusci/core/github.py +665 -0
- cumulusci/core/keychain/__init__.py +24 -0
- cumulusci/core/keychain/base_project_keychain.py +441 -0
- cumulusci/core/keychain/encrypted_file_project_keychain.py +945 -0
- cumulusci/core/keychain/environment_project_keychain.py +7 -0
- cumulusci/core/keychain/serialization.py +152 -0
- cumulusci/core/keychain/subprocess_keychain.py +24 -0
- cumulusci/core/keychain/tests/conftest.py +50 -0
- cumulusci/core/keychain/tests/test_base_project_keychain.py +299 -0
- cumulusci/core/keychain/tests/test_encrypted_file_project_keychain.py +1228 -0
- cumulusci/core/metadeploy/__init__.py +0 -0
- cumulusci/core/metadeploy/api.py +88 -0
- cumulusci/core/metadeploy/plans.py +25 -0
- cumulusci/core/metadeploy/tests/test_api.py +276 -0
- cumulusci/core/runtime.py +115 -0
- cumulusci/core/sfdx.py +162 -0
- cumulusci/core/source/__init__.py +16 -0
- cumulusci/core/source/github.py +50 -0
- cumulusci/core/source/local_folder.py +35 -0
- cumulusci/core/source_transforms/__init__.py +0 -0
- cumulusci/core/source_transforms/tests/test_transforms.py +1091 -0
- cumulusci/core/source_transforms/transforms.py +532 -0
- cumulusci/core/tasks.py +404 -0
- cumulusci/core/template_utils.py +59 -0
- cumulusci/core/tests/__init__.py +0 -0
- cumulusci/core/tests/cassettes/TestDatasetsE2E.test_datasets_e2e.yaml +215 -0
- cumulusci/core/tests/cassettes/TestDatasetsE2E.test_datasets_extract_standard_objects.yaml +199 -0
- cumulusci/core/tests/cassettes/TestDatasetsE2E.test_datasets_read_explicit_extract_declaration.yaml +3 -0
- cumulusci/core/tests/fake_remote_repo/cumulusci.yml +32 -0
- cumulusci/core/tests/fake_remote_repo/tasks/directory/example_2.py +6 -0
- cumulusci/core/tests/fake_remote_repo/tasks/example.py +43 -0
- cumulusci/core/tests/fake_remote_repo_2/cumulusci.yml +11 -0
- cumulusci/core/tests/fake_remote_repo_2/tasks/example_3.py +6 -0
- cumulusci/core/tests/test_datasets_e2e.py +386 -0
- cumulusci/core/tests/test_exceptions.py +11 -0
- cumulusci/core/tests/test_flowrunner.py +836 -0
- cumulusci/core/tests/test_github.py +942 -0
- cumulusci/core/tests/test_sfdx.py +138 -0
- cumulusci/core/tests/test_source.py +678 -0
- cumulusci/core/tests/test_tasks.py +262 -0
- cumulusci/core/tests/test_utils.py +141 -0
- cumulusci/core/tests/test_utils_merge_config.py +276 -0
- cumulusci/core/tests/test_versions.py +76 -0
- cumulusci/core/tests/untrusted_repo_child/cumulusci.yml +7 -0
- cumulusci/core/tests/untrusted_repo_child/tasks/untrusted_child.py +6 -0
- cumulusci/core/tests/untrusted_repo_parent/cumulusci.yml +26 -0
- cumulusci/core/tests/untrusted_repo_parent/tasks/untrusted_parent.py +6 -0
- cumulusci/core/tests/utils.py +116 -0
- cumulusci/core/tests/yaml/global.yaml +0 -0
- cumulusci/core/utils.py +402 -0
- cumulusci/core/versions.py +149 -0
- cumulusci/cumulusci.yml +1621 -0
- cumulusci/files/admin_profile.xml +20 -0
- cumulusci/files/delete_excludes.txt +424 -0
- cumulusci/files/templates/project/README.md +12 -0
- cumulusci/files/templates/project/cumulusci.yml +63 -0
- cumulusci/files/templates/project/dot-gitignore +60 -0
- cumulusci/files/templates/project/mapping.yml +45 -0
- cumulusci/files/templates/project/scratch_def.json +25 -0
- cumulusci/oauth/__init__.py +0 -0
- cumulusci/oauth/client.py +400 -0
- cumulusci/oauth/exceptions.py +9 -0
- cumulusci/oauth/salesforce.py +95 -0
- cumulusci/oauth/tests/__init__.py +0 -0
- cumulusci/oauth/tests/cassettes/test_get_device_code.yaml +22 -0
- cumulusci/oauth/tests/cassettes/test_get_device_oauth_token.yaml +74 -0
- cumulusci/oauth/tests/test_client.py +308 -0
- cumulusci/oauth/tests/test_salesforce.py +46 -0
- cumulusci/plugins/__init__.py +3 -0
- cumulusci/plugins/plugin_base.py +93 -0
- cumulusci/plugins/plugin_loader.py +59 -0
- cumulusci/robotframework/CumulusCI.py +340 -0
- cumulusci/robotframework/CumulusCI.robot +7 -0
- cumulusci/robotframework/Performance.py +165 -0
- cumulusci/robotframework/Salesforce.py +936 -0
- cumulusci/robotframework/Salesforce.robot +192 -0
- cumulusci/robotframework/SalesforceAPI.py +416 -0
- cumulusci/robotframework/SalesforcePlaywright.py +220 -0
- cumulusci/robotframework/SalesforcePlaywright.robot +40 -0
- cumulusci/robotframework/__init__.py +2 -0
- cumulusci/robotframework/base_library.py +39 -0
- cumulusci/robotframework/faker_mixin.py +89 -0
- cumulusci/robotframework/form_handlers.py +222 -0
- cumulusci/robotframework/javascript/cci_init.js +34 -0
- cumulusci/robotframework/javascript/cumulusci.js +4 -0
- cumulusci/robotframework/locator_manager.py +197 -0
- cumulusci/robotframework/locators_56.py +88 -0
- cumulusci/robotframework/locators_57.py +5 -0
- cumulusci/robotframework/pageobjects/BasePageObjects.py +433 -0
- cumulusci/robotframework/pageobjects/ObjectManagerPageObject.py +246 -0
- cumulusci/robotframework/pageobjects/PageObjectLibrary.py +45 -0
- cumulusci/robotframework/pageobjects/PageObjects.py +351 -0
- cumulusci/robotframework/pageobjects/__init__.py +12 -0
- cumulusci/robotframework/pageobjects/baseobjects.py +120 -0
- cumulusci/robotframework/perftests/short/collection_perf.robot +105 -0
- cumulusci/robotframework/tests/CustomObjectTestPage.py +10 -0
- cumulusci/robotframework/tests/FooTestPage.py +8 -0
- cumulusci/robotframework/tests/cumulusci/base.robot +40 -0
- cumulusci/robotframework/tests/cumulusci/bulkdata.robot +38 -0
- cumulusci/robotframework/tests/cumulusci/communities.robot +57 -0
- cumulusci/robotframework/tests/cumulusci/datagen.robot +84 -0
- cumulusci/robotframework/tests/salesforce/TestLibraryA.py +24 -0
- cumulusci/robotframework/tests/salesforce/TestLibraryB.py +20 -0
- cumulusci/robotframework/tests/salesforce/TestListener.py +93 -0
- cumulusci/robotframework/tests/salesforce/api.robot +178 -0
- cumulusci/robotframework/tests/salesforce/browsers.robot +143 -0
- cumulusci/robotframework/tests/salesforce/classic.robot +51 -0
- cumulusci/robotframework/tests/salesforce/create_contact.robot +59 -0
- cumulusci/robotframework/tests/salesforce/faker.robot +68 -0
- cumulusci/robotframework/tests/salesforce/forms.robot +172 -0
- cumulusci/robotframework/tests/salesforce/label_locator.robot +244 -0
- cumulusci/robotframework/tests/salesforce/labels.html +33 -0
- cumulusci/robotframework/tests/salesforce/locators.robot +149 -0
- cumulusci/robotframework/tests/salesforce/pageobjects/base_pageobjects.robot +100 -0
- cumulusci/robotframework/tests/salesforce/pageobjects/example_page_object.py +25 -0
- cumulusci/robotframework/tests/salesforce/pageobjects/listing_page.robot +115 -0
- cumulusci/robotframework/tests/salesforce/pageobjects/objectmanager.robot +74 -0
- cumulusci/robotframework/tests/salesforce/pageobjects/pageobjects.robot +171 -0
- cumulusci/robotframework/tests/salesforce/performance.robot +109 -0
- cumulusci/robotframework/tests/salesforce/playwright/javascript_keywords.robot +33 -0
- cumulusci/robotframework/tests/salesforce/playwright/open_test_browser.robot +48 -0
- cumulusci/robotframework/tests/salesforce/playwright/playwright.robot +24 -0
- cumulusci/robotframework/tests/salesforce/playwright/ui.robot +32 -0
- cumulusci/robotframework/tests/salesforce/populate.robot +89 -0
- cumulusci/robotframework/tests/salesforce/test_testlistener.py +37 -0
- cumulusci/robotframework/tests/salesforce/ui.robot +361 -0
- cumulusci/robotframework/tests/test_cumulusci_library.py +304 -0
- cumulusci/robotframework/tests/test_locator_manager.py +158 -0
- cumulusci/robotframework/tests/test_pageobjects.py +291 -0
- cumulusci/robotframework/tests/test_performance.py +38 -0
- cumulusci/robotframework/tests/test_salesforce.py +79 -0
- cumulusci/robotframework/tests/test_salesforce_locators.py +73 -0
- cumulusci/robotframework/tests/test_template_util.py +53 -0
- cumulusci/robotframework/tests/test_utils.py +106 -0
- cumulusci/robotframework/utils.py +283 -0
- cumulusci/salesforce_api/__init__.py +0 -0
- cumulusci/salesforce_api/exceptions.py +23 -0
- cumulusci/salesforce_api/filterable_objects.py +96 -0
- cumulusci/salesforce_api/mc_soap_envelopes.py +89 -0
- cumulusci/salesforce_api/metadata.py +721 -0
- cumulusci/salesforce_api/org_schema.py +571 -0
- cumulusci/salesforce_api/org_schema_models.py +226 -0
- cumulusci/salesforce_api/package_install.py +265 -0
- cumulusci/salesforce_api/package_zip.py +301 -0
- cumulusci/salesforce_api/rest_deploy.py +148 -0
- cumulusci/salesforce_api/retrieve_profile_api.py +301 -0
- cumulusci/salesforce_api/soap_envelopes.py +177 -0
- cumulusci/salesforce_api/tests/__init__.py +0 -0
- cumulusci/salesforce_api/tests/metadata_test_strings.py +24 -0
- cumulusci/salesforce_api/tests/test_metadata.py +1015 -0
- cumulusci/salesforce_api/tests/test_package_install.py +219 -0
- cumulusci/salesforce_api/tests/test_package_zip.py +380 -0
- cumulusci/salesforce_api/tests/test_rest_deploy.py +264 -0
- cumulusci/salesforce_api/tests/test_retrieve_profile_api.py +337 -0
- cumulusci/salesforce_api/tests/test_utils.py +124 -0
- cumulusci/salesforce_api/utils.py +51 -0
- cumulusci/schema/cumulusci.jsonschema.json +782 -0
- cumulusci/tasks/__init__.py +0 -0
- cumulusci/tasks/apex/__init__.py +0 -0
- cumulusci/tasks/apex/anon.py +157 -0
- cumulusci/tasks/apex/batch.py +180 -0
- cumulusci/tasks/apex/testrunner.py +835 -0
- cumulusci/tasks/apex/tests/cassettes/ManualEditTestApexIntegrationTests.test_run_tests__integration_test.yaml +703 -0
- cumulusci/tasks/apex/tests/test_apex_tasks.py +1558 -0
- cumulusci/tasks/base_source_control_task.py +17 -0
- cumulusci/tasks/bulkdata/__init__.py +15 -0
- cumulusci/tasks/bulkdata/base_generate_data_task.py +96 -0
- cumulusci/tasks/bulkdata/dates.py +97 -0
- cumulusci/tasks/bulkdata/delete.py +156 -0
- cumulusci/tasks/bulkdata/extract.py +441 -0
- cumulusci/tasks/bulkdata/extract_dataset_utils/calculate_dependencies.py +117 -0
- cumulusci/tasks/bulkdata/extract_dataset_utils/extract_yml.py +123 -0
- cumulusci/tasks/bulkdata/extract_dataset_utils/hardcoded_default_declarations.py +49 -0
- cumulusci/tasks/bulkdata/extract_dataset_utils/synthesize_extract_declarations.py +283 -0
- cumulusci/tasks/bulkdata/extract_dataset_utils/tests/test_extract_yml.py +142 -0
- cumulusci/tasks/bulkdata/extract_dataset_utils/tests/test_synthesize_extract_declarations.py +575 -0
- cumulusci/tasks/bulkdata/factory_utils.py +134 -0
- cumulusci/tasks/bulkdata/generate.py +4 -0
- cumulusci/tasks/bulkdata/generate_and_load_data.py +232 -0
- cumulusci/tasks/bulkdata/generate_and_load_data_from_yaml.py +19 -0
- cumulusci/tasks/bulkdata/generate_from_yaml.py +183 -0
- cumulusci/tasks/bulkdata/generate_mapping.py +434 -0
- cumulusci/tasks/bulkdata/generate_mapping_utils/dependency_map.py +169 -0
- cumulusci/tasks/bulkdata/generate_mapping_utils/extract_mapping_file_generator.py +45 -0
- cumulusci/tasks/bulkdata/generate_mapping_utils/generate_mapping_from_declarations.py +121 -0
- cumulusci/tasks/bulkdata/generate_mapping_utils/load_mapping_file_generator.py +127 -0
- cumulusci/tasks/bulkdata/generate_mapping_utils/mapping_generator_post_processes.py +53 -0
- cumulusci/tasks/bulkdata/generate_mapping_utils/mapping_transforms.py +139 -0
- cumulusci/tasks/bulkdata/generate_mapping_utils/tests/test_generate_extract_mapping_from_declarations.py +135 -0
- cumulusci/tasks/bulkdata/generate_mapping_utils/tests/test_generate_load_mapping_from_declarations.py +330 -0
- cumulusci/tasks/bulkdata/generate_mapping_utils/tests/test_mapping_generator_post_processes.py +60 -0
- cumulusci/tasks/bulkdata/generate_mapping_utils/tests/test_mapping_transforms.py +188 -0
- cumulusci/tasks/bulkdata/load.py +1196 -0
- cumulusci/tasks/bulkdata/mapping_parser.py +811 -0
- cumulusci/tasks/bulkdata/query_transformers.py +264 -0
- cumulusci/tasks/bulkdata/select_utils.py +792 -0
- cumulusci/tasks/bulkdata/snowfakery.py +753 -0
- cumulusci/tasks/bulkdata/snowfakery_utils/queue_manager.py +478 -0
- cumulusci/tasks/bulkdata/snowfakery_utils/snowfakery_run_until.py +141 -0
- cumulusci/tasks/bulkdata/snowfakery_utils/snowfakery_working_directory.py +53 -0
- cumulusci/tasks/bulkdata/snowfakery_utils/subtask_configurator.py +64 -0
- cumulusci/tasks/bulkdata/step.py +1242 -0
- cumulusci/tasks/bulkdata/tests/__init__.py +0 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_random_strategy.yaml +147 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_similarity_annoy_strategy.yaml +123 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_similarity_select_and_insert_strategy.yaml +313 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_similarity_select_and_insert_strategy_bulk.yaml +550 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_similarity_strategy.yaml +175 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_standard_strategy.yaml +147 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestSnowfakery.test_run_until_records_in_org__multiple_needed.yaml +69 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestSnowfakery.test_run_until_records_in_org__none_needed.yaml +22 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestSnowfakery.test_run_until_records_in_org__one_needed.yaml +24 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestSnowfakery.test_snowfakery_query_salesforce.yaml +25 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestUpdatesIntegrationTests.test_updates_task.yaml +80 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestUpsert.test_simple_upsert__rest.yaml +270 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestUpsert.test_upsert__rest.yaml +267 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestUpsert.test_upsert_complex_external_id_field__rest.yaml +369 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestUpsert.test_upsert_complex_external_id_field_rest__duplicate_error.yaml +204 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestUpsert.test_upsert_complex_fields__bulk.yaml +675 -0
- cumulusci/tasks/bulkdata/tests/dummy_data_factory.py +36 -0
- cumulusci/tasks/bulkdata/tests/integration_test_utils.py +49 -0
- cumulusci/tasks/bulkdata/tests/mapping-oid.yml +87 -0
- cumulusci/tasks/bulkdata/tests/mapping_after.yml +38 -0
- cumulusci/tasks/bulkdata/tests/mapping_poly.yml +34 -0
- cumulusci/tasks/bulkdata/tests/mapping_poly_incomplete.yml +20 -0
- cumulusci/tasks/bulkdata/tests/mapping_poly_wrong.yml +21 -0
- cumulusci/tasks/bulkdata/tests/mapping_select.yml +20 -0
- cumulusci/tasks/bulkdata/tests/mapping_select_invalid_strategy.yml +20 -0
- cumulusci/tasks/bulkdata/tests/mapping_select_invalid_threshold__invalid_number.yml +21 -0
- cumulusci/tasks/bulkdata/tests/mapping_select_invalid_threshold__invalid_strategy.yml +21 -0
- cumulusci/tasks/bulkdata/tests/mapping_select_invalid_threshold__non_float.yml +21 -0
- cumulusci/tasks/bulkdata/tests/mapping_select_missing_priority_fields.yml +22 -0
- cumulusci/tasks/bulkdata/tests/mapping_select_no_priority_fields.yml +18 -0
- cumulusci/tasks/bulkdata/tests/mapping_simple.yml +27 -0
- cumulusci/tasks/bulkdata/tests/mapping_v1.yml +28 -0
- cumulusci/tasks/bulkdata/tests/mapping_v2.yml +21 -0
- cumulusci/tasks/bulkdata/tests/mapping_v3.yml +32 -0
- cumulusci/tasks/bulkdata/tests/mapping_vanilla_sf.yml +69 -0
- cumulusci/tasks/bulkdata/tests/mock_data_factory_without_mapping.py +12 -0
- cumulusci/tasks/bulkdata/tests/person_accounts.yml +23 -0
- cumulusci/tasks/bulkdata/tests/person_accounts_minimal.yml +15 -0
- cumulusci/tasks/bulkdata/tests/recordtypes.yml +8 -0
- cumulusci/tasks/bulkdata/tests/recordtypes_2.yml +6 -0
- cumulusci/tasks/bulkdata/tests/recordtypes_with_ispersontype.yml +8 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/child/child2.yml +3 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/child.yml +4 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/gen_npsp_standard_objects.recipe.yml +89 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/include_parent.yml +3 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/npsp_standard_objects_macros.yml +34 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/options.recipe.yml +6 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/query_snowfakery.recipe.yml +16 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/sf_standard_object_macros.yml +83 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery.load.yml +2 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery.recipe.yml +13 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery_2.load.yml +5 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery_channels.load.yml +13 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery_channels.recipe.yml +12 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery_channels_2.load.yml +13 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/unique_values.recipe.yml +4 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/upsert.recipe.yml +23 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/upsert_2.recipe.yml +29 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/upsert_before.yml +10 -0
- cumulusci/tasks/bulkdata/tests/test_base_generate_data_tasks.py +61 -0
- cumulusci/tasks/bulkdata/tests/test_dates.py +99 -0
- cumulusci/tasks/bulkdata/tests/test_delete.py +404 -0
- cumulusci/tasks/bulkdata/tests/test_extract.py +1311 -0
- cumulusci/tasks/bulkdata/tests/test_factory_utils.py +55 -0
- cumulusci/tasks/bulkdata/tests/test_generate_and_load.py +252 -0
- cumulusci/tasks/bulkdata/tests/test_generate_from_snowfakery_task.py +343 -0
- cumulusci/tasks/bulkdata/tests/test_generatemapping.py +1039 -0
- cumulusci/tasks/bulkdata/tests/test_load.py +3175 -0
- cumulusci/tasks/bulkdata/tests/test_mapping_parser.py +1658 -0
- cumulusci/tasks/bulkdata/tests/test_query_db__joins_self_lookups.yml +12 -0
- cumulusci/tasks/bulkdata/tests/test_query_db_joins_lookups.yml +26 -0
- cumulusci/tasks/bulkdata/tests/test_query_db_joins_lookups_select.yml +48 -0
- cumulusci/tasks/bulkdata/tests/test_select.py +171 -0
- cumulusci/tasks/bulkdata/tests/test_select_utils.py +1057 -0
- cumulusci/tasks/bulkdata/tests/test_snowfakery.py +1153 -0
- cumulusci/tasks/bulkdata/tests/test_step.py +3957 -0
- cumulusci/tasks/bulkdata/tests/test_updates.py +513 -0
- cumulusci/tasks/bulkdata/tests/test_upsert.py +1015 -0
- cumulusci/tasks/bulkdata/tests/test_utils.py +158 -0
- cumulusci/tasks/bulkdata/tests/testdata.db +0 -0
- cumulusci/tasks/bulkdata/tests/update_describe.py +50 -0
- cumulusci/tasks/bulkdata/tests/update_person_accounts.yml +23 -0
- cumulusci/tasks/bulkdata/tests/utils.py +114 -0
- cumulusci/tasks/bulkdata/update_data.py +260 -0
- cumulusci/tasks/bulkdata/upsert_utils.py +130 -0
- cumulusci/tasks/bulkdata/utils.py +249 -0
- cumulusci/tasks/command.py +178 -0
- cumulusci/tasks/connectedapp.py +186 -0
- cumulusci/tasks/create_package_version.py +778 -0
- cumulusci/tasks/datadictionary.py +745 -0
- cumulusci/tasks/dx_convert_from.py +26 -0
- cumulusci/tasks/github/__init__.py +17 -0
- cumulusci/tasks/github/base.py +16 -0
- cumulusci/tasks/github/commit_status.py +13 -0
- cumulusci/tasks/github/merge.py +11 -0
- cumulusci/tasks/github/publish.py +11 -0
- cumulusci/tasks/github/pull_request.py +11 -0
- cumulusci/tasks/github/release.py +11 -0
- cumulusci/tasks/github/release_report.py +11 -0
- cumulusci/tasks/github/tag.py +11 -0
- cumulusci/tasks/github/tests/__init__.py +0 -0
- cumulusci/tasks/github/tests/test_util.py +202 -0
- cumulusci/tasks/github/tests/test_vcs_migration.py +44 -0
- cumulusci/tasks/github/tests/util_github_api.py +666 -0
- cumulusci/tasks/github/util.py +252 -0
- cumulusci/tasks/marketing_cloud/__init__.py +0 -0
- cumulusci/tasks/marketing_cloud/api.py +188 -0
- cumulusci/tasks/marketing_cloud/base.py +38 -0
- cumulusci/tasks/marketing_cloud/deploy.py +345 -0
- cumulusci/tasks/marketing_cloud/get_user_info.py +40 -0
- cumulusci/tasks/marketing_cloud/mc_constants.py +1 -0
- cumulusci/tasks/marketing_cloud/tests/__init__.py +0 -0
- cumulusci/tasks/marketing_cloud/tests/conftest.py +46 -0
- cumulusci/tasks/marketing_cloud/tests/expected-payload.json +110 -0
- cumulusci/tasks/marketing_cloud/tests/test_api.py +97 -0
- cumulusci/tasks/marketing_cloud/tests/test_api_soap_envelopes.py +145 -0
- cumulusci/tasks/marketing_cloud/tests/test_base.py +14 -0
- cumulusci/tasks/marketing_cloud/tests/test_deploy.py +400 -0
- cumulusci/tasks/marketing_cloud/tests/test_get_user_info.py +141 -0
- cumulusci/tasks/marketing_cloud/tests/validation-response.json +39 -0
- cumulusci/tasks/metadata/__init__.py +0 -0
- cumulusci/tasks/metadata/ee_src.py +94 -0
- cumulusci/tasks/metadata/managed_src.py +100 -0
- cumulusci/tasks/metadata/metadata_map.yml +868 -0
- cumulusci/tasks/metadata/modify.py +99 -0
- cumulusci/tasks/metadata/package.py +684 -0
- cumulusci/tasks/metadata/tests/__init__.py +0 -0
- cumulusci/tasks/metadata/tests/package_metadata/namespaced_report_folder/.hidden/.keep +0 -0
- cumulusci/tasks/metadata/tests/package_metadata/namespaced_report_folder/destructiveChanges.xml +9 -0
- cumulusci/tasks/metadata/tests/package_metadata/namespaced_report_folder/package.xml +9 -0
- cumulusci/tasks/metadata/tests/package_metadata/namespaced_report_folder/package_install_uninstall.xml +11 -0
- cumulusci/tasks/metadata/tests/package_metadata/namespaced_report_folder/reports/namespace__TestFolder/TestReport.report +3 -0
- cumulusci/tasks/metadata/tests/sample_package.xml +9 -0
- cumulusci/tasks/metadata/tests/test_ee_src.py +112 -0
- cumulusci/tasks/metadata/tests/test_managed_src.py +111 -0
- cumulusci/tasks/metadata/tests/test_modify.py +123 -0
- cumulusci/tasks/metadata/tests/test_package.py +476 -0
- cumulusci/tasks/metadata_etl/__init__.py +29 -0
- cumulusci/tasks/metadata_etl/base.py +436 -0
- cumulusci/tasks/metadata_etl/duplicate_rules.py +24 -0
- cumulusci/tasks/metadata_etl/field_sets.py +70 -0
- cumulusci/tasks/metadata_etl/help_text.py +92 -0
- cumulusci/tasks/metadata_etl/layouts.py +550 -0
- cumulusci/tasks/metadata_etl/objects.py +68 -0
- cumulusci/tasks/metadata_etl/permissions.py +167 -0
- cumulusci/tasks/metadata_etl/picklists.py +221 -0
- cumulusci/tasks/metadata_etl/remote_site_settings.py +99 -0
- cumulusci/tasks/metadata_etl/sharing.py +138 -0
- cumulusci/tasks/metadata_etl/tests/test_base.py +512 -0
- cumulusci/tasks/metadata_etl/tests/test_duplicate_rules.py +22 -0
- cumulusci/tasks/metadata_etl/tests/test_field_sets.py +156 -0
- cumulusci/tasks/metadata_etl/tests/test_help_text.py +387 -0
- cumulusci/tasks/metadata_etl/tests/test_ip_ranges.py +85 -0
- cumulusci/tasks/metadata_etl/tests/test_layouts.py +858 -0
- cumulusci/tasks/metadata_etl/tests/test_objects.py +236 -0
- cumulusci/tasks/metadata_etl/tests/test_permissions.py +223 -0
- cumulusci/tasks/metadata_etl/tests/test_picklists.py +547 -0
- cumulusci/tasks/metadata_etl/tests/test_remote_site_settings.py +46 -0
- cumulusci/tasks/metadata_etl/tests/test_sharing.py +333 -0
- cumulusci/tasks/metadata_etl/tests/test_value_sets.py +298 -0
- cumulusci/tasks/metadata_etl/value_sets.py +106 -0
- cumulusci/tasks/metadeploy.py +393 -0
- cumulusci/tasks/metaxml.py +88 -0
- cumulusci/tasks/preflight/__init__.py +0 -0
- cumulusci/tasks/preflight/dataset_load.py +49 -0
- cumulusci/tasks/preflight/licenses.py +86 -0
- cumulusci/tasks/preflight/packages.py +14 -0
- cumulusci/tasks/preflight/permsets.py +23 -0
- cumulusci/tasks/preflight/recordtypes.py +16 -0
- cumulusci/tasks/preflight/retrieve_tasks.py +30 -0
- cumulusci/tasks/preflight/settings.py +77 -0
- cumulusci/tasks/preflight/sobjects.py +202 -0
- cumulusci/tasks/preflight/tests/test_dataset_load.py +85 -0
- cumulusci/tasks/preflight/tests/test_licenses.py +174 -0
- cumulusci/tasks/preflight/tests/test_packages.py +14 -0
- cumulusci/tasks/preflight/tests/test_permset_preflights.py +51 -0
- cumulusci/tasks/preflight/tests/test_recordtypes.py +30 -0
- cumulusci/tasks/preflight/tests/test_retrieve_tasks.py +62 -0
- cumulusci/tasks/preflight/tests/test_settings.py +130 -0
- cumulusci/tasks/preflight/tests/test_sobjects.py +231 -0
- cumulusci/tasks/push/README.md +59 -0
- cumulusci/tasks/push/__init__.py +0 -0
- cumulusci/tasks/push/push_api.py +659 -0
- cumulusci/tasks/push/pushfails.py +136 -0
- cumulusci/tasks/push/tasks.py +476 -0
- cumulusci/tasks/push/tests/conftest.py +263 -0
- cumulusci/tasks/push/tests/test_push_api.py +951 -0
- cumulusci/tasks/push/tests/test_push_tasks.py +659 -0
- cumulusci/tasks/release_notes/README.md +63 -0
- cumulusci/tasks/release_notes/__init__.py +0 -0
- cumulusci/tasks/release_notes/exceptions.py +5 -0
- cumulusci/tasks/release_notes/generator.py +137 -0
- cumulusci/tasks/release_notes/parser.py +232 -0
- cumulusci/tasks/release_notes/provider.py +44 -0
- cumulusci/tasks/release_notes/task.py +300 -0
- cumulusci/tasks/release_notes/tests/__init__.py +0 -0
- cumulusci/tasks/release_notes/tests/change_notes/full/example1.md +17 -0
- cumulusci/tasks/release_notes/tests/change_notes/multi/1.txt +1 -0
- cumulusci/tasks/release_notes/tests/change_notes/multi/2.txt +1 -0
- cumulusci/tasks/release_notes/tests/change_notes/multi/3.txt +1 -0
- cumulusci/tasks/release_notes/tests/change_notes/single/1.txt +1 -0
- cumulusci/tasks/release_notes/tests/test_generator.py +582 -0
- cumulusci/tasks/release_notes/tests/test_parser.py +867 -0
- cumulusci/tasks/release_notes/tests/test_provider.py +512 -0
- cumulusci/tasks/release_notes/tests/test_task.py +461 -0
- cumulusci/tasks/release_notes/tests/utils.py +153 -0
- cumulusci/tasks/robotframework/__init__.py +3 -0
- cumulusci/tasks/robotframework/debugger/DebugListener.py +100 -0
- cumulusci/tasks/robotframework/debugger/__init__.py +10 -0
- cumulusci/tasks/robotframework/debugger/model.py +87 -0
- cumulusci/tasks/robotframework/debugger/ui.py +259 -0
- cumulusci/tasks/robotframework/libdoc.py +269 -0
- cumulusci/tasks/robotframework/robotframework.py +392 -0
- cumulusci/tasks/robotframework/stylesheet.css +130 -0
- cumulusci/tasks/robotframework/template.html +109 -0
- cumulusci/tasks/robotframework/tests/TestLibrary.py +18 -0
- cumulusci/tasks/robotframework/tests/TestPageObjects.py +31 -0
- cumulusci/tasks/robotframework/tests/TestResource.robot +8 -0
- cumulusci/tasks/robotframework/tests/failing_tests.robot +16 -0
- cumulusci/tasks/robotframework/tests/performance.robot +23 -0
- cumulusci/tasks/robotframework/tests/test_browser_proxies.py +137 -0
- cumulusci/tasks/robotframework/tests/test_debugger.py +360 -0
- cumulusci/tasks/robotframework/tests/test_robot_parallel.py +141 -0
- cumulusci/tasks/robotframework/tests/test_robotframework.py +860 -0
- cumulusci/tasks/salesforce/BaseRetrieveMetadata.py +58 -0
- cumulusci/tasks/salesforce/BaseSalesforceApiTask.py +45 -0
- cumulusci/tasks/salesforce/BaseSalesforceMetadataApiTask.py +18 -0
- cumulusci/tasks/salesforce/BaseSalesforceTask.py +4 -0
- cumulusci/tasks/salesforce/BaseUninstallMetadata.py +41 -0
- cumulusci/tasks/salesforce/CreateCommunity.py +124 -0
- cumulusci/tasks/salesforce/CreatePackage.py +29 -0
- cumulusci/tasks/salesforce/Deploy.py +240 -0
- cumulusci/tasks/salesforce/DeployBundles.py +88 -0
- cumulusci/tasks/salesforce/DescribeMetadataTypes.py +26 -0
- cumulusci/tasks/salesforce/EnsureRecordTypes.py +202 -0
- cumulusci/tasks/salesforce/GetInstalledPackages.py +8 -0
- cumulusci/tasks/salesforce/ListCommunities.py +40 -0
- cumulusci/tasks/salesforce/ListCommunityTemplates.py +19 -0
- cumulusci/tasks/salesforce/PublishCommunity.py +62 -0
- cumulusci/tasks/salesforce/RetrievePackaged.py +41 -0
- cumulusci/tasks/salesforce/RetrieveReportsAndDashboards.py +82 -0
- cumulusci/tasks/salesforce/RetrieveUnpackaged.py +36 -0
- cumulusci/tasks/salesforce/SOQLQuery.py +39 -0
- cumulusci/tasks/salesforce/UninstallLocal.py +15 -0
- cumulusci/tasks/salesforce/UninstallLocalBundles.py +28 -0
- cumulusci/tasks/salesforce/UninstallLocalNamespacedBundles.py +58 -0
- cumulusci/tasks/salesforce/UninstallPackage.py +32 -0
- cumulusci/tasks/salesforce/UninstallPackaged.py +56 -0
- cumulusci/tasks/salesforce/UpdateAdminProfile.py +8 -0
- cumulusci/tasks/salesforce/__init__.py +79 -0
- cumulusci/tasks/salesforce/activate_flow.py +74 -0
- cumulusci/tasks/salesforce/check_components.py +324 -0
- cumulusci/tasks/salesforce/composite.py +142 -0
- cumulusci/tasks/salesforce/create_permission_sets.py +35 -0
- cumulusci/tasks/salesforce/custom_settings.py +134 -0
- cumulusci/tasks/salesforce/custom_settings_wait.py +132 -0
- cumulusci/tasks/salesforce/enable_prediction.py +107 -0
- cumulusci/tasks/salesforce/insert_record.py +40 -0
- cumulusci/tasks/salesforce/install_package_version.py +242 -0
- cumulusci/tasks/salesforce/license_preflights.py +8 -0
- cumulusci/tasks/salesforce/network_member_group.py +178 -0
- cumulusci/tasks/salesforce/nonsourcetracking.py +228 -0
- cumulusci/tasks/salesforce/org_settings.py +193 -0
- cumulusci/tasks/salesforce/package_upload.py +328 -0
- cumulusci/tasks/salesforce/profiles.py +74 -0
- cumulusci/tasks/salesforce/promote_package_version.py +376 -0
- cumulusci/tasks/salesforce/retrieve_profile.py +195 -0
- cumulusci/tasks/salesforce/salesforce_files.py +244 -0
- cumulusci/tasks/salesforce/sourcetracking.py +507 -0
- cumulusci/tasks/salesforce/tests/__init__.py +3 -0
- cumulusci/tasks/salesforce/tests/test_CreateCommunity.py +278 -0
- cumulusci/tasks/salesforce/tests/test_CreatePackage.py +22 -0
- cumulusci/tasks/salesforce/tests/test_Deploy.py +470 -0
- cumulusci/tasks/salesforce/tests/test_DeployBundles.py +76 -0
- cumulusci/tasks/salesforce/tests/test_EnsureRecordTypes.py +345 -0
- cumulusci/tasks/salesforce/tests/test_ListCommunities.py +84 -0
- cumulusci/tasks/salesforce/tests/test_ListCommunityTemplates.py +49 -0
- cumulusci/tasks/salesforce/tests/test_PackageUpload.py +547 -0
- cumulusci/tasks/salesforce/tests/test_ProfileGrantAllAccess.py +699 -0
- cumulusci/tasks/salesforce/tests/test_PublishCommunity.py +181 -0
- cumulusci/tasks/salesforce/tests/test_RetrievePackaged.py +24 -0
- cumulusci/tasks/salesforce/tests/test_RetrieveReportsAndDashboards.py +56 -0
- cumulusci/tasks/salesforce/tests/test_RetrieveUnpackaged.py +21 -0
- cumulusci/tasks/salesforce/tests/test_SOQLQuery.py +30 -0
- cumulusci/tasks/salesforce/tests/test_UninstallLocal.py +15 -0
- cumulusci/tasks/salesforce/tests/test_UninstallLocalBundles.py +19 -0
- cumulusci/tasks/salesforce/tests/test_UninstallLocalNamespacedBundles.py +22 -0
- cumulusci/tasks/salesforce/tests/test_UninstallPackage.py +19 -0
- cumulusci/tasks/salesforce/tests/test_UninstallPackaged.py +66 -0
- cumulusci/tasks/salesforce/tests/test_UninstallPackagedIncremental.py +127 -0
- cumulusci/tasks/salesforce/tests/test_activate_flow.py +132 -0
- cumulusci/tasks/salesforce/tests/test_base_tasks.py +110 -0
- cumulusci/tasks/salesforce/tests/test_check_components.py +445 -0
- cumulusci/tasks/salesforce/tests/test_composite.py +250 -0
- cumulusci/tasks/salesforce/tests/test_create_permission_sets.py +41 -0
- cumulusci/tasks/salesforce/tests/test_custom_settings.py +227 -0
- cumulusci/tasks/salesforce/tests/test_custom_settings_wait.py +174 -0
- cumulusci/tasks/salesforce/tests/test_describemetadatatypes.py +18 -0
- cumulusci/tasks/salesforce/tests/test_enable_prediction.py +240 -0
- cumulusci/tasks/salesforce/tests/test_insert_record.py +110 -0
- cumulusci/tasks/salesforce/tests/test_install_package_version.py +464 -0
- cumulusci/tasks/salesforce/tests/test_network_member_group.py +444 -0
- cumulusci/tasks/salesforce/tests/test_nonsourcetracking.py +235 -0
- cumulusci/tasks/salesforce/tests/test_org_settings.py +407 -0
- cumulusci/tasks/salesforce/tests/test_profiles.py +202 -0
- cumulusci/tasks/salesforce/tests/test_retrieve_profile.py +287 -0
- cumulusci/tasks/salesforce/tests/test_salesforce_files.py +228 -0
- cumulusci/tasks/salesforce/tests/test_sourcetracking.py +350 -0
- cumulusci/tasks/salesforce/tests/test_trigger_handlers.py +300 -0
- cumulusci/tasks/salesforce/tests/test_update_dependencies.py +509 -0
- cumulusci/tasks/salesforce/tests/util.py +79 -0
- cumulusci/tasks/salesforce/trigger_handlers.py +119 -0
- cumulusci/tasks/salesforce/uninstall_packaged_incremental.py +136 -0
- cumulusci/tasks/salesforce/update_dependencies.py +290 -0
- cumulusci/tasks/salesforce/update_profile.py +339 -0
- cumulusci/tasks/salesforce/users/permsets.py +227 -0
- cumulusci/tasks/salesforce/users/photos.py +162 -0
- cumulusci/tasks/salesforce/users/tests/photo.mock.txt +1 -0
- cumulusci/tasks/salesforce/users/tests/test_permsets.py +950 -0
- cumulusci/tasks/salesforce/users/tests/test_photos.py +373 -0
- cumulusci/tasks/sample_data/capture_sample_data.py +77 -0
- cumulusci/tasks/sample_data/load_sample_data.py +85 -0
- cumulusci/tasks/sample_data/test_capture_sample_data.py +117 -0
- cumulusci/tasks/sample_data/test_load_sample_data.py +121 -0
- cumulusci/tasks/sfdx.py +83 -0
- cumulusci/tasks/tests/__init__.py +1 -0
- cumulusci/tasks/tests/conftest.py +30 -0
- cumulusci/tasks/tests/test_command.py +129 -0
- cumulusci/tasks/tests/test_connectedapp.py +236 -0
- cumulusci/tasks/tests/test_create_package_version.py +847 -0
- cumulusci/tasks/tests/test_datadictionary.py +1575 -0
- cumulusci/tasks/tests/test_dx_convert_from.py +60 -0
- cumulusci/tasks/tests/test_metadeploy.py +624 -0
- cumulusci/tasks/tests/test_metaxml.py +99 -0
- cumulusci/tasks/tests/test_promote_package_version.py +488 -0
- cumulusci/tasks/tests/test_pushfails.py +96 -0
- cumulusci/tasks/tests/test_salesforce.py +72 -0
- cumulusci/tasks/tests/test_sfdx.py +105 -0
- cumulusci/tasks/tests/test_util.py +207 -0
- cumulusci/tasks/util.py +261 -0
- cumulusci/tasks/vcs/__init__.py +19 -0
- cumulusci/tasks/vcs/commit_status.py +58 -0
- cumulusci/tasks/vcs/create_commit_status.py +37 -0
- cumulusci/tasks/vcs/download_extract.py +199 -0
- cumulusci/tasks/vcs/merge.py +298 -0
- cumulusci/tasks/vcs/publish.py +207 -0
- cumulusci/tasks/vcs/pull_request.py +9 -0
- cumulusci/tasks/vcs/release.py +134 -0
- cumulusci/tasks/vcs/release_report.py +105 -0
- cumulusci/tasks/vcs/tag.py +31 -0
- cumulusci/tasks/vcs/tests/github/test_commit_status.py +196 -0
- cumulusci/tasks/vcs/tests/github/test_download_extract.py +896 -0
- cumulusci/tasks/vcs/tests/github/test_merge.py +1118 -0
- cumulusci/tasks/vcs/tests/github/test_publish.py +823 -0
- cumulusci/tasks/vcs/tests/github/test_pull_request.py +29 -0
- cumulusci/tasks/vcs/tests/github/test_release.py +390 -0
- cumulusci/tasks/vcs/tests/github/test_release_report.py +109 -0
- cumulusci/tasks/vcs/tests/github/test_tag.py +90 -0
- cumulusci/tasks/vlocity/exceptions.py +2 -0
- cumulusci/tasks/vlocity/tests/test_vlocity.py +283 -0
- cumulusci/tasks/vlocity/vlocity.py +342 -0
- cumulusci/tests/__init__.py +1 -0
- cumulusci/tests/cassettes/GET_sobjects_Account_PersonAccount_describe.yaml +18 -0
- cumulusci/tests/cassettes/TestIntegrationInfrastructure.test_integration_tests.yaml +19 -0
- cumulusci/tests/pytest_plugins/pytest_sf_orgconnect.py +307 -0
- cumulusci/tests/pytest_plugins/pytest_sf_vcr.py +275 -0
- cumulusci/tests/pytest_plugins/pytest_sf_vcr_serializer.py +160 -0
- cumulusci/tests/pytest_plugins/pytest_typeguard.py +5 -0
- cumulusci/tests/pytest_plugins/test_vcr_string_compressor.py +49 -0
- cumulusci/tests/pytest_plugins/vcr_string_compressor.py +97 -0
- cumulusci/tests/shared_cassettes/GET_sobjects_Account_describe.yaml +18 -0
- cumulusci/tests/shared_cassettes/GET_sobjects_Case_describe.yaml +18 -0
- cumulusci/tests/shared_cassettes/GET_sobjects_Contact_describe.yaml +4838 -0
- cumulusci/tests/shared_cassettes/GET_sobjects_Custom__c_describe.yaml +242 -0
- cumulusci/tests/shared_cassettes/GET_sobjects_Event_describe.yaml +19 -0
- cumulusci/tests/shared_cassettes/GET_sobjects_Global_describe.yaml +1338 -0
- cumulusci/tests/shared_cassettes/GET_sobjects_Lead_describe.yaml +18 -0
- cumulusci/tests/shared_cassettes/GET_sobjects_OpportunityContactRole_describe.yaml +34 -0
- cumulusci/tests/shared_cassettes/GET_sobjects_Opportunity_describe.yaml +1261 -0
- cumulusci/tests/shared_cassettes/GET_sobjects_Organization.yaml +49 -0
- cumulusci/tests/shared_cassettes/vcr_string_templates/batchInfoList_xml.tpl +15 -0
- cumulusci/tests/shared_cassettes/vcr_string_templates/batchInfo_xml.tpl +13 -0
- cumulusci/tests/shared_cassettes/vcr_string_templates/jobInfo_insert_xml.tpl +24 -0
- cumulusci/tests/shared_cassettes/vcr_string_templates/jobInfo_upsert_xml.tpl +25 -0
- cumulusci/tests/test_entry_points.py +20 -0
- cumulusci/tests/test_integration_infrastructure.py +131 -0
- cumulusci/tests/test_main.py +9 -0
- cumulusci/tests/test_schema.py +32 -0
- cumulusci/tests/test_utils.py +657 -0
- cumulusci/tests/test_vcr_serializer.py +134 -0
- cumulusci/tests/uncompressed_cassette.yaml +83 -0
- cumulusci/tests/util.py +344 -0
- cumulusci/utils/__init__.py +731 -0
- cumulusci/utils/classutils.py +9 -0
- cumulusci/utils/collections.py +32 -0
- cumulusci/utils/deprecation.py +11 -0
- cumulusci/utils/encryption.py +31 -0
- cumulusci/utils/fileutils.py +295 -0
- cumulusci/utils/git.py +142 -0
- cumulusci/utils/http/multi_request.py +214 -0
- cumulusci/utils/http/requests_utils.py +103 -0
- cumulusci/utils/http/tests/cassettes/ManualEditTestCompositeParallelSalesforce.test_http_headers.yaml +32 -0
- cumulusci/utils/http/tests/cassettes/TestCompositeParallelSalesforce.test_composite_parallel_salesforce.yaml +65 -0
- cumulusci/utils/http/tests/cassettes/TestCompositeParallelSalesforce.test_errors.yaml +24 -0
- cumulusci/utils/http/tests/cassettes/TestCompositeParallelSalesforce.test_reference_ids.yaml +49 -0
- cumulusci/utils/http/tests/test_multi_request.py +255 -0
- cumulusci/utils/iterators.py +21 -0
- cumulusci/utils/logging.py +128 -0
- cumulusci/utils/metaprogramming.py +10 -0
- cumulusci/utils/options.py +138 -0
- cumulusci/utils/parallel/queries_in_parallel/run_queries_in_parallel.py +29 -0
- cumulusci/utils/parallel/queries_in_parallel/tests/test_run_queries_in_parallel.py +50 -0
- cumulusci/utils/parallel/task_worker_queues/parallel_worker.py +238 -0
- cumulusci/utils/parallel/task_worker_queues/parallel_worker_queue.py +243 -0
- cumulusci/utils/parallel/task_worker_queues/tests/test_parallel_worker.py +353 -0
- cumulusci/utils/salesforce/count_sobjects.py +46 -0
- cumulusci/utils/salesforce/soql.py +17 -0
- cumulusci/utils/salesforce/tests/cassettes/ManualEdit_TestCountSObjects.test_count_sobjects__network_errors.yaml +23 -0
- cumulusci/utils/salesforce/tests/cassettes/TestCountSObjects.test_count_sobjects__errors.yaml +33 -0
- cumulusci/utils/salesforce/tests/cassettes/TestCountSObjects.test_count_sobjects_simple.yaml +29 -0
- cumulusci/utils/salesforce/tests/test_count_sobjects.py +29 -0
- cumulusci/utils/salesforce/tests/test_soql.py +30 -0
- cumulusci/utils/tests/cassettes/ManualEditTestDescribeOrg.test_minimal_schema.yaml +36 -0
- cumulusci/utils/tests/cassettes/ManualEdit_test_describe_to_sql.yaml +191 -0
- cumulusci/utils/tests/test_fileutils.py +284 -0
- cumulusci/utils/tests/test_git.py +85 -0
- cumulusci/utils/tests/test_logging.py +70 -0
- cumulusci/utils/tests/test_option_parsing.py +188 -0
- cumulusci/utils/tests/test_org_schema.py +691 -0
- cumulusci/utils/tests/test_org_schema_models.py +79 -0
- cumulusci/utils/tests/test_waiting.py +25 -0
- cumulusci/utils/version_strings.py +391 -0
- cumulusci/utils/waiting.py +42 -0
- cumulusci/utils/xml/__init__.py +91 -0
- cumulusci/utils/xml/metadata_tree.py +299 -0
- cumulusci/utils/xml/robot_xml.py +114 -0
- cumulusci/utils/xml/salesforce_encoding.py +100 -0
- cumulusci/utils/xml/test/test_metadata_tree.py +251 -0
- cumulusci/utils/xml/test/test_salesforce_encoding.py +173 -0
- cumulusci/utils/yaml/cumulusci_yml.py +401 -0
- cumulusci/utils/yaml/model_parser.py +156 -0
- cumulusci/utils/yaml/safer_loader.py +74 -0
- cumulusci/utils/yaml/tests/bad_cci.yml +5 -0
- cumulusci/utils/yaml/tests/cassettes/TestCumulusciYml.test_validate_url__with_errors.yaml +20 -0
- cumulusci/utils/yaml/tests/test_cumulusci_yml.py +286 -0
- cumulusci/utils/yaml/tests/test_model_parser.py +175 -0
- cumulusci/utils/yaml/tests/test_safer_loader.py +88 -0
- cumulusci/utils/ziputils.py +61 -0
- cumulusci/vcs/base.py +143 -0
- cumulusci/vcs/bootstrap.py +272 -0
- cumulusci/vcs/github/__init__.py +24 -0
- cumulusci/vcs/github/adapter.py +689 -0
- cumulusci/vcs/github/release_notes/generator.py +219 -0
- cumulusci/vcs/github/release_notes/parser.py +151 -0
- cumulusci/vcs/github/release_notes/provider.py +143 -0
- cumulusci/vcs/github/service.py +569 -0
- cumulusci/vcs/github/tests/test_adapter.py +138 -0
- cumulusci/vcs/github/tests/test_service.py +408 -0
- cumulusci/vcs/models.py +586 -0
- cumulusci/vcs/tests/conftest.py +41 -0
- cumulusci/vcs/tests/dummy_service.py +241 -0
- cumulusci/vcs/tests/test_vcs_base.py +687 -0
- cumulusci/vcs/tests/test_vcs_bootstrap.py +727 -0
- cumulusci/vcs/utils/__init__.py +31 -0
- cumulusci/vcs/vcs_source.py +287 -0
- cumulusci_plus-5.0.0.dist-info/METADATA +145 -0
- cumulusci_plus-5.0.0.dist-info/RECORD +744 -0
- cumulusci_plus-5.0.0.dist-info/WHEEL +4 -0
- cumulusci_plus-5.0.0.dist-info/entry_points.txt +3 -0
- cumulusci_plus-5.0.0.dist-info/licenses/AUTHORS.rst +41 -0
- cumulusci_plus-5.0.0.dist-info/licenses/LICENSE +30 -0
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
from typing import Dict
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
import yaml
|
|
6
|
+
|
|
7
|
+
from cumulusci.core.exceptions import TaskOptionsError
|
|
8
|
+
from cumulusci.core.utils import process_bool_arg, process_list_arg
|
|
9
|
+
from cumulusci.salesforce_api.org_schema import Field, get_org_schema
|
|
10
|
+
from cumulusci.tasks.salesforce import BaseSalesforceApiTask
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class GenerateMapping(BaseSalesforceApiTask):
|
|
14
|
+
task_docs = """
|
|
15
|
+
Generate a mapping file for use with the `extract_dataset` and `load_dataset` tasks.
|
|
16
|
+
This task will examine the schema in the specified org and attempt to infer a
|
|
17
|
+
mapping suitable for extracting data in packaged and custom objects as well as
|
|
18
|
+
customized standard objects.
|
|
19
|
+
|
|
20
|
+
Mappings must be serializable, and hence must resolve reference cycles - situations
|
|
21
|
+
where Object A refers to B, and B also refers to A. Mapping generation will stop
|
|
22
|
+
and request user input to resolve such cycles by identifying the correct load order.
|
|
23
|
+
If you would rather the mapping generator break such a cycle randomly, set the
|
|
24
|
+
`break_cycles` option to `auto`.
|
|
25
|
+
|
|
26
|
+
Alternately, specify the `ignore` option with the name of one of the
|
|
27
|
+
lookup fields to suppress it and break the cycle. `ignore` can be specified as a list in
|
|
28
|
+
`cumulusci.yml` or as a comma-separated string at the command line.
|
|
29
|
+
|
|
30
|
+
In most cases, the mapping generated will need minor tweaking by the user. Note
|
|
31
|
+
that the mapping omits features that are not currently well supported by the
|
|
32
|
+
`extract_dataset` and `load_dataset` tasks, such as references to
|
|
33
|
+
the `User` object.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
task_options = {
|
|
37
|
+
"path": {"description": "Location to write the mapping file", "required": True},
|
|
38
|
+
"namespace_prefix": {"description": "The namespace prefix to use"},
|
|
39
|
+
"ignore": {
|
|
40
|
+
"description": "Object API names, or fields in Object.Field format, to ignore"
|
|
41
|
+
},
|
|
42
|
+
"break_cycles": {
|
|
43
|
+
"description": "If the generator is unsure of the order to load, what to do? "
|
|
44
|
+
"Set to `ask` (the default) to allow the user to choose or `auto` to pick randomly."
|
|
45
|
+
},
|
|
46
|
+
"include": {
|
|
47
|
+
"description": "Object names to include even if they might not otherwise be included."
|
|
48
|
+
},
|
|
49
|
+
"strip_namespace": {
|
|
50
|
+
"description": "If True, CumulusCI removes the project's namespace where found in fields "
|
|
51
|
+
" and objects to support automatic namespace injection. On by default."
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
core_fields = ["Name", "FirstName", "LastName"]
|
|
56
|
+
|
|
57
|
+
def _init_options(self, kwargs):
|
|
58
|
+
super(GenerateMapping, self)._init_options(kwargs)
|
|
59
|
+
if "namespace_prefix" not in self.options:
|
|
60
|
+
self.options["namespace_prefix"] = ""
|
|
61
|
+
|
|
62
|
+
if self.options["namespace_prefix"] and not self.options[
|
|
63
|
+
"namespace_prefix"
|
|
64
|
+
].endswith("__"):
|
|
65
|
+
self.options["namespace_prefix"] += "__"
|
|
66
|
+
|
|
67
|
+
self.options["ignore"] = process_list_arg(self.options.get("ignore", []))
|
|
68
|
+
break_cycles = self.options.setdefault("break_cycles", "ask")
|
|
69
|
+
if break_cycles not in ["ask", "auto"]:
|
|
70
|
+
raise TaskOptionsError(
|
|
71
|
+
f"`break_cycles` should be `ask` or `auto`, not {break_cycles}"
|
|
72
|
+
)
|
|
73
|
+
self.options["include"] = process_list_arg(self.options.get("include", []))
|
|
74
|
+
strip_namespace = self.options.get("strip_namespace")
|
|
75
|
+
|
|
76
|
+
self.options["strip_namespace"] = process_bool_arg(
|
|
77
|
+
True if strip_namespace is None else strip_namespace
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
def _run_task(self):
|
|
81
|
+
self.logger.info("Collecting sObject information")
|
|
82
|
+
with get_org_schema(self.sf, self.org_config) as org_schema:
|
|
83
|
+
self._collect_objects(org_schema)
|
|
84
|
+
self._simplify_schema(org_schema)
|
|
85
|
+
filename = self.options["path"]
|
|
86
|
+
self.logger.info(f"Creating mapping schema {filename}")
|
|
87
|
+
self._build_mapping()
|
|
88
|
+
with open(filename, "w") as f:
|
|
89
|
+
yaml.dump(self.mapping, f, sort_keys=False)
|
|
90
|
+
|
|
91
|
+
def _collect_objects(self, org_schema):
|
|
92
|
+
"""Walk the global describe and identify the sObjects we need to include in a minimal operation."""
|
|
93
|
+
self.mapping_objects = self.options["include"]
|
|
94
|
+
|
|
95
|
+
sobject_names = set(org_schema.keys())
|
|
96
|
+
|
|
97
|
+
unknown_objects = set(self.mapping_objects) - sobject_names
|
|
98
|
+
|
|
99
|
+
if unknown_objects:
|
|
100
|
+
raise TaskOptionsError(f"{unknown_objects} cannot be found in the org.")
|
|
101
|
+
|
|
102
|
+
# First, we'll get a list of all objects that are either
|
|
103
|
+
# (a) custom, no namespace
|
|
104
|
+
# (b) custom, with our namespace
|
|
105
|
+
# (c) not ours (standard or other package), but have fields with our namespace or no namespace
|
|
106
|
+
for objname, obj in org_schema.items():
|
|
107
|
+
if self._is_our_custom_api_name(objname) or self._has_our_custom_fields(
|
|
108
|
+
obj
|
|
109
|
+
):
|
|
110
|
+
if (
|
|
111
|
+
self._is_object_mappable(obj)
|
|
112
|
+
and objname not in self.mapping_objects
|
|
113
|
+
):
|
|
114
|
+
self.mapping_objects.append(objname)
|
|
115
|
+
|
|
116
|
+
# Add any objects that are required by our own,
|
|
117
|
+
# meaning any object we are looking up to with a custom field,
|
|
118
|
+
# or any master-detail parent of any included object.
|
|
119
|
+
index = 0
|
|
120
|
+
while index < len(self.mapping_objects):
|
|
121
|
+
obj = self.mapping_objects[index]
|
|
122
|
+
for field in org_schema[obj].fields.values():
|
|
123
|
+
if field["type"] == "reference":
|
|
124
|
+
if field["relationshipOrder"] == 1 or self._is_any_custom_api_name(
|
|
125
|
+
field["name"]
|
|
126
|
+
):
|
|
127
|
+
self.mapping_objects.extend(
|
|
128
|
+
[
|
|
129
|
+
obj
|
|
130
|
+
for obj in field["referenceTo"]
|
|
131
|
+
if obj not in self.mapping_objects
|
|
132
|
+
and self._is_object_mappable(org_schema[obj])
|
|
133
|
+
]
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
index += 1
|
|
137
|
+
|
|
138
|
+
def _simplify_schema(self, org_schema):
|
|
139
|
+
"""Simplify and filter schema, including field details and interobject
|
|
140
|
+
references, into self.simple_schema and self.refs"""
|
|
141
|
+
|
|
142
|
+
# Now, find all the fields we need to include.
|
|
143
|
+
# For custom objects, we include all custom fields. This includes custom objects
|
|
144
|
+
# that our package doesn't own.
|
|
145
|
+
# For standard objects, we include all custom fields, all required standard fields,
|
|
146
|
+
# and master-detail relationships. Required means createable and not nillable.
|
|
147
|
+
# In all cases, ensure that RecordTypeId is included if and only if there are Record Types
|
|
148
|
+
self.simple_schema = {}
|
|
149
|
+
self.refs = defaultdict(lambda: defaultdict(dict))
|
|
150
|
+
for obj in self.mapping_objects:
|
|
151
|
+
self.simple_schema[obj] = {}
|
|
152
|
+
|
|
153
|
+
for field in org_schema[obj]["fields"].values():
|
|
154
|
+
if any(
|
|
155
|
+
[
|
|
156
|
+
self._is_any_custom_api_name(field["name"]),
|
|
157
|
+
self._is_core_field(field["name"]),
|
|
158
|
+
self._is_required_field(field),
|
|
159
|
+
self._is_lookup_to_included_object(field),
|
|
160
|
+
]
|
|
161
|
+
):
|
|
162
|
+
if self._is_field_mappable(obj, field):
|
|
163
|
+
self.simple_schema[obj][field["name"]] = field
|
|
164
|
+
|
|
165
|
+
if field["type"] == "reference":
|
|
166
|
+
for target in field["referenceTo"]:
|
|
167
|
+
# We've already vetted that this field is referencing
|
|
168
|
+
# included objects, via `_is_field_mappable()`
|
|
169
|
+
if target != obj:
|
|
170
|
+
self.refs[obj][target][field["name"]] = field
|
|
171
|
+
|
|
172
|
+
if (
|
|
173
|
+
field["name"] == "RecordTypeId"
|
|
174
|
+
and org_schema[obj].recordTypeInfos
|
|
175
|
+
and len(org_schema[obj].recordTypeInfos) > 1
|
|
176
|
+
):
|
|
177
|
+
# "Master" is included even if no RTs.c
|
|
178
|
+
self.simple_schema[obj][field["name"]] = field
|
|
179
|
+
|
|
180
|
+
def _build_mapping(self):
|
|
181
|
+
"""Output self.simple_schema in mapping file format by constructing a dict and serializing to YAML"""
|
|
182
|
+
objs = list(self.simple_schema.keys())
|
|
183
|
+
|
|
184
|
+
stack = self._split_dependencies(objs, self.refs)
|
|
185
|
+
ns = self.project_config.project__package__namespace
|
|
186
|
+
|
|
187
|
+
def strip_namespace(element):
|
|
188
|
+
if self.options["strip_namespace"] and ns and element.startswith(f"{ns}__"):
|
|
189
|
+
return element[len(ns) + 2 :]
|
|
190
|
+
else:
|
|
191
|
+
return element
|
|
192
|
+
|
|
193
|
+
self.mapping = {}
|
|
194
|
+
for orig_obj in stack:
|
|
195
|
+
# Check if it's safe for us to strip the namespace from this object
|
|
196
|
+
stripped_obj = strip_namespace(orig_obj)
|
|
197
|
+
obj = stripped_obj if stripped_obj not in stack else orig_obj
|
|
198
|
+
key = f"Insert {obj}"
|
|
199
|
+
self.mapping[key] = {}
|
|
200
|
+
self.mapping[key]["sf_object"] = obj
|
|
201
|
+
fields = []
|
|
202
|
+
lookups = []
|
|
203
|
+
for field in self.simple_schema[orig_obj].values():
|
|
204
|
+
if field["type"] == "reference" and field["name"] != "RecordTypeId":
|
|
205
|
+
# For lookups, namespace stripping takes place below.
|
|
206
|
+
lookups.append(field["name"])
|
|
207
|
+
else:
|
|
208
|
+
fields.append(field["name"])
|
|
209
|
+
if fields:
|
|
210
|
+
fields_stripped = [
|
|
211
|
+
strip_namespace(f) if strip_namespace(f) not in fields else f
|
|
212
|
+
for f in fields
|
|
213
|
+
]
|
|
214
|
+
fields_stripped.sort()
|
|
215
|
+
self.mapping[key]["fields"] = fields_stripped
|
|
216
|
+
if lookups:
|
|
217
|
+
lookups.sort()
|
|
218
|
+
self.mapping[key]["lookups"] = {}
|
|
219
|
+
for orig_field in lookups:
|
|
220
|
+
# First, determine what manner of lookup we have here.
|
|
221
|
+
stripped_field = (
|
|
222
|
+
strip_namespace(orig_field)
|
|
223
|
+
if strip_namespace(orig_field) not in lookups
|
|
224
|
+
else orig_field
|
|
225
|
+
)
|
|
226
|
+
referenceTo = self.simple_schema[orig_obj][orig_field][
|
|
227
|
+
"referenceTo"
|
|
228
|
+
]
|
|
229
|
+
|
|
230
|
+
# Can we safely namespace-strip this reference?
|
|
231
|
+
stripped_references = [
|
|
232
|
+
strip_namespace(orig_reference)
|
|
233
|
+
if strip_namespace(orig_reference) not in stack
|
|
234
|
+
else orig_reference
|
|
235
|
+
for orig_reference in referenceTo
|
|
236
|
+
]
|
|
237
|
+
|
|
238
|
+
# The maximum reference index to set the after to the last
|
|
239
|
+
# sobject mentioned in the reference (polymorphic support)
|
|
240
|
+
max_reference_index = max(
|
|
241
|
+
stack.index(orig_reference) for orig_reference in referenceTo
|
|
242
|
+
)
|
|
243
|
+
if max_reference_index >= stack.index(orig_obj): # Dependent lookup
|
|
244
|
+
self.mapping[key]["lookups"][stripped_field] = {
|
|
245
|
+
"table": stripped_references,
|
|
246
|
+
"after": f"Insert {stripped_references[referenceTo.index(stack[max_reference_index])]}",
|
|
247
|
+
}
|
|
248
|
+
else: # Regular lookup
|
|
249
|
+
self.mapping[key]["lookups"][stripped_field] = {
|
|
250
|
+
"table": stripped_references
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
def _split_dependencies(self, objs, dependencies):
|
|
254
|
+
"""Attempt to flatten the object network into a sequence of load operations."""
|
|
255
|
+
stack = []
|
|
256
|
+
objs_remaining = sorted(objs)
|
|
257
|
+
|
|
258
|
+
# The structure of `dependencies` is:
|
|
259
|
+
# key = object, value = set of objects it references.
|
|
260
|
+
|
|
261
|
+
# Iterate through our list of objects
|
|
262
|
+
# For each object, if it is not dependent on any other objects, place it at the end of the stack.
|
|
263
|
+
# Once an object is placed in the stack, remove dependencies to it (they're satisfied)
|
|
264
|
+
while objs_remaining:
|
|
265
|
+
objs_without_deps = [
|
|
266
|
+
obj
|
|
267
|
+
for obj in objs_remaining
|
|
268
|
+
if obj not in dependencies or not dependencies[obj]
|
|
269
|
+
]
|
|
270
|
+
assert all(objs_without_deps)
|
|
271
|
+
|
|
272
|
+
if not objs_without_deps:
|
|
273
|
+
choice = self.choose_next_object(objs_remaining, dependencies)
|
|
274
|
+
assert choice
|
|
275
|
+
objs_without_deps = [choice]
|
|
276
|
+
|
|
277
|
+
for obj in objs_without_deps:
|
|
278
|
+
stack.append(obj)
|
|
279
|
+
|
|
280
|
+
# Remove all dependencies on this object (they're satisfied)
|
|
281
|
+
for other_obj in dependencies:
|
|
282
|
+
if obj in dependencies.get(other_obj):
|
|
283
|
+
del dependencies[other_obj][obj]
|
|
284
|
+
|
|
285
|
+
# Remove this object from our remaining set.
|
|
286
|
+
objs_remaining.remove(obj)
|
|
287
|
+
|
|
288
|
+
return stack
|
|
289
|
+
|
|
290
|
+
def find_free_object(self, objs_remaining: list, dependencies: dict):
|
|
291
|
+
# if you change this code, remember that
|
|
292
|
+
# peeking into a generator consumes it
|
|
293
|
+
free_objs = (
|
|
294
|
+
sobj
|
|
295
|
+
for sobj in objs_remaining
|
|
296
|
+
if only_has_soft_dependencies(sobj, dependencies[sobj])
|
|
297
|
+
)
|
|
298
|
+
first_free_obj = next(free_objs, None)
|
|
299
|
+
|
|
300
|
+
return first_free_obj
|
|
301
|
+
|
|
302
|
+
def choose_next_object(self, objs_remaining: list, dependencies: dict):
|
|
303
|
+
free_obj = self.find_free_object(objs_remaining, dependencies)
|
|
304
|
+
if free_obj:
|
|
305
|
+
return free_obj
|
|
306
|
+
|
|
307
|
+
if self.options["break_cycles"] == "auto":
|
|
308
|
+
return tuple(objs_remaining)[0]
|
|
309
|
+
else:
|
|
310
|
+
return self.ask_user(objs_remaining, dependencies)
|
|
311
|
+
|
|
312
|
+
def ask_user(self, objs_remaining, dependencies):
|
|
313
|
+
self.logger.info(
|
|
314
|
+
"CumulusCI needs help to complete the mapping; the schema contains reference cycles and unresolved dependencies."
|
|
315
|
+
)
|
|
316
|
+
self.logger.info("Remaining objects:")
|
|
317
|
+
for obj in objs_remaining:
|
|
318
|
+
self.logger.info(obj)
|
|
319
|
+
for other_obj in dependencies[obj]:
|
|
320
|
+
self.logger.info(
|
|
321
|
+
f" references {other_obj} via: {', '.join(dependencies[obj][other_obj])}"
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
return click.prompt(
|
|
325
|
+
"Which object should we load first?",
|
|
326
|
+
type=click.Choice(tuple(objs_remaining)),
|
|
327
|
+
show_choices=True,
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
def _is_any_custom_api_name(self, api_name: str):
|
|
331
|
+
"""True if the entity name is custom (including any package)."""
|
|
332
|
+
return api_name.endswith("__c")
|
|
333
|
+
|
|
334
|
+
def _is_our_custom_api_name(self, api_name: str):
|
|
335
|
+
"""True if the entity name is custom and has our namespace prefix (if we have one)
|
|
336
|
+
or if the entity does not have a namespace"""
|
|
337
|
+
return self._is_any_custom_api_name(api_name) and (
|
|
338
|
+
(
|
|
339
|
+
self.options["namespace_prefix"]
|
|
340
|
+
and api_name.startswith(self.options["namespace_prefix"])
|
|
341
|
+
)
|
|
342
|
+
or api_name.count("__") == 1
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
def _is_core_field(self, api_name):
|
|
346
|
+
"""True if this field is one that we should always include regardless
|
|
347
|
+
of other settings or field configuration, such as Contact.FirstName.
|
|
348
|
+
DB-level required fields don't need to be so handled."""
|
|
349
|
+
|
|
350
|
+
return api_name in self.core_fields
|
|
351
|
+
|
|
352
|
+
def _is_object_mappable(self, obj):
|
|
353
|
+
"""True if this object is one we can map, meaning it's an sObject and not
|
|
354
|
+
some other kind of entity, it's not ignored, it's Bulk API compatible,
|
|
355
|
+
and it's not in a hard-coded list of entities we can't currently handle."""
|
|
356
|
+
|
|
357
|
+
return not any(
|
|
358
|
+
[
|
|
359
|
+
obj["name"] in self.options["ignore"], # User-specified exclusions
|
|
360
|
+
obj["name"].endswith(
|
|
361
|
+
"ChangeEvent"
|
|
362
|
+
), # Change Data Capture entities (which get custom fields)
|
|
363
|
+
obj["name"].endswith("__mdt"), # Custom Metadata Types (MDAPI only)
|
|
364
|
+
obj["name"].endswith("__e"), # Platform Events
|
|
365
|
+
obj["customSetting"], # Not Bulk API compatible
|
|
366
|
+
obj["name"] # Objects we can't or shouldn't load/save
|
|
367
|
+
in [
|
|
368
|
+
"User",
|
|
369
|
+
"Group",
|
|
370
|
+
"LookedUpFromActivity",
|
|
371
|
+
"OpenActivity",
|
|
372
|
+
"Task",
|
|
373
|
+
"Event",
|
|
374
|
+
"ActivityHistory",
|
|
375
|
+
],
|
|
376
|
+
]
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
def _is_field_mappable(self, obj, field):
|
|
380
|
+
"""True if this field is one we can map, meaning it's not ignored,
|
|
381
|
+
it's createable by the Bulk API, it's not a deprecated field,
|
|
382
|
+
and it's not a type of reference we can't handle without special
|
|
383
|
+
configuration (self-lookup or reference to objects not included
|
|
384
|
+
in this operation)."""
|
|
385
|
+
return not any(
|
|
386
|
+
[
|
|
387
|
+
field["name"] == "Id", # Omit Id fields for auto-pks
|
|
388
|
+
f"{obj}.{field['name']}" in self.options["ignore"], # User-ignored list
|
|
389
|
+
"(Deprecated)" in field["label"], # Deprecated managed fields
|
|
390
|
+
field["type"] == "base64", # No Bulk API support for base64 blob fields
|
|
391
|
+
not field["createable"], # Non-writeable fields
|
|
392
|
+
field["type"] == "reference" # Outside lookups
|
|
393
|
+
and not self._are_lookup_targets_in_operation(field),
|
|
394
|
+
]
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
def _is_required_field(self, field):
|
|
398
|
+
"""True if the field is either database-level required or a master-detail
|
|
399
|
+
relationship field."""
|
|
400
|
+
return (field["createable"] and not field["nillable"]) or (
|
|
401
|
+
field["type"] == "reference" and field["relationshipOrder"] == 1
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
def _has_our_custom_fields(self, obj):
|
|
405
|
+
"""True if the object is owned by us or contains any field owned by us."""
|
|
406
|
+
return any(
|
|
407
|
+
[self._is_our_custom_api_name(fieldname) for fieldname in obj.fields]
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
def _are_lookup_targets_in_operation(self, field):
|
|
411
|
+
"""True if this lookup field aims at objects we are already including (all targets
|
|
412
|
+
must match, although we don't provide actual support for polymorphism)."""
|
|
413
|
+
return all([f in self.mapping_objects for f in field["referenceTo"]])
|
|
414
|
+
|
|
415
|
+
def _is_lookup_to_included_object(self, field):
|
|
416
|
+
"""True if this field is a lookup and also references only objects we are
|
|
417
|
+
already including."""
|
|
418
|
+
return field["type"] == "reference" and self._are_lookup_targets_in_operation(
|
|
419
|
+
field
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def only_has_soft_dependencies(
|
|
424
|
+
sobj: str, obj_dependencies: Dict[str, Dict[str, Field]]
|
|
425
|
+
):
|
|
426
|
+
for target_obj, field_deps in obj_dependencies.items():
|
|
427
|
+
for field_name, field_data in field_deps.items():
|
|
428
|
+
# all nillable references are considered soft dependencies.
|
|
429
|
+
#
|
|
430
|
+
# A single hard dependency renders an object "not yet free"
|
|
431
|
+
if not field_data.nillable:
|
|
432
|
+
return False
|
|
433
|
+
|
|
434
|
+
return True
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import typing as T
|
|
2
|
+
from collections import defaultdict
|
|
3
|
+
|
|
4
|
+
from cumulusci.tasks.bulkdata.extract_dataset_utils.calculate_dependencies import (
|
|
5
|
+
SObjDependency,
|
|
6
|
+
)
|
|
7
|
+
from cumulusci.utils.collections import OrderedSet, OrderedSetType
|
|
8
|
+
|
|
9
|
+
StrDependencyMapping = T.Mapping[str, OrderedSetType[SObjDependency]]
|
|
10
|
+
SObjectRuleDeclaration = (
|
|
11
|
+
"snowfakery.cci_mapping_files.declaration_parser.SObjectRuleDeclaration"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DependencyMap:
|
|
16
|
+
"""Index which tables depend on which other ones (through lookups)
|
|
17
|
+
|
|
18
|
+
To make lookups easy later.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
# a dictionary allowing easy lookup of inferred/soft dependencies by parent table
|
|
22
|
+
soft_dependencies: StrDependencyMapping
|
|
23
|
+
|
|
24
|
+
# a dictionary allowing lookups by (tablename, fieldname) pairs
|
|
25
|
+
reference_fields: StrDependencyMapping
|
|
26
|
+
|
|
27
|
+
_sorted_tables = None # cache for sorted table computation
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
table_names: T.Iterable[str],
|
|
32
|
+
dependencies: T.Iterable[SObjDependency],
|
|
33
|
+
):
|
|
34
|
+
self.dependencies = defaultdict(OrderedSet)
|
|
35
|
+
self.reference_fields = {}
|
|
36
|
+
self._map_references(dependencies)
|
|
37
|
+
self.table_names = tuple(table_names)
|
|
38
|
+
|
|
39
|
+
def _map_references(
|
|
40
|
+
self,
|
|
41
|
+
intertable_dependencies: T.Iterable[SObjDependency],
|
|
42
|
+
):
|
|
43
|
+
for dep in intertable_dependencies:
|
|
44
|
+
table_deps = self.dependencies[dep.table_name_from]
|
|
45
|
+
table_deps.add(dep)
|
|
46
|
+
self.reference_fields[
|
|
47
|
+
(dep.table_name_from, dep.field_name)
|
|
48
|
+
] = dep.table_names_to
|
|
49
|
+
|
|
50
|
+
def target_table_for(
|
|
51
|
+
self, tablename: str, fieldname: str
|
|
52
|
+
) -> T.Optional[T.Union[str, T.Tuple[str, ...]]]:
|
|
53
|
+
return self.reference_fields.get((tablename, fieldname))
|
|
54
|
+
|
|
55
|
+
def get_dependency_order(self):
|
|
56
|
+
"""Sort the dependencies to output tables in the right order."""
|
|
57
|
+
if not self._sorted_tables:
|
|
58
|
+
# SnowfakeryPersonAccounts: Add this back in when Snowfakery is integrated with this code.
|
|
59
|
+
# _remove_person_contact_id(self.dependencies)
|
|
60
|
+
self._sorted_tables = _sort_by_dependencies(
|
|
61
|
+
tuple(self.table_names), self.dependencies
|
|
62
|
+
)
|
|
63
|
+
return self._sorted_tables
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _sort_by_dependencies(
|
|
67
|
+
table_names: T.Sequence[str],
|
|
68
|
+
dependencies: T.Mapping[str, OrderedSet],
|
|
69
|
+
priority: int = 0,
|
|
70
|
+
):
|
|
71
|
+
"Sort tables by dependency relationships."
|
|
72
|
+
sorted_tables = []
|
|
73
|
+
dependencies = dict(dependencies)
|
|
74
|
+
|
|
75
|
+
while table_names:
|
|
76
|
+
remaining = len(table_names)
|
|
77
|
+
leaf_tables = [
|
|
78
|
+
table
|
|
79
|
+
for table in table_names
|
|
80
|
+
if _table_is_free(table, dependencies, sorted_tables, priority)
|
|
81
|
+
]
|
|
82
|
+
sorted_tables.extend(leaf_tables)
|
|
83
|
+
table_names = [table for table in table_names if table not in sorted_tables]
|
|
84
|
+
|
|
85
|
+
# Nothing changed, so we need to attempt other techniques.
|
|
86
|
+
if len(table_names) == remaining:
|
|
87
|
+
# this is a bit tricky.
|
|
88
|
+
# run the algorithm with ONLY the declared/hard
|
|
89
|
+
# dependencies and see if it comes to resolution
|
|
90
|
+
|
|
91
|
+
higher_priority_sort = _sort_dependencies_higher_priority(
|
|
92
|
+
table_names, dependencies, priority
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
if higher_priority_sort:
|
|
96
|
+
sorted_tables.extend(higher_priority_sort)
|
|
97
|
+
else:
|
|
98
|
+
# I'm stuck. Try randomly
|
|
99
|
+
sorted_tables.append(sorted(table_names)[0])
|
|
100
|
+
|
|
101
|
+
return sorted_tables
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _sort_dependencies_higher_priority(
|
|
105
|
+
table_names: T.Iterable[str],
|
|
106
|
+
dependencies: T.Mapping[str, OrderedSet],
|
|
107
|
+
priority: int,
|
|
108
|
+
):
|
|
109
|
+
"""After a priority-ignoring dependency sort has failed, try sorting ONLY
|
|
110
|
+
on that subset of dependencies marked with a higher priority. This function
|
|
111
|
+
is mututally recursive with `_sort_by_dependencies` so it will ratchet
|
|
112
|
+
up the priority potentially multiple times."""
|
|
113
|
+
relevant_dependencies = (
|
|
114
|
+
dep.priority
|
|
115
|
+
for depset in dependencies.values()
|
|
116
|
+
for dep in depset
|
|
117
|
+
if dep.priority > priority
|
|
118
|
+
)
|
|
119
|
+
lowest_priority_dependency = min(relevant_dependencies, default=None)
|
|
120
|
+
|
|
121
|
+
if lowest_priority_dependency is not None:
|
|
122
|
+
return _sort_by_dependencies(
|
|
123
|
+
table_names, dependencies, lowest_priority_dependency
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _table_is_free(
|
|
128
|
+
table_name: str,
|
|
129
|
+
dependencies: StrDependencyMapping,
|
|
130
|
+
sorted_tables: T.Sequence[str],
|
|
131
|
+
priority: int,
|
|
132
|
+
):
|
|
133
|
+
"""Check if every child of this table is already sorted
|
|
134
|
+
|
|
135
|
+
Look at the unit test test_table_is_free_simple to see some
|
|
136
|
+
usage examples.
|
|
137
|
+
"""
|
|
138
|
+
tables_this_table_depends_upon = dependencies.get(table_name, OrderedSet()).copy()
|
|
139
|
+
for dependency in sorted(tables_this_table_depends_upon):
|
|
140
|
+
table_names = (
|
|
141
|
+
[dependency.table_names_to]
|
|
142
|
+
if isinstance(dependency.table_names_to, str)
|
|
143
|
+
else dependency.table_names_to
|
|
144
|
+
)
|
|
145
|
+
if (
|
|
146
|
+
all(table in sorted_tables or table == table_name for table in table_names)
|
|
147
|
+
or dependency.priority < priority
|
|
148
|
+
):
|
|
149
|
+
tables_this_table_depends_upon.remove(dependency)
|
|
150
|
+
|
|
151
|
+
return len(tables_this_table_depends_upon) == 0
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# SnowfakeryPersonAccounts: This code will be enabled when Snowfakery
|
|
155
|
+
# is integrated with this code.
|
|
156
|
+
# def _remove_person_contact_id(dependencies: T.Mapping[str, SObjDependency]):
|
|
157
|
+
# """Don't allow relationships between a personcontact and an account to mess
|
|
158
|
+
# up the load order.
|
|
159
|
+
|
|
160
|
+
# Code adapted from Snowfakery
|
|
161
|
+
# """
|
|
162
|
+
# if "Account" in dependencies:
|
|
163
|
+
# dep_to_person_contact = [
|
|
164
|
+
# dep
|
|
165
|
+
# for dep in dependencies["Account"]
|
|
166
|
+
# if dep.table_name_to.lower() == "personcontact"
|
|
167
|
+
# ]
|
|
168
|
+
# for dep in dep_to_person_contact:
|
|
169
|
+
# dependencies["Account"].remove(dep)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# pyright: strict
|
|
2
|
+
|
|
3
|
+
import typing as T
|
|
4
|
+
|
|
5
|
+
from cumulusci.salesforce_api.org_schema import Schema
|
|
6
|
+
from cumulusci.tasks.bulkdata.generate_mapping_utils.generate_mapping_from_declarations import (
|
|
7
|
+
SimplifiedExtractDeclarationWithLookups,
|
|
8
|
+
classify_and_filter_lookups,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from ..extract_dataset_utils.synthesize_extract_declarations import (
|
|
12
|
+
ExtractDeclaration,
|
|
13
|
+
flatten_declarations,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _mapping_decl_for_extract_decl(
|
|
18
|
+
decl: SimplifiedExtractDeclarationWithLookups,
|
|
19
|
+
):
|
|
20
|
+
"""Make a CCI extract mapping step from a SimplifiedExtractDeclarationWithLookups"""
|
|
21
|
+
lookups = {lookup: {"table": tables} for lookup, tables in decl.lookups.items()}
|
|
22
|
+
mapping_dict: dict[str, T.Any] = {
|
|
23
|
+
"sf_object": decl.sf_object,
|
|
24
|
+
}
|
|
25
|
+
if decl.where:
|
|
26
|
+
mapping_dict["soql_filter"] = decl.where
|
|
27
|
+
if decl.api:
|
|
28
|
+
mapping_dict["api"] = decl.api.value
|
|
29
|
+
mapping_dict["fields"] = decl.fields
|
|
30
|
+
if lookups:
|
|
31
|
+
mapping_dict["lookups"] = lookups
|
|
32
|
+
|
|
33
|
+
return (f"Extract {decl.sf_object}", mapping_dict)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def create_extract_mapping_file_from_declarations(
|
|
37
|
+
decls: T.List[ExtractDeclaration], schema: Schema, opt_in_only: T.Sequence[str]
|
|
38
|
+
):
|
|
39
|
+
"""Create a mapping file sufficient for driving an extract process
|
|
40
|
+
from an extract declarations file."""
|
|
41
|
+
assert decls is not None
|
|
42
|
+
simplified_decls = flatten_declarations(decls, schema, opt_in_only)
|
|
43
|
+
simplified_decls = classify_and_filter_lookups(simplified_decls, schema)
|
|
44
|
+
mappings = [_mapping_decl_for_extract_decl(decl) for decl in simplified_decls]
|
|
45
|
+
return dict(pair for pair in mappings if pair)
|