OpenAssetIO [beta]
An abstract API for generalising interactions between a host application and an asset management system
Examples
Note
This section is a work-in-progress. Over time we will add flow diagrams for key operations that happen through the API, along with more extensive sample implementations for both hosts and managers. Currently it is limited to illustrating a few common operations that a host of the API may perform.
Warning
At this stage, until we ship a sample manager implementation, the code for later examples won't actually function.

Initializing the API in a Host

This example covers the steps required to initialize the API within a 'host' tool, script or application that wishes to interact with an Asset Management System.

It makes use of the Plugin System to discover available PythonPluginSystemManagerPlugins.

It also includes a bare-minimum example of a HostInterface implementation.

1 from openassetio.log import ConsoleLogger, SeverityFilter
2 from openassetio.hostApi import HostInterface, Manager, ManagerFactory
3 from openassetio.pluginSystem import PythonPluginSystemManagerImplementationFactory
4 
5 class ExamplesHost(HostInterface):
6  """
7  A minimal host implementation.
8  """
9  def identifier(self):
10  return "org.openassetio.examples"
11 
12  def displayName(self):
13  return "OpenAssetIO Examples"
14 
15 # For simplicity, use a filtered console logger, this logs to
16 # stderr based on the value of OPENASSETIO_LOGGING_SEVERITY.
17 # Practically you may wish to provide a bridge to your own logging
18 # mechanism if you have one.
19 logger = SeverityFilter(ConsoleLogger())
20 
21 # We need to provide the mechanism by which managers are created, the
22 # built-in plugin system allows these to be loaded from
23 # OPENASSETIO_PLUGIN_PATH.
24 factory_impl = PythonPluginSystemManagerImplementationFactory(logger)
25 
26 # We then need our implementation of the HostInterface class
27 host_interface = ExamplesHost()
28 
29 # We can now create an OpenAssetIO ManagerFactory. The ManagerFactory
30 # allows us to query the available managers, and pick one to talk to.
31 managerFactory = ManagerFactory(host_interface, factory_impl, logger)

Setting up a Manager

This example makes use of the newly initialized factory to show how to construct and configure a specific manager (it assumes that some example Asset Management System has a plugin, installed on $OPENASSETIO_PLUGIN_PATH).

We will be providing an example manager implementation soon!

1 availableManagers = managerFactory.availableManagers()
2 > {
3 > 'org.openassetio.example.manager':
4 > ManagerFactory.ManagerDetail(
5 > identifier='org.openassetio.example.manager',
6 > displayName='Example Asset Manager',
7 > info={})
8 > }
9 > }
10 
11 # Once we know which manager we wish to use, we can ask the factory
12 # to create one for us.
13 manager = managerFactory.createManager('org.openassetio.example.manager')
14 
15 # We now have an instance of the requested manager, but it is not
16 # quite ready for use yet. The manager returned by the
17 # ManagerFactory needs to be initialized before it can be used to
18 # query or publish assets. Setup is split into two stages to allow
19 # adjustments to its settings to be made prior to use if required.
20 
21 # A manager's current (or in this case default) settings can be
22 # queried if needed:
23 settings = manager.settings()
24 # ...and updated with new values as desired.
25 settings["server"] = "my.server.com"
26 
27 # Finally, we can initialize the manager with the desired settings,
28 # preparing it for use. Note that this may include non-trivial
29 # amounts of work. Settings updates are sparse, so if you don't have
30 # any custom settings, you can pass an empty dictionary here.
31 manager.initialize(settings)

To make it easier to deploy a range of OpenAssetIO enabled hosts, the API supports a simple file-based configuration mechanism. Users set the $OPENASSETIO_DEFAULT_CONFIG environment variable to point to a suitable TOML file, which contains their preferred manager identifier and settings. As a Host, you can use the defaultManagerForInterface method instead of creating your own ManagerFactory. This will return a fully initialized manager using this configuration if set:

1 manager = ManagerFactory.defaultManagerForInterface(
2  host_interface, impl_factory, logger)

Resolving a Reference

This example shows how to use the instantiated manager to resolve a string (some_string) that is assumed to be an entity reference to an entity with the LocatableContent Trait (from the MediaCreation package) covering use of the correct context.

Note
The caller must convert a string to an EntityReference object in order to use any OpenAssetIO API that expects an Entity Reference. There is more than one approach to this. Below we rely on the exception thrown by createEntityReference when given an invalid reference. Alternatively, we could use createEntityReferenceIfValid and test if the result is falsey.
Ensuring that an entity reference is valid before handing it to the manager reduces the validation overhead in the manager's implementation of the API. This affords significant gains in real-world production use cases where thousands of references may be operated upon in time-critical scenarios.

The API middleware provides assorted short-circuit validation optimisations that can reduce the number of inter-language hops required. See ManagerInterface.info and the kInfoKey_EntityReferencesMatchPrefix key.

1 from openassetio.access import ResolveAccess
2 from openassetio_mediacreation.traits.content import LocatableContentTrait
3 
4 # Note: this will raise an exception if given a string that is not
5 # recognized by this manager as a valid entity reference (ValueError
6 # in Python, std::domain_error in C++). Consider
7 # createEntityReferenceIfValid, if unsure of the string.
8 entity_reference = manager.createEntityReference(some_string)
9 
10 # All calls to the manager must have a Context, these should always
11 # be created by the target manager. The Context ensures that any
12 # manager state is properly managed between API calls.
13 context = manager.createContext()
14 
15 # We can now resolve a token we may have if it is a reference. In
16 # this example, we'll attempt to resolve the LocatableContent trait
17 # for the entity. We inform the manager that we just want to read the
18 # current data for the entity (kRead). See the publishing examples
19 # for use of the alternative `kWrite` access mode.
20 
21 # First check that the manager has implemented resolution capability.
22 if not manager.hasCapability(Manager.Capability.kResolution):
23  return
24 
25 resolved_asset = manager.resolve(
26  entity_reference, {LocatableContentTrait.kId},
27  ResolveAccess.kRead, context)
28 url = LocatableContentTrait(resolved_asset).getLocation() # May be None

Inspecting a reference

This example shows how an entity reference of unknown provenance can be inspected to determine the qualities of the entity it points to.

Let's say we're an audio application written in Qt, and someone has just dragged and dropped a line of text into the application. We want to know if the text is an entity reference, and if so, does it point to an audio clip.

1 from openassetio.access import EntityTraitsAccess
2 from openassetio_mediacreation.traits.content import LocatableContentTrait
3 from openassetio_mediacreation.specifications.audio import SampledAudioResource
4 
5 class MyAudioApp(QMainWindow):
6 
7  def __init__(self, manager):
8  super().__init__()
9  self.__manager = manager
10  self.__context = manager.createContext()
11  self.setAcceptDrops(True)
12  # [... Other Qt setup]
13 
14  def dragEnterEvent(self, event):
15  if event.mimeData().hasUrls():
16  event.acceptProposedAction()
17 
18  def dropEvent(self, event):
19  if not event.mimeData().hasUrls():
20  return
21  uri = event.mimeData().urls().first().toString()
22  ref = self.__manager.createEntityReferenceIfValid(uri)
23  if ref is None:
24  return
25 
26  entity_trait_set = self.__manager.entityTraits(
27  ref, EntityTraitsAccess.kRead, self.__context)
28 
29  # Ignore this drop if the entity is not of the correct type for
30  # this application.
31  if not SampledAudioResource.kTraitSet <= entity_trait_set
32  return
33 
34  resolved_asset = manager.resolve(
35  entity_reference, {LocatableContentTrait.kId},
36  ResolveAccess.kRead, context)
37  url = LocatableContentTrait(resolved_asset).getLocation()
38 
39  self.load_audio_from_url(url)
40 
41  # [... Other application setup]

Discovering capability

In the previous example, we saw how to resolve a specific trait for an entity. In a real-world scenario, it is important to remember that:

  • Managers may not support a particular workflow at all.
  • Managers may not handle certain types of entity (determined by their Trait Set).
  • Managers may not be able to provide data for all traits.

Capabilities

The hasCapability method allows a host to determine which workflows that a manager has implemented.

This is achieved by grouping sets of methods potentially implemented by the manager into "capabilities". These capabilities are defined in Capability.

Calling a method not implemented by the manager will result in an exception. Therefore hosts should check hasCapability before calling into any of these optional methods. The return value of hasCapability is runtime invariant, post-initialize, therefore the check only needs to be made once per manager.

Policy

The managementPolicy method allows a host to query a manager's behaviours and intentions towards different types of entity.

This method should be used wherever possible to adapt the host's behaviour so that:

  • The manager is not invoked in relation to unsupported entity types.
  • User workflows are appropriate to the supported behaviour for a given entity type.

Failing to check the policy leaves a host vulnerable to recieving empty, null or invalid data by making queries outside of the managers realm of understanding.

Example

This example demonstrates how to query a manager in relation to a specific entity type - in this case a simple text file, and inspect its capabilities and the data it may be able to resolve for it.

1 from openassetio.access import PolicyAccess
2 
3 # Commented imports are illustrative and may not exist yet
4 from openassetio_mediacreation.traits.managementPolicy import (
5  ManagedTrait,
6 )
7 from openassetio_mediacreation.traits.content import (
8  LocatableContentTrait, # TextEncodingTrait
9 )
10 from openassetio_mediacreation.specifications.files import (
11  # TextFileSpecification
12 )
13 
14 # Ensure that the manager is capable of the resolution workflow.
15 if not manager.hasCapability(Manager.Capability.kResolution):
16  return
17 
18 # We use the well-known specification for a text file to determine
19 # the correct trait set to query. Using the standard definition
20 # ensures consistent behaviour across managers/hosts. We request
21 # the manager's policy for read access to this entity type.
22 [policy] = manager.managementPolicy(
23  [TextFileSpecification.kTraitSet], PolicyAccess.kRead, context)
24 
25 # We can now check which traits were imbued in the policy, the
26 # absence of a trait means it is unsupported.
27 
28 if not ManagedTrait.isImbuedTo(policy):
29  # The manager doesn't want to handle text files, we should not
30  # attempt to resolve/publish this type of entity.
31  return
32 
33 # As well as policy-specific traits, the result will be imbued with
34 # traits from the queried trait set that the manager is capable of
35 # providing data for. If you have additional host-specific traits,
36 # you can append these to the ones from the relevant specification.
37 # Here we check for support for the specific text file traits we are
38 # interested in using.
39 
40 if LocatableContentTrait.isImbuedTo(policy):
41  print("The manager can provide the URL for the file")
42 
43 if TextEncodingTrait.isImbuedTo(policy):
44  print("The manager can provide the text encoding used")

Publishing a File

This example demonstrates how an API host should involve the manager in the creation of new data. In this case, a simple text file.

1 from openassetio.access import (
2  PolicyAccess, ResolveAccess, PublishingAccess)
3 # Commented imports are illustrative and may not exist yet
4 from openassetio_mediacreation.traits.managementPolicy import ManagedTrait
5 from openassetio_mediacreation.specifications.files import (
6  # TextFileSpecification
7 )
8 
9 # Ensure that the manager supports publishing.
10 if not manager.hasCapability(Manager.Capability.kPublishing):
11  return
12 
13 # As ever, an appropriately configured context is required
14 context = manager.createContext()
15 
16 # The first step is to see if the manager wants to manage text files.
17 # Note that this time we request the manager's policy for write
18 # access.
19 [policy] = manager.managementPolicy(
20  [TextFileSpecification.kTraitSet], PolicyAccess.kWrite, context)
21 
22 if not ManagedTrait.isImbuedTo(policy):
23  # The manager doesn't care about this type of asset
24  return
25 
26 # Managers may want to dictate to the host some of the data to be
27 # published, e.g. tell us where to put files. So we ask the manager
28 # which traits, if any, it is interested in "driving" itself.
29 [manager_driven_policy] = manager.managementPolicy(
30  [TextFileSpecification.kTraitSet], PolicyAccess.kManagerDriven, context)
31 
32 # Choose some defaults in case the manager cannot drive these values.
33 save_path = os.path.join(os.path.expanduser('~'), 'greeting.txt')
34 encoding = "utf-8"
35 
36 # Whenever we make new data, we always tell the manager first,
37 # This allows it to create a placeholder version or similar.
38 # We must provide the manager with any relevant information that the
39 # host owns (i.e. won't be queried from the manager during
40 # publishing) and can be provided up-front.
41 file_spec = TextFileSpecification.create()
42 file_spec.markupTrait().setMarkupType("plain")
43 
44 # Our intent is to write data to this entity, not to create a new
45 # related entity (kCreateRelated), so we use the kWrite access mode.
46 # NOTE: It is critical to always use the working_ref from now on.
47 working_ref = manager.preflight(
48  entity_ref, file_spec, PublishingAccess.kWrite, context)
49 
50 # If the manager wants to drive at least one of the traits we're
51 # publishing, we can `resolve` with the `kManagerDriven` access mode
52 # to retrieve the values the manager wants us to use. If we attempt
53 # to `resolve` without checking the policy first, we risk an error if
54 # the manager does not support this operation.
55 if (LocatableContentTrait.isImbuedTo(manager_driven_policy) or
56  TextEncodingTrait.isImbuedTo(manager_driven_policy)):
57  working_data = manager.resolve(
58  working_ref,
59  {LocatableContentTrait.kId, TextEncodingTrait.kId},
60  ResolveAccess.kManagerDriven, context)
61  if save_url := LocatableContentTrait(working_data).getLocation():
62  # Assume `file://` URL
63  save_path = utils.pathFromUrl(save_url)
64  encoding = TextEncodingTrait(working_data).getEncoding(defaultValue=encoding):
65 
66 # Now we can write the file
67 with open(save_path, 'w', encoding=encoding) as f:
68  f.write("Hello from the documentation example\n")
69 
70 # Prepare the entity specification to register, with the data about
71 # where we actually wrote the data to, and with what encoding.
72 file_spec.locatableContentTrait().setLocation(pathToURL(save_path))
73 file_spec.textEncodingTrait().setEncoding(encoding)
74 
75 # Now the data has been written, we register the file and the publish
76 # is complete.
77 final_ref = manager.register(working_ref, file_spec.traitsData(),
78  PublishAccess.kWrite, context)
79 
80 # Keep this around for later, in case it is useful
81 with open(os.path.join(os.path.expanduser('~'), 'history', 'a') as f:
82  f.write(f"{final_ref}\n")

Generating a Thumbnail During Publish

This example demonstrates the correct handling in a host of a hypothetical WantsThumbnail trait if set by a manager in its managementPolicy response.

It follows on from the preceding publishing example.

Note
This example uses imaginary, illustrative traits and specifications that are yet to be finalized.
1 # Ensure that the manager supports publishing.
2 if not manager.hasCapability(Manager.Capability.kPublishing):
3  return
4 
5 # See if the manager wants a thumbnail
6 if not WantsThumbnailTrait.isImbuedTo(policy):
7  return
8 
9 # Preflight the thumbnail spec's traits with the target entity's
10 # reference, this gives us a reference we can now use for all
11 # interactions relating to the thumbnail itself.
12 thumbnail_ref = manager.preflight(
13  final_ref, ThumbnailFileSpecification.kTraitSet,
14  PublishAccess.kCreateRelated, context)
15 
16 thumbnail_path = os.path.join(os.path.expanduser('~'), 'greeting.preview.png')
17 thumbnail_attr = {"width": 128, "height": 128}
18 
19 # See if the manager can tell us where to put it, and what it should be like
20 if (LocatableContentTrait.isImbuedTo(manager_driven_policy) or
21  RasterTrait.isImbuedTo(manager_driven_policy)):
22  requested = manager.resolve(
23  thumbnail_ref,
24  {LocatableContentTrait.kId, RasterTrait.kId},
25  ResolveAccess.kManagerDriven, context)
26  if requested_path := LocatableContentTrait(requested).getLocation():
27  thumbnail_path = utils.pathFromURL(requested_path)
28  raster_trait = RasterTrait(requested)
29  if raster_trait.isImbued():
30  # 'get' calls can take a default value to avoid `None` if missing.
31  thumbnail_attr["width"] = raster_trait.getWidth(thumbnail_attr["width"])
32  thumbnail_attr["height"] = raster_trait.getHeight(thumbnail_attr["height"])
33 
34 # Generate a thumbnail using the supplied criteria
35 mk_thumbnail(thumbnail_path, thumbnail_attr["width"], thumbnail_attr["height"])
36 
37 # Register the thumbnail to the thumbnail ref (not the entity),
38 # configuring the context to say we're going to ignore the final ref
39 
40 thumbail_spec = ThumbnailFileSpecification.create()
41 thumbnail_spec.fileTrait().setPath(thumbnail_path)
42 raster_trait = thumbnail_spec.rasterTrait()
43 raster_trait.setWidth(thumbnail_attr["width"])
44 raster_trait.setHeight(thumbnail_attr["height"])
45 
46 manager.register(
47  thumbnail_ref, thumbnail_spec.traitsData(),
48  PublishAccess.kWrite, context)