Skip to content

Commit 081abcf

Browse files
committed
[Core] Add EntityTraits.
EntityTraits is a required method, and the template dosen't work without it. Add the method, as well as a business logic test similar to resolve. as well as relevant compliance suite fixtures. Signed-off-by: Elliot Morris <[email protected]>
1 parent bf7eed0 commit 081abcf

File tree

4 files changed

+170
-13
lines changed

4 files changed

+170
-13
lines changed

plugin/my_asset_manager/MyAssetManagerInterface.py

Lines changed: 94 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@
1818
# below two imports instead.
1919
# from openassetio.traits import TraitsData
2020
# from openassetio.errors import BatchElementError
21-
from openassetio.access import PolicyAccess, ResolveAccess
21+
from openassetio.access import PolicyAccess, ResolveAccess, EntityTraitsAccess
2222
from openassetio.managerApi import ManagerInterface
2323
from openassetio_mediacreation.traits.content import LocatableContentTrait
2424
from openassetio_mediacreation.traits.managementPolicy import ManagedTrait
25+
from openassetio_mediacreation.traits.application import ConfigTrait
26+
from openassetio_mediacreation.traits.usage import EntityTrait
2527

2628
# OpenAssetIO is building out the implementation vertically, there are
2729
# known fails for missing abstract methods.
@@ -67,6 +69,7 @@ def hasCapability(self, capability):
6769
ManagerInterface.Capability.kEntityReferenceIdentification,
6870
ManagerInterface.Capability.kManagementPolicyQueries,
6971
ManagerInterface.Capability.kResolution,
72+
ManagerInterface.Capability.kEntityTraitIntrospection,
7073
):
7174
return True
7275

@@ -121,6 +124,90 @@ def isEntityReferenceString(self, someString, hostSession):
121124
# info()
122125
return someString.startswith(self.__reference_prefix)
123126

127+
def entityTraits(
128+
self,
129+
entityReferences,
130+
entityTraitsAccess,
131+
context,
132+
_hostSession,
133+
successCallback,
134+
errorCallback,
135+
):
136+
# This function is used by the host to retrieve the trait sets
137+
# for specific entities. The behaviour of this function differs
138+
# per access mode. `kRead` is a request for an exhaustive trait
139+
# set for an entity according to this manager, whilst `kWrite`
140+
# is a request for the minimal trait set required to publish to
141+
# that entity. As this manager example is read-only, we will
142+
# simply reject any `kWrite` requests.
143+
144+
# For the purposes of this template, we use this fake map of
145+
# traits to serve as our "database", arbitrarily assuming that
146+
# asset 2 is a config entity of some sort.
147+
# Replace this with querying your backend systems.
148+
managed_assets_map = {
149+
"my_asset_manager:///anAsset": {EntityTrait.kId, LocatableContentTrait.kId},
150+
"my_asset_manager:///anAsset2": {
151+
EntityTrait.kId,
152+
LocatableContentTrait.kId,
153+
ConfigTrait.kId,
154+
},
155+
"my_asset_manager:///anAsset3": {EntityTrait.kId, LocatableContentTrait.kId},
156+
}
157+
158+
# If your manager doesn't support write, like this one, reject
159+
# a write access mode via calling the error callback.
160+
if entityTraitsAccess != EntityTraitsAccess.kRead:
161+
result = BatchElementError(
162+
BatchElementError.ErrorCode.kEntityAccessError, "Entities are read-only"
163+
)
164+
for idx in range(len(entityReferences)):
165+
errorCallback(idx, result)
166+
return
167+
168+
# Iterate over all the entity references, calling the correct
169+
# error/success callbacks into the host.
170+
# You should handle success/failure on an entity-by-entity
171+
# basis, do not abort your entire operation because any single
172+
# entity is malformed/can't be processed for any reason, use
173+
# the error callback and continue.
174+
for idx, ref in enumerate(entityReferences):
175+
# It may be that one of the references you are provided is
176+
# recognized for this manager, but has some syntax error or
177+
# is otherwise incorrect for your specific resolve context.
178+
# For example, an asset reference that specifies a version
179+
# for an un-versioned entity could be considered malformed.
180+
#
181+
# N.B. It's not required to perform an explicit check here
182+
# if this is naturally serviced during your backend lookup,
183+
# the key is not to error the whole batch, but use the error
184+
# callback for relevant references.
185+
identifier_is_malformed = is_malformed_ref(ref)
186+
if identifier_is_malformed:
187+
error_result = BatchElementError(
188+
BatchElementError.ErrorCode.kMalformedEntityReference,
189+
"Entity identifier is malformed",
190+
)
191+
errorCallback(idx, error_result)
192+
else:
193+
# If our manager has the asset in question, we can
194+
# let the host know which traits make up this specific
195+
# entity.
196+
if ref.toString() in managed_assets_map:
197+
# Return the traits imbued the the entity in
198+
# question
199+
success_result = managed_assets_map[ref.toString()]
200+
successCallback(idx, success_result)
201+
else:
202+
# Otherwise, we don't know about the entity, so call
203+
# the error callback with an entity resolution error
204+
# for this specific entity.
205+
error_result = BatchElementError(
206+
BatchElementError.ErrorCode.kEntityResolutionError,
207+
f"Entity '{ref.toString()}' not found",
208+
)
209+
errorCallback(idx, error_result)
210+
124211
def resolve(
125212
self,
126213
entityReferences,
@@ -208,11 +295,11 @@ def resolve(
208295
errorCallback(idx, error_result)
209296

210297

211-
# Internal function used in Resolve, replace with logic based on what a
212-
# malformed ref means in your backend. For the demonstrative purposes of
213-
# this template, we pretend to support query parameters, then invent a
214-
# completely arbitrary query parameter that we don't support. (We then
215-
# test our implementation using the api compliance suite, see
216-
# fixtures.py)
298+
# Internal function used in Resolve and EntityTraits, replace with logic
299+
# based on what a malformed ref means in your backend. For the
300+
# demonstrative purposes of this template, we pretend to support query
301+
# parameters, then invent a completely arbitrary query parameter that we
302+
# don't support. (We then test our implementation using the api
303+
# compliance suite, see fixtures.py)
217304
def is_malformed_ref(entityReference):
218305
return "?unsupportedQueryParam" in entityReference.toString()

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ name = "my_asset_manager"
66
version = "1.0.0"
77
requires-python = ">=3.7"
88
dependencies = [
9-
"openassetio >= 1.0.0a14",
10-
"openassetio-mediacreation == 1.0.0a7"
9+
"openassetio>=1.0.0b1.rev0",
10+
"openassetio-mediacreation == 1.0.0a8"
1111
]
1212

1313
authors = [

tests/business_logic_suite.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@
88

99
# pylint: disable=invalid-name, missing-function-docstring, missing-class-docstring
1010

11-
from openassetio.access import ResolveAccess
11+
from openassetio.access import ResolveAccess, EntityTraitsAccess
1212
from openassetio.test.manager.harness import FixtureAugmentedTestCase
1313
from openassetio_mediacreation.traits.content import LocatableContentTrait
14+
from openassetio_mediacreation.traits.usage import EntityTrait
1415

1516

1617
class Test_resolve(FixtureAugmentedTestCase):
1718
"""
18-
Test suite for the business logic of MyAssetManager
19+
Test suite for the Resolve business logic of MyAssetManager
1920
2021
The test here is illustrative only, you should extend this suite
2122
to provide full coverage of all of the behaviour of your asset
@@ -57,3 +58,38 @@ def error_cb(idx, batchElementError):
5758
self.assertTrue(result[0].hasTrait(trait))
5859
for property_, value in self.__test_entity[1][trait].items():
5960
self.assertEqual(result[0].getTraitProperty(trait, property_), value)
61+
62+
63+
class Test_entityTraits(FixtureAugmentedTestCase):
64+
"""
65+
Test suite for the EntityTraits business logic of MyAssetManager
66+
67+
The test here is illustrative only, you should extend this suite
68+
to provide full coverage of all of the behaviour of your asset
69+
manager.
70+
"""
71+
72+
def test_when_refs_found_then_success_callback_called_with_expected_values(self):
73+
entity_reference = self._manager.createEntityReference("my_asset_manager:///anAsset")
74+
75+
context = self.createTestContext()
76+
77+
results = [None]
78+
79+
def success_cb(idx, trait_set):
80+
print("Success", trait_set)
81+
results[idx] = trait_set
82+
83+
def error_cb(idx, batchElementError):
84+
self.fail(
85+
f"Unexpected error for '{entity_reference.toString()}':"
86+
f" {batchElementError.message}"
87+
)
88+
89+
self._manager.entityTraits(
90+
[entity_reference], EntityTraitsAccess.kRead, context, success_cb, error_cb
91+
)
92+
93+
self.assertTrue(len(results) == 1)
94+
expected_trait_set = {EntityTrait.kId, LocatableContentTrait.kId}
95+
assert results[0] == expected_trait_set

tests/fixtures.py

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,19 @@
66
"""
77
from openassetio import constants
88
from openassetio_mediacreation.traits.content import LocatableContentTrait
9-
9+
from openassetio_mediacreation.traits.application import ConfigTrait
10+
from openassetio_mediacreation.traits.usage import EntityTrait
1011

1112
IDENTIFIER = "myorg.manager.my_asset_manager"
1213

1314
VALID_REF = "my_asset_manager:///AssetIdentifier"
1415
NON_REF = "not a Ŕeference"
1516
MALFORMED_REF = "my_asset_manager:///AssetIdentifier?unsupportedQueryParam"
1617
EXISTING_REF = "my_asset_manager:///anAsset"
18+
MISSING_ENTITY_REF = "my_asset_manager:///missing_entity"
19+
ERROR_MSG_MALFORMED_REF = "Entity identifier is malformed"
20+
ERROR_MSG_MISSING_ENTITY = "Entity 'my_asset_manager:///missing_entity' not found"
21+
ERROR_READ_ONLY_ACCESS = "Entities are read-only"
1722

1823
# This dictionary serves as expected outputs for the OpenAssetIO api
1924
# compliance suite. This suite tests that your implementation functions
@@ -39,12 +44,41 @@
3944
"a_set_of_valid_traits": {LocatableContentTrait.kId},
4045
"a_reference_to_a_readonly_entity": EXISTING_REF,
4146
"the_error_string_for_a_reference_to_a_readonly_entity": "Entities are read-only",
42-
"a_reference_to_a_missing_entity": "my_asset_manager:///missing_entity",
47+
"a_reference_to_a_missing_entity": MISSING_ENTITY_REF,
4348
"the_error_string_for_a_reference_to_a_missing_entity": (
4449
"Entity 'my_asset_manager:///missing_entity' not found"
4550
),
4651
"a_malformed_reference": MALFORMED_REF,
4752
"the_error_string_for_a_malformed_reference": "Entity identifier is malformed",
4853
}
4954
},
55+
"Test_entityTraits": {
56+
"shared": {
57+
"a_reference_to_a_readonly_entity": EXISTING_REF,
58+
"a_reference_to_a_missing_entity": MISSING_ENTITY_REF,
59+
"a_malformed_reference": MALFORMED_REF,
60+
},
61+
"test_when_querying_malformed_reference_then_malformed_reference_error_is_returned": {
62+
"expected_error_message": ERROR_MSG_MALFORMED_REF,
63+
},
64+
"test_when_querying_missing_reference_for_read_then_resolution_error_is_returned": {
65+
"expected_error_message": ERROR_MSG_MISSING_ENTITY
66+
},
67+
"test_when_read_only_entity_queried_for_write_then_access_error_is_returned": {
68+
"expected_error_message": ERROR_READ_ONLY_ACCESS
69+
},
70+
"test_when_multiple_references_for_read_then_same_number_of_returned_trait_sets": {
71+
"first_entity_reference": "my_asset_manager:///anAsset",
72+
"second_entity_reference": "my_asset_manager:///anAsset2",
73+
"first_entity_trait_set": {
74+
EntityTrait.kId,
75+
LocatableContentTrait.kId,
76+
},
77+
"second_entity_trait_set": {
78+
EntityTrait.kId,
79+
LocatableContentTrait.kId,
80+
ConfigTrait.kId,
81+
},
82+
},
83+
},
5084
}

0 commit comments

Comments
 (0)