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,811 @@
|
|
|
1
|
+
import typing as T
|
|
2
|
+
from collections import OrderedDict
|
|
3
|
+
from datetime import date
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from functools import lru_cache
|
|
6
|
+
from logging import getLogger
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import IO, Any, Callable, Dict, List, Mapping, Optional, Tuple, Union
|
|
9
|
+
|
|
10
|
+
from pydantic import Field, ValidationError, root_validator, validator
|
|
11
|
+
from simple_salesforce import Salesforce
|
|
12
|
+
from typing_extensions import Literal
|
|
13
|
+
|
|
14
|
+
from cumulusci.core.enums import StrEnum
|
|
15
|
+
from cumulusci.core.exceptions import BulkDataException
|
|
16
|
+
from cumulusci.tasks.bulkdata.dates import iso_to_date
|
|
17
|
+
from cumulusci.tasks.bulkdata.select_utils import SelectOptions, SelectStrategy
|
|
18
|
+
from cumulusci.tasks.bulkdata.step import DataApi, DataOperationType
|
|
19
|
+
from cumulusci.tasks.bulkdata.utils import CaseInsensitiveDict
|
|
20
|
+
from cumulusci.utils import convert_to_snake_case
|
|
21
|
+
from cumulusci.utils.yaml.model_parser import CCIDictModel
|
|
22
|
+
|
|
23
|
+
logger = getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class MappingLookup(CCIDictModel):
|
|
27
|
+
"Lookup relationship between two tables."
|
|
28
|
+
table: Union[str, List[str]] # Support for polymorphic lookups
|
|
29
|
+
key_field: Optional[str] = None
|
|
30
|
+
value_field: Optional[str] = None
|
|
31
|
+
join_field: Optional[str] = None
|
|
32
|
+
after: Optional[str] = None
|
|
33
|
+
aliased_table: Optional[Any] = None
|
|
34
|
+
parent_tables: Optional[Any] = None
|
|
35
|
+
name: Optional[str] = None # populated by parent
|
|
36
|
+
|
|
37
|
+
def get_lookup_key_field(self, model=None):
|
|
38
|
+
"Find the field name for this lookup."
|
|
39
|
+
guesses = []
|
|
40
|
+
if self.get("key_field"):
|
|
41
|
+
guesses.append(self.get("key_field"))
|
|
42
|
+
|
|
43
|
+
guesses.append(self.name)
|
|
44
|
+
|
|
45
|
+
if not model:
|
|
46
|
+
return guesses[0]
|
|
47
|
+
|
|
48
|
+
# CCI used snake_case until mid-2020.
|
|
49
|
+
# At some point this code could probably be simplified.
|
|
50
|
+
snake_cased_guesses = list(map(convert_to_snake_case, guesses))
|
|
51
|
+
guesses = guesses + snake_cased_guesses
|
|
52
|
+
for guess in guesses:
|
|
53
|
+
if hasattr(model, guess):
|
|
54
|
+
return guess
|
|
55
|
+
raise KeyError(
|
|
56
|
+
f"Could not find a key field for {self.name}.\n"
|
|
57
|
+
+ f"Tried {', '.join(guesses)}"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
class Config:
|
|
61
|
+
# name is an injected field (from the parent dict)
|
|
62
|
+
# so don't try to serialize it as part of the model
|
|
63
|
+
fields = {"name": {"exclude": True}}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
SHOULD_REPORT_RECORD_TYPE_DEPRECATION = True
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class BulkMode(StrEnum):
|
|
70
|
+
serial = Serial = "Serial"
|
|
71
|
+
parallel = Parallel = "Parallel"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
ENUM_VALUES = {
|
|
75
|
+
v.value.lower(): v.value
|
|
76
|
+
for enum in [BulkMode, DataApi, DataOperationType]
|
|
77
|
+
for v in enum.__members__.values()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class MappingStep(CCIDictModel):
|
|
82
|
+
"Step in a load or extract process"
|
|
83
|
+
sf_object: str
|
|
84
|
+
table: Optional[str] = None
|
|
85
|
+
fields_: Dict[str, str] = Field({}, alias="fields")
|
|
86
|
+
lookups: Dict[str, MappingLookup] = {}
|
|
87
|
+
static: Dict[str, str] = {}
|
|
88
|
+
filters: List[str] = []
|
|
89
|
+
action: DataOperationType = DataOperationType.INSERT
|
|
90
|
+
api: DataApi = DataApi.SMART
|
|
91
|
+
batch_size: int = None
|
|
92
|
+
oid_as_pk: bool = False # this one should be discussed and probably deprecated
|
|
93
|
+
record_type: Optional[str] = None # should be discussed and probably deprecated
|
|
94
|
+
bulk_mode: Optional[
|
|
95
|
+
Literal["Serial", "Parallel"]
|
|
96
|
+
] = None # default should come from task options
|
|
97
|
+
anchor_date: Optional[Union[str, date]] = None
|
|
98
|
+
soql_filter: Optional[str] = None # soql_filter property
|
|
99
|
+
select_options: Optional[SelectOptions] = Field(
|
|
100
|
+
default_factory=lambda: SelectOptions(strategy=SelectStrategy.STANDARD)
|
|
101
|
+
)
|
|
102
|
+
update_key: T.Union[str, T.Tuple[str, ...]] = () # only for upserts
|
|
103
|
+
|
|
104
|
+
@validator("bulk_mode", "api", "action", pre=True)
|
|
105
|
+
def case_normalize(cls, val):
|
|
106
|
+
if isinstance(val, Enum):
|
|
107
|
+
return val
|
|
108
|
+
if val is not None:
|
|
109
|
+
return ENUM_VALUES.get(val.lower())
|
|
110
|
+
|
|
111
|
+
@validator("update_key", pre=True)
|
|
112
|
+
def split_update_key(cls, val):
|
|
113
|
+
if isinstance(val, (list, tuple)):
|
|
114
|
+
assert all(isinstance(v, str) for v in val), "All keys should be strings"
|
|
115
|
+
return tuple(v.strip() for v in val)
|
|
116
|
+
if isinstance(val, str):
|
|
117
|
+
return tuple(v.strip() for v in val.split(","))
|
|
118
|
+
else:
|
|
119
|
+
assert isinstance(
|
|
120
|
+
val, (str, list, tuple)
|
|
121
|
+
), "`update_key` should be a field name or list of field names."
|
|
122
|
+
assert False, "Should be unreachable" # pragma: no cover
|
|
123
|
+
|
|
124
|
+
@root_validator
|
|
125
|
+
def validate_priority_fields(cls, values):
|
|
126
|
+
select_options = values.get("select_options")
|
|
127
|
+
fields_ = values.get("fields_", {})
|
|
128
|
+
lookups = values.get("lookups", {})
|
|
129
|
+
|
|
130
|
+
if select_options and select_options.priority_fields:
|
|
131
|
+
priority_field_names = set(select_options.priority_fields.keys())
|
|
132
|
+
field_names = set(fields_.keys())
|
|
133
|
+
lookup_names = set(lookups.keys())
|
|
134
|
+
|
|
135
|
+
# Check if all priority fields are present in the fields
|
|
136
|
+
missing_fields = priority_field_names - field_names
|
|
137
|
+
missing_fields = missing_fields - lookup_names
|
|
138
|
+
if missing_fields:
|
|
139
|
+
raise ValueError(
|
|
140
|
+
f"Priority fields {missing_fields} are not present in 'fields' or 'lookups'"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
return values
|
|
144
|
+
|
|
145
|
+
def get_oid_as_pk(self):
|
|
146
|
+
"""Returns True if using Salesforce Ids as primary keys."""
|
|
147
|
+
return "Id" in self.fields
|
|
148
|
+
|
|
149
|
+
def get_destination_record_type_table(self):
|
|
150
|
+
"""Returns the name of the record type table for the target org."""
|
|
151
|
+
return f"{self.sf_object}_rt_target_mapping"
|
|
152
|
+
|
|
153
|
+
def get_source_record_type_table(self):
|
|
154
|
+
"""Returns the name of the record type table for the source org."""
|
|
155
|
+
return f"{self.sf_object}_rt_mapping"
|
|
156
|
+
|
|
157
|
+
def get_sf_id_table(self):
|
|
158
|
+
"""Returns the name of the table for storing Salesforce Ids."""
|
|
159
|
+
return f"{self.table}_sf_ids"
|
|
160
|
+
|
|
161
|
+
def get_complete_field_map(self, include_id=False):
|
|
162
|
+
"""Return a field map that includes both `fields` and `lookups`.
|
|
163
|
+
If include_id is True, add the Id field if not already present."""
|
|
164
|
+
fields = {}
|
|
165
|
+
|
|
166
|
+
if include_id and "Id" not in self.fields:
|
|
167
|
+
fields["Id"] = "sf_id"
|
|
168
|
+
|
|
169
|
+
fields.update(self.fields)
|
|
170
|
+
fields.update(
|
|
171
|
+
{
|
|
172
|
+
lookup: self.lookups[lookup].get_lookup_key_field()
|
|
173
|
+
for lookup in self.lookups
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
return fields
|
|
178
|
+
|
|
179
|
+
def get_fields_by_type(self, field_type: str, sf: Salesforce):
|
|
180
|
+
describe = getattr(sf, self.sf_object).describe()
|
|
181
|
+
describe = CaseInsensitiveDict(
|
|
182
|
+
{entry["name"]: entry for entry in describe["fields"]}
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
return [f for f in describe if describe[f]["type"] == field_type]
|
|
186
|
+
|
|
187
|
+
def get_load_field_list(self):
|
|
188
|
+
"""Build a flat list of columns for the given mapping,
|
|
189
|
+
including fields, lookups, and statics."""
|
|
190
|
+
lookups = self.lookups
|
|
191
|
+
|
|
192
|
+
# Build the list of fields to import
|
|
193
|
+
columns = []
|
|
194
|
+
columns.extend(self.fields.keys())
|
|
195
|
+
|
|
196
|
+
# Don't include lookups with an `after:` spec (dependent lookups)
|
|
197
|
+
columns.extend([f for f in lookups if not lookups[f].after])
|
|
198
|
+
columns.extend(self.static.keys())
|
|
199
|
+
|
|
200
|
+
# If we're using Record Type mapping, `RecordTypeId` goes at the end.
|
|
201
|
+
if "RecordTypeId" in columns:
|
|
202
|
+
columns.remove("RecordTypeId")
|
|
203
|
+
|
|
204
|
+
if self.action is DataOperationType.INSERT and "Id" in columns:
|
|
205
|
+
columns.remove("Id")
|
|
206
|
+
if self.record_type or "RecordTypeId" in self.fields:
|
|
207
|
+
columns.append("RecordTypeId")
|
|
208
|
+
|
|
209
|
+
return columns
|
|
210
|
+
|
|
211
|
+
def get_extract_field_list(self):
|
|
212
|
+
"""Build a ordered list of Salesforce fields for the given mapping, including fields, lookups, and record types,
|
|
213
|
+
for an extraction operation.
|
|
214
|
+
The Id field is guaranteed to come first in the list."""
|
|
215
|
+
|
|
216
|
+
# Build the list of fields to import
|
|
217
|
+
fields = ["Id"]
|
|
218
|
+
fields.extend([f for f in self.fields.keys() if f != "Id"])
|
|
219
|
+
fields.extend(self.lookups.keys())
|
|
220
|
+
|
|
221
|
+
return fields
|
|
222
|
+
|
|
223
|
+
def get_relative_date_context(self, fields: List[str], sf: Salesforce):
|
|
224
|
+
date_fields = [
|
|
225
|
+
fields.index(f)
|
|
226
|
+
for f in self.get_fields_by_type("date", sf)
|
|
227
|
+
if f in self.fields
|
|
228
|
+
]
|
|
229
|
+
date_time_fields = [
|
|
230
|
+
fields.index(f)
|
|
231
|
+
for f in self.get_fields_by_type("datetime", sf)
|
|
232
|
+
if f in self.fields
|
|
233
|
+
]
|
|
234
|
+
|
|
235
|
+
return (date_fields, date_time_fields, date.today())
|
|
236
|
+
|
|
237
|
+
@validator("batch_size")
|
|
238
|
+
@classmethod
|
|
239
|
+
def validate_batch_size(cls, v, values):
|
|
240
|
+
if values["api"] == DataApi.REST:
|
|
241
|
+
assert 0 < v <= 200, "Max 200 batch_size for REST loads"
|
|
242
|
+
elif values["api"] == DataApi.BULK:
|
|
243
|
+
assert 0 < v <= 10_000, "Max 10,000 batch_size for bulk or smart loads"
|
|
244
|
+
elif values["api"] == DataApi.SMART and v is not None:
|
|
245
|
+
assert 0 < v < 200, "Max 200 batch_size for Smart loads"
|
|
246
|
+
logger.warning(
|
|
247
|
+
"If you set a `batch_size` you should also set an `api` to `rest` or `bulk`. "
|
|
248
|
+
"`batch_size` means different things for `rest` and `bulk`. "
|
|
249
|
+
"Please see the documentation for further details. "
|
|
250
|
+
"https://cumulusci.readthedocs.io/en/latest/data.html#api-selection"
|
|
251
|
+
)
|
|
252
|
+
else: # pragma: no cover
|
|
253
|
+
# should not happen
|
|
254
|
+
assert f"Unknown API {values['api']}"
|
|
255
|
+
return v
|
|
256
|
+
|
|
257
|
+
@validator("anchor_date")
|
|
258
|
+
@classmethod
|
|
259
|
+
def validate_anchor_date(cls, v):
|
|
260
|
+
if v is not None:
|
|
261
|
+
return iso_to_date(v)
|
|
262
|
+
|
|
263
|
+
@validator("record_type")
|
|
264
|
+
@classmethod
|
|
265
|
+
def record_type_is_deprecated(cls, v):
|
|
266
|
+
if SHOULD_REPORT_RECORD_TYPE_DEPRECATION:
|
|
267
|
+
logger.warning(
|
|
268
|
+
"record_type is deprecated. Just supply a RecordTypeId column declaration and it will be inferred"
|
|
269
|
+
)
|
|
270
|
+
return v
|
|
271
|
+
|
|
272
|
+
@validator("oid_as_pk")
|
|
273
|
+
@classmethod
|
|
274
|
+
def oid_as_pk_is_deprecated(cls, v):
|
|
275
|
+
if v:
|
|
276
|
+
raise ValueError(
|
|
277
|
+
"oid_as_pk is no longer supported. Include the Id field if desired."
|
|
278
|
+
)
|
|
279
|
+
return v
|
|
280
|
+
|
|
281
|
+
@validator("fields_", pre=True)
|
|
282
|
+
@classmethod
|
|
283
|
+
def standardize_fields_to_dict(cls, values):
|
|
284
|
+
if values is None:
|
|
285
|
+
values = {}
|
|
286
|
+
if type(values) is list:
|
|
287
|
+
values = {elem: elem for elem in values}
|
|
288
|
+
|
|
289
|
+
return CaseInsensitiveDict(values)
|
|
290
|
+
|
|
291
|
+
@root_validator
|
|
292
|
+
@classmethod
|
|
293
|
+
def set_default_table(cls, values):
|
|
294
|
+
"""Automatically populate the `table` key with `sf_object`, if not present."""
|
|
295
|
+
if values["table"] is None:
|
|
296
|
+
values["table"] = values.get("sf_object")
|
|
297
|
+
|
|
298
|
+
return values
|
|
299
|
+
|
|
300
|
+
@root_validator # not really a validator, more like a post-processor
|
|
301
|
+
@classmethod
|
|
302
|
+
def fixup_lookup_names(cls, v):
|
|
303
|
+
"Allow lookup objects to know the key they were attached to in the mapping file."
|
|
304
|
+
for name, lookup in v.get("lookups", {}).items():
|
|
305
|
+
lookup.name = name
|
|
306
|
+
return v
|
|
307
|
+
|
|
308
|
+
@root_validator
|
|
309
|
+
@classmethod
|
|
310
|
+
def validate_update_key_and_upsert(cls, v):
|
|
311
|
+
"""Check that update_key and action are synchronized"""
|
|
312
|
+
update_key = v.get("update_key")
|
|
313
|
+
action = v.get("action")
|
|
314
|
+
|
|
315
|
+
if action == DataOperationType.UPSERT:
|
|
316
|
+
assert update_key, "'update_key' must always be supplied for upsert."
|
|
317
|
+
assert (
|
|
318
|
+
len(update_key) == 1
|
|
319
|
+
), "simple upserts can only support one field at a time."
|
|
320
|
+
elif action in (DataOperationType.ETL_UPSERT, DataOperationType.SMART_UPSERT):
|
|
321
|
+
assert update_key, "'update_key' must always be supplied for upsert."
|
|
322
|
+
else:
|
|
323
|
+
assert not update_key, "Update key should only be specified for upserts"
|
|
324
|
+
|
|
325
|
+
if update_key:
|
|
326
|
+
for key in update_key:
|
|
327
|
+
assert key.lower() in (
|
|
328
|
+
f.lower() for f in v["fields_"]
|
|
329
|
+
), f"`update_key`: {key} not found in `fields``"
|
|
330
|
+
|
|
331
|
+
return v
|
|
332
|
+
|
|
333
|
+
@staticmethod
|
|
334
|
+
def _is_injectable(element: str) -> bool:
|
|
335
|
+
return element.count("__") == 1
|
|
336
|
+
|
|
337
|
+
def _get_required_permission_types(
|
|
338
|
+
self, operation: DataOperationType
|
|
339
|
+
) -> T.Tuple[str]:
|
|
340
|
+
"""Return a tuple of the permission types required to execute an operation"""
|
|
341
|
+
if (
|
|
342
|
+
operation is DataOperationType.QUERY
|
|
343
|
+
or self.action is DataOperationType.SELECT
|
|
344
|
+
):
|
|
345
|
+
return ("queryable",)
|
|
346
|
+
if (
|
|
347
|
+
operation is DataOperationType.INSERT
|
|
348
|
+
and self.action is DataOperationType.UPDATE
|
|
349
|
+
):
|
|
350
|
+
return ("updateable",)
|
|
351
|
+
if operation in (
|
|
352
|
+
DataOperationType.UPSERT,
|
|
353
|
+
DataOperationType.ETL_UPSERT,
|
|
354
|
+
) or self.action in (DataOperationType.UPSERT, DataOperationType.ETL_UPSERT):
|
|
355
|
+
return ("updateable", "createable")
|
|
356
|
+
|
|
357
|
+
return ("createable",)
|
|
358
|
+
|
|
359
|
+
def _check_object_permission(
|
|
360
|
+
self, global_describe: Mapping, sobject: str, operation: DataOperationType
|
|
361
|
+
):
|
|
362
|
+
assert sobject in global_describe
|
|
363
|
+
perms = self._get_required_permission_types(operation)
|
|
364
|
+
return all(global_describe[sobject][perm] for perm in perms)
|
|
365
|
+
|
|
366
|
+
def _check_field_permission(
|
|
367
|
+
self, describe: Mapping, field: str, operation: DataOperationType
|
|
368
|
+
):
|
|
369
|
+
perms = self._get_required_permission_types(operation)
|
|
370
|
+
# Fields don't have "queryable" permission.
|
|
371
|
+
return field in describe and all(
|
|
372
|
+
# To discuss: is this different than `describe[field].get(perm, True)`
|
|
373
|
+
describe[field].get(perm) if perm in describe[field] else True
|
|
374
|
+
for perm in perms
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
def _validate_field_dict(
|
|
378
|
+
self,
|
|
379
|
+
describe: CaseInsensitiveDict,
|
|
380
|
+
field_dict: Dict[str, Any],
|
|
381
|
+
inject: Optional[Callable[[str], str]],
|
|
382
|
+
strip: Optional[Callable[[str], str]],
|
|
383
|
+
drop_missing: bool,
|
|
384
|
+
data_operation_type: DataOperationType,
|
|
385
|
+
) -> bool:
|
|
386
|
+
ret = True
|
|
387
|
+
|
|
388
|
+
def replace_if_necessary(dct, name, replacement):
|
|
389
|
+
if name not in describe and replacement in describe:
|
|
390
|
+
dct[replacement] = dct[name]
|
|
391
|
+
del dct[name]
|
|
392
|
+
return replacement
|
|
393
|
+
else:
|
|
394
|
+
return name
|
|
395
|
+
|
|
396
|
+
orig_fields = field_dict.copy()
|
|
397
|
+
special_names = {"id": "Id", "ispersonaccount": "IsPersonAccount"}
|
|
398
|
+
for f, entry in orig_fields.items():
|
|
399
|
+
# Do we need to inject this field?
|
|
400
|
+
if f.lower() in special_names:
|
|
401
|
+
del field_dict[f]
|
|
402
|
+
canonical_name = special_names[f.lower()]
|
|
403
|
+
field_dict[canonical_name] = entry
|
|
404
|
+
continue
|
|
405
|
+
|
|
406
|
+
if inject and self._is_injectable(f) and inject(f) not in orig_fields:
|
|
407
|
+
if f in describe and inject(f) in describe:
|
|
408
|
+
logger.warning(
|
|
409
|
+
f"Both {self.sf_object}.{f} and {self.sf_object}.{inject(f)} are present in the target org. Using {f}."
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
f = replace_if_necessary(field_dict, f, inject(f))
|
|
413
|
+
if strip:
|
|
414
|
+
f = replace_if_necessary(field_dict, f, strip(f))
|
|
415
|
+
|
|
416
|
+
# Canonicalize the key's case
|
|
417
|
+
try:
|
|
418
|
+
new_name = describe.canonical_key(f)
|
|
419
|
+
except KeyError:
|
|
420
|
+
logger.warning(
|
|
421
|
+
f"Field {self.sf_object}.{f} does not exist or is not visible to the current user."
|
|
422
|
+
)
|
|
423
|
+
else:
|
|
424
|
+
del field_dict[f]
|
|
425
|
+
field_dict[new_name] = entry
|
|
426
|
+
f = new_name
|
|
427
|
+
|
|
428
|
+
# Do we have the right permissions for this field, or do we need to drop it?
|
|
429
|
+
is_after_lookup = hasattr(field_dict[f], "after")
|
|
430
|
+
relevant_operation = (
|
|
431
|
+
data_operation_type if not is_after_lookup else DataOperationType.UPDATE
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
error_in_f = False
|
|
435
|
+
|
|
436
|
+
if f not in describe:
|
|
437
|
+
logger.warning(
|
|
438
|
+
f"Field {self.sf_object}.{f} does not exist or is not visible to the current user."
|
|
439
|
+
)
|
|
440
|
+
error_in_f = True
|
|
441
|
+
elif not self._check_field_permission(
|
|
442
|
+
describe,
|
|
443
|
+
f,
|
|
444
|
+
relevant_operation,
|
|
445
|
+
):
|
|
446
|
+
relevant_permissions = self._get_required_permission_types(
|
|
447
|
+
relevant_operation
|
|
448
|
+
)
|
|
449
|
+
logger.warning(
|
|
450
|
+
f"Field {self.sf_object}.{f} does not have the correct permissions "
|
|
451
|
+
+ f"{relevant_permissions} for this operation."
|
|
452
|
+
)
|
|
453
|
+
error_in_f = True
|
|
454
|
+
|
|
455
|
+
if error_in_f:
|
|
456
|
+
if drop_missing:
|
|
457
|
+
del field_dict[f]
|
|
458
|
+
else:
|
|
459
|
+
ret = False
|
|
460
|
+
|
|
461
|
+
return ret
|
|
462
|
+
|
|
463
|
+
def _validate_sobject(
|
|
464
|
+
self,
|
|
465
|
+
global_describe: CaseInsensitiveDict,
|
|
466
|
+
inject: Optional[Callable[[str], str]],
|
|
467
|
+
strip: Optional[Callable[[str], str]],
|
|
468
|
+
data_operation_type: DataOperationType,
|
|
469
|
+
) -> bool:
|
|
470
|
+
# Determine whether we need to inject or strip our sObject.
|
|
471
|
+
|
|
472
|
+
self.sf_object = (
|
|
473
|
+
_inject_or_strip_name(self.sf_object, inject, global_describe)
|
|
474
|
+
or _inject_or_strip_name(self.sf_object, strip, global_describe)
|
|
475
|
+
or self.sf_object
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
try:
|
|
479
|
+
self.sf_object = global_describe.canonical_key(self.sf_object)
|
|
480
|
+
except KeyError:
|
|
481
|
+
logger.warning(
|
|
482
|
+
f"sObject {self.sf_object} does not exist or is not visible to the current user."
|
|
483
|
+
)
|
|
484
|
+
return False
|
|
485
|
+
|
|
486
|
+
# Validate our access to this sObject.
|
|
487
|
+
if not self._check_object_permission(
|
|
488
|
+
global_describe, self.sf_object, data_operation_type
|
|
489
|
+
):
|
|
490
|
+
logger.warning(
|
|
491
|
+
f"sObject {self.sf_object} does not have the correct permissions for {data_operation_type}."
|
|
492
|
+
)
|
|
493
|
+
return False
|
|
494
|
+
|
|
495
|
+
return True
|
|
496
|
+
|
|
497
|
+
def check_required(self, fields_describe):
|
|
498
|
+
required_fields = set()
|
|
499
|
+
for field in fields_describe:
|
|
500
|
+
defaulted = (
|
|
501
|
+
fields_describe[field]["defaultValue"] is not None
|
|
502
|
+
or fields_describe[field]["nillable"]
|
|
503
|
+
or fields_describe[field]["defaultedOnCreate"]
|
|
504
|
+
)
|
|
505
|
+
if fields_describe[field]["createable"] and not defaulted:
|
|
506
|
+
required_fields.add(field)
|
|
507
|
+
missing_fields = required_fields.difference(
|
|
508
|
+
set(self.fields.keys()) | set(self.lookups)
|
|
509
|
+
)
|
|
510
|
+
if len(missing_fields) > 0:
|
|
511
|
+
logger.error(
|
|
512
|
+
f"One or more required fields are missing for loading on {self.sf_object} :{missing_fields}"
|
|
513
|
+
)
|
|
514
|
+
return False
|
|
515
|
+
else:
|
|
516
|
+
return True
|
|
517
|
+
|
|
518
|
+
def validate_and_inject_namespace(
|
|
519
|
+
self,
|
|
520
|
+
sf: Salesforce,
|
|
521
|
+
namespace: Optional[str],
|
|
522
|
+
operation: DataOperationType,
|
|
523
|
+
inject_namespaces: bool = False,
|
|
524
|
+
drop_missing: bool = False,
|
|
525
|
+
is_load: bool = False,
|
|
526
|
+
):
|
|
527
|
+
"""Process the schema elements in this step.
|
|
528
|
+
|
|
529
|
+
First, we inject the namespace into object and field names where applicable.
|
|
530
|
+
Second, we validate that all fields are accessible by the running user with the
|
|
531
|
+
correct permission level for this operation.
|
|
532
|
+
Lastly, if drop_missing is True, we strip any fields that are not present (namespaced
|
|
533
|
+
or otherwise) from the target org.
|
|
534
|
+
|
|
535
|
+
Return True if this object should be processed. If drop_missing is True, a False return
|
|
536
|
+
value indicates we should skip this object. If drop_missing is False, a False return
|
|
537
|
+
value indicates that one or more schema elements couldn't be validated."""
|
|
538
|
+
|
|
539
|
+
if namespace and inject_namespaces:
|
|
540
|
+
|
|
541
|
+
def inject(element: str):
|
|
542
|
+
return f"{namespace}__{element}"
|
|
543
|
+
|
|
544
|
+
def strip(element: str):
|
|
545
|
+
parts = element.split("__")
|
|
546
|
+
if len(parts) == 3 and parts[0] == namespace:
|
|
547
|
+
return parts[1] + "__" + parts[2]
|
|
548
|
+
else:
|
|
549
|
+
return element
|
|
550
|
+
|
|
551
|
+
else:
|
|
552
|
+
inject = strip = None
|
|
553
|
+
|
|
554
|
+
global_describe = CaseInsensitiveDict(
|
|
555
|
+
{entry["name"]: entry for entry in sf.describe()["sobjects"]}
|
|
556
|
+
)
|
|
557
|
+
if not self._validate_sobject(global_describe, inject, strip, operation):
|
|
558
|
+
# Don't attempt to validate field permissions if the object doesn't exist.
|
|
559
|
+
return False
|
|
560
|
+
|
|
561
|
+
# Validate, inject, and drop (if configured) fields.
|
|
562
|
+
# By this point, we know the attribute is valid.
|
|
563
|
+
describe = self.describe_data(sf)
|
|
564
|
+
fields_correct = self._validate_field_dict(
|
|
565
|
+
describe, self.fields, inject, strip, drop_missing, operation
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
lookups_correct = self._validate_field_dict(
|
|
569
|
+
describe, self.lookups, inject, strip, drop_missing, operation
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
if is_load:
|
|
573
|
+
# Show warning logs for unspecified required fields
|
|
574
|
+
self.check_required(describe)
|
|
575
|
+
|
|
576
|
+
if not (fields_correct and lookups_correct):
|
|
577
|
+
return False
|
|
578
|
+
|
|
579
|
+
# inject namespaces into the update_key
|
|
580
|
+
if self.update_key:
|
|
581
|
+
assert isinstance(self.update_key, Tuple)
|
|
582
|
+
update_keys = {k: k for k in self.update_key}
|
|
583
|
+
if not self._validate_field_dict(
|
|
584
|
+
describe,
|
|
585
|
+
update_keys,
|
|
586
|
+
inject,
|
|
587
|
+
strip,
|
|
588
|
+
drop_missing=False,
|
|
589
|
+
data_operation_type=operation,
|
|
590
|
+
):
|
|
591
|
+
return False
|
|
592
|
+
self.update_key = tuple(update_keys.keys())
|
|
593
|
+
|
|
594
|
+
return True
|
|
595
|
+
|
|
596
|
+
def describe_data(self, sf: Salesforce):
|
|
597
|
+
return describe_data(self.sf_object, sf)
|
|
598
|
+
|
|
599
|
+
def dict(self, by_alias=True, exclude_defaults=True, **kwargs):
|
|
600
|
+
out = super().dict(
|
|
601
|
+
by_alias=by_alias, exclude_defaults=exclude_defaults, **kwargs
|
|
602
|
+
)
|
|
603
|
+
if fields := out.get("fields"):
|
|
604
|
+
# Convert dicts of {"Name": "Name", "Role": "Role"}
|
|
605
|
+
# (an old-fashioned syntax)
|
|
606
|
+
# into a more modern ["Name", "Role"] -type format.
|
|
607
|
+
keys = list(fields.keys())
|
|
608
|
+
if keys == list(fields.values()):
|
|
609
|
+
out["fields"] = keys
|
|
610
|
+
|
|
611
|
+
# flatten enum to string
|
|
612
|
+
if isinstance(out.get("api"), DataApi):
|
|
613
|
+
out["api"] = out["api"].value
|
|
614
|
+
return out
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
class MappingSteps(CCIDictModel):
|
|
618
|
+
"Mapping of named steps"
|
|
619
|
+
__root__: Dict[str, MappingStep]
|
|
620
|
+
|
|
621
|
+
@root_validator(pre=False)
|
|
622
|
+
@classmethod
|
|
623
|
+
def validate_and_inject_mapping(cls, values):
|
|
624
|
+
if values:
|
|
625
|
+
oids = ["Id" in s.fields_ for s in values["__root__"].values()]
|
|
626
|
+
assert all(oids) or not any(
|
|
627
|
+
oids
|
|
628
|
+
), "Id must be mapped in all steps or in no steps."
|
|
629
|
+
|
|
630
|
+
return values
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
ValidationError = ValidationError # export Pydantic's Validation Error under an alias
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
def parse_from_yaml(source: Union[str, Path, IO]) -> Dict:
|
|
637
|
+
"Parse from a path, url, path-like or file-like"
|
|
638
|
+
return MappingSteps.parse_from_yaml(source)
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
def _infer_and_validate_lookups(mapping: Dict, sf: Salesforce):
|
|
642
|
+
"""Validate that all the lookup tables mentioned are valid references
|
|
643
|
+
to the lookup. Also verify that the mapping for the tables are mentioned
|
|
644
|
+
before they are mentioned in the lookups"""
|
|
645
|
+
# store the table name and their sobject
|
|
646
|
+
sf_objects = OrderedDict((m.table, m.sf_object) for m in mapping.values())
|
|
647
|
+
|
|
648
|
+
fail = False
|
|
649
|
+
|
|
650
|
+
for idx, m in enumerate(mapping.values()):
|
|
651
|
+
describe = CaseInsensitiveDict(
|
|
652
|
+
{f["name"]: f for f in getattr(sf, m.sf_object).describe()["fields"]}
|
|
653
|
+
)
|
|
654
|
+
|
|
655
|
+
for lookup_name, lookup in m.lookups.items():
|
|
656
|
+
if lookup.after:
|
|
657
|
+
# If configured by the user, skip.
|
|
658
|
+
# TODO: do we need more validation here?
|
|
659
|
+
continue
|
|
660
|
+
|
|
661
|
+
field_describe = describe.get(lookup_name, {})
|
|
662
|
+
reference_to_objects = field_describe.get("referenceTo", [])
|
|
663
|
+
target_objects = []
|
|
664
|
+
|
|
665
|
+
lookup_tables = (
|
|
666
|
+
[lookup.table] if isinstance(lookup.table, str) else lookup.table
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
for table in lookup_tables:
|
|
670
|
+
try:
|
|
671
|
+
sf_object = sf_objects[table]
|
|
672
|
+
# Check if sf_object is a valid lookup
|
|
673
|
+
if sf_object in reference_to_objects:
|
|
674
|
+
target_objects.append(sf_object)
|
|
675
|
+
else:
|
|
676
|
+
logger.error(
|
|
677
|
+
f"The lookup {sf_object} is not a valid lookup for {lookup_name} in sf_object: {m.sf_object}"
|
|
678
|
+
)
|
|
679
|
+
fail = True
|
|
680
|
+
except KeyError:
|
|
681
|
+
logger.error(
|
|
682
|
+
f"The table {table} does not exist in the mapping file"
|
|
683
|
+
)
|
|
684
|
+
fail = True
|
|
685
|
+
|
|
686
|
+
if fail:
|
|
687
|
+
continue
|
|
688
|
+
|
|
689
|
+
if len(target_objects) == 1:
|
|
690
|
+
# This is a non-polymorphic lookup.
|
|
691
|
+
target_index = list(sf_objects.values()).index(target_objects[0])
|
|
692
|
+
if (
|
|
693
|
+
target_index > idx or target_index == idx
|
|
694
|
+
) and m.action != DataOperationType.SELECT:
|
|
695
|
+
# This is a non-polymorphic after step.
|
|
696
|
+
lookup.after = list(mapping.keys())[idx]
|
|
697
|
+
else:
|
|
698
|
+
# This is a polymorphic lookup.
|
|
699
|
+
# Make sure that any lookup targets present in the operation precede this step.
|
|
700
|
+
target_indices = [
|
|
701
|
+
list(sf_objects.values()).index(t) for t in target_objects
|
|
702
|
+
]
|
|
703
|
+
if not all([target_index < idx for target_index in target_indices]):
|
|
704
|
+
logger.error(
|
|
705
|
+
f"All included target objects ({','.join(target_objects)}) for the field {m.sf_object}.{lookup_name} "
|
|
706
|
+
f"must precede {m.sf_object} in the mapping."
|
|
707
|
+
)
|
|
708
|
+
fail = True
|
|
709
|
+
continue
|
|
710
|
+
|
|
711
|
+
if fail:
|
|
712
|
+
raise BulkDataException(
|
|
713
|
+
"One or more relationship errors blocked the operation."
|
|
714
|
+
)
|
|
715
|
+
|
|
716
|
+
|
|
717
|
+
def validate_and_inject_mapping(
|
|
718
|
+
*,
|
|
719
|
+
mapping: Dict,
|
|
720
|
+
sf: Salesforce,
|
|
721
|
+
namespace: str,
|
|
722
|
+
data_operation: DataOperationType,
|
|
723
|
+
inject_namespaces: bool,
|
|
724
|
+
drop_missing: bool,
|
|
725
|
+
org_has_person_accounts_enabled: bool = False,
|
|
726
|
+
):
|
|
727
|
+
# Check if operation is load or extract
|
|
728
|
+
is_load = True if data_operation == DataOperationType.INSERT else False
|
|
729
|
+
|
|
730
|
+
should_continue = [
|
|
731
|
+
m.validate_and_inject_namespace(
|
|
732
|
+
sf, namespace, data_operation, inject_namespaces, drop_missing, is_load
|
|
733
|
+
)
|
|
734
|
+
for m in mapping.values()
|
|
735
|
+
]
|
|
736
|
+
|
|
737
|
+
if not drop_missing and not all(should_continue):
|
|
738
|
+
raise BulkDataException(
|
|
739
|
+
"One or more schema or permissions errors blocked the operation.\n"
|
|
740
|
+
"If you would like to attempt the load regardless, you can specify "
|
|
741
|
+
"'--drop_missing_schema True' on the command option and ensure all required fields are included in the mapping file."
|
|
742
|
+
)
|
|
743
|
+
|
|
744
|
+
if drop_missing:
|
|
745
|
+
# Drop any steps with sObjects that are not present.
|
|
746
|
+
for include, step_name in zip(should_continue, list(mapping.keys())):
|
|
747
|
+
if not include:
|
|
748
|
+
del mapping[step_name]
|
|
749
|
+
|
|
750
|
+
# Remove any remaining lookups to dropped objects.
|
|
751
|
+
for m in mapping.values():
|
|
752
|
+
describe = getattr(sf, m.sf_object).describe()
|
|
753
|
+
describe = {entry["name"]: entry for entry in describe["fields"]}
|
|
754
|
+
|
|
755
|
+
for field in list(m.lookups.keys()):
|
|
756
|
+
lookup = m.lookups[field]
|
|
757
|
+
if isinstance(lookup.table, list):
|
|
758
|
+
lookup_tables = lookup.table
|
|
759
|
+
else:
|
|
760
|
+
lookup_tables = [lookup.table]
|
|
761
|
+
if all(
|
|
762
|
+
table not in [step.table for step in mapping.values()]
|
|
763
|
+
for table in lookup_tables
|
|
764
|
+
):
|
|
765
|
+
del m.lookups[field]
|
|
766
|
+
|
|
767
|
+
# Make sure this didn't cause the operation to be invalid
|
|
768
|
+
# by dropping a required field.
|
|
769
|
+
if not describe[field]["nillable"]:
|
|
770
|
+
raise BulkDataException(
|
|
771
|
+
f"{m.sf_object}.{field} is a required field, but the target object "
|
|
772
|
+
f"{describe[field]['referenceTo']} was removed from the operation "
|
|
773
|
+
"due to missing permissions."
|
|
774
|
+
)
|
|
775
|
+
|
|
776
|
+
# Infer/validate lookups
|
|
777
|
+
if is_load:
|
|
778
|
+
_infer_and_validate_lookups(mapping, sf)
|
|
779
|
+
|
|
780
|
+
# If the org has person accounts enable, add a field mapping to track "IsPersonAccount".
|
|
781
|
+
# IsPersonAccount field values are used to properly load person account records.
|
|
782
|
+
if org_has_person_accounts_enabled and data_operation == DataOperationType.QUERY:
|
|
783
|
+
for step in mapping.values():
|
|
784
|
+
if step["sf_object"] in ("Account", "Contact"):
|
|
785
|
+
step["fields"]["IsPersonAccount"] = "IsPersonAccount"
|
|
786
|
+
|
|
787
|
+
|
|
788
|
+
def _inject_or_strip_name(name, transform, global_describe):
|
|
789
|
+
if not transform:
|
|
790
|
+
return None
|
|
791
|
+
new_name = transform(name)
|
|
792
|
+
|
|
793
|
+
if name == new_name:
|
|
794
|
+
return None
|
|
795
|
+
|
|
796
|
+
if name in global_describe and new_name in global_describe:
|
|
797
|
+
logger.warning(
|
|
798
|
+
f"Both {name} and {new_name} are present in the target org. Using {name}."
|
|
799
|
+
)
|
|
800
|
+
return None
|
|
801
|
+
|
|
802
|
+
if name not in global_describe and new_name in global_describe:
|
|
803
|
+
return new_name
|
|
804
|
+
|
|
805
|
+
return None
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
@lru_cache(maxsize=50)
|
|
809
|
+
def describe_data(obj: str, sf: Salesforce):
|
|
810
|
+
describe = getattr(sf, obj).describe()
|
|
811
|
+
return CaseInsensitiveDict({entry["name"]: entry for entry in describe["fields"]})
|