[Docs] Add C++ example Jupyter Notebook
Description
Having some fun experimenting with xeus-cling C++ JupyterLab Notebooks.
As you might expect, there's quite a lot of boilerplate in getting this up and running.
Supplying OpenAssetIO and its dependencies is done by installing into the same Conda environment that the Notebook runs in, meaning they are trivially on the compiler search path.
Regardless of the Jupyter stuff, Conda (or rather, the Mamba rewrite) is quite a nice virtual environment tool. It operates at a higher level than Python's venv , since Python itself is a Conda package. Very handy for creating an isolated build environment without the drawbacks of Docker.
- [ ] I have updated the release notes.
- [ ] I have updated all relevant user documentation.
Test Instructions
Hopefully the README is sufficient.
Click here to show a successful run of the C++ Notebook exported as Markdown
Load a Python plugin
It is assumed this Notebook is running in an environment with OpenAssetIO and OpenAssetIO-MediaCreation installed into it, such that libraries and headers are trivially discoverable (e.g. Conda environment described in the README).
Load Python library
Load the Python available in the Conda environment this Notebook is running in.
Use xeus-cling pragma extension to add search paths and load the library.
#pragma cling add_library_path("$CONDA_PREFIX/lib")
#pragma cling load("python3.11")
Load OpenAssetIO library
#pragma cling load("openassetio-python")
Bootstrap Python
#include <python3.11/Python.h>
Py_Initialize();
Define a logger
#include <iostream>
#include <openassetio/log/LoggerInterface.hpp>
struct Logger : openassetio::log::LoggerInterface {
void log(Severity severity, const openassetio::Str& message) override {
std::cout << message << "\n";
}
};
auto logger = std::make_shared<Logger>();
Query Python plugins
#include <memory>
#include <openassetio/hostApi/ManagerImplementationFactoryInterface.hpp>
#include <openassetio/python/hostApi.hpp>
const auto factory = openassetio::python::hostApi::createPythonPluginSystemManagerImplementationFactory(logger);
const openassetio::Identifiers identifiers = factory->identifiers();
identifiers
PythonPluginSystem: Searching packages for 'openassetio.manager_plugin' entry points.
PythonPluginSystem: Found entry point in plugin_package_or_module
PythonPluginSystem: Registered plug-in '<class 'openassetio_manager_bal.BasicAssetLibraryPlugin'>' from '/opt/conda/envs/jupyter-deleteme/lib/python3.11/site-packages/openassetio_manager_bal/__init__.py'
{ "org.openassetio.examples.manager.bal" }
Define host interface
#include <openassetio/hostApi/HostInterface.hpp>
struct Host : openassetio::hostApi::HostInterface {
openassetio::Identifier identifier() const { return "org.demo"; }
openassetio::Str displayName() const { return "Demo Host"; }
openassetio::InfoDictionary info() const { return {}; }
};
const auto host = std::make_shared<Host>();
Get a manager instance
Here we pass a path to the config.toml, we could also use the OPENASSETIO_DEFAULT_CONFIG environment variable.
#include <openassetio/hostApi/ManagerFactory.hpp>
const auto manager = openassetio::hostApi::ManagerFactory::defaultManagerForInterface("resources/config.toml", host, factory, logger)
Loading default manager config at 'resources/config.toml'
Instantiating org.openassetio.examples.manager.bal
Loading library from '/home/dave/workspace/cloud/assetapi/OpenAssetIO/doc/jupyter-notebooks/resources/database.json'
Running with simulated query latency of 10ms
Entity reference prefix 'bal:///' provided by manager's info() dict. Subsequent calls to isEntityReferenceString will use this prefix rather than call the manager's implementation.
Resolve some data
#include <openassetio/hostApi/Manager.hpp>
#include <openassetio_mediacreation/traits/content.hpp>
using openassetio_mediacreation::traits::content::LocatableContentTrait;
const openassetio::ContextPtr context = manager->createContext();
const openassetio::EntityReference ref = manager->createEntityReference("bal:///demo_project/logos/openassetio/latest");
const auto data = manager->resolve(ref, {LocatableContentTrait::kId}, context);
std::string url = LocatableContentTrait(data).getLocation();
url
"file:///home/dave/workspace/cloud/assetapi/OpenAssetIO/doc/jupyter-notebooks/resources/logo_v2.jpg"
auto file_path = url.substr(7);
file_path
"/home/dave/workspace/cloud/assetapi/OpenAssetIO/doc/jupyter-notebooks/resources/logo_v2.jpg"
(boilerplate to render images in the notebook)
This next snippet is from the xeus-cling docs, required in order to display images in the Notebook: https://xeus-cling.readthedocs.io/en/latest/rich_display.html
/**
* Modified from: https://xeus-cling.readthedocs.io/en/latest/rich_display.html
*
* Why isn't this built-in ???
*/
#include <string>
#include <fstream>
#include "xtl/xbase64.hpp"
#include "nlohmann/json.hpp"
namespace nl = nlohmann;
namespace im
{
struct jpg
{
inline jpg(const std::string& filename)
{
std::ifstream fin(filename, std::ios::binary);
m_buffer << fin.rdbuf();
}
std::stringstream m_buffer;
};
nl::json mime_bundle_repr(const jpg& i)
{
auto bundle = nl::json::object();
bundle["image/jpeg"] = xtl::base64encode(i.m_buffer.str());
return bundle;
}
}
Render the resolved and retrieved data
im::jpg logo{file_path};
logo

Looks great. Your point about this being a great dev test environment for PR's and the like is pretty appealing too, I do like the jupyter environment. I'm thrilled that we've confirmed c++ is reasonably doable in jupyter too.
I'm hesitant to put this into main for 2 reasons though
- The first you've said yourself, not having CI checks leaves some risk for this going out of date and not working for users after we update some.
- The second is that I think a python jupyter notebook would be much more plug and play than the C++ one, it wouldn't need the user to do any manual installs like this one does. I know this c++ one dosen't preclude the addition of a python one, but I'm not sure i'd want a notebook with this sort of setup to be the "main" one, if you get my gist. It's just something I think we could do with thinking about.
I'm hesitant to put this into main for 2 reasons though
If we still have concerns about this, please can we park this until we have the preflight changes done. We have a significant number of new users expressing interest in the project, and a stable core API is becoming more and more pressing to allow them to begin experimental implementations without immediate churn.
I think its about time we just closed this.
- It's been languishing for nearly a year.
- The concept is proved.
- Conda was new to us (me) and the instructions could do with finessing.
- Since this was created, we decided that notebooks belong in the MediaCreation repo.
- The API has moved along significantly since this was created.
- A new version of Cling has been released and there is also Clang-Repl which may supercede Cling.