Repository: SFDO-Tooling/CumulusCI
Pull request #3346 (2022-08-26 17:14:46)

Add Dataset abstraction to CCI internals (not user facing)


Patch:
diff --git a/cumulusci/core/datasets.py b/cumulusci/core/datasets.py
new file mode 100644
index 000000000..4ccd009ae
--- /dev/null
+++ b/cumulusci/core/datasets.py
@@ -0,0 +1,180 @@
+import typing as T
+from contextlib import contextmanager
+from dataclasses import dataclass
+from io import StringIO
+from pathlib import Path
+from tempfile import TemporaryDirectory
+
+import yaml
+from simple_salesforce import Salesforce
+
+from cumulusci.core.config import BaseProjectConfig, OrgConfig, TaskConfig
+from cumulusci.salesforce_api.org_schema import Filters, Schema, get_org_schema
+from cumulusci.tasks.bulkdata.extract import ExtractData
+from cumulusci.tasks.bulkdata.extract_dataset_utils.extract_yml import (
+    ExtractDeclaration,
+    ExtractRulesFile,
+)
+from cumulusci.tasks.bulkdata.extract_dataset_utils.synthesize_extract_declarations import (
+    SimplifiedExtractDeclaration,
+    flatten_declarations,
+)
+from cumulusci.tasks.bulkdata.generate_mapping_utils.extract_mapping_file_generator import (
+    create_extract_mapping_file_from_declarations,
+)
+from cumulusci.tasks.bulkdata.generate_mapping_utils.generate_mapping_from_declarations import (
+    create_load_mapping_file_from_extract_declarations,
+)
+from cumulusci.tasks.bulkdata.load import LoadData
+
+
+@dataclass
+class Dataset:
+    name: str
+    project_config: BaseProjectConfig
+    sf: Salesforce
+    org_config: OrgConfig
+    schema: Schema = None
+    initialized = False
+
+    @property
+    def path(self) -> Path:
+        return Path(self.project_config.repo_root) / "datasets" / self.name
+
+    @property
+    def extract_file(self) -> Path:
+        return self.path / f"{self.name}.extract.yml"
+
+    @property
+    def mapping_file(self) -> Path:
+        return self.path / f"{self.name}.mapping.yml"
+
+    @property
+    def data_file(self) -> Path:
+        return self.path / f"{self.name}.dataset.sql"
+
+    def __enter__(self, *args, **kwargs):
+        if self.schema is None:
+            self.schema_context = self._get_org_schema()
+            self.schema = self.schema_context.__enter__(*args, **kwargs)
+        else:
+            self.schema_context = None
+        self.initialized = True
+        return self
+
+    def __exit__(self, *args, **kwargs):
+        if self.schema_context:
+            self.schema_context.__exit__(*args, **kwargs)
+
+    def create(self):
+        assert (
+            self.initialized
+        ), "You must open this context manager. e.g. `with Dataset() as dataset`"
+
+        if not self.path.exists():
+            self.path.mkdir()
+
+        self.extract_file.write_text(DEFAULT_EXTRACT_DATA)
+        decls = ExtractRulesFile.parse_extract(StringIO(DEFAULT_EXTRACT_DATA))
+
+        decls = list(decls.values())
+        self._save_load_mapping(decls)
+
+    def _get_org_schema(self):
+        return get_org_schema(
+            self.sf,
+            self.org_config,
+            include_counts=True,
+            filters=[Filters.extractable, Filters.createable, Filters.populated],
+        )
+
+    def _save_load_mapping(self, decls: T.Sequence[ExtractDeclaration]) -> None:
+        mapping_data = create_load_mapping_file_from_extract_declarations(
+            decls, self.schema
+        )
+        with self.mapping_file.open("w") as f:
+            yaml.safe_dump(mapping_data, f, sort_keys=False)
+
+    @contextmanager
+    def temp_extract_mapping(self, schema):
+        with TemporaryDirectory() as t:
+            t = Path(t)
+            with self.extract_file.open() as f:
+                decls = ExtractRulesFile.parse_extract(f)
+
+            extract_mapping = t / "extract.mapping.yml"
+            with extract_mapping.open("w") as f:
+                yaml.safe_dump(
+                    create_extract_mapping_file_from_declarations(
+                        list(decls.values()), schema
+                    ),
+                    f,
+                )
+            yield extract_mapping
+
+    def extract(self):
+        with self.temp_extract_mapping(self.schema) as extract_mapping:
+            task = _make_task(
+                ExtractData,
+                project_config=self.project_config,
+                org_config=self.org_config,
+                sql_path=self.data_file,
+                mapping=str(extract_mapping),
+            )
+            task()
+        with open(self.extract_file) as f:
+            self._save_load_mapping(list(ExtractRulesFile.parse_extract(f).values()))
+
+    def load(self):
+
+        task = _make_task(
+            LoadData,
+            project_config=self.project_config,
+            org_config=self.org_config,
+            sql_path=self.data_file,
+            mapping=str(self.mapping_file),
+        )
+        task()
+
+    def read_schema_subset(self) -> T.Dict[str, T.List[str]]:
+        decls = ExtractRulesFile.parse_extract(self.extract_file)
+        flattened = flatten_declarations(list(decls.values()), self.schema)
+        return {obj.sf_object: obj.fields for obj in flattened}
+
+    def read_which_fields_selected(self) -> T.Dict[str, T.Dict[str, bool]]:
+        selected = self.read_schema_subset()
+        selected_fields = {
+            (obj, field) for obj, fields in selected.items() for field in fields
+        }
+        out = {}
+        for objname, obj in self.schema.items():
+            objrepr = out.setdefault(objname, {})
+            for field in obj.fields.keys():
+                is_selected = (objname, field) in selected_fields
+                objrepr[field] = is_selected
+        return out
+
+    def update_schema_subset(self, objs: T.Dict[str, T.List[str]]):
+        # TODO: Test round-tripping through this carefully...especially
+        #       for weird objects like WorkBadgeDefinitions
+        objs_dict = {name: {"fields": fields} for name, fields in objs.items()}
+        with self.extract_file.open("w") as f:
+            data = {"extract": objs_dict}
+            yaml.safe_dump(data, f, sort_keys=False)
+        decls = [
+            SimplifiedExtractDeclaration(sf_object=name, fields=fields)
+            for name, fields in objs.items()
+        ]
+        self._save_load_mapping(decls)
+
+
+def _make_task(task_class, project_config, org_config, **options):
+    task_config = TaskConfig({"options": options})
+    return task_class(project_config, task_config, org_config)
+
+
+DEFAULT_EXTRACT_DATA = """
+extract:
+    OBJECTS(ALL):
+        fields: FIELDS(ALL)
+"""
diff --git a/cumulusci/salesforce_api/filterable_objects.py b/cumulusci/salesforce_api/filterable_objects.py
index cfd64829d..b72a8df9c 100644
--- a/cumulusci/salesforce_api/filterable_objects.py
+++ b/cumulusci/salesforce_api/filterable_objects.py
@@ -33,6 +33,7 @@ NOT_EXTRACTABLE = NOT_COUNTABLE + (
     "PermissionSetGroupComponent",
     "PermissionSetLicenseAssign",
     "PermissionSetTabSetting",
+    "Profile",
     "User",
     "UserAppInfo",
     "UserAppMenuCustomization",


Test patch:
diff --git a/cumulusci/core/tests/cassettes/TestDatasetsE2E.test_datasets_e2e.yaml b/cumulusci/core/tests/cassettes/TestDatasetsE2E.test_datasets_e2e.yaml
new file mode 100644
index 000000000..42cdb269e
--- /dev/null
+++ b/cumulusci/core/tests/cassettes/TestDatasetsE2E.test_datasets_e2e.yaml
@@ -0,0 +1,260 @@
+version: 1
+interactions:
+    - &id009
+      request: &id005
+          method: GET
+          uri: https://orgname.my.salesforce.com/services/data/vxx.0/query/?q=select+count%28Id%29+from+Account
+          body: null
+          headers: &id001
+              Request-Headers:
+                  - Elided
+      response:
+          status: &id002
+              code: 200
+              message: OK
+          headers: &id003
+              Content-Type:
+                  - application/json;charset=UTF-8
+              Others: Elided
+          body:
+              string: "{\n  \"totalSize\" : 1,\n  \"done\" : true,\n  \"records\" : [ {\n\
+                  \    \"attributes\" : {\n      \"type\" : \"AggregateResult\"\n    },\n  \
+                  \  \"expr0\" : 6\n  } ]\n}"
+    - &id006
+      include_file: GET_sobjects_Organization.yaml
+    - &id004
+      request:
+          method: GET
+          uri: https://orgname.my.salesforce.com/services/data/vxx.0/limits/recordCount?sObjects=Account
+          body: null
+          headers: *id001
+      response:
+          status: *id002
+          headers: *id003
+          body:
+              string:
+                  "{\n  \"sObjects\" : [ {\n    \"count\" : 1,\n    \"name\" : \"Account\"\
+                  \n  } ]\n}"
+    - request:
+          method: GET
+          uri: https://orgname.my.salesforce.com/services/data/vxx.0/query/?q=SELECT+Id%2C+AccountNumber%2C+AccountSource%2C+AnnualRevenue%2C+BillingCity%2C+BillingCountry%2C+BillingGeocodeAccuracy%2C+BillingLatitude%2C+BillingLongitude%2C+BillingPostalCode%2C+BillingState%2C+BillingStreet%2C+CleanStatus%2C+DunsNumber%2C+Fax%2C+Industry%2C+Jigsaw%2C+NaicsCode%2C+NaicsDesc%2C+Name%2C+NumberOfEmployees%2C+Ownership%2C+Phone%2C+Rating%2C+ShippingCity%2C+ShippingCountry%2C+ShippingGeocodeAccuracy%2C+ShippingLatitude%2C+ShippingLongitude%2C+ShippingPostalCode%2C+ShippingState%2C+ShippingStreet%2C+Sic%2C+SicDesc%2C+Site%2C+TickerSymbol%2C+Tradestyle%2C+Type%2C+Website%2C+YearStarted%2C+ParentId+FROM+Account+WHERE+Name+%21%3D+%27Sample+Account+for+Entitlements%27
+          body: null
+          headers: *id001
+      response:
+          status: *id002
+          headers: *id003
+          body:
+              string: "{\n  \"totalSize\" : 6,\n  \"done\" : true,\n  \"records\" : [ {\n\
+                  \    \"attributes\" : {\n      \"type\" : \"Account\",\n      \"url\" : \"\
+                  /services/data/vxx.0/sobjects/Account/0018D00000H55iKQAR\"\n    },\n    \"\
+                  Id\" : \"0018D00000H55iKQAR\",\n    \"AccountNumber\" : null,\n    \"AccountSource\"\
+                  \ : null,\n    \"AnnualRevenue\" : null,\n    \"BillingCity\" : null,\n  \
+                  \  \"BillingCountry\" : null,\n    \"BillingGeocodeAccuracy\" : null,\n  \
+                  \  \"BillingLatitude\" : null,\n    \"BillingLongitude\" : null,\n    \"BillingPostalCode\"\
+                  \ : null,\n    \"BillingState\" : null,\n    \"BillingStreet\" : null,\n \
+                  \   \"CleanStatus\" : \"Pending\",\n    \"DunsNumber\" : null,\n    \"Fax\"\
+                  \ : null,\n    \"Industry\" : null,\n    \"Jigsaw\" : null,\n    \"NaicsCode\"\
+                  \ : null,\n    \"NaicsDesc\" : null,\n    \"Name\" : \"Account 5\",\n    \"\
+                  NumberOfEmployees\" : null,\n    \"Ownership\" : null,\n    \"Phone\" : null,\n\
+                  \    \"Rating\" : null,\n    \"ShippingCity\" : null,\n    \"ShippingCountry\"\
+                  \ : null,\n    \"ShippingGeocodeAccuracy\" : null,\n    \"ShippingLatitude\"\
+                  \ : null,\n    \"ShippingLongitude\" : null,\n    \"ShippingPostalCode\" :\
+                  \ null,\n    \"ShippingState\" : null,\n    \"ShippingStreet\" : null,\n \
+                  \   \"Sic\" : null,\n    \"SicDesc\" : null,\n    \"Site\" : null,\n    \"\
+                  TickerSymbol\" : null,\n    \"Tradestyle\" : null,\n    \"Type\" : null,\n\
+                  \    \"Website\" : null,\n    \"YearStarted\" : null,\n    \"ParentId\" :\
+                  \ null\n  }, {\n    \"attributes\" : {\n      \"type\" : \"Account\",\n  \
+                  \    \"url\" : \"/services/data/vxx.0/sobjects/Account/0018D00000H55iAQAR\"\
+                  \n    },\n    \"Id\" : \"0018D00000H55iAQAR\",\n    \"AccountNumber\" : null,\n\
+                  \    \"AccountSource\" : null,\n    \"AnnualRevenue\" : null,\n    \"BillingCity\"\
+                  \ : null,\n    \"BillingCountry\" : null,\n    \"BillingGeocodeAccuracy\"\
+                  \ : null,\n    \"BillingLatitude\" : null,\n    \"BillingLongitude\" : null,\n\
+                  \    \"BillingPostalCode\" : null,\n    \"BillingState\" : null,\n    \"BillingStreet\"\
+                  \ : null,\n    \"CleanStatus\" : \"Pending\",\n    \"DunsNumber\" : null,\n\
+                  \    \"Fax\" : null,\n    \"Industry\" : null,\n    \"Jigsaw\" : null,\n \
+                  \   \"NaicsCode\" : null,\n    \"NaicsDesc\" : null,\n    \"Name\" : \"Account\
+                  \ 3\",\n    \"NumberOfEmployees\" : null,\n    \"Ownership\" : null,\n   \
+                  \ \"Phone\" : null,\n    \"Rating\" : null,\n    \"ShippingCity\" : null,\n\
+                  \    \"ShippingCountry\" : null,\n    \"ShippingGeocodeAccuracy\" : null,\n\
+                  \    \"ShippingLatitude\" : null,\n    \"ShippingLongitude\" : null,\n   \
+                  \ \"ShippingPostalCode\" : null,\n    \"ShippingState\" : null,\n    \"ShippingStreet\"\
+                  \ : null,\n    \"Sic\" : null,\n    \"SicDesc\" : null,\n    \"Site\" : null,\n\
+                  \    \"TickerSymbol\" : null,\n    \"Tradestyle\" : null,\n    \"Type\" :\
+                  \ null,\n    \"Website\" : null,\n    \"YearStarted\" : null,\n    \"ParentId\"\
+                  \ : null\n  }, {\n    \"attributes\" : {\n      \"type\" : \"Account\",\n\
+                  \      \"url\" : \"/services/data/vxx.0/sobjects/Account/0018D00000H55hqQAB\"\
+                  \n    },\n    \"Id\" : \"0018D00000H55hqQAB\",\n    \"AccountNumber\" : null,\n\
+                  \    \"AccountSource\" : null,\n    \"AnnualRevenue\" : null,\n    \"BillingCity\"\
+                  \ : null,\n    \"BillingCountry\" : null,\n    \"BillingGeocodeAccuracy\"\
+                  \ : null,\n    \"BillingLatitude\" : null,\n    \"BillingLongitude\" : null,\n\
+                  \    \"BillingPostalCode\" : null,\n    \"BillingState\" : null,\n    \"BillingStreet\"\
+                  \ : null,\n    \"CleanStatus\" : \"Pending\",\n    \"DunsNumber\" : null,\n\
+                  \    \"Fax\" : null,\n    \"Industry\" : null,\n    \"Jigsaw\" : null,\n \
+                  \   \"NaicsCode\" : null,\n    \"NaicsDesc\" : null,\n    \"Name\" : \"Account\
+                  \ 0\",\n    \"NumberOfEmployees\" : null,\n    \"Ownership\" : null,\n   \
+                  \ \"Phone\" : null,\n    \"Rating\" : null,\n    \"ShippingCity\" : null,\n\
+                  \    \"ShippingCountry\" : null,\n    \"ShippingGeocodeAccuracy\" : null,\n\
+                  \    \"ShippingLatitude\" : null,\n    \"ShippingLongitude\" : null,\n   \
+                  \ \"ShippingPostalCode\" : null,\n    \"ShippingState\" : null,\n    \"ShippingStreet\"\
+                  \ : null,\n    \"Sic\" : null,\n    \"SicDesc\" : null,\n    \"Site\" : null,\n\
+                  \    \"TickerSymbol\" : null,\n    \"Tradestyle\" : null,\n    \"Type\" :\
+                  \ null,\n    \"Website\" : null,\n    \"YearStarted\" : null,\n    \"ParentId\"\
+                  \ : null\n  }, {\n    \"attributes\" : {\n      \"type\" : \"Account\",\n\
+                  \      \"url\" : \"/services/data/vxx.0/sobjects/Account/0018D00000H55hvQAB\"\
+                  \n    },\n    \"Id\" : \"0018D00000H55hvQAB\",\n    \"AccountNumber\" : null,\n\
+                  \    \"AccountSource\" : null,\n    \"AnnualRevenue\" : null,\n    \"BillingCity\"\
+                  \ : null,\n    \"BillingCountry\" : null,\n    \"BillingGeocodeAccuracy\"\
+                  \ : null,\n    \"BillingLatitude\" : null,\n    \"BillingLongitude\" : null,\n\
+                  \    \"BillingPostalCode\" : null,\n    \"BillingState\" : null,\n    \"BillingStreet\"\
+                  \ : null,\n    \"CleanStatus\" : \"Pending\",\n    \"DunsNumber\" : null,\n\
+                  \    \"Fax\" : null,\n    \"Industry\" : null,\n    \"Jigsaw\" : null,\n \
+                  \   \"NaicsCode\" : null,\n    \"NaicsDesc\" : null,\n    \"Name\" : \"Account\
+                  \ 1\",\n    \"NumberOfEmployees\" : null,\n    \"Ownership\" : null,\n   \
+                  \ \"Phone\" : null,\n    \"Rating\" : null,\n    \"ShippingCity\" : null,\n\
+                  \    \"ShippingCountry\" : null,\n    \"ShippingGeocodeAccuracy\" : null,\n\
+                  \    \"ShippingLatitude\" : null,\n    \"ShippingLongitude\" : null,\n   \
+                  \ \"ShippingPostalCode\" : null,\n    \"ShippingState\" : null,\n    \"ShippingStreet\"\
+                  \ : null,\n    \"Sic\" : null,\n    \"SicDesc\" : null,\n    \"Site\" : null,\n\
+                  \    \"TickerSymbol\" : null,\n    \"Tradestyle\" : null,\n    \"Type\" :\
+                  \ null,\n    \"Website\" : null,\n    \"YearStarted\" : null,\n    \"ParentId\"\
+                  \ : null\n  }, {\n    \"attributes\" : {\n      \"type\" : \"Account\",\n\
+                  \      \"url\" : \"/services/data/vxx.0/sobjects/Account/0018D00000H55iFQAR\"\
+                  \n    },\n    \"Id\" : \"0018D00000H55iFQAR\",\n    \"AccountNumber\" : null,\n\
+                  \    \"AccountSource\" : null,\n    \"AnnualRevenue\" : null,\n    \"BillingCity\"\
+                  \ : null,\n    \"BillingCountry\" : null,\n    \"BillingGeocodeAccuracy\"\
+                  \ : null,\n    \"BillingLatitude\" : null,\n    \"BillingLongitude\" : null,\n\
+                  \    \"BillingPostalCode\" : null,\n    \"BillingState\" : null,\n    \"BillingStreet\"\
+                  \ : null,\n    \"CleanStatus\" : \"Pending\",\n    \"DunsNumber\" : null,\n\
+                  \    \"Fax\" : null,\n    \"Industry\" : null,\n    \"Jigsaw\" : null,\n \
+                  \   \"NaicsCode\" : null,\n    \"NaicsDesc\" : null,\n    \"Name\" : \"Account\
+                  \ 4\",\n    \"NumberOfEmployees\" : null,\n    \"Ownership\" : null,\n   \
+                  \ \"Phone\" : null,\n    \"Rating\" : null,\n    \"ShippingCity\" : null,\n\
+                  \    \"ShippingCountry\" : null,\n    \"ShippingGeocodeAccuracy\" : null,\n\
+                  \    \"ShippingLatitude\" : null,\n    \"ShippingLongitude\" : null,\n   \
+                  \ \"ShippingPostalCode\" : null,\n    \"ShippingState\" : null,\n    \"ShippingStreet\"\
+                  \ : null,\n    \"Sic\" : null,\n    \"SicDesc\" : null,\n    \"Site\" : null,\n\
+                  \    \"TickerSymbol\" : null,\n    \"Tradestyle\" : null,\n    \"Type\" :\
+                  \ null,\n    \"Website\" : null,\n    \"YearStarted\" : null,\n    \"ParentId\"\
+                  \ : null\n  }, {\n    \"attributes\" : {\n      \"type\" : \"Account\",\n\
+                  \      \"url\" : \"/services/data/vxx.0/sobjects/Account/0018D00000H55i0QAB\"\
+                  \n    },\n    \"Id\" : \"0018D00000H55i0QAB\",\n    \"AccountNumber\" : null,\n\
+                  \    \"AccountSource\" : null,\n    \"AnnualRevenue\" : null,\n    \"BillingCity\"\
+                  \ : null,\n    \"BillingCountry\" : null,\n    \"BillingGeocodeAccuracy\"\
+                  \ : null,\n    \"BillingLatitude\" : null,\n    \"BillingLongitude\" : null,\n\
+                  \    \"BillingPostalCode\" : null,\n    \"BillingState\" : null,\n    \"BillingStreet\"\
+                  \ : null,\n    \"CleanStatus\" : \"Pending\",\n    \"DunsNumber\" : null,\n\
+                  \    \"Fax\" : null,\n    \"Industry\" : null,\n    \"Jigsaw\" : null,\n \
+                  \   \"NaicsCode\" : null,\n    \"NaicsDesc\" : null,\n    \"Name\" : \"Account\
+                  \ 2\",\n    \"NumberOfEmployees\" : null,\n    \"Ownership\" : null,\n   \
+                  \ \"Phone\" : null,\n    \"Rating\" : null,\n    \"ShippingCity\" : null,\n\
+                  \    \"ShippingCountry\" : null,\n    \"ShippingGeocodeAccuracy\" : null,\n\
+                  \    \"ShippingLatitude\" : null,\n    \"ShippingLongitude\" : null,\n   \
+                  \ \"ShippingPostalCode\" : null,\n    \"ShippingState\" : null,\n    \"ShippingStreet\"\
+                  \ : null,\n    \"Sic\" : null,\n    \"SicDesc\" : null,\n    \"Site\" : null,\n\
+                  \    \"TickerSymbol\" : null,\n    \"Tradestyle\" : null,\n    \"Type\" :\
+                  \ null,\n    \"Website\" : null,\n    \"YearStarted\" : null,\n    \"ParentId\"\
+                  \ : null\n  } ]\n}"
+    - &id007
+      include_file: GET_sobjects_Global_describe.yaml
+    - *id004
+    - request:
+          method: GET
+          uri: https://orgname.my.salesforce.com/services/data/vxx.0/query/?q=SELECT+Id+FROM+Account
+          body: null
+          headers: *id001
+      response:
+          status: *id002
+          headers: *id003
+          body:
+              string: "{\n  \"totalSize\" : 6,\n  \"done\" : true,\n  \"records\" : [ {\n\
+                  \    \"attributes\" : {\n      \"type\" : \"Account\",\n      \"url\" : \"\
+                  /services/data/vxx.0/sobjects/Account/0018D00000H55hqQAB\"\n    },\n    \"\
+                  Id\" : \"0018D00000H55hqQAB\"\n  }, {\n    \"attributes\" : {\n      \"type\"\
+                  \ : \"Account\",\n      \"url\" : \"/services/data/vxx.0/sobjects/Account/0018D00000H55hvQAB\"\
+                  \n    },\n    \"Id\" : \"0018D00000H55hvQAB\"\n  }, {\n    \"attributes\"\
+                  \ : {\n      \"type\" : \"Account\",\n      \"url\" : \"/services/data/vxx.0/sobjects/Account/0018D00000H55i0QAB\"\
+                  \n    },\n    \"Id\" : \"0018D00000H55i0QAB\"\n  }, {\n    \"attributes\"\
+                  \ : {\n      \"type\" : \"Account\",\n      \"url\" : \"/services/data/vxx.0/sobjects/Account/0018D00000H55iAQAR\"\
+                  \n    },\n    \"Id\" : \"0018D00000H55iAQAR\"\n  }, {\n    \"attributes\"\
+                  \ : {\n      \"type\" : \"Account\",\n      \"url\" : \"/services/data/vxx.0/sobjects/Account/0018D00000H55iFQAR\"\
+                  \n    },\n    \"Id\" : \"0018D00000H55iFQAR\"\n  }, {\n    \"attributes\"\
+                  \ : {\n      \"type\" : \"Account\",\n      \"url\" : \"/services/data/vxx.0/sobjects/Account/0018D00000H55iKQAR\"\
+                  \n    },\n    \"Id\" : \"0018D00000H55iKQAR\"\n  } ]\n}"
+    - &id008
+      include_file: GET_sobjects_Account_describe.yaml
+    - request:
+          method: DELETE
+          uri: https://orgname.my.salesforce.com/services/data/vxx.0/composite/sobjects?ids=0018D00000H55hqQAB,0018D00000H55hvQAB,0018D00000H55i0QAB,0018D00000H55iAQAR,0018D00000H55iFQAR,0018D00000H55iKQAR
+          body: null
+          headers: *id001
+      response:
+          status: *id002
+          headers: *id003
+          body:
+              string:
+                  "[ {\n  \"id\" : \"0018D00000H55hqQAB\",\n  \"success\" : true,\n  \"\
+                  errors\" : [ ]\n}, {\n  \"id\" : \"0018D00000H55hvQAB\",\n  \"success\" :\
+                  \ true,\n  \"errors\" : [ ]\n}, {\n  \"id\" : \"0018D00000H55i0QAB\",\n  \"\
+                  success\" : true,\n  \"errors\" : [ ]\n}, {\n  \"id\" : \"0018D00000H55iAQAR\"\
+                  ,\n  \"success\" : true,\n  \"errors\" : [ ]\n}, {\n  \"id\" : \"0018D00000H55iFQAR\"\
+                  ,\n  \"success\" : true,\n  \"errors\" : [ ]\n}, {\n  \"id\" : \"0018D00000H55iKQAR\"\
+                  ,\n  \"success\" : true,\n  \"errors\" : [ ]\n} ]"
+    - request:
+          method: GET
+          uri: https://orgname.my.salesforce.com/services/data/vxx.0/limits/recordCount?sObjects=Contact
+          body: null
+          headers: *id001
+      response:
+          status: *id002
+          headers: *id003
+          body:
+              string: "{\n  \"sObjects\" : [ ]\n}"
+    - request:
+          method: GET
+          uri: https://orgname.my.salesforce.com/services/data/vxx.0/query/?q=SELECT+Id+FROM+Contact
+          body: null
+          headers: *id001
+      response:
+          status: *id002
+          headers: *id003
+          body:
+              string: "{\n  \"totalSize\" : 0,\n  \"done\" : true,\n  \"records\" : [ ]\n}"
+    - request: *id005
+      response:
+          status: *id002
+          headers: *id003
+          body:
+              string: "{\n  \"totalSize\" : 1,\n  \"done\" : true,\n  \"records\" : [ {\n\
+                  \    \"attributes\" : {\n      \"type\" : \"AggregateResult\"\n    },\n  \
+                  \  \"expr0\" : 0\n  } ]\n}"
+    - *id006
+    - *id007
+    - *id008
+    - *id008
+    - request:
+          method: POST
+          uri: https://orgname.my.salesforce.com/services/data/vxx.0/composite/sobjects
+          body:
+              '{"allOrNone": false, "records": [{"CleanStatus": "Pending", "Name": "Account
+              5", "attributes": {"type": "Account"}}, {"CleanStatus": "Pending", "Name": "Account
+              3", "attributes": {"type": "Account"}}, {"CleanStatus": "Pending", "Name": "Account
+              0", "attributes": {"type": "Account"}}, {"CleanStatus": "Pending", "Name": "Account
+              1", "attributes": {"type": "Account"}}, {"CleanStatus": "Pending", "Name": "Account
+              4", "attributes": {"type": "Account"}}, {"CleanStatus": "Pending", "Name": "Account
+              2", "attributes": {"type": "Account"}}]}'
+          headers: *id001
+      response:
+          status: *id002
+          headers: *id003
+          body:
+              string:
+                  "[ {\n  \"id\" : \"0018D00000H55iUQAR\",\n  \"success\" : true,\n  \"\
+                  errors\" : [ ]\n}, {\n  \"id\" : \"0018D00000H55iVQAR\",\n  \"success\" :\
+                  \ true,\n  \"errors\" : [ ]\n}, {\n  \"id\" : \"0018D00000H55iWQAR\",\n  \"\
+                  success\" : true,\n  \"errors\" : [ ]\n}, {\n  \"id\" : \"0018D00000H55iXQAR\"\
+                  ,\n  \"success\" : true,\n  \"errors\" : [ ]\n}, {\n  \"id\" : \"0018D00000H55iYQAR\"\
+                  ,\n  \"success\" : true,\n  \"errors\" : [ ]\n}, {\n  \"id\" : \"0018D00000H55iZQAR\"\
+                  ,\n  \"success\" : true,\n  \"errors\" : [ ]\n} ]"
+    - *id008
+    - *id007
+    - *id009
diff --git a/cumulusci/core/tests/cassettes/TestDatasetsE2E.test_datasets_extract_standard_objects.yaml b/cumulusci/core/tests/cassettes/TestDatasetsE2E.test_datasets_extract_standard_objects.yaml
new file mode 100644
index 000000000..184698e31
--- /dev/null
+++ b/cumulusci/core/tests/cassettes/TestDatasetsE2E.test_datasets_extract_standard_objects.yaml
@@ -0,0 +1,180 @@
+version: 1
+interactions:
+    - include_file: GET_sobjects_Organization.yaml
+    - &id001
+      include_file: GET_sobjects_Global_describe.yaml
+    - include_file: GET_sobjects_Account_describe.yaml
+    - *id001
+    - include_file: GET_sobjects_Contact_describe.yaml
+    - *id001
+    - include_file: GET_sobjects_Opportunity_describe.yaml
+    - request:
+          method: GET
+          uri: https://orgname.my.salesforce.com/services/data/vxx.0/limits/recordCount?sObjects=Account
+          body: null
+          headers: &id002
+              Request-Headers:
+                  - Elided
+      response:
+          status: &id003
+              code: 200
+              message: OK
+          headers: &id004
+              Content-Type:
+                  - application/json;charset=UTF-8
+              Others: Elided
+          body:
+              string:
+                  "{\n  \"sObjects\" : [ {\n    \"count\" : 1,\n    \"name\" : \"Account\"\
+                  \n  } ]\n}"
+    - request:
+          method: GET
+          uri: https://orgname.my.salesforce.com/services/data/vxx.0/query/?q=SELECT+Id%2C+AccountNumber%2C+AccountSource%2C+AnnualRevenue%2C+BillingCity%2C+BillingCountry%2C+BillingGeocodeAccuracy%2C+BillingLatitude%2C+BillingLongitude%2C+BillingPostalCode%2C+BillingState%2C+BillingStreet%2C+CleanStatus%2C+Description%2C+DunsNumber%2C+Fax%2C+Industry%2C+Jigsaw%2C+NaicsCode%2C+NaicsDesc%2C+Name%2C+NumberOfEmployees%2C+Ownership%2C+Phone%2C+Rating%2C+ShippingCity%2C+ShippingCountry%2C+ShippingGeocodeAccuracy%2C+ShippingLatitude%2C+ShippingLongitude%2C+ShippingPostalCode%2C+ShippingState%2C+ShippingStreet%2C+Sic%2C+SicDesc%2C+Site%2C+TickerSymbol%2C+Tradestyle%2C+Type%2C+Website%2C+YearStarted%2C+ParentId+FROM+Account+WHERE+Name+%21%3D+%27Sample+Account+for+Entitlements%27
+          body: null
+          headers: *id002
+      response:
+          status: *id003
+          headers: *id004
+          body:
+              string: "{\n  \"totalSize\" : 6,\n  \"done\" : true,\n  \"records\" : [ {\n\
+                  \    \"attributes\" : {\n      \"type\" : \"Account\",\n      \"url\" : \"\
+                  /services/data/vxx.0/sobjects/Account/0018D00000H55ipQAB\"\n    },\n    \"\
+                  Id\" : \"0018D00000H55ipQAB\",\n    \"AccountNumber\" : null,\n    \"AccountSource\"\
+                  \ : null,\n    \"AnnualRevenue\" : null,\n    \"BillingCity\" : null,\n  \
+                  \  \"BillingCountry\" : null,\n    \"BillingGeocodeAccuracy\" : null,\n  \
+                  \  \"BillingLatitude\" : null,\n    \"BillingLongitude\" : null,\n    \"BillingPostalCode\"\
+                  \ : null,\n    \"BillingState\" : null,\n    \"BillingStreet\" : null,\n \
+                  \   \"CleanStatus\" : \"Pending\",\n    \"Description\" : null,\n    \"DunsNumber\"\
+                  \ : null,\n    \"Fax\" : null,\n    \"Industry\" : null,\n    \"Jigsaw\" :\
+                  \ null,\n    \"NaicsCode\" : null,\n    \"NaicsDesc\" : null,\n    \"Name\"\
+                  \ : \"Account 3\",\n    \"NumberOfEmployees\" : null,\n    \"Ownership\" :\
+                  \ null,\n    \"Phone\" : null,\n    \"Rating\" : null,\n    \"ShippingCity\"\
+                  \ : null,\n    \"ShippingCountry\" : null,\n    \"ShippingGeocodeAccuracy\"\
+                  \ : null,\n    \"ShippingLatitude\" : null,\n    \"ShippingLongitude\" : null,\n\
+                  \    \"ShippingPostalCode\" : null,\n    \"ShippingState\" : null,\n    \"\
+                  ShippingStreet\" : null,\n    \"Sic\" : null,\n    \"SicDesc\" : null,\n \
+                  \   \"Site\" : null,\n    \"TickerSymbol\" : null,\n    \"Tradestyle\" : null,\n\
+                  \    \"Type\" : null,\n    \"Website\" : null,\n    \"YearStarted\" : null,\n\
+                  \    \"ParentId\" : null\n  }, {\n    \"attributes\" : {\n      \"type\" :\
+                  \ \"Account\",\n      \"url\" : \"/services/data/vxx.0/sobjects/Account/0018D00000H55hYQAR\"\
+                  \n    },\n    \"Id\" : \"0018D00000H55hYQAR\",\n    \"AccountNumber\" : null,\n\
+                  \    \"AccountSource\" : null,\n    \"AnnualRevenue\" : null,\n    \"BillingCity\"\
+                  \ : null,\n    \"BillingCountry\" : null,\n    \"BillingGeocodeAccuracy\"\
+                  \ : null,\n    \"BillingLatitude\" : null,\n    \"BillingLongitude\" : null,\n\
+                  \    \"BillingPostalCode\" : null,\n    \"BillingState\" : null,\n    \"BillingStreet\"\
+                  \ : null,\n    \"CleanStatus\" : \"Pending\",\n    \"Description\" : null,\n\
+                  \    \"DunsNumber\" : null,\n    \"Fax\" : null,\n    \"Industry\" : null,\n\
+                  \    \"Jigsaw\" : null,\n    \"NaicsCode\" : null,\n    \"NaicsDesc\" : null,\n\
+                  \    \"Name\" : \"Account 0\",\n    \"NumberOfEmployees\" : null,\n    \"\
+                  Ownership\" : null,\n    \"Phone\" : null,\n    \"Rating\" : null,\n    \"\
+                  ShippingCity\" : null,\n    \"ShippingCountry\" : null,\n    \"ShippingGeocodeAccuracy\"\
+                  \ : null,\n    \"ShippingLatitude\" : null,\n    \"ShippingLongitude\" : null,\n\
+                  \    \"ShippingPostalCode\" : null,\n    \"ShippingState\" : null,\n    \"\
+                  ShippingStreet\" : null,\n    \"Sic\" : null,\n    \"SicDesc\" : null,\n \
+                  \   \"Site\" : null,\n    \"TickerSymbol\" : null,\n    \"Tradestyle\" : null,\n\
+                  \    \"Type\" : null,\n    \"Website\" : null,\n    \"YearStarted\" : null,\n\
+                  \    \"ParentId\" : null\n  }, {\n    \"attributes\" : {\n      \"type\" :\
+                  \ \"Account\",\n      \"url\" : \"/services/data/vxx.0/sobjects/Account/0018D00000H55ifQAB\"\
+                  \n    },\n    \"Id\" : \"0018D00000H55ifQAB\",\n    \"AccountNumber\" : null,\n\
+                  \    \"AccountSource\" : null,\n    \"AnnualRevenue\" : null,\n    \"BillingCity\"\
+                  \ : null,\n    \"BillingCountry\" : null,\n    \"BillingGeocodeAccuracy\"\
+                  \ : null,\n    \"BillingLatitude\" : null,\n    \"BillingLongitude\" : null,\n\
+                  \    \"BillingPostalCode\" : null,\n    \"BillingState\" : null,\n    \"BillingStreet\"\
+                  \ : null,\n    \"CleanStatus\" : \"Pending\",\n    \"Description\" : null,\n\
+                  \    \"DunsNumber\" : null,\n    \"Fax\" : null,\n    \"Industry\" : null,\n\
+                  \    \"Jigsaw\" : null,\n    \"NaicsCode\" : null,\n    \"NaicsDesc\" : null,\n\
+                  \    \"Name\" : \"Account 1\",\n    \"NumberOfEmployees\" : null,\n    \"\
+                  Ownership\" : null,\n    \"Phone\" : null,\n    \"Rating\" : null,\n    \"\
+                  ShippingCity\" : null,\n    \"ShippingCountry\" : null,\n    \"ShippingGeocodeAccuracy\"\
+                  \ : null,\n    \"ShippingLatitude\" : null,\n    \"ShippingLongitude\" : null,\n\
+                  \    \"ShippingPostalCode\" : null,\n    \"ShippingState\" : null,\n    \"\
+                  ShippingStreet\" : null,\n    \"Sic\" : null,\n    \"SicDesc\" : null,\n \
+                  \   \"Site\" : null,\n    \"TickerSymbol\" : null,\n    \"Tradestyle\" : null,\n\
+                  \    \"Type\" : null,\n    \"Website\" : null,\n    \"YearStarted\" : null,\n\
+                  \    \"ParentId\" : null\n  }, {\n    \"attributes\" : {\n      \"type\" :\
+                  \ \"Account\",\n      \"url\" : \"/services/data/vxx.0/sobjects/Account/0018D00000H55izQAB\"\
+                  \n    },\n    \"Id\" : \"0018D00000H55izQAB\",\n    \"AccountNumber\" : null,\n\
+                  \    \"AccountSource\" : null,\n    \"AnnualRevenue\" : null,\n    \"BillingCity\"\
+                  \ : null,\n    \"BillingCountry\" : null,\n    \"BillingGeocodeAccuracy\"\
+                  \ : null,\n    \"BillingLatitude\" : null,\n    \"BillingLongitude\" : null,\n\
+                  \    \"BillingPostalCode\" : null,\n    \"BillingState\" : null,\n    \"BillingStreet\"\
+                  \ : null,\n    \"CleanStatus\" : \"Pending\",\n    \"Description\" : null,\n\
+                  \    \"DunsNumber\" : null,\n    \"Fax\" : null,\n    \"Industry\" : null,\n\
+                  \    \"Jigsaw\" : null,\n    \"NaicsCode\" : null,\n    \"NaicsDesc\" : null,\n\
+                  \    \"Name\" : \"Account 5\",\n    \"NumberOfEmployees\" : null,\n    \"\
+                  Ownership\" : null,\n    \"Phone\" : null,\n    \"Rating\" : null,\n    \"\
+                  ShippingCity\" : null,\n    \"ShippingCountry\" : null,\n    \"ShippingGeocodeAccuracy\"\
+                  \ : null,\n    \"ShippingLatitude\" : null,\n    \"ShippingLongitude\" : null,\n\
+                  \    \"ShippingPostalCode\" : null,\n    \"ShippingState\" : null,\n    \"\
+                  ShippingStreet\" : null,\n    \"Sic\" : null,\n    \"SicDesc\" : null,\n \
+                  \   \"Site\" : null,\n    \"TickerSymbol\" : null,\n    \"Tradestyle\" : null,\n\
+                  \    \"Type\" : null,\n    \"Website\" : null,\n    \"YearStarted\" : null,\n\
+                  \    \"ParentId\" : null\n  }, {\n    \"attributes\" : {\n      \"type\" :\
+                  \ \"Account\",\n      \"url\" : \"/services/data/vxx.0/sobjects/Account/0018D00000H55ikQAB\"\
+                  \n    },\n    \"Id\" : \"0018D00000H55ikQAB\",\n    \"AccountNumber\" : null,\n\
+                  \    \"AccountSource\" : null,\n    \"AnnualRevenue\" : null,\n    \"BillingCity\"\
+                  \ : null,\n    \"BillingCountry\" : null,\n    \"BillingGeocodeAccuracy\"\
+                  \ : null,\n    \"BillingLatitude\" : null,\n    \"BillingLongitude\" : null,\n\
+                  \    \"BillingPostalCode\" : null,\n    \"BillingState\" : null,\n    \"BillingStreet\"\
+                  \ : null,\n    \"CleanStatus\" : \"Pending\",\n    \"Description\" : null,\n\
+                  \    \"DunsNumber\" : null,\n    \"Fax\" : null,\n    \"Industry\" : null,\n\
+                  \    \"Jigsaw\" : null,\n    \"NaicsCode\" : null,\n    \"NaicsDesc\" : null,\n\
+                  \    \"Name\" : \"Account 2\",\n    \"NumberOfEmployees\" : null,\n    \"\
+                  Ownership\" : null,\n    \"Phone\" : null,\n    \"Rating\" : null,\n    \"\
+                  ShippingCity\" : null,\n    \"ShippingCountry\" : null,\n    \"ShippingGeocodeAccuracy\"\
+                  \ : null,\n    \"ShippingLatitude\" : null,\n    \"ShippingLongitude\" : null,\n\
+                  \    \"ShippingPostalCode\" : null,\n    \"ShippingState\" : null,\n    \"\
+                  ShippingStreet\" : null,\n    \"Sic\" : null,\n    \"SicDesc\" : null,\n \
+                  \   \"Site\" : null,\n    \"TickerSymbol\" : null,\n    \"Tradestyle\" : null,\n\
+                  \    \"Type\" : null,\n    \"Website\" : null,\n    \"YearStarted\" : null,\n\
+                  \    \"ParentId\" : null\n  }, {\n    \"attributes\" : {\n      \"type\" :\
+                  \ \"Account\",\n      \"url\" : \"/services/data/vxx.0/sobjects/Account/0018D00000H55iuQAB\"\
+                  \n    },\n    \"Id\" : \"0018D00000H55iuQAB\",\n    \"AccountNumber\" : null,\n\
+                  \    \"AccountSource\" : null,\n    \"AnnualRevenue\" : null,\n    \"BillingCity\"\
+                  \ : null,\n    \"BillingCountry\" : null,\n    \"BillingGeocodeAccuracy\"\
+                  \ : null,\n    \"BillingLatitude\" : null,\n    \"BillingLongitude\" : null,\n\
+                  \    \"BillingPostalCode\" : null,\n    \"BillingState\" : null,\n    \"BillingStreet\"\
+                  \ : null,\n    \"CleanStatus\" : \"Pending\",\n    \"Description\" : null,\n\
+                  \    \"DunsNumber\" : null,\n    \"Fax\" : null,\n    \"Industry\" : null,\n\
+                  \    \"Jigsaw\" : null,\n    \"NaicsCode\" : null,\n    \"NaicsDesc\" : null,\n\
+                  \    \"Name\" : \"Account 4\",\n    \"NumberOfEmployees\" : null,\n    \"\
+                  Ownership\" : null,\n    \"Phone\" : null,\n    \"Rating\" : null,\n    \"\
+                  ShippingCity\" : null,\n    \"ShippingCountry\" : null,\n    \"ShippingGeocodeAccuracy\"\
+                  \ : null,\n    \"ShippingLatitude\" : null,\n    \"ShippingLongitude\" : null,\n\
+                  \    \"ShippingPostalCode\" : null,\n    \"ShippingState\" : null,\n    \"\
+                  ShippingStreet\" : null,\n    \"Sic\" : null,\n    \"SicDesc\" : null,\n \
+                  \   \"Site\" : null,\n    \"TickerSymbol\" : null,\n    \"Tradestyle\" : null,\n\
+                  \    \"Type\" : null,\n    \"Website\" : null,\n    \"YearStarted\" : null,\n\
+                  \    \"ParentId\" : null\n  } ]\n}"
+    - request:
+          method: GET
+          uri: https://orgname.my.salesforce.com/services/data/vxx.0/limits/recordCount?sObjects=Contact
+          body: null
+          headers: *id002
+      response: &id005
+          status: *id003
+          headers: *id004
+          body:
+              string: "{\n  \"sObjects\" : [ ]\n}"
+    - request:
+          method: GET
+          uri: https://orgname.my.salesforce.com/services/data/vxx.0/query/?q=SELECT+Id%2C+AssistantName%2C+AssistantPhone%2C+Birthdate%2C+CleanStatus%2C+Department%2C+Description%2C+Email%2C+EmailBouncedDate%2C+EmailBouncedReason%2C+Fax%2C+FirstName%2C+HomePhone%2C+Jigsaw%2C+LastName%2C+LeadSource%2C+MailingCity%2C+MailingCountry%2C+MailingGeocodeAccuracy%2C+MailingLatitude%2C+MailingLongitude%2C+MailingPostalCode%2C+MailingState%2C+MailingStreet%2C+MobilePhone%2C+OtherCity%2C+OtherCountry%2C+OtherGeocodeAccuracy%2C+OtherLatitude%2C+OtherLongitude%2C+OtherPhone%2C+OtherPostalCode%2C+OtherState%2C+OtherStreet%2C+Phone%2C+Salutation%2C+Title%2C+AccountId%2C+ReportsToId+FROM+Contact
+          body: null
+          headers: *id002
+      response: &id006
+          status: *id003
+          headers: *id004
+          body:
+              string: "{\n  \"totalSize\" : 0,\n  \"done\" : true,\n  \"records\" : [ ]\n}"
+    - request:
+          method: GET
+          uri: https://orgname.my.salesforce.com/services/data/vxx.0/limits/recordCount?sObjects=Opportunity
+          body: null
+          headers: *id002
+      response: *id005
+    - request:
+          method: GET
+          uri: https://orgname.my.salesforce.com/services/data/vxx.0/query/?q=SELECT+Id%2C+Amount%2C+CloseDate%2C+Description%2C+ForecastCategoryName%2C+IsPrivate%2C+LeadSource%2C+Name%2C+NextStep%2C+Probability%2C+StageName%2C+TotalOpportunityQuantity%2C+Type%2C+AccountId%2C+ContactId+FROM+Opportunity
+          body: null
+          headers: *id002
+      response: *id006
diff --git a/cumulusci/core/tests/test_datasets_e2e.py b/cumulusci/core/tests/test_datasets_e2e.py
new file mode 100644
index 000000000..dc1aadb8d
--- /dev/null
+++ b/cumulusci/core/tests/test_datasets_e2e.py
@@ -0,0 +1,170 @@
+import time
+from shutil import rmtree
+from unittest.mock import patch
+
+import pytest
+
+from cumulusci.core.datasets import Dataset
+from cumulusci.salesforce_api.org_schema import Filters
+from cumulusci.tasks.bulkdata.extract_dataset_utils.tests.test_synthesize_extract_declarations import (
+    _fake_get_org_schema,
+    describe_for,
+)
+from cumulusci.tasks.bulkdata.tests.integration_test_utils import ensure_accounts
+
+ensure_accounts = ensure_accounts
+
+
+class Timer:
+    def __init__(self):
+        self.old_time = time.time()
+
+    def checkpoint(self, name):
+        cur = time.time()
+        print(name, cur - self.old_time)
+        self.old_time = time.time()
+
+
+@pytest.mark.vcr()
+class TestDatasetsE2E:
+    def test_datasets_e2e(
+        self,
+        sf,
+        project_config,
+        org_config,
+        delete_data_from_org,
+        ensure_accounts,
+        run_code_without_recording,
+    ):
+        timer = Timer()
+        timer.checkpoint("Started")
+        object_counts = {"Account": 6, "Contact": 1, "Opportunity": 5}
+        obj_describes = (
+            describe_for("Account"),
+            describe_for("Contact"),
+            describe_for("Opportunity"),
+        )
+
+        with patch.object(
+            type(org_config), "is_person_accounts_enabled", False
+        ), _fake_get_org_schema(
+            org_config,
+            obj_describes,
+            object_counts,
+            include_counts=True,
+            filters=[Filters.extractable, Filters.createable],
+            included_objects=["Account", "Contact", "Opportunity"],
+        ) as schema, ensure_accounts(
+            6
+        ), Dataset(
+            "foo", project_config, sf, org_config, schema=schema
+        ) as dataset:
+            timer.checkpoint("In Dataset")
+            if dataset.path.exists():
+                rmtree(dataset.path)
+
+            self.demo_dataset(
+                dataset, timer, sf, delete_data_from_org, run_code_without_recording
+            )
+
+    def demo_dataset(
+        self, dataset, timer, sf, delete_data_from_org, run_code_without_recording
+    ):
+        def count(sobject):
+            return sf.query(f"select count(Id) from {sobject}")["records"][0]["expr0"]
+
+        assert count("Account") == 6
+        timer.checkpoint("Before create")
+        # Create a dataset directory and associated files
+        dataset.create()
+        timer.checkpoint("After create")
+
+        # Read selected schema objects and fields into a T.Dict[str, T.List[str]]
+        # Datastructure like {"Account": ["Name", "Description"]}
+        objs = dataset.read_schema_subset()
+        timer.checkpoint("Read Subset")
+
+        # Save a similar datastructure back to the file system
+        objs = {"Account": objs["Account"]}
+        objs["Account"].remove("Description")
+        objs["Account"].remove("History__c")
+        objs["Account"].remove("ns__Description__c")
+        objs["Account"].remove("Primary_Contact__c")
+        dataset.update_schema_subset(objs)
+        timer.checkpoint("Updated Subset")
+
+        # Read full schema in a Datastructure like {"Account": {"Name": True, "Description": False}}
+        objs = dataset.read_which_fields_selected()
+        assert objs["Account"]["Name"] is True
+        assert objs["Account"]["Description"] is False
+        timer.checkpoint("Read selected")
+
+        # Run an extract to the filesystem
+        with patch("cumulusci.tasks.bulkdata.extract.validate_and_inject_mapping"):
+            dataset.extract()
+        timer.checkpoint("Extract")
+
+        run_code_without_recording(
+            lambda: delete_data_from_org(
+                [
+                    "Account",
+                    "Contact",
+                ]
+            )
+        )
+        timer.checkpoint("Deleted")
+
+        assert count("Account") == 0
+        dataset.load()
+        timer.checkpoint("Loaded")
+
+        assert count("Account") == 6
+        timer.checkpoint("Verified")
+
+    def test_datasets_extract_standard_objects(
+        self, sf, project_config, org_config, delete_data_from_org, ensure_accounts
+    ):
+        timer = Timer()
+        timer.checkpoint("Started")
+        object_counts = {"Account": 6, "Contact": 1, "Opportunity": 5}
+        obj_describes = (
+            describe_for("Account"),
+            describe_for("Contact"),
+            describe_for("Opportunity"),
+        )
+        with patch.object(type(org_config), "is_person_accounts_enabled", False), patch(
+            "cumulusci.core.datasets.get_org_schema",
+            lambda _sf, org_config, **kwargs: _fake_get_org_schema(
+                org_config,
+                obj_describes,
+                object_counts,
+                included_objects=["Account", "Contact", "Opportunity"],
+                **kwargs,
+            ),
+        ), ensure_accounts(6), Dataset(
+            "bar", project_config, sf, org_config
+        ) as dataset:
+            timer.checkpoint("In Dataset")
+            if dataset.path.exists():
+                rmtree(dataset.path)
+
+            dataset.create()
+            timer.checkpoint("After create")
+
+            # Read selected schema objects and fields into a T.Dict[str, T.List[str]]
+            # Datastructure like {"Account": ["Name", "Description"]}
+            objs = dataset.read_schema_subset()
+            timer.checkpoint("Read Subset")
+
+            # Save a similar datastructure back to the file system
+            objs = {
+                objname: [f for f in fields if "__" not in f]
+                for objname, fields in objs.items()
+                if objname not in ["EdgeMartDataFile"] and "__" not in objname
+            }
+            dataset.update_schema_subset(objs)
+            timer.checkpoint("Updated Subset")
+
+            # Run an extract to the filesystem
+            dataset.extract()
+            timer.checkpoint("Extract")