Repository: OpenAssetIO/OpenAssetIO (language: python)

Issue:
Conveniences for `entityTraits`
# Acceptance Criteria
- Variant return convenience for `entityTraits` added to `Manager`
- Direct return exceptional return convenience for `entityTraits` added to `Manager`
- Singular/Batch.
- New methods bound to python
- New methods tested via the established test pattern in python
- Methods should be of signiature specified in linked notebook


Discussion / hints:

Fix patch:
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 3f615665b..b73ba69cc 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -11,6 +11,12 @@ v1.0.0-beta.x.y
   non-normalised device paths (i.e. `\\?\`-prefixed).
   [#1257](https://github.com/OpenAssetIO/OpenAssetIO/issues/1257)
 
+- Added `entityTraits` overloads for convenience, providing alternatives
+  to the core callback-based workflow. Includes a more direct method for
+  resolving a single entity reference, and exception vs. result object
+  workflows.
+  [#1217](https://github.com/OpenAssetIO/OpenAssetIO/issues/1217)
+
 v1.0.0-beta.2.0
 ---------------
 
diff --git a/src/openassetio-core/include/openassetio/hostApi/Manager.hpp b/src/openassetio-core/include/openassetio/hostApi/Manager.hpp
index 5fd10996d..95c01dce8 100644
--- a/src/openassetio-core/include/openassetio/hostApi/Manager.hpp
+++ b/src/openassetio-core/include/openassetio/hostApi/Manager.hpp
@@ -836,6 +836,160 @@ class OPENASSETIO_CORE_EXPORT Manager final {
                             const ContextConstPtr& context,
                             const EntityTraitsSuccessCallback& successCallback,
                             const BatchElementErrorCallback& errorCallback);
+
+  /**
+   * Retrieve the @ref trait_set of an @ref entity.
+   *
+   * See documentation for the <!--
+   * --> @ref entityTraits(const EntityReferences&, <!--
+   * --> access::EntityTraitsAccess ,const ContextConstPtr&, <!--
+   * --> const EntityTraitsSuccessCallback&, <!--
+   * --> const BatchElementErrorCallback& errorCallback)
+   * "callback variation" for more details on resolution behaviour.
+   *
+   * Errors that occur will be thrown as an exception, either from the
+   * @ref manager plugin (for errors not specific to the entity
+   * reference) or as a @fqref{errors.BatchElementException}
+   * "BatchElementException"-derived error.
+   *
+   * @param entityReference Entity reference to query.
+   *
+   * @param entityTraitsAccess The intended usage of the data.
+   *
+   * @param context The calling context.
+   *
+   * @param errorPolicyTag  Parameter for selecting the appropriate
+   * overload (tagged dispatch idiom). See @ref
+   * BatchElementErrorPolicyTag::Exception.
+   *
+   * @return Populated trait set.
+   *
+   * @throws errors.BatchElementException Converted exception thrown
+   * when the manager emits a @fqref{errors.BatchElementError}
+   * "BatchElementError".
+   */
+  trait::TraitSet entityTraits(const EntityReference& entityReference,
+                               access::EntityTraitsAccess entityTraitsAccess,
+                               const ContextConstPtr& context,
+                               const BatchElementErrorPolicyTag::Exception& errorPolicyTag = {});
+
+  /**
+   * Provides either a populated @ref trait_set or a
+   * @fqref{errors.BatchElementError} "BatchElementError".
+   *
+   * If successful, the result is populated with the trait set of the
+   * @ref entity.
+   *
+   * Otherwise, the result is populated with an error object detailing
+   * the reason for the failure to retrieve the traits this particular
+   * entity.
+   *
+   * Errors that are not specific to the entity being queried will be
+   * thrown as an exception.
+   *
+   * See documentation for the <!--
+   * --> @ref entityTraits(const EntityReferences&, <!--
+   * --> access::EntityTraitsAccess, const ContextConstPtr&, <!--
+   * --> const EntityTraitsSuccessCallback&, <!--
+   * --> const BatchElementErrorCallback& errorCallback)
+   * "callback variation" for more details on resolution behaviour.
+   *
+   * @param entityReference Entity reference to query.
+   *
+   * @param entityTraitsAccess The intended usage of the data.
+   *
+   * @param context The calling context.
+   *
+   * @param errorPolicyTag  Parameter for selecting the appropriate
+   * overload (tagged dispatch idiom). See @ref
+   * BatchElementErrorPolicyTag::Variant.
+   *
+   * @return Object containing either the populated trait set or an
+   * error object.
+   */
+  std::variant<errors::BatchElementError, trait::TraitSet> entityTraits(
+      const EntityReference& entityReference, access::EntityTraitsAccess entityTraitsAccess,
+      const ContextConstPtr& context, const BatchElementErrorPolicyTag::Variant& errorPolicyTag);
+
+  /**
+   * Retrieve the @ref trait_set of one or more @ref entity "entities".
+   *
+   * See documentation for the <!--
+   * --> @ref entityTraits(const EntityReferences&, <!--
+   * --> access::EntityTraitsAccess, const ContextConstPtr&, <!--
+   * --> const EntityTraitsSuccessCallback&, <!--
+   * --> const BatchElementErrorCallback& errorCallback)
+   * "callback variation" for more details on resolution behaviour.
+   *
+   * Any errors that occur will be immediately thrown as an exception,
+   * either from the @ref manager plugin (for errors not specific to the
+   * entity reference) or as a @fqref{errors.BatchElementException}
+   * "BatchElementException"-derived error.
+   *
+   * @param entityReferences Entity references to query.
+   *
+   * @param entityTraitsAccess The intended usage of the data.
+   *
+   * @param context The calling context.
+   *
+   * @param errorPolicyTag  Parameter for selecting the appropriate
+   * overload (tagged dispatch idiom). See @ref
+   * BatchElementErrorPolicyTag::Exception.
+   *
+   * @return List of populated trait sets.
+   *
+   * @throws errors.BatchElementException Converted exception thrown
+   * when the manager emits a @fqref{errors.BatchElementError}
+   * "BatchElementError".
+   * @throws errors.NotImplementedException Thrown when this method is
+   * not implemented by the manager. Check that this method is
+   * implemented before use by calling @ref hasCapability with @ref
+   * Capability.kResolution.
+   */
+  std::vector<trait::TraitSet> entityTraits(
+      const EntityReferences& entityReferences, access::EntityTraitsAccess entityTraitsAccess,
+      const ContextConstPtr& context,
+      const BatchElementErrorPolicyTag::Exception& errorPolicyTag = {});
+
+  /**
+   * Provides either a populated @ref trait_set or a
+   * @fqref{errors.BatchElementError} "BatchElementError" for each given
+   * @ref entity_reference.
+   *
+   * For successful references, the corresponding element of the result
+   * is populated with its trait set.
+   *
+   * Otherwise, the corresponding element of the result is populated
+   * with an error object detailing the reason for the failure to
+   * entityTraits that particular entity.
+   *
+   * Errors that are not specific to an entity will be thrown as an
+   * exception, failing the whole batch.
+   *
+   * See documentation for the <!--
+   * --> @ref entityTraits(const EntityReferences&, <!--
+   * --> access::EntityTraitsAccess, const ContextConstPtr&, <!--
+   * --> const EntityTraitsSuccessCallback&, <!--
+   * --> const BatchElementErrorCallback& errorCallback)
+   * "callback variation" for more details on resolution behaviour.
+   *
+   * @param entityReferences Entity references to query.
+   *
+   * @param entityTraitsAccess The intended usage of the data.
+   *
+   * @param context The calling context.
+   *
+   * @param errorPolicyTag  Parameter for selecting the appropriate
+   * overload (tag dispatch idiom). See @ref
+   * BatchElementErrorPolicyTag::Variant.
+   *
+   * @return List of objects, each containing either the populated trait
+   * set or an error.
+   */
+  std::vector<std::variant<errors::BatchElementError, trait::TraitSet>> entityTraits(
+      const EntityReferences& entityReferences, access::EntityTraitsAccess entityTraitsAccess,
+      const ContextConstPtr& context, const BatchElementErrorPolicyTag::Variant& errorPolicyTag);
+
   /**
    * @}
    */
diff --git a/src/openassetio-core/src/hostApi/ManagerConveniences.cpp b/src/openassetio-core/src/hostApi/ManagerConveniences.cpp
index 366801d8c..5bfa48d7e 100644
--- a/src/openassetio-core/src/hostApi/ManagerConveniences.cpp
+++ b/src/openassetio-core/src/hostApi/ManagerConveniences.cpp
@@ -19,6 +19,89 @@ namespace hostApi {
 // alternate, often friendlier signatures wrapping the core batch-first
 // callback-based member functions found in `Manager.cpp`
 
+// Singular Except
+trait::TraitSet hostApi::Manager::entityTraits(
+    const EntityReference &entityReference, const access::EntityTraitsAccess entityTraitsAccess,
+    const ContextConstPtr &context,
+    [[maybe_unused]] const BatchElementErrorPolicyTag::Exception &errorPolicyTag) {
+  trait::TraitSet result;
+  entityTraits(
+      {entityReference}, entityTraitsAccess, context,
+      [&result]([[maybe_unused]] std::size_t index, trait::TraitSet traitSet) {
+        result = std::move(traitSet);
+      },
+      [&entityReference, entityTraitsAccess](std::size_t index, errors::BatchElementError error) {
+        auto msg = errors::createBatchElementExceptionMessage(
+            error, index, entityReference,
+            static_cast<internal::access::Access>(entityTraitsAccess));
+        throw errors::BatchElementException(index, std::move(error), msg);
+      });
+
+  return result;
+}
+
+// Singular variant
+std::variant<errors::BatchElementError, trait::TraitSet> hostApi::Manager::entityTraits(
+    const EntityReference &entityReference, const access::EntityTraitsAccess entityTraitsAccess,
+    const ContextConstPtr &context,
+    [[maybe_unused]] const BatchElementErrorPolicyTag::Variant &errorPolicyTag) {
+  std::variant<errors::BatchElementError, trait::TraitSet> result;
+  entityTraits(
+      {entityReference}, entityTraitsAccess, context,
+      [&result]([[maybe_unused]] std::size_t index, trait::TraitSet traitSet) {
+        result = std::move(traitSet);
+      },
+      [&result]([[maybe_unused]] std::size_t index, errors::BatchElementError error) {
+        result = std::move(error);
+      });
+
+  return result;
+}
+
+// Multi except
+std::vector<trait::TraitSet> hostApi::Manager::entityTraits(
+    const EntityReferences &entityReferences, const access::EntityTraitsAccess entityTraitsAccess,
+    const ContextConstPtr &context,
+    [[maybe_unused]] const BatchElementErrorPolicyTag::Exception &errorPolicyTag) {
+  std::vector<trait::TraitSet> results;
+  results.resize(entityReferences.size());
+
+  entityTraits(
+      entityReferences, entityTraitsAccess, context,
+      [&results](std::size_t index, trait::TraitSet traitSet) {
+        results[index] = std::move(traitSet);
+      },
+      [&entityReferences, entityTraitsAccess](std::size_t index, errors::BatchElementError error) {
+        // Implemented as if FAILFAST is true.
+        auto msg = errors::createBatchElementExceptionMessage(
+            error, index, entityReferences[index],
+            static_cast<internal::access::Access>(entityTraitsAccess));
+        throw errors::BatchElementException(index, std::move(error), msg);
+      });
+
+  return results;
+}
+
+// Multi variant
+std::vector<std::variant<errors::BatchElementError, trait::TraitSet>>
+hostApi::Manager::entityTraits(
+    const EntityReferences &entityReferences, const access::EntityTraitsAccess entityTraitsAccess,
+    const ContextConstPtr &context,
+    [[maybe_unused]] const BatchElementErrorPolicyTag::Variant &errorPolicyTag) {
+  std::vector<std::variant<errors::BatchElementError, trait::TraitSet>> results;
+  results.resize(entityReferences.size());
+  entityTraits(
+      entityReferences, entityTraitsAccess, context,
+      [&results](std::size_t index, trait::TraitSet traitSet) {
+        results[index] = std::move(traitSet);
+      },
+      [&results](std::size_t index, errors::BatchElementError error) {
+        results[index] = std::move(error);
+      });
+
+  return results;
+}
+
 // Singular Except
 trait::TraitsDataPtr hostApi::Manager::resolve(
     const EntityReference &entityReference, const trait::TraitSet &traitSet,
diff --git a/src/openassetio-python/cmodule/src/hostApi/ManagerBinding.cpp b/src/openassetio-python/cmodule/src/hostApi/ManagerBinding.cpp
index 4cc416200..6fbe15a88 100644
--- a/src/openassetio-python/cmodule/src/hostApi/ManagerBinding.cpp
+++ b/src/openassetio-python/cmodule/src/hostApi/ManagerBinding.cpp
@@ -100,10 +100,65 @@ void registerManager(const py::module& mod) {
       .def("entityExists", &Manager::entityExists, py::arg("entityReferences"),
            py::arg("context").none(false), py::arg("successCallback"), py::arg("errorCallback"),
            py::call_guard<py::gil_scoped_release>{})
-      .def("entityTraits", &Manager::entityTraits, py::arg("entityReferences"),
-           py::arg("entityTraitsAccess"), py::arg("context").none(false),
-           py::arg("successCallback"), py::arg("errorCallback"),
+      .def("entityTraits",
+           py::overload_cast<const EntityReferences&, access::EntityTraitsAccess,
+                             const ContextConstPtr&, const Manager::EntityTraitsSuccessCallback&,
+                             const Manager::BatchElementErrorCallback&>(&Manager::entityTraits),
+           py::arg("entityReferences"), py::arg("entityTraitsAccess"),
+           py::arg("context").none(false), py::arg("successCallback"), py::arg("errorCallback"),
+           py::call_guard<py::gil_scoped_release>{})
+      .def("entityTraits",
+           py::overload_cast<const EntityReference&, access::EntityTraitsAccess,
+                             const ContextConstPtr&,
+                             const Manager::BatchElementErrorPolicyTag::Exception&>(
+               &Manager::entityTraits),
+           py::arg("entityReference"), py::arg("entityTraitsAccess"),
+           py::arg("context").none(false), py::arg("errorPolicyTag"),
+           py::call_guard<py::gil_scoped_release>{})
+      .def("entityTraits",
+           py::overload_cast<const EntityReference&, access::EntityTraitsAccess,
+                             const ContextConstPtr&,
+                             const Manager::BatchElementErrorPolicyTag::Variant&>(
+               &Manager::entityTraits),
+           py::arg("entityReference"), py::arg("entityTraitsAccess"),
+           py::arg("context").none(false), py::arg("errorPolicyTag"),
+           py::call_guard<py::gil_scoped_release>{})
+      .def(
+          "entityTraits",
+          // TODO(DF): Technically we shouldn't need this overload,
+          // see similar comment for `resolve`.
+          [](Manager& self, const EntityReference& entityReference,
+             const access::EntityTraitsAccess access, const ContextConstPtr& context) {
+            return self.entityTraits(entityReference, access, context);
+          },
+          py::arg("entityReference"), py::arg("entityTraitsAccess"),
+          py::arg("context").none(false), py::call_guard<py::gil_scoped_release>{})
+      .def("entityTraits",
+           py::overload_cast<const EntityReferences&, access::EntityTraitsAccess,
+                             const ContextConstPtr&,
+                             const Manager::BatchElementErrorPolicyTag::Exception&>(
+               &Manager::entityTraits),
+           py::arg("entityReferences"), py::arg("entityTraitsAccess"),
+           py::arg("context").none(false), py::arg("errorPolicyTag"),
+           py::call_guard<py::gil_scoped_release>{})
+      .def("entityTraits",
+           py::overload_cast<const EntityReferences&, access::EntityTraitsAccess,
+                             const ContextConstPtr&,
+                             const Manager::BatchElementErrorPolicyTag::Variant&>(
+               &Manager::entityTraits),
+           py::arg("entityReferences"), py::arg("entityTraitsAccess"),
+           py::arg("context").none(false), py::arg("errorPolicyTag"),
            py::call_guard<py::gil_scoped_release>{})
+      .def(
+          "entityTraits",
+          // TODO(DF): Technically we shouldn't need this overload,
+          // see similar comment for `resolve`.
+          [](Manager& self, const EntityReferences& entityReferences,
+             const access::EntityTraitsAccess entityTraitsAccess, const ContextConstPtr& context) {
+            return self.entityTraits(entityReferences, entityTraitsAccess, context);
+          },
+          py::arg("entityReferences"), py::arg("entityTraitsAccess"),
+          py::arg("context").none(false), py::call_guard<py::gil_scoped_release>{})
       .def("hasCapability", &Manager::hasCapability, py::arg("capability"),
            py::call_guard<py::gil_scoped_release>{})
       .def("updateTerminology", &Manager::updateTerminology, py::arg("terms"),


Test patch:
diff --git a/src/openassetio-python/tests/cmodule/gil/test_manager_gil.py b/src/openassetio-python/tests/cmodule/gil/test_manager_gil.py
index c00a5becd..dd6802e56 100644
--- a/src/openassetio-python/tests/cmodule/gil/test_manager_gil.py
+++ b/src/openassetio-python/tests/cmodule/gil/test_manager_gil.py
@@ -111,12 +111,18 @@ def test_entityExists(self, a_threaded_manager, a_context):
 
         a_threaded_manager.entityExists([], a_context, fail, fail)
 
-    def test_entityTraits(self, a_threaded_manager, a_context):
-        # Defend against forgetting to include convenience signatures in
-        # this test, once added.
-        assert "Overloaded" not in a_threaded_manager.entityExists.__doc__
+    def test_entityTraits(self, a_threaded_manager, a_context, an_entity_reference):
+        ref = an_entity_reference
+        an_access = access.EntityTraitsAccess.kRead
+        tag = Manager.BatchElementErrorPolicyTag
 
-        a_threaded_manager.entityTraits([], access.EntityTraitsAccess.kRead, a_context, fail, fail)
+        a_threaded_manager.entityTraits([], an_access, a_context, fail, fail)
+        a_threaded_manager.entityTraits(ref, an_access, a_context)
+        a_threaded_manager.entityTraits(ref, an_access, a_context, tag.kException)
+        a_threaded_manager.entityTraits(ref, an_access, a_context, tag.kVariant)
+        a_threaded_manager.entityTraits([], an_access, a_context)
+        a_threaded_manager.entityTraits([], an_access, a_context, tag.kException)
+        a_threaded_manager.entityTraits([], an_access, a_context, tag.kVariant)
 
     def test_flushCaches(self, a_threaded_manager):
         a_threaded_manager.flushCaches()
diff --git a/src/openassetio-python/tests/package/hostApi/test_manager.py b/src/openassetio-python/tests/package/hostApi/test_manager.py
index 715dae383..47e6c3053 100644
--- a/src/openassetio-python/tests/package/hostApi/test_manager.py
+++ b/src/openassetio-python/tests/package/hostApi/test_manager.py
@@ -1462,49 +1462,161 @@ def test_when_batched_throwing_overload_errors_then_raises(
         )
 
 
-class Test_Manager_entityTraits:
-    def test_wraps_the_corresponding_method_of_the_held_interface(
+class Test_Manager_entityTraits(BatchFirstMethodTest):
+    @pytest.fixture(autouse=True)
+    def constructor(
         self,
-        manager,
-        mock_manager_interface,
-        a_host_session,
-        some_refs,
-        an_entity_trait_set,
-        a_context,
-        a_batch_element_error,
+        subtests,
         invoke_entityTraits_success_cb,
         invoke_entityTraits_error_cb,
+        a_batch_element_error,
+        a_context,
+        a_host_session,
+        manager,
+        mock_manager_interface,
     ):
-        success_callback = mock.Mock()
-        error_callback = mock.Mock()
+        self.subtests = subtests
+        self.invoke_success_cb = invoke_entityTraits_success_cb
+        self.invoke_error_cb = invoke_entityTraits_error_cb
+        self.a_batch_element_error = a_batch_element_error
+        self.a_context = a_context
+        self.a_host_session = a_host_session
 
-        method = mock_manager_interface.mock.entityTraits
+        self.method = manager.entityTraits
+        self.mock_interface_method = mock_manager_interface.mock.entityTraits
 
-        def call_callbacks(*_args):
-            invoke_entityTraits_success_cb(123, an_entity_trait_set)
-            invoke_entityTraits_error_cb(456, a_batch_element_error)
+    def test_callback_overload_wraps_the_corresponding_method_of_the_held_interface(
+        self, two_refs, an_entity_trait_set
+    ):
+        self.assert_callback_overload_wraps_the_corresponding_method_of_the_held_interface(
+            method_specific_args_for_batch_of_two=(
+                two_refs,
+                access.EntityTraitsAccess.kRead,
+            ),
+            one_success_result=an_entity_trait_set,
+        )
 
-        method.side_effect = call_callbacks
+    def test_singular_overload_success(self, a_ref, an_entity_trait_set):
+        self.assert_singular_overload_success(
+            method_specific_args=(
+                a_ref,
+                access.EntityTraitsAccess.kRead,
+            ),
+            batched_method_specific_args=(
+                [a_ref],
+                access.EntityTraitsAccess.kRead,
+            ),
+            expected_result=an_entity_trait_set,
+            assert_result_identity=False,
+        )
 
-        manager.entityTraits(
-            some_refs,
-            access.EntityTraitsAccess.kRead,
-            a_context,
-            success_callback,
-            error_callback,
+    def test_batch_overload_success(self, two_refs, two_entity_trait_sets):
+        self.assert_batch_overload_success(
+            method_specific_args_for_batch_of_two=(
+                two_refs,
+                access.EntityTraitsAccess.kRead,
+            ),
+            expected_results=two_entity_trait_sets,
+            assert_result_identity=False,
         )
 
-        method.assert_called_once_with(
-            some_refs,
-            access.EntityTraitsAccess.kRead,
-            a_context,
-            a_host_session,
-            mock.ANY,
-            mock.ANY,
+    assert_result_identity = (False,)
+
+    def test_when_batch_overload_receives_output_out_of_order_then_results_reordered(
+        self, two_refs, two_entity_trait_sets
+    ):
+        self.assert_batch_overload_success_out_of_order(
+            method_specific_args_for_batch_of_two=(
+                two_refs,
+                access.EntityTraitsAccess.kRead,
+            ),
+            expected_results=two_entity_trait_sets,
+            assert_result_identity=False,
         )
 
-        success_callback.assert_called_once_with(123, an_entity_trait_set)
-        error_callback.assert_called_once_with(456, a_batch_element_error)
+    def test_when_singular_variant_overload_errors_then_error_returned(self, a_ref):
+        self.assert_singular_variant_overload_error(
+            method_specific_args=(
+                a_ref,
+                access.EntityTraitsAccess.kRead,
+            ),
+            batched_method_specific_args=(
+                [a_ref],
+                access.EntityTraitsAccess.kRead,
+            ),
+        )
+
+    def test_when_batch_variant_overload_receives_mixed_output_then_mixed_results_returned(
+        self, two_refs, an_entity_trait_set
+    ):
+        self.assert_batch_variant_overload_mixed_output(
+            method_specific_args_for_batch_of_two=(
+                two_refs,
+                access.EntityTraitsAccess.kRead,
+            ),
+            one_success_result=an_entity_trait_set,
+        )
+
+    def test_when_batch_variant_overload_receives_mixed_output_out_of_order_then_results_reordered(
+        self, four_refs, two_entity_trait_sets
+    ):
+        self.assert_batch_variant_overload_mixed_output_out_of_order(
+            method_specific_args_for_batch_of_four=(
+                four_refs,
+                access.EntityTraitsAccess.kRead,
+            ),
+            two_success_results=two_entity_trait_sets,
+        )
+
+    @pytest.mark.parametrize(
+        "access_mode", [access.EntityTraitsAccess.kRead, access.EntityTraitsAccess.kWrite]
+    )
+    @pytest.mark.parametrize("error_code", BatchFirstMethodTest.batch_element_error_codes)
+    def test_when_singular_throwing_overload_errors_then_raises(
+        self, a_ref, access_mode, error_code
+    ):
+        batch_element_error = BatchElementError(error_code, "some error")
+        expected_error_message = self._make_expected_err_msg(
+            batch_element_error,
+            access_mode,
+            a_ref,
+        )
+
+        self.assert_singular_throwing_overload_raises(
+            method_specific_args=(
+                a_ref,
+                access_mode,
+            ),
+            batched_method_specific_args=(
+                [a_ref],
+                access_mode,
+            ),
+            batch_element_error=batch_element_error,
+            expected_error_message=expected_error_message,
+        )
+
+    @pytest.mark.parametrize(
+        "access_mode", [access.EntityTraitsAccess.kRead, access.EntityTraitsAccess.kWrite]
+    )
+    @pytest.mark.parametrize("error_code", BatchFirstMethodTest.batch_element_error_codes)
+    def test_when_batched_throwing_overload_errors_then_raises(
+        self, two_refs, access_mode, error_code
+    ):
+        batch_element_error = BatchElementError(error_code, "some error")
+        expected_error_message = self._make_expected_err_msg(
+            batch_element_error,
+            access_mode,
+            two_refs[0],
+        )
+
+        self.assert_batched_throwing_overload_raises(
+            method_specific_args_for_batch_of_two=(
+                two_refs,
+                access_mode,
+            ),
+            batch_element_error=batch_element_error,
+            expected_error_message=expected_error_message,
+        )
 
 
 class Test_Manager_managementPolicy:
@@ -2122,6 +2234,11 @@ def a_batch_element_error():
     return BatchElementError(BatchElementError.ErrorCode.kUnknown, "some message")
 
 
+@pytest.fixture
+def two_entity_trait_sets():
+    return [{"blob", "lolcat"}, {"lolcat", "dog"}]
+
+
 @pytest.fixture
 def an_entity_trait_set():
     return {"blob", "lolcat"}