Skip to content

Commit 8bb21be

Browse files
Merge pull request #19 from elliotcmorris/work/18-add-entityTraits
Add Entity Traits Method
2 parents bf7eed0 + e3db618 commit 8bb21be

File tree

6 files changed

+179
-30
lines changed

6 files changed

+179
-30
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,23 +27,19 @@ jobs:
2727
python -m pytest -v ./tests
2828
2929
test:
30-
name: ${{ matrix.os }} ${{ matrix.python }} ${{ matrix.openassetio }}
30+
name: ${{ matrix.os }} ${{ matrix.python }}
3131
runs-on: ${{ matrix.os }}
3232
strategy:
3333
fail-fast: false
3434
matrix:
3535
os: ["windows-latest", "ubuntu-latest", "macos-latest"]
3636
python: ["3.7", "3.9", "3.10"]
37-
# We use a deprecation for demonstrative purposes, so test
38-
# before/after
39-
openassetio: ["\"openassetio==1.0.0a14\"", "\"openassetio>=1.0.0b1.rev0\""]
4037
steps:
4138
- uses: actions/checkout@v4
4239
- uses: actions/setup-python@v5
4340
with:
4441
python-version: ${{ matrix.python }}
4542
- run: |
46-
python -m pip install ${{ matrix.openassetio }}
4743
python -m pip install .
4844
python -m pip install -r tests/requirements.txt
4945
python -m pytest -v ./tests

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,18 +143,18 @@ Test directory, assumes a `pytest` testing environment. Uses the
143143
harness](https://openassetio.github.io/OpenAssetIO/testing.html#testing_manager_plugins)
144144
to run apiCompliance checks, as well as business logic tests.
145145

146-
- [`business_logic_suite.py`](test/business_logic_suite.py): Tests for
146+
- [`business_logic_suite.py`](tests/business_logic_suite.py): Tests for
147147
the behaviour of the manager. Does it resolve assets correctly, etc.
148148
Invoked from `tests.py`
149-
- [`conftest.py`](test/conftest.py): Pytest fixtures necessary for
149+
- [`conftest.py`](tests/conftest.py): Pytest fixtures necessary for
150150
running the tests.
151-
- [`fixtures.py`](test/fixtures.py): Data concerning the manager
151+
- [`fixtures.py`](tests/fixtures.py): Data concerning the manager
152152
necessary to run the test harness. See [the
153153
documentation.](https://openassetio.github.io/OpenAssetIO/testing.html#testing_manager_plugins_fixtures)
154-
- [`requirements.txt`](test/requirements.txt): Requirements necessary to
154+
- [`requirements.txt`](tests/requirements.txt): Requirements necessary to
155155
run the tests. Generally installed with `python -m pip install -r
156156
tests/requirements.txt` from the root directory.
157-
- [`tests.py`](test/tests.py): Main test entry point. Executes the
157+
- [`test_manager.py`](tests/test_manager.py): Main test entry point. Executes the
158158
manager `business_logic_suite`, as well as [OpenAssetIOs
159159
apiComplianceSuite.](https://github.com/OpenAssetIO/OpenAssetIO/blob/main/src/openassetio-python/package/openassetio/test/manager/apiComplianceSuite.py)
160160

plugin/my_asset_manager/MyAssetManagerInterface.py

Lines changed: 97 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,15 @@
1111
# the this class. See the notes under the "Initialization" section of:
1212
# https://openassetio.github.io/OpenAssetIO/classopenassetio_1_1v1_1_1manager_api_1_1_manager_interface.html#details (pylint: disable=line-too-long)
1313
# As such, any expensive module imports should be deferred.
14-
from openassetio import constants, TraitsData, BatchElementError
15-
16-
# TraitsData and BatchElementError got moved with a deprecation. If you
17-
# don't need to support versions prior to beta 1.0, you should use the
18-
# below two imports instead.
19-
# from openassetio.traits import TraitsData
20-
# from openassetio.errors import BatchElementError
21-
from openassetio.access import PolicyAccess, ResolveAccess
14+
from openassetio import constants
15+
from openassetio.trait import TraitsData
16+
from openassetio.errors import BatchElementError
17+
from openassetio.access import PolicyAccess, ResolveAccess, EntityTraitsAccess
2218
from openassetio.managerApi import ManagerInterface
2319
from openassetio_mediacreation.traits.content import LocatableContentTrait
2420
from openassetio_mediacreation.traits.managementPolicy import ManagedTrait
21+
from openassetio_mediacreation.traits.application import ConfigTrait
22+
from openassetio_mediacreation.traits.usage import EntityTrait
2523

2624
# OpenAssetIO is building out the implementation vertically, there are
2725
# known fails for missing abstract methods.
@@ -67,6 +65,7 @@ def hasCapability(self, capability):
6765
ManagerInterface.Capability.kEntityReferenceIdentification,
6866
ManagerInterface.Capability.kManagementPolicyQueries,
6967
ManagerInterface.Capability.kResolution,
68+
ManagerInterface.Capability.kEntityTraitIntrospection,
7069
):
7170
return True
7271

@@ -121,6 +120,90 @@ def isEntityReferenceString(self, someString, hostSession):
121120
# info()
122121
return someString.startswith(self.__reference_prefix)
123122

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

210293

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)
294+
# Internal function used in Resolve and EntityTraits, replace with logic
295+
# based on what a malformed ref means in your backend. For the
296+
# demonstrative purposes of this template, we pretend to support query
297+
# parameters, then invent a completely arbitrary query parameter that we
298+
# don't support. (We then test our implementation using the api
299+
# compliance suite, see fixtures.py)
217300
def is_malformed_ref(entityReference):
218301
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)