Repository: OpenAssetIO/OpenAssetIO (language: python)

Issue:
Conveniences for `getWithRelationship[s]`
## Notes

Once this is done, https://github.com/OpenAssetIO/OpenAssetIO-MediaCreation/issues/62 can be started.


Discussion / hints:
GetWithRelationship[s] has a differing signature to most of our methods, due to the type-hint parameter. This makes it difficult to integrate into the now more formalist test framework.

Fix patch:
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 9e97c8642..5cee2a4d8 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -29,6 +29,13 @@ v1.0.0-beta.x.y
 
 ### Improvements
 
+- Added `getRelationship(s)` overloads for convenience, providing
+  alternatives to the core callback-based workflow. Includes a more
+  direct method for resolving relationships for single entity
+  references, or single relationships, as well exception vs. result
+  object workflows.
+  [#973](https://github.com/OpenAssetIO/OpenAssetIO/issues/973)
+
 - Added `.pyi` stub files to the Python package to aid IDE code
   completion for Python bindings of C++ types.
   [#1252](https://github.com/OpenAssetIO/OpenAssetIO/issues/1252)
diff --git a/src/openassetio-core/include/openassetio/hostApi/Manager.hpp b/src/openassetio-core/include/openassetio/hostApi/Manager.hpp
index 95c01dce8..52f9d45a1 100644
--- a/src/openassetio-core/include/openassetio/hostApi/Manager.hpp
+++ b/src/openassetio-core/include/openassetio/hostApi/Manager.hpp
@@ -1414,13 +1414,16 @@ class OPENASSETIO_CORE_EXPORT Manager final {
       std::function<void(std::size_t, EntityReferencePagerPtr)>;
 
   /**
-   * Query entity references that are related to the input
+   * Query for entity references that are related to the input
    * references by the relationship defined by a set of traits and
    * their properties.
    *
    * This is an essential function in this API - as it is widely used
    * to query other entities or organisational structure.
    *
+   * When calling this method, you can expect to receive one result
+   * per @ref entity_reference provided.
+   *
    * @note Consult the documentation for the relevant relationship
    * traits to determine if the order of entities in the inner lists
    * of matching references is considered meaningful.
@@ -1482,8 +1485,264 @@ class OPENASSETIO_CORE_EXPORT Manager final {
                            const trait::TraitSet& resultTraitSet = {});
 
   /**
-   * Queries entity references that are related to the input reference
-   * by the relationships defined by a set of traits and their
+   * Query for entity references that are related to the input
+   * reference by the relationship defined by a set of traits and
+   * their properties.
+   *
+   * See documentation for the <!--
+   * --> @ref getWithRelationship(const EntityReferences&, <!--
+   * --> const trait::TraitsDataPtr&, size_t, <!--
+   * --> access::RelationsAccess, const ContextConstPtr&, <!--
+   * --> const RelationshipQuerySuccessCallback&, <!--
+   * --> const BatchElementErrorCallback& errorCallback, <!--
+   * --> const trait::TraitSet&)
+   * "callback variation" for more details on relationship behaviour.
+   *
+   * Any errors that occur during the query will be immediately thrown
+   * as an exception, either from the @ref manager plugin (for errors
+   * not specific to the entity relationship) or as a
+   * @fqref{errors.BatchElementException}
+   * "BatchElementException"-derived error.
+   *
+   * @param entityReference An @ref entity_reference to query
+   * the specified relationship for.
+   *
+   * @param relationshipTraitsData The traits of the relationship to
+   * query.
+   *
+   * @param pageSize The size of each page of data. The page size is
+   * fixed for the lifetime of pager object given to the @p
+   * successCallback. Must be greater than zero.
+   *
+   * @param relationsAccess The intended usage of the returned
+   * references.
+   *
+   * @param context The calling context.
+   *
+   * @param resultTraitSet A hint as to what traits the returned
+   * entities should have.
+   *
+   * @param errorPolicyTag  Parameter for selecting the appropriate
+   * overload (tagged dispatch idiom). See @ref
+   * BatchElementErrorPolicyTag::Exception.
+   *
+   * @return @ref EntityReferencePager pointer. Pages over
+   * an unbounded set of entity references related to the input
+   * entity reference.
+   *
+   * @throws errors.InputValidationException if @p pageSize is zero.
+   *
+   * @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.kRelationshipQueries.
+   *
+   * @see @ref Capability.kRelationshipQueries
+   */
+  EntityReferencePagerPtr getWithRelationship(
+      const EntityReference& entityReference, const trait::TraitsDataPtr& relationshipTraitsData,
+      size_t pageSize, access::RelationsAccess relationsAccess, const ContextConstPtr& context,
+      const trait::TraitSet& resultTraitSet,
+      const BatchElementErrorPolicyTag::Exception& errorPolicyTag = {});
+
+  /**
+   * Query for entity references that are related to the input
+   * reference by the relationship defined by a set of traits and
+   * their properties.
+   *
+   * See documentation for the <!--
+   * --> @ref getWithRelationship(const EntityReferences&, <!--
+   * --> const trait::TraitsDataPtr&, size_t, <!--
+   * --> access::RelationsAccess, const ContextConstPtr&, <!--
+   * --> const RelationshipQuerySuccessCallback&, <!--
+   * --> const BatchElementErrorCallback& errorCallback, <!--
+   * --> const trait::TraitSet&)
+   * "callback variation" for more details on relationship behaviour.
+   *
+   * If successful, the result is populated with an
+   * EntityReferencePager, which pages over an unbounded set of entity
+   * references related to the input entity reference, or an error.
+   *
+   * Otherwise, the result is populated with an error object detailing
+   * the reason for the failure to fetch the related entities.
+   *
+   * Errors that are not specific to the entity relationship will be
+   * thrown as an exception.
+   *
+   * @param entityReference An @ref entity_reference to query
+   * the specified relationship for.
+   *
+   * @param relationshipTraitsData The traits of the relationship to
+   * query.
+   *
+   * @param pageSize The size of each page of data. The page size is
+   * fixed for the lifetime of pager object given to the @p
+   * successCallback. Must be greater than zero.
+   *
+   * @param relationsAccess The intended usage of the returned
+   * references.
+   *
+   * @param context The calling context.
+   *
+   * @param resultTraitSet A hint as to what traits the returned
+   * entities should have.
+   *
+   * @param errorPolicyTag  Parameter for selecting the appropriate
+   * overload (tagged dispatch idiom). See @ref
+   * BatchElementErrorPolicyTag::Variant.
+   *
+   * @return Object containing either the EntityReferencePager pointer,
+   * which pages over an unbounded set of entity references related to
+   * the input entity reference, or an error.
+   *
+   * @throws errors.InputValidationException if @p pageSize is zero.
+   *
+   * @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.kRelationshipQueries.
+   *
+   * @see @ref Capability.kRelationshipQueries
+   */
+  std::variant<errors::BatchElementError, EntityReferencePagerPtr> getWithRelationship(
+      const EntityReference& entityReference, const trait::TraitsDataPtr& relationshipTraitsData,
+      size_t pageSize, access::RelationsAccess relationsAccess, const ContextConstPtr& context,
+      const trait::TraitSet& resultTraitSet,
+      const BatchElementErrorPolicyTag::Variant& errorPolicyTag);
+
+  /**
+   * Query for entity references that are related to the input
+   * references by the relationship defined by a set of traits and
+   * their properties.
+   *
+   * See documentation for the <!--
+   * --> @ref getWithRelationship(const EntityReferences&, <!--
+   * --> const trait::TraitsDataPtr&, size_t, <!--
+   * --> access::RelationsAccess, const ContextConstPtr&, <!--
+   * --> const RelationshipQuerySuccessCallback&, <!--
+   * --> const BatchElementErrorCallback& errorCallback, <!--
+   * --> const trait::TraitSet&)
+   * "callback variation" for more details on relationship behaviour.
+   *
+   * Any errors that occur during the query will be immediately thrown
+   * as an exception, either from the @ref manager plugin (for errors
+   * not specific to the entity relationship) or as a
+   * @fqref{errors.BatchElementException}
+   * "BatchElementException"-derived error.
+   *
+   * @param entityReferences A list of @ref entity_reference to query
+   * the specified relationship for.
+   *
+   * @param relationshipTraitsData The traits of the relationship to
+   * query.
+   *
+   * @param pageSize The size of each page of data. The page size is
+   * fixed for the lifetime of pager object given to the @p
+   * successCallback. Must be greater than zero.
+   *
+   * @param relationsAccess The intended usage of the returned
+   * references.
+   *
+   * @param context The calling context.
+   *
+   * @param resultTraitSet A hint as to what traits the returned
+   * entities should have.
+   *
+   * @param errorPolicyTag  Parameter for selecting the appropriate
+   * overload (tagged dispatch idiom). See @ref
+   * BatchElementErrorPolicyTag::Exception.
+   *
+   * @return List of @ref EntityReferencePager pointers. These page over
+   * unbounded sets of entity references related to the input
+   * entity references.
+   *
+   * @throws errors.InputValidationException if @p pageSize is zero.
+   *
+   * @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.kRelationshipQueries.
+   *
+   * @see @ref Capability.kRelationshipQueries
+   */
+  std::vector<EntityReferencePagerPtr> getWithRelationship(
+      const EntityReferences& entityReferences, const trait::TraitsDataPtr& relationshipTraitsData,
+      size_t pageSize, access::RelationsAccess relationsAccess, const ContextConstPtr& context,
+      const trait::TraitSet& resultTraitSet,
+      const BatchElementErrorPolicyTag::Exception& errorPolicyTag = {});
+
+  /**
+   * Query for entity references that are related to the input
+   * references by the relationship defined by a set of traits and
+   * their properties.
+   *
+   * See documentation for the <!--
+   * --> @ref getWithRelationship(const EntityReferences&, <!--
+   * --> const trait::TraitsDataPtr&, size_t, <!--
+   * --> access::RelationsAccess, const ContextConstPtr&, <!--
+   * --> const RelationshipQuerySuccessCallback&, <!--
+   * --> const BatchElementErrorCallback& errorCallback, <!--
+   * --> const trait::TraitSet&)
+   * "callback variation" for more details on relationship behaviour.
+   *
+   * For successful relationships, the corresponding element of the
+   * result is populated with an EntityReferencePager, which pages over
+   * an unbounded set of entity references related to the corresponding
+   * input entity reference.
+   *
+   * Otherwise, the corresponding element of the result is populated
+   * with an error object detailing the reason for the failure to
+   * fetch the related entities for that particular relationship.
+   *
+   * Errors that are not specific to an entity relationship will be
+   * thrown as an exception, failing the whole batch.
+   *
+   * @param entityReferences A list of @ref entity_reference to query
+   * the specified relationship for.
+   *
+   * @param relationshipTraitsData The traits of the relationship to
+   * query.
+   *
+   * @param pageSize The size of each page of data. The page size is
+   * fixed for the lifetime of pager object given to the @p
+   * successCallback. Must be greater than zero.
+   *
+   * @param relationsAccess The intended usage of the returned
+   * references.
+   *
+   * @param context The calling context.
+   *
+   * @param resultTraitSet A hint as to what traits the returned
+   * entities should have.
+   *
+   * @param errorPolicyTag  Parameter for selecting the appropriate
+   * overload (tagged dispatch idiom). See @ref
+   * BatchElementErrorPolicyTag::Variant.
+   *
+   * @return List of objects, each containing either the
+   * EntityReferencePager pointer, which pages over an unbounded set of
+   * entity references related to the input entity reference, or an
+   * error.
+   *
+   * @throws errors.InputValidationException if @p pageSize is zero.
+   *
+   * @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.kRelationshipQueries.
+   *
+   * @see @ref Capability.kRelationshipQueries
+   */
+  std::vector<std::variant<errors::BatchElementError, EntityReferencePagerPtr>>
+  getWithRelationship(const EntityReferences& entityReferences,
+                      const trait::TraitsDataPtr& relationshipTraitsData, size_t pageSize,
+                      access::RelationsAccess relationsAccess, const ContextConstPtr& context,
+                      const trait::TraitSet& resultTraitSet,
+                      const BatchElementErrorPolicyTag::Variant& errorPolicyTag);
+
+  /**
+   * Query for entity references that are related to the input reference
+   * by the relationships defined by sets of traits and their
    * properties.
    *
    * This is an essential function in this API - as it is widely used to
@@ -1493,6 +1752,9 @@ class OPENASSETIO_CORE_EXPORT Manager final {
    * traits to determine if the order of entities in the inner lists of
    * matching references is considered meaningful.
    *
+   * When calling this method, you can expect to receive one result
+   * per relationship provided in @p relationshipTraitsDatas.
+   *
    * If any relationship definition is unknown, then an empty list will
    * be returned for that relationship, and no errors will be raised.
    *
@@ -1552,6 +1814,136 @@ class OPENASSETIO_CORE_EXPORT Manager final {
                             const RelationshipQuerySuccessCallback& successCallback,
                             const BatchElementErrorCallback& errorCallback,
                             const trait::TraitSet& resultTraitSet = {});
+
+  /**
+   * Query for entity references that are related to the input
+   * reference by the relationships defined by sets of traits and
+   * their properties.
+   *
+   * See documentation for the <!--
+   * --> @ref getWithRelationships(const EntityReference&, <!--
+   * --> const trait::TraitsDatas&, size_t, <!--
+   * --> access::RelationsAccess, const ContextConstPtr&, <!--
+   * --> const RelationshipQuerySuccessCallback&, <!--
+   * --> const BatchElementErrorCallback& errorCallback, <!--
+   * --> const trait::TraitSet&)
+   * "callback variation" for more details on relationship behaviour.
+   *
+   * Any errors that occur during the query will be immediately thrown
+   * as an exception, either from the @ref manager plugin (for errors
+   * not specific to the entity relationship) or as a
+   * @fqref{errors.BatchElementException}
+   * "BatchElementException"-derived error.
+   *
+   * @param entityReference The @ref entity_reference to query the
+   * specified relationships for.
+   *
+   * @param relationshipTraitsDatas The traits of the relationships to
+   * query.
+   *
+   * @param pageSize The size of each page of data. The page size is
+   * fixed for the lifetime of pager object given to the @p
+   * successCallback. Must be greater than zero.
+   *
+   * @param relationsAccess The intended usage of the returned
+   * references.
+   *
+   * @param context The calling context.
+   *
+   * @param resultTraitSet A hint as to what traits the returned
+   * entities should have.
+   *
+   * @param errorPolicyTag  Parameter for selecting the appropriate
+   * overload (tagged dispatch idiom). See @ref
+   * BatchElementErrorPolicyTag::Exception.
+   *
+   * @return List of @ref EntityReferencePager pointers. These page over
+   * unbounded sets of entity references related to the input
+   * entity reference.
+   *
+   * @throws errors.InputValidationException if @p pageSize is zero.
+   *
+   * @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.kRelationshipQueries.
+   *
+   * @see @ref Capability.kRelationshipQueries
+   */
+  std::vector<EntityReferencePagerPtr> getWithRelationships(
+      const EntityReference& entityReference, const trait::TraitsDatas& relationshipTraitsDatas,
+      size_t pageSize, access::RelationsAccess relationsAccess, const ContextConstPtr& context,
+      const trait::TraitSet& resultTraitSet,
+      const BatchElementErrorPolicyTag::Exception& errorPolicyTag = {});
+
+  /**
+   * Query for entity references that are related to the input
+   * reference by the relationships defined by sets of traits and
+   * their properties.
+   *
+   * See documentation for the <!--
+   * --> @ref getWithRelationships(const EntityReference&, <!--
+   * --> const trait::TraitsDatas&, size_t, <!--
+   * --> access::RelationsAccess, const ContextConstPtr&, <!--
+   * --> const RelationshipQuerySuccessCallback&, <!--
+   * --> const BatchElementErrorCallback& errorCallback, <!--
+   * --> const trait::TraitSet&)
+   * "callback variation" for more details on relationship behaviour.
+   *
+   * For successful relationships, the corresponding element of the
+   * result is populated with an EntityReferencePager, which pages over
+   * an unbounded set of entity references related to the corresponding
+   * input entity reference
+   *
+   * Otherwise, the corresponding element of the result is populated
+   * with an error object detailing the reason for the failure to
+   * fetch the related entities for that particular relationship.
+   *
+   * Errors that are not specific to an entity relationship will be
+   * thrown as an exception, failing the whole batch.
+   *
+   * @param entityReference The @ref entity_reference to query the
+   * specified relationships for.
+   *
+   * @param relationshipTraitsDatas The traits of the relationships to
+   * query.
+   *
+   * @param pageSize The size of each page of data. The page size is
+   * fixed for the lifetime of pager object given to the @p
+   * successCallback. Must be greater than zero.
+   *
+   * @param relationsAccess The intended usage of the returned
+   * references.
+   *
+   * @param context The calling context.
+   *
+   * @param resultTraitSet A hint as to what traits the returned
+   * entities should have.
+   *
+   * @param errorPolicyTag  Parameter for selecting the appropriate
+   * overload (tagged dispatch idiom). See @ref
+   * BatchElementErrorPolicyTag::Variant.
+   *
+   * @return List of objects, each containing either the
+   * EntityReferencePager pointer, which pages over an unbounded set of
+   * entity references related to the input entity reference, or an error.
+   *
+   * @throws errors.InputValidationException if @p pageSize is zero.
+   *
+   * @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.kRelationshipQueries.
+   *
+   * @see @ref Capability.kRelationshipQueries
+   */
+  std::vector<std::variant<errors::BatchElementError, EntityReferencePagerPtr>>
+  getWithRelationships(const EntityReference& entityReference,
+                       const trait::TraitsDatas& relationshipTraitsDatas, size_t pageSize,
+                       access::RelationsAccess relationsAccess, const ContextConstPtr& context,
+                       const trait::TraitSet& resultTraitSet,
+                       const BatchElementErrorPolicyTag::Variant& errorPolicyTag);
+
   /// @}
 
   /**
diff --git a/src/openassetio-core/src/hostApi/ManagerConveniences.cpp b/src/openassetio-core/src/hostApi/ManagerConveniences.cpp
index 5bfa48d7e..8e846714e 100644
--- a/src/openassetio-core/src/hostApi/ManagerConveniences.cpp
+++ b/src/openassetio-core/src/hostApi/ManagerConveniences.cpp
@@ -19,6 +19,10 @@ namespace hostApi {
 // alternate, often friendlier signatures wrapping the core batch-first
 // callback-based member functions found in `Manager.cpp`
 
+/******************************************
+ * entityTraits
+ ******************************************/
+
 // Singular Except
 trait::TraitSet hostApi::Manager::entityTraits(
     const EntityReference &entityReference, const access::EntityTraitsAccess entityTraitsAccess,
@@ -102,6 +106,10 @@ hostApi::Manager::entityTraits(
   return results;
 }
 
+/******************************************
+ * resolve
+ ******************************************/
+
 // Singular Except
 trait::TraitsDataPtr hostApi::Manager::resolve(
     const EntityReference &entityReference, const trait::TraitSet &traitSet,
@@ -184,6 +192,10 @@ hostApi::Manager::resolve(
   return resolveResult;
 }
 
+/******************************************
+ * preflight
+ ******************************************/
+
 EntityReference Manager::preflight(
     const EntityReference &entityReference, const trait::TraitsDataPtr &traitsHint,
     const access::PublishingAccess publishingAccess, const ContextConstPtr &context,
@@ -262,6 +274,10 @@ std::vector<std::variant<errors::BatchElementError, EntityReference>> Manager::p
   return results;
 }
 
+/******************************************
+ * register_
+ ******************************************/
+
 // Singular Except
 EntityReference hostApi::Manager::register_(
     const EntityReference &entityReference, const trait::TraitsDataPtr &entityTraitsData,
@@ -344,6 +360,150 @@ std::vector<std::variant<errors::BatchElementError, EntityReference>> hostApi::M
   return result;
 }
 
+/******************************************
+ * getWithRelationship
+ ******************************************/
+
+// Singular Except
+EntityReferencePagerPtr hostApi::Manager::getWithRelationship(
+    const EntityReference &entityReference, const trait::TraitsDataPtr &relationshipTraitsData,
+    const size_t pageSize, const access::RelationsAccess relationsAccess,
+    const ContextConstPtr &context, const trait::TraitSet &resultTraitSet,
+    [[maybe_unused]] const BatchElementErrorPolicyTag::Exception &errorPolicyTag) {
+  EntityReferencePagerPtr result = nullptr;
+  getWithRelationship(
+      {entityReference}, relationshipTraitsData, pageSize, relationsAccess, context,
+      [&result]([[maybe_unused]] std::size_t index, EntityReferencePagerPtr pager) {
+        result = std::move(pager);
+      },
+      [&entityReference, relationsAccess](std::size_t index, errors::BatchElementError error) {
+        auto msg = errors::createBatchElementExceptionMessage(
+            error, index, entityReference, static_cast<internal::access::Access>(relationsAccess));
+        throw errors::BatchElementException(index, std::move(error), msg);
+      },
+      resultTraitSet);
+
+  return result;
+}
+
+// Singular Variant
+std::variant<errors::BatchElementError, EntityReferencePagerPtr>
+hostApi::Manager::getWithRelationship(
+    const EntityReference &entityReference, const trait::TraitsDataPtr &relationshipTraitsData,
+    const size_t pageSize, const access::RelationsAccess relationsAccess,
+    const ContextConstPtr &context, const trait::TraitSet &resultTraitSet,
+    [[maybe_unused]] const BatchElementErrorPolicyTag::Variant &errorPolicyTag) {
+  std::variant<errors::BatchElementError, EntityReferencePagerPtr> result;
+  getWithRelationship(
+      {entityReference}, relationshipTraitsData, pageSize, relationsAccess, context,
+      [&result]([[maybe_unused]] std::size_t index, EntityReferencePagerPtr pager) {
+        result = std::move(pager);
+      },
+      [&result]([[maybe_unused]] std::size_t index, errors::BatchElementError error) {
+        result = std::move(error);
+      },
+      resultTraitSet);
+
+  return result;
+}
+
+// Multi Except
+std::vector<EntityReferencePagerPtr> hostApi::Manager::getWithRelationship(
+    const EntityReferences &entityReferences, const trait::TraitsDataPtr &relationshipTraitsData,
+    const size_t pageSize, const access::RelationsAccess relationsAccess,
+    const ContextConstPtr &context, const trait::TraitSet &resultTraitSet,
+    [[maybe_unused]] const BatchElementErrorPolicyTag::Exception &errorPolicyTag) {
+  std::vector<EntityReferencePagerPtr> result;
+  result.resize(entityReferences.size());
+
+  getWithRelationship(
+      entityReferences, relationshipTraitsData, pageSize, relationsAccess, context,
+      [&result](std::size_t index, EntityReferencePagerPtr pager) {
+        result[index] = std::move(pager);
+      },
+      [&entityReferences, relationsAccess](std::size_t index, errors::BatchElementError error) {
+        auto msg = errors::createBatchElementExceptionMessage(
+            error, index, entityReferences[index],
+            static_cast<internal::access::Access>(relationsAccess));
+        throw errors::BatchElementException(index, std::move(error), msg);
+      },
+      resultTraitSet);
+
+  return result;
+}
+
+// Multi Variant
+std::vector<std::variant<errors::BatchElementError, EntityReferencePagerPtr>>
+hostApi::Manager::getWithRelationship(
+    const EntityReferences &entityReferences, const trait::TraitsDataPtr &relationshipTraitsData,
+    const size_t pageSize, const access::RelationsAccess relationsAccess,
+    const ContextConstPtr &context, const trait::TraitSet &resultTraitSet,
+    [[maybe_unused]] const BatchElementErrorPolicyTag::Variant &errorPolicyTag) {
+  std::vector<std::variant<errors::BatchElementError, EntityReferencePagerPtr>> result;
+  result.resize(entityReferences.size());
+
+  getWithRelationship(
+      entityReferences, relationshipTraitsData, pageSize, relationsAccess, context,
+      [&result](std::size_t index, EntityReferencePagerPtr pager) {
+        result[index] = std::move(pager);
+      },
+      [&result](std::size_t index, errors::BatchElementError error) {
+        result[index] = std::move(error);
+      },
+      resultTraitSet);
+
+  return result;
+}
+
+/******************************************
+ * getWithRelationships. No singulars as they mirror GetWithRelationship
+ ******************************************/
+
+// Multi Except
+std::vector<EntityReferencePagerPtr> hostApi::Manager::getWithRelationships(
+    const EntityReference &entityReference, const trait::TraitsDatas &relationshipTraitsDatas,
+    const size_t pageSize, const access::RelationsAccess relationsAccess,
+    const ContextConstPtr &context, const trait::TraitSet &resultTraitSet,
+    [[maybe_unused]] const BatchElementErrorPolicyTag::Exception &errorPolicyTag) {
+  std::vector<EntityReferencePagerPtr> result;
+  result.resize(relationshipTraitsDatas.size(), nullptr);
+  getWithRelationships(
+      entityReference, relationshipTraitsDatas, pageSize, relationsAccess, context,
+      [&result](std::size_t index, EntityReferencePagerPtr pager) {
+        result[index] = std::move(pager);
+      },
+      [&entityReference, relationsAccess](std::size_t index, errors::BatchElementError error) {
+        auto msg = errors::createBatchElementExceptionMessage(
+            error, index, entityReference, static_cast<internal::access::Access>(relationsAccess));
+        throw errors::BatchElementException(index, std::move(error), msg);
+      },
+      resultTraitSet);
+
+  return result;
+}
+
+// Multi Variant
+std::vector<std::variant<errors::BatchElementError, EntityReferencePagerPtr>>
+hostApi::Manager::getWithRelationships(
+    const EntityReference &entityReference, const trait::TraitsDatas &relationshipTraitsDatas,
+    const size_t pageSize, const access::RelationsAccess relationsAccess,
+    const ContextConstPtr &context, const trait::TraitSet &resultTraitSet,
+    [[maybe_unused]] const BatchElementErrorPolicyTag::Variant &errorPolicyTag) {
+  std::vector<std::variant<errors::BatchElementError, EntityReferencePagerPtr>> result;
+  result.resize(relationshipTraitsDatas.size());
+  getWithRelationships(
+      entityReference, relationshipTraitsDatas, pageSize, relationsAccess, context,
+      [&result](std::size_t index, EntityReferencePagerPtr pager) {
+        result[index] = std::move(pager);
+      },
+      [&result](std::size_t index, errors::BatchElementError error) {
+        result[index] = std::move(error);
+      },
+      resultTraitSet);
+
+  return result;
+}
+
 }  // namespace hostApi
 }  // namespace OPENASSETIO_CORE_ABI_VERSION
 }  // namespace openassetio
diff --git a/src/openassetio-python/cmodule/src/hostApi/ManagerBinding.cpp b/src/openassetio-python/cmodule/src/hostApi/ManagerBinding.cpp
index 6fbe15a88..9a1e16ca5 100644
--- a/src/openassetio-python/cmodule/src/hostApi/ManagerBinding.cpp
+++ b/src/openassetio-python/cmodule/src/hostApi/ManagerBinding.cpp
@@ -235,10 +235,84 @@ void registerManager(const py::module& mod) {
            py::arg("defaultEntityAccess"), py::arg("context").none(false),
            py::arg("successCallback"), py::arg("errorCallback"),
            py::call_guard<py::gil_scoped_release>{})
-      .def("getWithRelationship", &Manager::getWithRelationship, py::arg("entityReferences"),
-           py::arg("relationshipTraitsData").none(false), py::arg("pageSize"),
-           py::arg("relationsAccess"), py::arg("context").none(false), py::arg("successCallback"),
-           py::arg("errorCallback"), py::arg("resultTraitSet") = trait::TraitSet{},
+      .def("getWithRelationship",
+           py::overload_cast<const EntityReferences&, const trait::TraitsDataPtr&, size_t,
+                             access::RelationsAccess, const ContextConstPtr&,
+                             const Manager::RelationshipQuerySuccessCallback&,
+                             const Manager::BatchElementErrorCallback&, const trait::TraitSet&>(
+               &Manager::getWithRelationship),
+           py::arg("entityReferences"), py::arg("relationshipTraitsData").none(false),
+           py::arg("pageSize"), py::arg("relationsAccess"), py::arg("context").none(false),
+           py::arg("successCallback"), py::arg("errorCallback"),
+           py::arg("resultTraitSet") = trait::TraitSet{}, py::call_guard<py::gil_scoped_release>{})
+      // TODO(DF): Technically we shouldn't need this overload,
+      // since we can use a similar trick to C++ to default the
+      // appropriate overload's tag parameter, e.g.
+      // `py::arg("errorPolicyTag") = {}`. However, this causes a
+      // memory leak in pybind11.
+      .def(
+          "getWithRelationship",
+          [](Manager& self, const EntityReference& entityReference,
+             const trait::TraitsDataPtr& relationshipTraitsData, size_t pageSize,
+             const access::RelationsAccess relationsAccess, const ContextConstPtr& context,
+             const trait::TraitSet& resultTraitSet) {
+            return self.getWithRelationship(entityReference, relationshipTraitsData, pageSize,
+                                            relationsAccess, context, resultTraitSet);
+          },
+          py::arg("entityReference"), py::arg("relationshipTraitsData").none(false),
+          py::arg("pageSize"), py::arg("relationsAccess"), py::arg("context").none(false),
+          py::arg("resultTraitSet"), py::call_guard<py::gil_scoped_release>{})
+      .def(
+          "getWithRelationship",
+          [](Manager& self, const EntityReferences& entityReferences,
+             const trait::TraitsDataPtr& relationshipTraitsData, size_t pageSize,
+             const access::RelationsAccess relationsAccess, const ContextConstPtr& context,
+             const trait::TraitSet& resultTraitSet) {
+            return self.getWithRelationship(entityReferences, relationshipTraitsData, pageSize,
+                                            relationsAccess, context, resultTraitSet);
+          },
+          py::arg("entityReferences"), py::arg("relationshipTraitsData").none(false),
+          py::arg("pageSize"), py::arg("relationsAccess"), py::arg("context").none(false),
+          py::arg("resultTraitSet"), py::call_guard<py::gil_scoped_release>{})
+      .def("getWithRelationship",
+           py::overload_cast<const EntityReference&, const trait::TraitsDataPtr&, size_t,
+                             access::RelationsAccess, const ContextConstPtr&,
+                             const trait::TraitSet&,
+                             const Manager::BatchElementErrorPolicyTag::Exception&>(
+               &Manager::getWithRelationship),
+           py::arg("entityReference"), py::arg("relationshipTraitsData").none(false),
+           py::arg("pageSize"), py::arg("relationsAccess"), py::arg("context").none(false),
+           py::arg("resultTraitSet"), py::arg("errorPolicyTag"),
+           py::call_guard<py::gil_scoped_release>{})
+      .def("getWithRelationship",
+           py::overload_cast<const EntityReference&, const trait::TraitsDataPtr&, size_t,
+                             access::RelationsAccess, const ContextConstPtr&,
+                             const trait::TraitSet&,
+                             const Manager::BatchElementErrorPolicyTag::Variant&>(
+               &Manager::getWithRelationship),
+           py::arg("entityReference"), py::arg("relationshipTraitsData").none(false),
+           py::arg("pageSize"), py::arg("relationsAccess"), py::arg("context").none(false),
+           py::arg("resultTraitSet"), py::arg("errorPolicyTag"),
+           py::call_guard<py::gil_scoped_release>{})
+      .def("getWithRelationship",
+           py::overload_cast<const EntityReferences&, const trait::TraitsDataPtr&, size_t,
+                             access::RelationsAccess, const ContextConstPtr&,
+                             const trait::TraitSet&,
+                             const Manager::BatchElementErrorPolicyTag::Exception&>(
+               &Manager::getWithRelationship),
+           py::arg("entityReferences"), py::arg("relationshipTraitsData").none(false),
+           py::arg("pageSize"), py::arg("relationsAccess"), py::arg("context").none(false),
+           py::arg("resultTraitSet"), py::arg("errorPolicyTag"),
+           py::call_guard<py::gil_scoped_release>{})
+      .def("getWithRelationship",
+           py::overload_cast<const EntityReferences&, const trait::TraitsDataPtr&, size_t,
+                             access::RelationsAccess, const ContextConstPtr&,
+                             const trait::TraitSet&,
+                             const Manager::BatchElementErrorPolicyTag::Variant&>(
+               &Manager::getWithRelationship),
+           py::arg("entityReferences"), py::arg("relationshipTraitsData").none(false),
+           py::arg("pageSize"), py::arg("relationsAccess"), py::arg("context").none(false),
+           py::arg("resultTraitSet"), py::arg("errorPolicyTag"),
            py::call_guard<py::gil_scoped_release>{})
       .def(
           "getWithRelationships",
@@ -249,14 +323,62 @@ void registerManager(const py::module& mod) {
              const Manager::BatchElementErrorCallback& errorCallback,
              const trait::TraitSet& resultTraitSet) {
             validateTraitsDatas(relationshipTraitsDatas);
-            self.getWithRelationships(entityReference, relationshipTraitsDatas, pageSize,
-                                      relationsAccess, context, successCallback, errorCallback,
-                                      resultTraitSet);
+            return self.getWithRelationships(entityReference, relationshipTraitsDatas, pageSize,
+                                             relationsAccess, context, successCallback,
+                                             errorCallback, resultTraitSet);
           },
           py::arg("entityReference"), py::arg("relationshipTraitsDatas"), py::arg("pageSize"),
           py::arg("relationsAccess"), py::arg("context").none(false), py::arg("successCallback"),
           py::arg("errorCallback"), py::arg("resultTraitSet") = trait::TraitSet{},
           py::call_guard<py::gil_scoped_release>{})
+      // TODO(DF): Technically we shouldn't need this overload,
+      // since we can use a similar trick to C++ to default the
+      // appropriate overload's tag parameter, e.g.
+      // `py::arg("errorPolicyTag") = {}`. However, this causes a
+      // memory leak in pybind11.
+      .def(
+          "getWithRelationships",
+          [](Manager& self, const EntityReference& entityReference,
+             const trait::TraitsDatas& relationshipTraitsDatas, size_t pageSize,
+             const access::RelationsAccess relationsAccess, const ContextConstPtr& context,
+             const trait::TraitSet& resultTraitSet) {
+            validateTraitsDatas(relationshipTraitsDatas);
+            return self.getWithRelationships(entityReference, relationshipTraitsDatas, pageSize,
+                                             relationsAccess, context, resultTraitSet);
+          },
+          py::arg("entityReference"), py::arg("relationshipTraitsDatas"), py::arg("pageSize"),
+          py::arg("relationsAccess"), py::arg("context").none(false), py::arg("resultTraitSet"),
+          py::call_guard<py::gil_scoped_release>{})
+      .def(
+          "getWithRelationships",
+          [](Manager& self, const EntityReference& entityReference,
+             const trait::TraitsDatas& relationshipTraitsDatas, size_t pageSize,
+             access::RelationsAccess relationsAccess, const ContextConstPtr& context,
+             const trait::TraitSet& resultTraitSet,
+             const Manager::BatchElementErrorPolicyTag::Exception& errorPolicyTag) {
+            validateTraitsDatas(relationshipTraitsDatas);
+            return self.getWithRelationships(entityReference, relationshipTraitsDatas, pageSize,
+                                             relationsAccess, context, resultTraitSet,
+                                             errorPolicyTag);
+          },
+          py::arg("entityReference"), py::arg("relationshipTraitsDatas"), py::arg("pageSize"),
+          py::arg("relationsAccess"), py::arg("context").none(false), py::arg("resultTraitSet"),
+          py::arg("errorPolicyTag"), py::call_guard<py::gil_scoped_release>{})
+      .def(
+          "getWithRelationships",
+          [](Manager& self, const EntityReference& entityReference,
+             const trait::TraitsDatas& relationshipTraitsDatas, size_t pageSize,
+             access::RelationsAccess relationsAccess, const ContextConstPtr& context,
+             const trait::TraitSet& resultTraitSet,
+             const Manager::BatchElementErrorPolicyTag::Variant& errorPolicyTag) {
+            validateTraitsDatas(relationshipTraitsDatas);
+            return self.getWithRelationships(entityReference, relationshipTraitsDatas, pageSize,
+                                             relationsAccess, context, resultTraitSet,
+                                             errorPolicyTag);
+          },
+          py::arg("entityReference"), py::arg("relationshipTraitsDatas"), py::arg("pageSize"),
+          py::arg("relationsAccess"), py::arg("context").none(false), py::arg("resultTraitSet"),
+          py::arg("errorPolicyTag"), py::call_guard<py::gil_scoped_release>{})
       .def(
           "preflight",
           [](Manager& self, const EntityReferences& entityReferences,


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 dd6802e56..bee1289c8 100644
--- a/src/openassetio-python/tests/cmodule/gil/test_manager_gil.py
+++ b/src/openassetio-python/tests/cmodule/gil/test_manager_gil.py
@@ -127,11 +127,9 @@ def test_entityTraits(self, a_threaded_manager, a_context, an_entity_reference):
     def test_flushCaches(self, a_threaded_manager):
         a_threaded_manager.flushCaches()
 
-    def test_getWithRelationship(self, a_threaded_manager, a_traits_data, a_context):
-        # Defend against forgetting to include convenience signatures in
-        # this test, once added.
-        assert "Overloaded" not in a_threaded_manager.getWithRelationship.__doc__
-
+    def test_getWithRelationship(
+        self, a_threaded_manager, an_entity_reference, a_traits_data, a_context
+    ):
         a_threaded_manager.getWithRelationship(
             [],
             a_traits_data,
@@ -142,11 +140,37 @@ def test_getWithRelationship(self, a_threaded_manager, a_traits_data, a_context)
             fail,
         )
 
-    def test_getWithRelationships(self, a_threaded_manager, an_entity_reference, a_context):
-        # Defend against forgetting to include convenience signatures in
-        # this test, once added.
-        assert "Overloaded" not in a_threaded_manager.getWithRelationships.__doc__
+        tag = Manager.BatchElementErrorPolicyTag
+
+        a_threaded_manager.getWithRelationship(
+            an_entity_reference,
+            a_traits_data,
+            1,
+            access.RelationsAccess.kRead,
+            a_context,
+            set(),
+            tag.kVariant,
+        )
+
+        a_threaded_manager.getWithRelationship(
+            an_entity_reference,
+            a_traits_data,
+            1,
+            access.RelationsAccess.kRead,
+            a_context,
+            set(),
+            tag.kException,
+        )
+
+        a_threaded_manager.getWithRelationship(
+            [], a_traits_data, 1, access.RelationsAccess.kRead, a_context, set(), tag.kVariant
+        )
 
+        a_threaded_manager.getWithRelationship(
+            [], a_traits_data, 1, access.RelationsAccess.kRead, a_context, set(), tag.kException
+        )
+
+    def test_getWithRelationships(self, a_threaded_manager, an_entity_reference, a_context):
         a_threaded_manager.getWithRelationships(
             an_entity_reference,
             [],
@@ -157,6 +181,30 @@ def test_getWithRelationships(self, a_threaded_manager, an_entity_reference, a_c
             fail,
         )
 
+        tag = Manager.BatchElementErrorPolicyTag
+
+        # No singular getWithRelationships conveniences.
+
+        a_threaded_manager.getWithRelationships(
+            an_entity_reference,
+            [],
+            1,
+            access.RelationsAccess.kRead,
+            a_context,
+            set(),
+            tag.kVariant,
+        )
+
+        a_threaded_manager.getWithRelationships(
+            an_entity_reference,
+            [],
+            1,
+            access.RelationsAccess.kRead,
+            a_context,
+            set(),
+            tag.kException,
+        )
+
     def test_hasCapability(self, a_threaded_manager):
         a_threaded_manager.hasCapability(Manager.Capability.kExistenceQueries)
 
diff --git a/src/openassetio-python/tests/conftest.py b/src/openassetio-python/tests/conftest.py
index dddaad3e9..82c2faefb 100644
--- a/src/openassetio-python/tests/conftest.py
+++ b/src/openassetio-python/tests/conftest.py
@@ -114,6 +114,15 @@ def mock_entity_reference_pager_interface():
     return MockEntityReferencePagerInterface()
 
 
+@pytest.fixture
+def mock_entity_reference_pager_interface_2():
+    """
+    Fixture for an `EntityReferencePagerInterface` that forwards method
+    calls to an internal public `mock.Mock` instance.
+    """
+    return MockEntityReferencePagerInterface()
+
+
 @pytest.fixture
 def mock_manager_interface(create_mock_manager_interface):
     """
diff --git a/src/openassetio-python/tests/package/hostApi/test_manager.py b/src/openassetio-python/tests/package/hostApi/test_manager.py
index 47e6c3053..ecf37a1a3 100644
--- a/src/openassetio-python/tests/package/hostApi/test_manager.py
+++ b/src/openassetio-python/tests/package/hostApi/test_manager.py
@@ -975,7 +975,13 @@ def next(self, hostSession):
         pass
 
 
-class Test_Manager_getWithRelationship:
+# The getWithRelationship tests are more repetitive than most tests
+# in this suite. They are unable to make use of the test
+# automation that we have written for the conveniences signatures, as
+# the arguments for getWithRelationship are not in the standard format.
+
+
+class Test_Manager_getWithRelationship_with_callback_signiature:
     def test_wraps_the_corresponding_method_of_the_held_interface(
         self,
         manager,
@@ -1134,7 +1140,246 @@ def test_when_zero_pageSize_then_InputValidationException_is_raised(
             )
 
 
-class Test_Manager_getWithRelationships:
+class Test_Manager_getWithRelationship_singular_convenience:
+    @pytest.mark.parametrize(
+        "error_mode",
+        [
+            None,
+            Manager.BatchElementErrorPolicyTag.kException,
+            Manager.BatchElementErrorPolicyTag.kVariant,
+        ],
+    )
+    def test_when_success_then_entityReferencePager_returned(
+        self,
+        manager,
+        a_ref,
+        mock_manager_interface,
+        a_host_session,
+        an_empty_traitsdata,
+        an_entity_trait_set,
+        a_context,
+        mock_entity_reference_pager_interface,
+        invoke_getWithRelationship_success_cb,
+        error_mode,
+    ):
+        page_size = 3
+        method = mock_manager_interface.mock.getWithRelationship
+
+        def call_callbacks(*_args):
+            invoke_getWithRelationship_success_cb(0, mock_entity_reference_pager_interface)
+
+        method.side_effect = call_callbacks
+
+        args = {
+            "entityReference": a_ref,
+            "relationshipTraitsData": an_empty_traitsdata,
+            "pageSize": page_size,
+            "relationsAccess": access.RelationsAccess.kRead,
+            "context": a_context,
+            "resultTraitSet": an_entity_trait_set,
+        }
+
+        if error_mode is not None:
+            args["errorPolicyTag"] = error_mode
+
+        actual_pager = manager.getWithRelationship(**args)
+
+        method.assert_called_once_with(
+            [a_ref],
+            an_empty_traitsdata,
+            an_entity_trait_set,
+            page_size,
+            access.RelationsAccess.kRead,
+            a_context,
+            a_host_session,
+            mock.ANY,
+            mock.ANY,
+        )
+
+        assert isinstance(actual_pager, EntityReferencePager)
+
+    @pytest.mark.parametrize(
+        "error_mode",
+        [
+            None,
+            Manager.BatchElementErrorPolicyTag.kException,
+            Manager.BatchElementErrorPolicyTag.kVariant,
+        ],
+    )
+    def test_when_fail_then_error_emitted(
+        self,
+        manager,
+        a_ref,
+        mock_manager_interface,
+        a_host_session,
+        an_empty_traitsdata,
+        an_entity_trait_set,
+        a_context,
+        a_batch_element_error,
+        invoke_getWithRelationship_error_cb,
+        error_mode,
+    ):
+        page_size = 3
+        method = mock_manager_interface.mock.getWithRelationship
+
+        def call_callbacks(*_args):
+            invoke_getWithRelationship_error_cb(0, a_batch_element_error)
+
+        method.side_effect = call_callbacks
+
+        args = {
+            "entityReference": a_ref,
+            "relationshipTraitsData": an_empty_traitsdata,
+            "pageSize": page_size,
+            "relationsAccess": access.RelationsAccess.kRead,
+            "context": a_context,
+            "resultTraitSet": an_entity_trait_set,
+        }
+
+        if error_mode is not None:
+            args["errorPolicyTag"] = error_mode
+
+        if error_mode is None or error_mode is Manager.BatchElementErrorPolicyTag.kException:
+            with pytest.raises(BatchElementException) as exc_info:
+                manager.getWithRelationship(**args)
+
+            assert exc_info.value.error == a_batch_element_error
+            assert (
+                exc_info.value.message
+                == "unknown: some message [index=0] [access=read] [entity=asset://a]"
+            )
+        else:
+            variant_error = manager.getWithRelationship(**args)
+            assert variant_error == a_batch_element_error
+
+
+class Test_Manager_getWithRelationship_batch_convenience:
+
+    @pytest.mark.parametrize(
+        "error_mode",
+        [
+            None,
+            Manager.BatchElementErrorPolicyTag.kException,
+            Manager.BatchElementErrorPolicyTag.kVariant,
+        ],
+    )
+    def test_when_success_then_entityReferencePagers_returned(
+        self,
+        manager,
+        a_ref,
+        mock_manager_interface,
+        a_host_session,
+        an_empty_traitsdata,
+        an_entity_trait_set,
+        a_context,
+        mock_entity_reference_pager_interface,
+        mock_entity_reference_pager_interface_2,
+        invoke_getWithRelationship_success_cb,
+        error_mode,
+    ):
+
+        two_refs = [a_ref, a_ref]
+        page_size = 3
+        method = mock_manager_interface.mock.getWithRelationship
+
+        def call_callbacks(*_args):
+            mock_entity_reference_pager_interface.mock.hasNext.return_value = True
+            invoke_getWithRelationship_success_cb(0, mock_entity_reference_pager_interface)
+            mock_entity_reference_pager_interface_2.mock.hasNext.return_value = False
+            invoke_getWithRelationship_success_cb(1, mock_entity_reference_pager_interface_2)
+
+        method.side_effect = call_callbacks
+
+        args = {
+            "entityReferences": two_refs,
+            "relationshipTraitsData": an_empty_traitsdata,
+            "pageSize": page_size,
+            "relationsAccess": access.RelationsAccess.kRead,
+            "context": a_context,
+            "resultTraitSet": an_entity_trait_set,
+        }
+
+        if error_mode is not None:
+            args["errorPolicyTag"] = error_mode
+
+        actual_pagers = manager.getWithRelationship(**args)
+
+        method.assert_called_once_with(
+            two_refs,
+            an_empty_traitsdata,
+            an_entity_trait_set,
+            page_size,
+            access.RelationsAccess.kRead,
+            a_context,
+            a_host_session,
+            mock.ANY,
+            mock.ANY,
+        )
+
+        assert len(actual_pagers) == 2
+        assert actual_pagers[0].hasNext() is True
+        assert actual_pagers[1].hasNext() is False
+
+    @pytest.mark.parametrize(
+        "error_mode",
+        [
+            None,
+            Manager.BatchElementErrorPolicyTag.kException,
+            Manager.BatchElementErrorPolicyTag.kVariant,
+        ],
+    )
+    def test_when_fail_then_error_emitted(
+        self,
+        manager,
+        a_ref,
+        mock_manager_interface,
+        a_host_session,
+        an_empty_traitsdata,
+        an_entity_trait_set,
+        a_context,
+        a_batch_element_error,
+        a_batch_element_error_2,
+        invoke_getWithRelationship_error_cb,
+        error_mode,
+    ):
+
+        two_refs = [a_ref, a_ref]
+        page_size = 3
+        method = mock_manager_interface.mock.getWithRelationship
+
+        def call_callbacks(*_args):
+            invoke_getWithRelationship_error_cb(0, a_batch_element_error)
+            invoke_getWithRelationship_error_cb(1, a_batch_element_error_2)
+
+        method.side_effect = call_callbacks
+
+        args = {
+            "entityReferences": two_refs,
+            "relationshipTraitsData": an_empty_traitsdata,
+            "pageSize": page_size,
+            "relationsAccess": access.RelationsAccess.kRead,
+            "context": a_context,
+            "resultTraitSet": an_entity_trait_set,
+        }
+
+        if error_mode is not None:
+            args["errorPolicyTag"] = error_mode
+
+        if error_mode is None or error_mode is Manager.BatchElementErrorPolicyTag.kException:
+            with pytest.raises(BatchElementException) as exc_info:
+                manager.getWithRelationship(**args)
+
+            assert exc_info.value.error == a_batch_element_error
+            assert (
+                exc_info.value.message
+                == "unknown: some message [index=0] [access=read] [entity=asset://a]"
+            )
+        else:
+            variant_error = manager.getWithRelationship(**args)
+            assert variant_error == [a_batch_element_error, a_batch_element_error_2]
+
+
+class Test_Manager_getWithRelationships_with_callback_signature:
     def test_wraps_the_corresponding_method_of_the_held_interface(
         self,
         manager,
@@ -1288,6 +1533,134 @@ def test_when_zero_pageSize_then_InputValidationException_is_raised(
             )
 
 
+# GetWithRelationships have no singular conveniences, as they would just be
+# duplicates of GetWithRelationship
+
+
+class Test_Manager_getWithRelationships_with_batch_convenience:
+    @pytest.mark.parametrize(
+        "error_mode",
+        [
+            None,
+            Manager.BatchElementErrorPolicyTag.kException,
+            Manager.BatchElementErrorPolicyTag.kVariant,
+        ],
+    )
+    def test_when_success_then_entityReferencePagers_returned(
+        self,
+        manager,
+        a_ref,
+        mock_manager_interface,
+        a_host_session,
+        an_empty_traitsdata,
+        an_entity_trait_set,
+        a_context,
+        mock_entity_reference_pager_interface,
+        mock_entity_reference_pager_interface_2,
+        invoke_getWithRelationships_success_cb,
+        error_mode,
+    ):
+
+        two_traitsdatas = [an_empty_traitsdata, an_empty_traitsdata]
+        page_size = 3
+        method = mock_manager_interface.mock.getWithRelationships
+
+        def call_callbacks(*_args):
+            mock_entity_reference_pager_interface.mock.hasNext.return_value = True
+            invoke_getWithRelationships_success_cb(0, mock_entity_reference_pager_interface)
+            mock_entity_reference_pager_interface_2.mock.hasNext.return_value = False
+            invoke_getWithRelationships_success_cb(1, mock_entity_reference_pager_interface_2)
+
+        method.side_effect = call_callbacks
+
+        args = {
+            "entityReference": a_ref,
+            "relationshipTraitsDatas": two_traitsdatas,
+            "pageSize": page_size,
+            "relationsAccess": access.RelationsAccess.kRead,
+            "context": a_context,
+            "resultTraitSet": an_entity_trait_set,
+        }
+
+        if error_mode is not None:
+            args["errorPolicyTag"] = error_mode
+
+        actual_pagers = manager.getWithRelationships(**args)
+
+        method.assert_called_once_with(
+            a_ref,
+            two_traitsdatas,
+            an_entity_trait_set,
+            page_size,
+            access.RelationsAccess.kRead,
+            a_context,
+            a_host_session,
+            mock.ANY,
+            mock.ANY,
+        )
+
+        assert len(actual_pagers) == 2
+        assert actual_pagers[0].hasNext() is True
+        assert actual_pagers[1].hasNext() is False
+
+    @pytest.mark.parametrize(
+        "error_mode",
+        [
+            None,
+            Manager.BatchElementErrorPolicyTag.kException,
+            Manager.BatchElementErrorPolicyTag.kVariant,
+        ],
+    )
+    def test_when_fail_then_error_emitted(
+        self,
+        manager,
+        a_ref,
+        mock_manager_interface,
+        an_empty_traitsdata,
+        an_entity_trait_set,
+        a_context,
+        a_batch_element_error,
+        a_batch_element_error_2,
+        invoke_getWithRelationships_error_cb,
+        error_mode,
+    ):
+
+        two_traitsdatas = [an_empty_traitsdata, an_empty_traitsdata]
+        page_size = 3
+        method = mock_manager_interface.mock.getWithRelationships
+
+        def call_callbacks(*_args):
+            invoke_getWithRelationships_error_cb(0, a_batch_element_error)
+            invoke_getWithRelationships_error_cb(1, a_batch_element_error_2)
+
+        method.side_effect = call_callbacks
+
+        args = {
+            "entityReference": a_ref,
+            "relationshipTraitsDatas": two_traitsdatas,
+            "pageSize": page_size,
+            "relationsAccess": access.RelationsAccess.kRead,
+            "context": a_context,
+            "resultTraitSet": an_entity_trait_set,
+        }
+
+        if error_mode is not None:
+            args["errorPolicyTag"] = error_mode
+
+        if error_mode is None or error_mode is Manager.BatchElementErrorPolicyTag.kException:
+            with pytest.raises(BatchElementException) as exc_info:
+                manager.getWithRelationships(**args)
+
+            assert exc_info.value.error == a_batch_element_error
+            assert (
+                exc_info.value.message
+                == "unknown: some message [index=0] [access=read] [entity=asset://a]"
+            )
+        else:
+            variant_error = manager.getWithRelationships(**args)
+            assert variant_error == [a_batch_element_error, a_batch_element_error_2]
+
+
 class Test_Manager_BatchElementErrorPolicyTag:
     def test_unique(self):
         assert (
@@ -2234,6 +2607,13 @@ def a_batch_element_error():
     return BatchElementError(BatchElementError.ErrorCode.kUnknown, "some message")
 
 
+@pytest.fixture
+def a_batch_element_error_2():
+    return BatchElementError(
+        BatchElementError.ErrorCode.kInvalidEntityReference, "another message"
+    )
+
+
 @pytest.fixture
 def two_entity_trait_sets():
     return [{"blob", "lolcat"}, {"lolcat", "dog"}]