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,441 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
import re
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
|
|
5
|
+
from sqlalchemy import Column, MetaData, Table, Unicode, create_engine
|
|
6
|
+
from sqlalchemy.orm import create_session, mapper
|
|
7
|
+
|
|
8
|
+
from cumulusci.core.exceptions import (
|
|
9
|
+
BulkDataException,
|
|
10
|
+
ConfigError,
|
|
11
|
+
CumulusCIException,
|
|
12
|
+
TaskOptionsError,
|
|
13
|
+
)
|
|
14
|
+
from cumulusci.core.utils import process_bool_arg
|
|
15
|
+
from cumulusci.tasks.bulkdata.dates import adjust_relative_dates
|
|
16
|
+
from cumulusci.tasks.bulkdata.mapping_parser import (
|
|
17
|
+
parse_from_yaml,
|
|
18
|
+
validate_and_inject_mapping,
|
|
19
|
+
)
|
|
20
|
+
from cumulusci.tasks.bulkdata.step import (
|
|
21
|
+
DataOperationStatus,
|
|
22
|
+
DataOperationType,
|
|
23
|
+
get_query_operation,
|
|
24
|
+
)
|
|
25
|
+
from cumulusci.tasks.bulkdata.utils import (
|
|
26
|
+
SqlAlchemyMixin,
|
|
27
|
+
consume,
|
|
28
|
+
create_table,
|
|
29
|
+
sql_bulk_insert_from_records,
|
|
30
|
+
sql_bulk_insert_from_records_incremental,
|
|
31
|
+
)
|
|
32
|
+
from cumulusci.tasks.salesforce import BaseSalesforceApiTask
|
|
33
|
+
from cumulusci.utils import log_progress
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ExtractData(SqlAlchemyMixin, BaseSalesforceApiTask):
|
|
37
|
+
"""Perform Bulk Queries to extract data for a mapping and persist to a SQL file or database."""
|
|
38
|
+
|
|
39
|
+
task_options = {
|
|
40
|
+
"database_url": {
|
|
41
|
+
"description": "A DATABASE_URL where the query output should be written"
|
|
42
|
+
},
|
|
43
|
+
"mapping": {
|
|
44
|
+
"description": "The path to a yaml file containing mappings of the database fields to Salesforce object fields",
|
|
45
|
+
"required": True,
|
|
46
|
+
},
|
|
47
|
+
"sql_path": {
|
|
48
|
+
"description": "If set, an SQL script will be generated at the path provided "
|
|
49
|
+
+ "This is useful for keeping data in the repository and allowing diffs."
|
|
50
|
+
},
|
|
51
|
+
"inject_namespaces": {
|
|
52
|
+
"description": "If True, the package namespace prefix will be "
|
|
53
|
+
"automatically added to (or removed from) objects "
|
|
54
|
+
"and fields based on the name used in the org. Defaults to True."
|
|
55
|
+
},
|
|
56
|
+
"drop_missing_schema": {
|
|
57
|
+
"description": "Set to True to skip any missing objects or fields instead of stopping with an error."
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
def _init_options(self, kwargs):
|
|
62
|
+
super(ExtractData, self)._init_options(kwargs)
|
|
63
|
+
if self.options.get("database_url"):
|
|
64
|
+
# prefer database_url if it's set
|
|
65
|
+
self.options["sql_path"] = None
|
|
66
|
+
elif not self.options.get("sql_path"):
|
|
67
|
+
raise TaskOptionsError(
|
|
68
|
+
"You must set either the database_url or sql_path option."
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
inject_namespaces = self.options.get("inject_namespaces")
|
|
72
|
+
self.options["inject_namespaces"] = process_bool_arg(
|
|
73
|
+
True if inject_namespaces is None else inject_namespaces
|
|
74
|
+
)
|
|
75
|
+
self.options["drop_missing_schema"] = process_bool_arg(
|
|
76
|
+
self.options.get("drop_missing_schema") or False
|
|
77
|
+
)
|
|
78
|
+
self._id_generators = {}
|
|
79
|
+
|
|
80
|
+
def _run_task(self):
|
|
81
|
+
self._init_mapping()
|
|
82
|
+
with self._init_db():
|
|
83
|
+
for mapping in self.mapping.values():
|
|
84
|
+
soql = self._soql_for_mapping(mapping)
|
|
85
|
+
self._run_query(soql, mapping)
|
|
86
|
+
|
|
87
|
+
self._map_autopks()
|
|
88
|
+
|
|
89
|
+
if self.options.get("sql_path"):
|
|
90
|
+
self._sqlite_dump()
|
|
91
|
+
|
|
92
|
+
@contextmanager
|
|
93
|
+
def _init_db(self):
|
|
94
|
+
"""Initialize the database and automapper."""
|
|
95
|
+
self.models = {}
|
|
96
|
+
|
|
97
|
+
with self._database_url() as database_url:
|
|
98
|
+
|
|
99
|
+
# initialize the DB engine
|
|
100
|
+
parent_engine = create_engine(database_url)
|
|
101
|
+
with parent_engine.connect() as connection:
|
|
102
|
+
# initialize DB metadata
|
|
103
|
+
self.metadata = MetaData()
|
|
104
|
+
self.metadata.bind = connection
|
|
105
|
+
|
|
106
|
+
# Create the tables
|
|
107
|
+
self._create_tables()
|
|
108
|
+
|
|
109
|
+
# initialize session
|
|
110
|
+
self.session = create_session(bind=connection, autocommit=False)
|
|
111
|
+
|
|
112
|
+
yield self.session, self.metadata, connection
|
|
113
|
+
|
|
114
|
+
def _init_mapping(self):
|
|
115
|
+
"""Load a YAML mapping file."""
|
|
116
|
+
mapping_file_path = self.options["mapping"]
|
|
117
|
+
if not mapping_file_path:
|
|
118
|
+
raise TaskOptionsError("Mapping file path required")
|
|
119
|
+
self.logger.info(f"Mapping file: {self.options['mapping']}")
|
|
120
|
+
|
|
121
|
+
self.mapping = parse_from_yaml(mapping_file_path)
|
|
122
|
+
|
|
123
|
+
validate_and_inject_mapping(
|
|
124
|
+
mapping=self.mapping,
|
|
125
|
+
sf=self.sf,
|
|
126
|
+
namespace=self.project_config.project__package__namespace,
|
|
127
|
+
data_operation=DataOperationType.QUERY,
|
|
128
|
+
inject_namespaces=self.options["inject_namespaces"],
|
|
129
|
+
drop_missing=self.options["drop_missing_schema"],
|
|
130
|
+
org_has_person_accounts_enabled=self.org_config.is_person_accounts_enabled,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def _soql_for_mapping(self, mapping):
|
|
134
|
+
"""Return a SOQL query suitable for extracting data for this mapping."""
|
|
135
|
+
sf_object = mapping.sf_object
|
|
136
|
+
fields = mapping.get_extract_field_list()
|
|
137
|
+
soql = f"SELECT {', '.join(fields)} FROM {sf_object}"
|
|
138
|
+
|
|
139
|
+
if mapping.record_type:
|
|
140
|
+
soql += f" WHERE RecordType.DeveloperName = '{mapping.record_type}'"
|
|
141
|
+
|
|
142
|
+
if mapping.soql_filter is not None:
|
|
143
|
+
soql = self.append_filter_clause(
|
|
144
|
+
soql=soql, filter_clause=mapping.soql_filter
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
return soql
|
|
148
|
+
|
|
149
|
+
def _run_query(self, soql, mapping):
|
|
150
|
+
"""Execute a Bulk or REST API query job and store the results."""
|
|
151
|
+
|
|
152
|
+
step = get_query_operation(
|
|
153
|
+
sobject=mapping.sf_object,
|
|
154
|
+
api=mapping.api,
|
|
155
|
+
fields=list(mapping.get_extract_field_list()),
|
|
156
|
+
api_options={},
|
|
157
|
+
context=self,
|
|
158
|
+
query=soql,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
self.logger.info(f"Extracting data for sObject {mapping['sf_object']}")
|
|
162
|
+
step.query()
|
|
163
|
+
|
|
164
|
+
if step.job_result.status is DataOperationStatus.SUCCESS:
|
|
165
|
+
if step.job_result.records_processed:
|
|
166
|
+
self.logger.info("Downloading and importing records")
|
|
167
|
+
self._import_results(mapping, step)
|
|
168
|
+
else:
|
|
169
|
+
self.logger.info(f"No records found for sObject {mapping['sf_object']}")
|
|
170
|
+
else:
|
|
171
|
+
raise BulkDataException(
|
|
172
|
+
f"Unable to execute query: {','.join(step.job_result.job_errors)}"
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
def _import_results(self, mapping, step):
|
|
176
|
+
"""Ingest results from the Bulk API query."""
|
|
177
|
+
conn = self.session.connection()
|
|
178
|
+
|
|
179
|
+
# Map SF field names to local db column names
|
|
180
|
+
field_map = mapping.get_complete_field_map(include_id=True)
|
|
181
|
+
columns = [field_map[f] for f in field_map] # Get values in insertion order.
|
|
182
|
+
|
|
183
|
+
record_type = mapping.record_type
|
|
184
|
+
if record_type:
|
|
185
|
+
columns.append("record_type")
|
|
186
|
+
|
|
187
|
+
# TODO: log_progress needs to know our batch size, when made configurable.
|
|
188
|
+
record_iterator = log_progress(step.get_results(), self.logger)
|
|
189
|
+
if record_type:
|
|
190
|
+
record_iterator = (record + [record_type] for record in record_iterator)
|
|
191
|
+
|
|
192
|
+
# Convert relative dates to stable dates.
|
|
193
|
+
if mapping.anchor_date:
|
|
194
|
+
date_context = mapping.get_relative_date_context(
|
|
195
|
+
list(field_map.keys()), self.sf
|
|
196
|
+
)
|
|
197
|
+
if date_context[0] or date_context[1]:
|
|
198
|
+
record_iterator = (
|
|
199
|
+
adjust_relative_dates(
|
|
200
|
+
mapping, date_context, record, DataOperationType.QUERY
|
|
201
|
+
)
|
|
202
|
+
for record in record_iterator
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
# Set Name field as blank for Person Account "Account" records.
|
|
206
|
+
if (
|
|
207
|
+
mapping.sf_object == "Account"
|
|
208
|
+
and "Name" in field_map
|
|
209
|
+
and self.org_config.is_person_accounts_enabled
|
|
210
|
+
):
|
|
211
|
+
# Bump indices by one since record's ID is the first column.
|
|
212
|
+
Name_index = columns.index(mapping.fields["Name"])
|
|
213
|
+
IsPersonAccount_index = columns.index(mapping.fields["IsPersonAccount"])
|
|
214
|
+
|
|
215
|
+
def strip_name_field(record):
|
|
216
|
+
nonlocal Name_index, IsPersonAccount_index
|
|
217
|
+
if record[IsPersonAccount_index].lower() == "true":
|
|
218
|
+
record[Name_index] = ""
|
|
219
|
+
return record
|
|
220
|
+
|
|
221
|
+
record_iterator = (strip_name_field(record) for record in record_iterator)
|
|
222
|
+
|
|
223
|
+
if mapping.get_oid_as_pk():
|
|
224
|
+
sql_bulk_insert_from_records(
|
|
225
|
+
connection=conn,
|
|
226
|
+
table=self.metadata.tables[mapping.table],
|
|
227
|
+
columns=columns,
|
|
228
|
+
record_iterable=record_iterator,
|
|
229
|
+
)
|
|
230
|
+
else:
|
|
231
|
+
# If using the autogenerated id field, split out the returned records
|
|
232
|
+
# into two separate streams and load into the main table and the sf_id_table
|
|
233
|
+
values, ids = itertools.tee(record_iterator)
|
|
234
|
+
|
|
235
|
+
# Generate naming format (<sobject>-<number>)
|
|
236
|
+
id_source_sobj, id_source_global = itertools.tee(
|
|
237
|
+
self._id_generator_for_object(mapping.sf_object)
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
f_ids = ((row[0], next(id_source_global)) for row in ids)
|
|
241
|
+
f_values = ([next(id_source_sobj)] + row[1:] for row in values)
|
|
242
|
+
|
|
243
|
+
values_chunks = sql_bulk_insert_from_records_incremental(
|
|
244
|
+
connection=conn,
|
|
245
|
+
table=self.metadata.tables[mapping.table],
|
|
246
|
+
columns=["id"] + columns[1:],
|
|
247
|
+
record_iterable=f_values,
|
|
248
|
+
)
|
|
249
|
+
ids_chunks = sql_bulk_insert_from_records_incremental(
|
|
250
|
+
connection=conn,
|
|
251
|
+
table=self.metadata.tables[mapping.get_sf_id_table()],
|
|
252
|
+
columns=["sf_id", "id"],
|
|
253
|
+
record_iterable=f_ids,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# do the inserts one chunk at a time based on all of the
|
|
257
|
+
# generators nested previously.
|
|
258
|
+
consume(zip(values_chunks, ids_chunks))
|
|
259
|
+
|
|
260
|
+
if "RecordTypeId" in mapping.fields:
|
|
261
|
+
self._extract_record_types(
|
|
262
|
+
mapping.sf_object,
|
|
263
|
+
mapping.get_source_record_type_table(),
|
|
264
|
+
conn,
|
|
265
|
+
self.org_config.is_person_accounts_enabled,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
self.session.commit()
|
|
269
|
+
|
|
270
|
+
def _id_generator_for_object(self, sobject: str):
|
|
271
|
+
"""Generates strings for local ids in format {sobject}-{counter}
|
|
272
|
+
(example: Account-2)"""
|
|
273
|
+
if sobject not in self._id_generators:
|
|
274
|
+
|
|
275
|
+
def _generate_ids():
|
|
276
|
+
counter = 1
|
|
277
|
+
while True:
|
|
278
|
+
yield f"{sobject}-{counter}"
|
|
279
|
+
counter += 1
|
|
280
|
+
|
|
281
|
+
self._id_generators[sobject] = _generate_ids()
|
|
282
|
+
|
|
283
|
+
return self._id_generators[sobject]
|
|
284
|
+
|
|
285
|
+
def _map_autopks(self):
|
|
286
|
+
# Convert Salesforce Ids to autopks
|
|
287
|
+
for m in self.mapping.values():
|
|
288
|
+
lookup_keys = list(m.lookups.keys())
|
|
289
|
+
if not m.get_oid_as_pk():
|
|
290
|
+
if lookup_keys:
|
|
291
|
+
self._convert_lookups_to_id(m, lookup_keys)
|
|
292
|
+
|
|
293
|
+
# Drop sf_id tables
|
|
294
|
+
for m in self.mapping.values():
|
|
295
|
+
if not m.get_oid_as_pk():
|
|
296
|
+
self.metadata.tables[m.get_sf_id_table()].drop()
|
|
297
|
+
|
|
298
|
+
def _get_mapping_for_table(self, table):
|
|
299
|
+
"""Return the first mapping for a table name"""
|
|
300
|
+
|
|
301
|
+
# Get all mappings for lookups
|
|
302
|
+
mappings = [
|
|
303
|
+
mapping
|
|
304
|
+
for mapping in self.mapping.values()
|
|
305
|
+
if (isinstance(table, str) and mapping["table"] == table)
|
|
306
|
+
or (isinstance(table, list) and mapping["table"] in table)
|
|
307
|
+
]
|
|
308
|
+
|
|
309
|
+
# For polymorphic lookups, raise exception if missing mappings
|
|
310
|
+
if isinstance(table, list) and len(mappings) != len(table):
|
|
311
|
+
missing_tables = set(table) - set(mapping["table"] for mapping in mappings)
|
|
312
|
+
raise CumulusCIException(
|
|
313
|
+
f"The following tables are missing in the mapping file: {missing_tables}"
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
return mappings
|
|
317
|
+
|
|
318
|
+
def _convert_lookups_to_id(self, mapping, lookup_keys):
|
|
319
|
+
"""Rewrite persisted Salesforce Ids to refer to auto-PKs."""
|
|
320
|
+
|
|
321
|
+
def throw(string): # pragma: no cover
|
|
322
|
+
raise BulkDataException(string)
|
|
323
|
+
|
|
324
|
+
for lookup_key in lookup_keys:
|
|
325
|
+
lookup_info = mapping.lookups.get(lookup_key) or throw(
|
|
326
|
+
f"Cannot find lookup info {lookup_key}"
|
|
327
|
+
)
|
|
328
|
+
model = self.models.get(mapping.table)
|
|
329
|
+
|
|
330
|
+
lookup_mappings = self._get_mapping_for_table(lookup_info.table) or throw(
|
|
331
|
+
f"Cannot find lookup mapping for {lookup_info.table}"
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
key_field = lookup_info.get_lookup_key_field()
|
|
335
|
+
|
|
336
|
+
key_attr = getattr(model, key_field, None) or throw(
|
|
337
|
+
f"key_field {key_field} not found in table {mapping.table}"
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
# Keep track of total mapping operations
|
|
341
|
+
total_mapping_operations = 0
|
|
342
|
+
|
|
343
|
+
for lookup_mapping in lookup_mappings:
|
|
344
|
+
lookup_model = self.models.get(lookup_mapping.get_sf_id_table())
|
|
345
|
+
try:
|
|
346
|
+
update_query = (
|
|
347
|
+
self.session.query(model)
|
|
348
|
+
.filter(key_attr.isnot(None), key_attr == lookup_model.sf_id)
|
|
349
|
+
.update({key_attr: lookup_model.id}, synchronize_session=False)
|
|
350
|
+
)
|
|
351
|
+
total_mapping_operations += update_query.rowcount
|
|
352
|
+
except NotImplementedError:
|
|
353
|
+
# Some databases such as sqlite don't support multitable update
|
|
354
|
+
mappings = []
|
|
355
|
+
for row, lookup_id in self.session.query(
|
|
356
|
+
model, lookup_model.id
|
|
357
|
+
).join(lookup_model, key_attr == lookup_model.sf_id):
|
|
358
|
+
mappings.append({"id": row.id, key_field: lookup_id})
|
|
359
|
+
total_mapping_operations += len(mappings)
|
|
360
|
+
self.session.bulk_update_mappings(model, mappings)
|
|
361
|
+
# Count the total number of rows excluding those with no entry for that field
|
|
362
|
+
total_rows = (
|
|
363
|
+
self.session.query(model)
|
|
364
|
+
.filter(
|
|
365
|
+
key_attr.isnot(None), # Ensure key_attr is not None
|
|
366
|
+
key_attr.isnot(""), # Ensure key_attr is not an empty string
|
|
367
|
+
)
|
|
368
|
+
.count()
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
if total_mapping_operations != total_rows:
|
|
372
|
+
raise ConfigError(
|
|
373
|
+
f"Total mapping operations ({total_mapping_operations}) do not match total non-empty rows ({total_rows}) for lookup_key: {lookup_key}. Mention all related tables for lookup: {lookup_key}"
|
|
374
|
+
)
|
|
375
|
+
self.session.commit()
|
|
376
|
+
|
|
377
|
+
def _create_tables(self):
|
|
378
|
+
"""Create a table for each mapping step."""
|
|
379
|
+
for mapping in self.mapping.values():
|
|
380
|
+
self._create_table(mapping)
|
|
381
|
+
self.metadata.create_all()
|
|
382
|
+
|
|
383
|
+
def _create_table(self, mapping):
|
|
384
|
+
"""Create a table for the given mapping."""
|
|
385
|
+
model_name = f"{mapping.table}Model"
|
|
386
|
+
mapper_kwargs = {}
|
|
387
|
+
self.models[mapping.table] = type(model_name, (object,), {})
|
|
388
|
+
|
|
389
|
+
t = create_table(mapping, self.metadata)
|
|
390
|
+
|
|
391
|
+
if "RecordTypeId" in mapping.fields:
|
|
392
|
+
# We're using Record Type Mapping support.
|
|
393
|
+
# If multiple mappings point to the same table, don't recreate the table
|
|
394
|
+
if mapping.get_source_record_type_table() not in self.models:
|
|
395
|
+
self._create_record_type_table(mapping.get_source_record_type_table())
|
|
396
|
+
|
|
397
|
+
if not mapping.get_oid_as_pk():
|
|
398
|
+
# If multiple mappings point to the same table, don't recreate the table
|
|
399
|
+
if mapping.get_sf_id_table() not in self.models:
|
|
400
|
+
sf_id_model_name = f"{mapping.get_sf_id_table()}Model"
|
|
401
|
+
self.models[mapping.get_sf_id_table()] = type(
|
|
402
|
+
sf_id_model_name, (object,), {}
|
|
403
|
+
)
|
|
404
|
+
sf_id_fields = [
|
|
405
|
+
Column("id", Unicode(255), primary_key=True),
|
|
406
|
+
Column("sf_id", Unicode(24)),
|
|
407
|
+
]
|
|
408
|
+
id_t = Table(mapping.get_sf_id_table(), self.metadata, *sf_id_fields)
|
|
409
|
+
mapper(self.models[mapping.get_sf_id_table()], id_t)
|
|
410
|
+
|
|
411
|
+
mapper(self.models[mapping.table], t, **mapper_kwargs)
|
|
412
|
+
|
|
413
|
+
def _sqlite_dump(self):
|
|
414
|
+
"""Write a SQLite script output file."""
|
|
415
|
+
path = self.options["sql_path"]
|
|
416
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
417
|
+
for line in self.session.connection().connection.iterdump():
|
|
418
|
+
f.write(line + "\n")
|
|
419
|
+
|
|
420
|
+
def append_filter_clause(self, soql, filter_clause):
|
|
421
|
+
"""Function that applies filter clause to soql if it is defined in mapping yml file"""
|
|
422
|
+
|
|
423
|
+
if not filter_clause:
|
|
424
|
+
return soql
|
|
425
|
+
|
|
426
|
+
# If WHERE keyword is specified in the maping file replace it with empty string.
|
|
427
|
+
# match WHERE keyword only at the start of the string and whitespace after it.
|
|
428
|
+
filter_clause = re.sub(
|
|
429
|
+
pattern=r"^WHERE\s+",
|
|
430
|
+
repl="",
|
|
431
|
+
string=filter_clause.strip(),
|
|
432
|
+
flags=re.IGNORECASE,
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
# If WHERE keyword is already in soql query(because of record type filter) add AND clause
|
|
436
|
+
if " WHERE " in soql:
|
|
437
|
+
soql = f"{soql} AND {filter_clause}"
|
|
438
|
+
else:
|
|
439
|
+
soql = f"{soql} WHERE {filter_clause}"
|
|
440
|
+
|
|
441
|
+
return soql
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import typing as T
|
|
2
|
+
|
|
3
|
+
from cumulusci.salesforce_api.filterable_objects import NOT_EXTRACTABLE
|
|
4
|
+
from cumulusci.salesforce_api.org_schema import Schema
|
|
5
|
+
|
|
6
|
+
from .synthesize_extract_declarations import (
|
|
7
|
+
SimplifiedExtractDeclaration,
|
|
8
|
+
synthesize_declaration_for_sobject,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SObjDependency(T.NamedTuple):
|
|
13
|
+
table_name_from: str
|
|
14
|
+
table_names_to: T.Union[str, T.Tuple[str, ...]]
|
|
15
|
+
field_name: str
|
|
16
|
+
priority: bool = False
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _calculate_dependencies_for_declarations(
|
|
20
|
+
decls: T.Sequence[SimplifiedExtractDeclaration], schema: Schema
|
|
21
|
+
) -> T.Dict[str, T.List[SObjDependency]]:
|
|
22
|
+
"""Ensure that required lookups are fulfilled for list of SimplifiedExtractDeclarations
|
|
23
|
+
|
|
24
|
+
Do this by adding their referent tables (in full) to the extract.
|
|
25
|
+
Module-internal helper function.
|
|
26
|
+
"""
|
|
27
|
+
dependencies = {}
|
|
28
|
+
for decl in decls:
|
|
29
|
+
assert isinstance(decl.sf_object, str)
|
|
30
|
+
new_dependencies = _collect_dependencies_for_sobject(
|
|
31
|
+
decl.sf_object, decl.fields, schema, only_required_fields=False
|
|
32
|
+
)
|
|
33
|
+
dependencies.update(new_dependencies)
|
|
34
|
+
return dependencies
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _collect_dependencies_for_sobject(
|
|
38
|
+
source_sfobject: str,
|
|
39
|
+
fields: T.List[str],
|
|
40
|
+
schema: Schema,
|
|
41
|
+
only_required_fields: bool,
|
|
42
|
+
) -> T.Dict[str, T.List[SObjDependency]]:
|
|
43
|
+
"""Ensure that required lookups are fulfilled for a single SObject
|
|
44
|
+
|
|
45
|
+
Do this by adding its referent tables (in full) to the extract.
|
|
46
|
+
Module-internal helper function.
|
|
47
|
+
"""
|
|
48
|
+
dependencies = {}
|
|
49
|
+
for field_name in fields:
|
|
50
|
+
field_info = schema[source_sfobject].fields[field_name]
|
|
51
|
+
if not field_info.createable: # pragma: no cover
|
|
52
|
+
continue
|
|
53
|
+
references = field_info.referenceTo
|
|
54
|
+
if references:
|
|
55
|
+
# Remove RecordType from references
|
|
56
|
+
if "RecordType" in references:
|
|
57
|
+
references.remove("RecordType")
|
|
58
|
+
if not references:
|
|
59
|
+
continue
|
|
60
|
+
|
|
61
|
+
targets = tuple(
|
|
62
|
+
target for target in references if target not in NOT_EXTRACTABLE
|
|
63
|
+
)
|
|
64
|
+
field_disallowed = not targets or not field_info.createable
|
|
65
|
+
field_allowed = not (only_required_fields or field_disallowed)
|
|
66
|
+
if field_info.requiredOnCreate or field_allowed:
|
|
67
|
+
dependencies.setdefault(source_sfobject, []).append(
|
|
68
|
+
SObjDependency(
|
|
69
|
+
source_sfobject,
|
|
70
|
+
targets,
|
|
71
|
+
field_name,
|
|
72
|
+
field_info.requiredOnCreate,
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
return dependencies
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def extend_declarations_to_include_referenced_tables(
|
|
80
|
+
decl_list: T.Sequence[SimplifiedExtractDeclaration], schema: Schema
|
|
81
|
+
) -> T.Sequence[SimplifiedExtractDeclaration]:
|
|
82
|
+
"""Extend the declarations to complete required or requested lookups recursively"""
|
|
83
|
+
decls = {decl.sf_object: decl for decl in decl_list}
|
|
84
|
+
dependencies = _calculate_dependencies_for_declarations(decl_list, schema)
|
|
85
|
+
to_process = list(decls.keys())
|
|
86
|
+
|
|
87
|
+
while to_process:
|
|
88
|
+
sf_object = to_process.pop()
|
|
89
|
+
assert isinstance(sf_object, str)
|
|
90
|
+
my_dependencies = dependencies.get(sf_object, ())
|
|
91
|
+
for dep in my_dependencies:
|
|
92
|
+
target_tables = dep.table_names_to
|
|
93
|
+
for target_table in target_tables:
|
|
94
|
+
sobj = schema.get(target_table)
|
|
95
|
+
target_extractable = (
|
|
96
|
+
target_table not in NOT_EXTRACTABLE and sobj and sobj.extractable
|
|
97
|
+
)
|
|
98
|
+
if target_table not in decls and target_extractable:
|
|
99
|
+
required_fields = [
|
|
100
|
+
field.name
|
|
101
|
+
for field in schema[target_table].fields.values()
|
|
102
|
+
if field.requiredOnCreate
|
|
103
|
+
]
|
|
104
|
+
decls[target_table] = synthesize_declaration_for_sobject(
|
|
105
|
+
target_table, required_fields, schema[target_table].fields
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
new_dependencies = _collect_dependencies_for_sobject(
|
|
109
|
+
target_table,
|
|
110
|
+
decls[target_table].fields,
|
|
111
|
+
schema,
|
|
112
|
+
only_required_fields=True,
|
|
113
|
+
)
|
|
114
|
+
dependencies.update(new_dependencies)
|
|
115
|
+
to_process.append(target_table)
|
|
116
|
+
|
|
117
|
+
return list(decls.values())
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import typing as T
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from pydantic import Field, validator
|
|
6
|
+
|
|
7
|
+
from cumulusci.core.enums import StrEnum
|
|
8
|
+
from cumulusci.tasks.bulkdata.utils import DataApi
|
|
9
|
+
from cumulusci.utils.yaml.model_parser import CCIDictModel, HashableBaseModel
|
|
10
|
+
|
|
11
|
+
object_decl = re.compile(r"objects\((\w+)\)", re.IGNORECASE)
|
|
12
|
+
field_decl = re.compile(r"fields\((\w+)\)", re.IGNORECASE)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SFObjectGroupTypes(StrEnum):
|
|
16
|
+
all = "all"
|
|
17
|
+
custom = "custom"
|
|
18
|
+
standard = "standard"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SFFieldGroupTypes(StrEnum):
|
|
22
|
+
all = "all"
|
|
23
|
+
custom = "custom"
|
|
24
|
+
standard = "standard"
|
|
25
|
+
required = "required"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ExtractDeclaration(HashableBaseModel):
|
|
29
|
+
where: T.Optional[str] = None
|
|
30
|
+
fields_: T.Union[T.List[str], str] = Field(["FIELDS(ALL)"], alias="fields")
|
|
31
|
+
api: DataApi = DataApi.SMART
|
|
32
|
+
sf_object: T.Optional[str] = None # injected, not declared explicitly
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def parse_field_complex_type(fieldspec):
|
|
36
|
+
"""If it's something like FIELDS(...), parse it out"""
|
|
37
|
+
if match := field_decl.match(fieldspec):
|
|
38
|
+
matching_group = match.groups()[0].lower()
|
|
39
|
+
field_group_type = getattr(SFFieldGroupTypes, matching_group, None)
|
|
40
|
+
return field_group_type
|
|
41
|
+
else:
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
def assert_sf_object_fits_pattern(self):
|
|
45
|
+
if self.is_group:
|
|
46
|
+
assert (
|
|
47
|
+
self.group_type in SFObjectGroupTypes
|
|
48
|
+
), f"Expected OBJECTS(ALL), OBJECTS(CUSTOM) or OBJECTS(STANDARD), not `{self.group_type.upper()}`"
|
|
49
|
+
else:
|
|
50
|
+
assert self.sf_object.isidentifier(), (
|
|
51
|
+
"Value should start with OBJECTS( or be a simple alphanumeric field name"
|
|
52
|
+
f" (underscores allowed) not {self.sf_object}"
|
|
53
|
+
)
|
|
54
|
+
return self.sf_object
|
|
55
|
+
|
|
56
|
+
def assert_check_where_against_complex(self):
|
|
57
|
+
"""Check that a where clause was not used with a group declaration."""
|
|
58
|
+
assert not (
|
|
59
|
+
self.where and self.is_group
|
|
60
|
+
), "Cannot specify a `where` clause on a declaration for multiple kinds of objects."
|
|
61
|
+
|
|
62
|
+
@validator("fields_")
|
|
63
|
+
def normalize_fields(cls, vals):
|
|
64
|
+
if isinstance(vals, str):
|
|
65
|
+
vals = [vals]
|
|
66
|
+
for val in vals:
|
|
67
|
+
cls.validate_field(val)
|
|
68
|
+
return vals
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
def validate_field(cls, val):
|
|
72
|
+
assert cls.parse_field_complex_type(val) or val.isidentifier(), val
|
|
73
|
+
if group_type := cls.parse_field_complex_type(val):
|
|
74
|
+
assert group_type, (
|
|
75
|
+
"group_type in OBJECT(group_type) should be one of "
|
|
76
|
+
f"{tuple(SFFieldGroupTypes.__members__.keys())}, "
|
|
77
|
+
f"not {val}"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
return val
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def is_group(self):
|
|
84
|
+
return bool(self.group_type)
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def group_type(self):
|
|
88
|
+
"""If it's something like OBJECT(...), parse it out"""
|
|
89
|
+
val = self._parse_group_type(self.sf_object)
|
|
90
|
+
if val:
|
|
91
|
+
group_type = getattr(SFObjectGroupTypes, val, None)
|
|
92
|
+
assert group_type, (
|
|
93
|
+
"group_type in OBJECT(group_type) should be one of "
|
|
94
|
+
f"{tuple(SFObjectGroupTypes.__members__.keys())}, "
|
|
95
|
+
f"not {val}"
|
|
96
|
+
)
|
|
97
|
+
return group_type
|
|
98
|
+
|
|
99
|
+
@staticmethod
|
|
100
|
+
def _parse_group_type(val):
|
|
101
|
+
if "(" in val:
|
|
102
|
+
return object_decl.match(val)[1].lower()
|
|
103
|
+
else:
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class ExtractRulesFile(CCIDictModel):
|
|
108
|
+
version: int = 1
|
|
109
|
+
extract: T.Dict[str, ExtractDeclaration]
|
|
110
|
+
|
|
111
|
+
@validator("extract")
|
|
112
|
+
def inject_sf_object_name(cls, val):
|
|
113
|
+
for sf_object, decl in val.items():
|
|
114
|
+
decl.sf_object = sf_object
|
|
115
|
+
decl.assert_sf_object_fits_pattern()
|
|
116
|
+
decl.assert_check_where_against_complex()
|
|
117
|
+
|
|
118
|
+
return val
|
|
119
|
+
|
|
120
|
+
@classmethod
|
|
121
|
+
def parse_extract(cls, source: T.Union[str, Path, T.IO]):
|
|
122
|
+
"""Return the extract key after parsing an extract file."""
|
|
123
|
+
return super().parse_from_yaml(source).extract
|