-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Docs] Add hybrid plugin system notebook
Part of OpenAssetIO/OpenAssetIO#1202. Add a Jupyter Notebook illustrating the design and usage of the hybrid plugin system. Signed-off-by: David Feltell <[email protected]>
- Loading branch information
Showing
4 changed files
with
492 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,396 @@ | ||
{ | ||
"cells": [ | ||
{ | ||
"metadata": {}, | ||
"cell_type": "markdown", | ||
"source": [ | ||
"# Hosts: Using hybrid plugins\n", | ||
"\n", | ||
"This notebook illustrates usage of the Hybrid Plugin System. We combine a Python manager plugin and a C++ manager plugin, and dispatch to the appropriate plugin based on its capabilities and priority. \n", | ||
"\n", | ||
"The primary use-case for this feature is to allow performance critical functionality to be written in performant C++, whilst less performance critical functionality can be written in more flexible Python.\n", | ||
"\n", | ||
"The hybrid plugin system is not limited to this use-case, however. You are free to combine any number of plugins in any supported language (only C++ and Python are available in the core library at time of writing). The hybrid plugin system also provides a more convenient abstraction for working with multiple plugin systems in general.\n", | ||
"\n", | ||
"## How it works\n", | ||
"\n", | ||
"Given a set of manager plugins, to be composed by the hybrid plugin system:\n", | ||
"\n", | ||
"* All plugins must use the same unique identifier.\n", | ||
"* Each plugin is discovered by a different child `ManagerImplementationFactoryInterface` instance (e.g. `PythonPluginSystemManagerImplementationFactory` or `CppPluginSystemManagerImplementationFactory`).\n", | ||
"* All the OpenAssetIO _required_ capabilities (i.e. entityReferenceIdentification, managementPolicyQueries, entityTraitIntrospection) must be satisfied by at least one of the plugins.\n", | ||
"* Priority order matches the order that `ManagerImplementationFactoryInterface` instances are provided (e.g. `CppPluginSystemManagerImplementationFactory` before `PythonPluginSystemManagerImplementationFactory`).\n", | ||
"* API calls will be routed to the first plugin that advertises it has the appropriate capability.\n", | ||
"\n", | ||
"Note that if only one child factory locates a plugin with the desired identifier, then that plugin is used directly. In this way, host applications making use of the hybrid plugin system don't lose out on any functionality or performance. As such, the hybrid plugin system should be used by default by most hosts, even if they don't make use of all of its functionality." | ||
], | ||
"id": "6ebc020b4c31f1b8" | ||
}, | ||
{ | ||
"metadata": {}, | ||
"cell_type": "markdown", | ||
"source": "## Example", | ||
"id": "959ede09b6700a1e" | ||
}, | ||
{ | ||
"metadata": {}, | ||
"cell_type": "markdown", | ||
"source": [ | ||
"### Preamble\n", | ||
"\n", | ||
"First lets get some boilerplate out of the way. See the \"Hello OpenAssetIO\" notebook for more details." | ||
], | ||
"id": "5b0c4ae1f1b914e6" | ||
}, | ||
{ | ||
"metadata": { | ||
"ExecuteTime": { | ||
"end_time": "2024-07-16T14:34:25.634357Z", | ||
"start_time": "2024-07-16T14:34:25.606468Z" | ||
} | ||
}, | ||
"cell_type": "code", | ||
"source": [ | ||
"import os\n", | ||
"\n", | ||
"\n", | ||
"try:\n", | ||
" import openassetio\n", | ||
" import openassetio_mediacreation\n", | ||
"except ImportError:\n", | ||
" print(\n", | ||
" \"This notebook requires the packages listed in `resources/requirements.txt` to be installed\")\n", | ||
" raise\n", | ||
"\n", | ||
"from resources import helpers\n", | ||
"\n", | ||
"from openassetio.hostApi import HostInterface, ManagerFactory\n", | ||
"from openassetio.log import ConsoleLogger, SeverityFilter\n", | ||
"\n", | ||
"\n", | ||
"class NotebookHostInterface(HostInterface):\n", | ||
" def identifier(self):\n", | ||
" return \"org.jupyter.notebook\"\n", | ||
"\n", | ||
" def displayName(self):\n", | ||
" return \"Jupyter Notebook\"\n", | ||
"\n", | ||
"\n", | ||
"host_interface = NotebookHostInterface()\n", | ||
"\n", | ||
"logger = SeverityFilter(ConsoleLogger())" | ||
], | ||
"id": "db2739c3f0a96d70", | ||
"outputs": [], | ||
"execution_count": 1 | ||
}, | ||
{ | ||
"metadata": {}, | ||
"cell_type": "markdown", | ||
"source": [ | ||
"\n", | ||
"### The two example plugins\n", | ||
"\n", | ||
"In order to illustrate the hybrid plugin system, we'll make use of two ready-made example plugins - the \"Basic Asset Libary\" (aka BAL, a pure Python manager/plugin) and the \"Simple C++ Manager\" (aka SimpleCppManager, a pure C++ manager/plugin). \n", | ||
"\n", | ||
"BAL should be installed into the Python environment of this notebook (see `resources/requirements.txt`), and so will be trivially discoverable by OpenAssetIO.\n", | ||
"\n", | ||
"SimpleCppManager is more complex, and must be built with a compiler toolchain compatible with the OpenAssetIO libraries in the Python environment of this notebook. See `resources/hybrid_plugin_system/SimpleCppManager/README.md` for more details. We assume it is installed into `resources/hybrid_plugin_system/SimpleCppManager`, and will be discovered by adding this location to the standard `OPENASSETIO_PLUGIN_PATH` environment variable." | ||
], | ||
"id": "791f52d06eea9aea" | ||
}, | ||
{ | ||
"metadata": { | ||
"ExecuteTime": { | ||
"end_time": "2024-07-16T14:34:25.638224Z", | ||
"start_time": "2024-07-16T14:34:25.635698Z" | ||
} | ||
}, | ||
"cell_type": "code", | ||
"source": [ | ||
"os.environ[\"OPENASSETIO_PLUGIN_PATH\"] = os.path.join(\n", | ||
" \"resources\", \"hybrid_plugin_system\", \"SimpleCppManager\")" | ||
], | ||
"id": "e3a94c5be66ad34e", | ||
"outputs": [], | ||
"execution_count": 2 | ||
}, | ||
{ | ||
"metadata": {}, | ||
"cell_type": "markdown", | ||
"source": [ | ||
"\n", | ||
"\n", | ||
"These two plugins each advertise a different unique identifier, so will not work with the hybrid plugin system out of the box. Luckily, they are both designed to be used in testing situations and are fully configurable. We can control the identifier that they will advertise using environment variables:\n" | ||
], | ||
"id": "e19f60f60b11d7f9" | ||
}, | ||
{ | ||
"metadata": { | ||
"ExecuteTime": { | ||
"end_time": "2024-07-16T14:34:25.643984Z", | ||
"start_time": "2024-07-16T14:34:25.639388Z" | ||
} | ||
}, | ||
"cell_type": "code", | ||
"source": [ | ||
"os.environ[\"OPENASSETIO_BAL_IDENTIFIER\"] = \"org.openassetio.examples.manager.hybrid\"\n", | ||
"os.environ[\"OPENASSETIO_SIMPLECPPMANAGER_IDENTIFIER\"] = \"org.openassetio.examples.manager.hybrid\"" | ||
], | ||
"id": "6c95ee177f2a9028", | ||
"outputs": [], | ||
"execution_count": 3 | ||
}, | ||
{ | ||
"metadata": {}, | ||
"cell_type": "markdown", | ||
"source": [ | ||
"Other configuration is provided by the configuration file `resources/hybrid_plugin_system/openassetio_config.toml`. Note that with hybrid plugins, the same configuration file is used for all the consistent plugins - this will be revisited later.\n", | ||
"\n", | ||
"Let's try to initialise the two managers separately and see what happens.\n" | ||
], | ||
"id": "2332a406713d0e76" | ||
}, | ||
{ | ||
"metadata": { | ||
"ExecuteTime": { | ||
"end_time": "2024-07-16T14:34:25.666995Z", | ||
"start_time": "2024-07-16T14:34:25.645711Z" | ||
} | ||
}, | ||
"cell_type": "code", | ||
"source": [ | ||
"from openassetio.errors import ConfigurationException\n", | ||
"from openassetio.pluginSystem import (\n", | ||
" CppPluginSystemManagerImplementationFactory, PythonPluginSystemManagerImplementationFactory)\n", | ||
"\n", | ||
"\n", | ||
"cpp_factory = CppPluginSystemManagerImplementationFactory(logger)\n", | ||
"\n", | ||
"try:\n", | ||
" _ = ManagerFactory.defaultManagerForInterface(\n", | ||
" \"resources/hybrid_plugin_system/openassetio_config.toml\",\n", | ||
" host_interface,\n", | ||
" cpp_factory,\n", | ||
" logger)\n", | ||
"\n", | ||
"except ConfigurationException as exc:\n", | ||
" helpers.display_result(f\"C++ plugin error: {exc}\")\n", | ||
"\n", | ||
"python_factory = PythonPluginSystemManagerImplementationFactory(logger)\n", | ||
"\n", | ||
"try:\n", | ||
" _ = ManagerFactory.defaultManagerForInterface(\n", | ||
" \"resources/hybrid_plugin_system/openassetio_config.toml\",\n", | ||
" host_interface,\n", | ||
" python_factory,\n", | ||
" logger)\n", | ||
"\n", | ||
"except ConfigurationException as exc:\n", | ||
" helpers.display_result(f\"Python plugin error: {exc}\")\n" | ||
], | ||
"id": "129439ba58f8e81f", | ||
"outputs": [ | ||
{ | ||
"data": { | ||
"text/markdown": "> **Result:**\n> `C++ plugin error: Manager implementation for 'org.openassetio.examples.manager.hybrid' does not support the required capabilities: managementPolicyQueries`" | ||
}, | ||
"metadata": {}, | ||
"output_type": "display_data" | ||
}, | ||
{ | ||
"data": { | ||
"text/markdown": "> **Result:**\n> `Python plugin error: Manager implementation for 'org.openassetio.examples.manager.hybrid' does not support the required capabilities: entityReferenceIdentification`" | ||
}, | ||
"metadata": {}, | ||
"output_type": "display_data" | ||
} | ||
], | ||
"execution_count": 4 | ||
}, | ||
{ | ||
"metadata": {}, | ||
"cell_type": "markdown", | ||
"source": "Neither of them advertise all the required capabilities! At least, on their own...", | ||
"id": "9328df2151a26971" | ||
}, | ||
{ | ||
"metadata": {}, | ||
"cell_type": "markdown", | ||
"source": [ | ||
"### The hybrid plugin system\n", | ||
"\n", | ||
"Given the two `ManagerImplementationFactoryInterface` instances (`cpp_factory` and `python_factory`), we can create a hybrid factory." | ||
], | ||
"id": "5fd4a729153276f9" | ||
}, | ||
{ | ||
"metadata": { | ||
"ExecuteTime": { | ||
"end_time": "2024-07-16T14:34:25.670851Z", | ||
"start_time": "2024-07-16T14:34:25.667896Z" | ||
} | ||
}, | ||
"cell_type": "code", | ||
"source": [ | ||
"from openassetio.pluginSystem import HybridPluginSystemManagerImplementationFactory\n", | ||
"\n", | ||
"\n", | ||
"hybrid_factory = HybridPluginSystemManagerImplementationFactory(\n", | ||
" [cpp_factory, python_factory], logger)\n", | ||
"\n", | ||
"manager = ManagerFactory.defaultManagerForInterface(\n", | ||
" \"resources/hybrid_plugin_system/openassetio_config.toml\",\n", | ||
" host_interface,\n", | ||
" hybrid_factory,\n", | ||
" logger)" | ||
], | ||
"id": "87c3d7e701b095d9", | ||
"outputs": [], | ||
"execution_count": 5 | ||
}, | ||
{ | ||
"metadata": {}, | ||
"cell_type": "markdown", | ||
"source": "Success! Now lets retrieve some data.", | ||
"id": "52d907bb3a19db77" | ||
}, | ||
{ | ||
"metadata": { | ||
"ExecuteTime": { | ||
"end_time": "2024-07-16T14:34:25.677445Z", | ||
"start_time": "2024-07-16T14:34:25.671716Z" | ||
} | ||
}, | ||
"cell_type": "code", | ||
"source": [ | ||
"from openassetio.access import ResolveAccess\n", | ||
"from openassetio_mediacreation.traits.identity import DisplayNameTrait\n", | ||
"\n", | ||
"\n", | ||
"context = manager.createContext()\n", | ||
"entity_ref = manager.createEntityReference(\"examplehybrid:///project_artwork/logos/openassetio\")\n", | ||
"\n", | ||
"trait_data = manager.resolve(entity_ref, {DisplayNameTrait.kId}, ResolveAccess.kRead, context)\n", | ||
"\n", | ||
"helpers.display_result(DisplayNameTrait(trait_data).getName())" | ||
], | ||
"id": "35b7fc091048a457", | ||
"outputs": [ | ||
{ | ||
"data": { | ||
"text/markdown": "> **Result:**\n> `The OpenAssetIO Logo`" | ||
}, | ||
"metadata": {}, | ||
"output_type": "display_data" | ||
} | ||
], | ||
"execution_count": 6 | ||
}, | ||
{ | ||
"metadata": {}, | ||
"cell_type": "markdown", | ||
"source": [ | ||
"We know that data came from the Simple C++ Manager plugin, since that is the only plugin that advertised the \"resolution\" capability.\n", | ||
"\n", | ||
"But what if both plugins advertise the same capability? In this case, the first plugin in the list supplied to the `HybridPluginSystemManagerImplementationFactory` constructor will be used. To illustrate, lets query the entity's traits." | ||
], | ||
"id": "85d2a4a633cc9e82" | ||
}, | ||
{ | ||
"metadata": { | ||
"ExecuteTime": { | ||
"end_time": "2024-07-16T14:34:25.682641Z", | ||
"start_time": "2024-07-16T14:34:25.678333Z" | ||
} | ||
}, | ||
"cell_type": "code", | ||
"source": [ | ||
"from openassetio.access import EntityTraitsAccess\n", | ||
"\n", | ||
"\n", | ||
"trait_set = manager.entityTraits(entity_ref, EntityTraitsAccess.kRead, context)\n", | ||
"\n", | ||
"helpers.display_result(trait_set)" | ||
], | ||
"id": "683fed98e47a97a", | ||
"outputs": [ | ||
{ | ||
"data": { | ||
"text/markdown": "> **Result:**\n> `{'openassetio-mediacreation:twoDimensional.Image', 'openassetio-mediacreation:usage.Entity', 'openassetio-mediacreation:identity.DisplayName'}`" | ||
}, | ||
"metadata": {}, | ||
"output_type": "display_data" | ||
} | ||
], | ||
"execution_count": 7 | ||
}, | ||
{ | ||
"metadata": {}, | ||
"cell_type": "markdown", | ||
"source": "Now lets try a different configuration of plugins", | ||
"id": "76fa30f9d2f31969" | ||
}, | ||
{ | ||
"metadata": { | ||
"ExecuteTime": { | ||
"end_time": "2024-07-16T14:34:25.698766Z", | ||
"start_time": "2024-07-16T14:34:25.683435Z" | ||
} | ||
}, | ||
"cell_type": "code", | ||
"source": [ | ||
"alt_hybrid_factory = HybridPluginSystemManagerImplementationFactory(\n", | ||
" [python_factory, cpp_factory], logger)\n", | ||
"\n", | ||
"alt_manager = ManagerFactory.defaultManagerForInterface(\n", | ||
" \"resources/hybrid_plugin_system/openassetio_config.toml\",\n", | ||
" host_interface,\n", | ||
" alt_hybrid_factory,\n", | ||
" logger)\n", | ||
"\n", | ||
"trait_set = alt_manager.entityTraits(entity_ref, EntityTraitsAccess.kRead, context)\n", | ||
"\n", | ||
"helpers.display_result(trait_set)" | ||
], | ||
"id": "e8008d8183fb262d", | ||
"outputs": [ | ||
{ | ||
"data": { | ||
"text/markdown": "> **Result:**\n> `{'openassetio-mediacreation:usage.Entity', 'openassetio-mediacreation:timeDomain.FrameRanged', 'openassetio-mediacreation:identity.DisplayName', 'openassetio-mediacreation:lifecycle.Version', 'openassetio-mediacreation:content.LocatableContent', 'openassetio-mediacreation:twoDimensional.Image'}`" | ||
}, | ||
"metadata": {}, | ||
"output_type": "display_data" | ||
} | ||
], | ||
"execution_count": 8 | ||
}, | ||
{ | ||
"metadata": {}, | ||
"cell_type": "markdown", | ||
"source": "The trait set of the entity is different! This is because both BAL and SimpleCppManager support the entityTraitIntrospection capability, and in the `alt_manager` case BAL was provided first when constructing the hybrid factory, so BAL was queried for the trait set rather than SimpleCppManager.", | ||
"id": "1fb66b7c6fd38071" | ||
} | ||
], | ||
"metadata": { | ||
"kernelspec": { | ||
"display_name": "Python 3", | ||
"language": "python", | ||
"name": "python3" | ||
}, | ||
"language_info": { | ||
"codemirror_mode": { | ||
"name": "ipython", | ||
"version": 2 | ||
}, | ||
"file_extension": ".py", | ||
"mimetype": "text/x-python", | ||
"name": "python", | ||
"nbconvert_exporter": "python", | ||
"pygments_lexer": "ipython2", | ||
"version": "2.7.6" | ||
} | ||
}, | ||
"nbformat": 4, | ||
"nbformat_minor": 5 | ||
} |
Oops, something went wrong.