Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support defaultEntityReference #122

Merged
merged 1 commit into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions plugin/openassetio_manager_bal/BasicAssetLibraryInterface.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ def hasCapability(self, capability):
ManagerInterface.Capability.kPublishing,
ManagerInterface.Capability.kRelationshipQueries,
ManagerInterface.Capability.kExistenceQueries,
ManagerInterface.Capability.kDefaultEntityReferences,
foundry-markf marked this conversation as resolved.
Show resolved Hide resolved
):
return True

Expand Down Expand Up @@ -233,6 +234,41 @@ def managementPolicy(self, traitSets, access, context, hostSession):
def isEntityReferenceString(self, someString, hostSession):
return someString.startswith(self.__entity_refrence_prefix())

@simulated_delay
def defaultEntityReference(
self, traitSets, defaultEntityAccess, context, hostSession, successCallback, errorCallback
):
if not self.hasCapability(self.Capability.kDefaultEntityReferences):
super().defaultEntityReference(
traitSets,
defaultEntityAccess,
context,
hostSession,
successCallback,
errorCallback,
)
return

for idx, trait_set in enumerate(traitSets):
try:
entity_name = bal.default_entity(
trait_set, kAccessNames[defaultEntityAccess], self.__library
)
entity_ref = None
# Entity can legitimately be None, meaning query was OK
# but there is no suitable default.
if entity_name is not None:
entity_ref = self.__build_entity_ref(
bal.EntityInfo(
name=entity_name,
access=kAccessNames[defaultEntityAccess],
version=None,
)
)
successCallback(idx, entity_ref)
foundry-markf marked this conversation as resolved.
Show resolved Hide resolved
except Exception as exc: # pylint: disable=broad-except
self.__handle_exception(exc, idx, errorCallback)

@simulated_delay
def entityExists(self, entityRefs, context, _hostSession, successCallback, errorCallback):
if not self.hasCapability(self.Capability.kExistenceQueries):
Expand Down Expand Up @@ -752,6 +788,8 @@ def __handle_exception(exc, idx, error_callback):
code = BatchElementError.ErrorCode.kEntityResolutionError
elif isinstance(exc, bal.InaccessibleEntity):
code = BatchElementError.ErrorCode.kEntityAccessError
elif isinstance(exc, bal.UnknownTraitSet):
code = BatchElementError.ErrorCode.kInvalidTraitSet
else:
raise exc

Expand Down
22 changes: 22 additions & 0 deletions plugin/openassetio_manager_bal/bal.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,19 @@ def exists(entity_info: EntityInfo, library: dict) -> bool:
return True


def default_entity(trait_set: Set[str], access: str, library: dict) -> str:
"""
Retrieves the default entity for the supplied trait set and access
mode, if one exists in the library, otherwise raises an exception.
"""
default_entities_for_access = library.get("defaultEntities", {}).get(access, [])
# Find the first default entity that matches the trait set
for default_entity_for_trait_set in default_entities_for_access:
if set(default_entity_for_trait_set["traits"]) == trait_set:
return default_entity_for_trait_set["entity"]
raise UnknownTraitSet(trait_set)


def entity(entity_info: EntityInfo, library: dict) -> Entity:
"""
Retrieves the Entity data addressed by the supplied EntityInfo
Expand Down Expand Up @@ -412,3 +425,12 @@ class InaccessibleEntity(RuntimeError):

def __init__(self, entity_info: EntityInfo):
super().__init__(f"Entity '{entity_info.name}' is inaccessible for {entity_info.access}")


class UnknownTraitSet(RuntimeError):
"""
An exception raised when BAL doesn't understand a given trait set.
"""

def __init__(self, trait_set: Set[str]):
super().__init__(f"Unknown trait set {trait_set}")
69 changes: 69 additions & 0 deletions schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,75 @@
"required": ["read", "write"],
"additionalProperties": false
},
"defaultEntities": {
"description": "A mapping of intended access and trait set to appropriate default entity",
"type": "object",
"properties": {
"read": {
"type": "array",
"items": {
"type": "object",
"properties": {
"traits": {
"type": "array",
"items": {
"type": "string"
}
},
"entity": {
"type": ["string", "null"]
}
},
"required": [
"traits",
"entity"
]
}
},
"write": {
"type": "array",
"items": {
"type": "object",
"properties": {
"traits": {
"type": "array",
"items": {
"type": "string"
}
},
"entity": {
"type": ["string", "null"]
}
},
"required": [
"traits",
"entity"
]
}
},
"createRelated": {
"type": "array",
"items": {
"type": "object",
"properties": {
"traits": {
"type": "array",
"items": {
"type": "string"
}
},
"entity": {
"type": ["string", "null"]
}
},
"required": [
"traits",
"entity"
]
}
}
}
},
"entities": {
"description": "The entities in the library, they key is used as the entity name.",
"type": "object",
Expand Down
82 changes: 81 additions & 1 deletion tests/bal_business_logic_suite.py
foundry-markf marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -380,12 +380,12 @@ class Test_hasCapability_default(FixtureAugmentedTestCase):
def test_when_hasCapability_called_then_expected_capabilities_reported(self):
self.assertFalse(self._manager.hasCapability(Manager.Capability.kStatefulContexts))
self.assertFalse(self._manager.hasCapability(Manager.Capability.kCustomTerminology))
self.assertFalse(self._manager.hasCapability(Manager.Capability.kDefaultEntityReferences))
foundry-markf marked this conversation as resolved.
Show resolved Hide resolved

self.assertTrue(self._manager.hasCapability(Manager.Capability.kResolution))
self.assertTrue(self._manager.hasCapability(Manager.Capability.kPublishing))
self.assertTrue(self._manager.hasCapability(Manager.Capability.kRelationshipQueries))
self.assertTrue(self._manager.hasCapability(Manager.Capability.kExistenceQueries))
self.assertTrue(self._manager.hasCapability(Manager.Capability.kDefaultEntityReferences))

def test_when_hasCapability_called_on_managerInterface_then_has_mandatory_capabilities(self):
interface = BasicAssetLibraryInterface()
Expand Down Expand Up @@ -615,6 +615,86 @@ def test_when_read_only_entity_then_EntityAccessError_returned(self):
self.assertEqual(actual_result, expected_result)


class Test_defaultEntityReference(FixtureAugmentedTestCase):
"""
Tests for the defaultEntityReference method.

Uses the `defaultEntities` entry in library_apiComplianceSuite.json.
"""

def test_when_read_trait_set_known_then_expected_reference_returned(self):
expected = [
self._manager.createEntityReference("bal:///a_default_read_entity_for_a_and_b"),
self._manager.createEntityReference("bal:///a_default_read_entity_for_b_and_c"),
]
access = DefaultEntityAccess.kRead

self.assert_expected_entity_refs_for_access(expected, access)

def test_when_write_trait_set_known_then_expected_reference_returned(self):
expected = [
self._manager.createEntityReference("bal:///a_default_write_entity_for_a_and_b"),
self._manager.createEntityReference("bal:///a_default_write_entity_for_b_and_c"),
]
access = DefaultEntityAccess.kWrite

self.assert_expected_entity_refs_for_access(expected, access)

def test_when_createRelated_trait_set_known_then_expected_reference_returned(self):
expected = [
self._manager.createEntityReference("bal:///a_default_relatable_entity_for_a_and_b"),
self._manager.createEntityReference("bal:///a_default_relatable_entity_for_b_and_c"),
]
access = DefaultEntityAccess.kCreateRelated

self.assert_expected_entity_refs_for_access(expected, access)

def test_when_no_default_then_entity_ref_is_None(self):
results = [0] # Don't initialise to None because that's the value we expect.

self._manager.defaultEntityReference(
[{"c", "d"}],
DefaultEntityAccess.kRead,
self.createTestContext(),
lambda idx, value: operator.setitem(results, idx, value),
lambda idx, error: self.fail("defaultEntityReference should not fail"),
)

[actual] = results

self.assertIsNone(actual)

def test_when_trait_set_not_known_then_InvalidTraitSet_error(self):
results = [None]

self._manager.defaultEntityReference(
[{"a", "b", "c"}],
DefaultEntityAccess.kRead,
self.createTestContext(),
lambda idx, value: self.fail("defaultEntityReference should not succeed"),
lambda idx, error: operator.setitem(results, idx, error),
)

[actual] = results

self.assertIsInstance(actual, BatchElementError)
self.assertEqual(actual.code, BatchElementError.ErrorCode.kInvalidTraitSet)
self.assertRegex(actual.message, r"^Unknown trait set {'[abc]', '[abc]', '[abc]'}")

def assert_expected_entity_refs_for_access(self, expected, access):
actual = [None, None]

self._manager.defaultEntityReference(
[{"a", "b"}, {"b", "c"}],
access,
self.createTestContext(),
lambda idx, value: operator.setitem(actual, idx, value),
lambda idx, error: self.fail("defaultEntityReference should not fail"),
)

self.assertEqual(actual, expected)


class Test_resolve(FixtureAugmentedTestCase):
"""
Tests that resolution returns the expected values.
Expand Down
57 changes: 57 additions & 0 deletions tests/resources/library_apiComplianceSuite.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,63 @@
"default": {}
}
},
"defaultEntities": {
"read": [
{
"traits": [
"b",
"c"
],
"entity": "a_default_read_entity_for_b_and_c"
},
{
"traits": [
"a",
"b"
],
"entity": "a_default_read_entity_for_a_and_b"
},
{
"traits": [
"c",
"d"
],
"entity": null
}
],
"write": [
{
"traits": [
"b",
"c"
],
"entity": "a_default_write_entity_for_b_and_c"
},
{
"traits": [
"a",
"b"
],
"entity": "a_default_write_entity_for_a_and_b"
}
],
"createRelated": [
{
"traits": [
"b",
"c"
],
"entity": "a_default_relatable_entity_for_b_and_c"
},
{
"traits": [
"a",
"b"
],
"entity": "a_default_relatable_entity_for_a_and_b"
}
]
},
"entities": {
"anAsset⭐︎": {
"versions": [
Expand Down
Loading