4.3. Creating a Python Extension¶
PyMØD is primarily Python bindings for libMØD made using Boost.Python. The following shows a template for creating a Python extension with C++ functions (e.g., to extend libMØD). The complete source code for the example can be found here.
4.3.1. Library Source and Headers¶
For this examples the following C++ source with header should be exposed in the Python module. Header:
#ifndef AWESOMELIB_HPP
#define AWESOMELIB_HPP
#include <mod/graph/Graph.hpp>
namespace awesome {
std::shared_ptr<mod::graph::Graph> doStuff();
} // namespace awesome
#endif // AWESOMELIB_HPP
Source:
#include "stuff.hpp"
namespace awesome {
std::shared_ptr<mod::graph::Graph> doStuff() {
auto g = mod::graph::Graph::fromSMILES("CCO");
g->setName("Ethanol");
return g;
}
} // namespace awesome
4.3.2. Exposing the Interface¶
See the Boost.Python documentation for instructions how to expose C++ code. Below is the code for creating our simple Python extension.
#include <boost/python.hpp>
#include "stuff.hpp"
#include <mod/Config.hpp>
namespace py = boost::python;
namespace {
// this can be used to make sure the extension and mod is using the same shared library
uintptr_t magicLibraryValue() {
return (uintptr_t)&mod::getConfig();
}
}
BOOST_PYTHON_MODULE(awesome) {
py::def("magicLibraryValue", &magicLibraryValue);
py::def("doStuff", &awesome::doStuff);
}
The example exposes a bit of extra functionality for a sanity check.
Python will dlopen
libMØD twice As both the PyMØD module and our extension
requires it. The library uses static variables and strange things might happen
if multiple instances of these exist. The MØD wrapper script changes the dlopen
flags (setting RTLD_GLOBAL
and RTLD_NOW
) which should prevent multiple
instances of the library. The function magicLibraryValue
can be used to check
that this is true (see the test script below).
4.3.3. Building With CMake¶
We can use CMake to build the example:
cmake_minimum_required(VERSION 3.15)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake)
project(PyModTestProject CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# MØD
# -------------------------------------------------------------------------
find_package(mod REQUIRED)
# Artefacts
# -------------------------------------------------------------------------
add_library(awesome MODULE
pyModule.cpp
stuff.cpp)
set_target_properties(awesome PROPERTIES PREFIX "") # so it doesn't get the "lib" prefix
target_link_libraries(awesome PUBLIC mod::pymodutils)
target_compile_options(awesome PRIVATE -Wall -Wextra -pedantic
-Wno-unused-parameter
-Wno-comment
-Wno-unused-local-typedefs)
4.3.4. Testing the Extension¶
After configuring and building there should be a shared library awesome.so
which contains
the extension. Executing the following script using the wrapper script in the same folder
as the shared libray should now work:
import awesome
# sanity check for multiple copies of libMØD
modValue = mod.magicLibraryValue()
ourValue = awesome.magicLibraryValue()
if modValue != ourValue:
print("mod =", modValue)
print("our =", ourValue)
raise Exception("Magic values differ! I.e., more than one instance of libMØD has been loaded.")
# end if check
g = awesome.doStuff()
print("Got a graph:", g.name)
g.print()
Command to execute:
mod -f test.py