Wrapping openbabel in python - using cython
The previous post showed a contrived example of how one could access the openbabel functionality in python using the boost libraries. There is alternative to boost to wrap around c/c++ code, it’s called cython. Here is an example openbabel wrapper, exposing some very simple functionality. Just before diving in, you should know that there already exists a wrapper around openbabel in python (it is called pybel). What I’m showing here doesn’t even come close to pybel in terms of usefulness, the idea here is to show a demo of how easy wrapping things in python is.
#! /usr/bin/python
from ez_setup import use_setuptools
use_setuptools()
from setuptools import setup, Extension, find_packages
from Cython.Distutils import build_ext
import sys, os
import glob
import subprocess as sub
def get_include(name="openbabel"):
p = sub.Popen('locate %s' % name, stdout=sub.PIPE, stderr=sub.PIPE, shell=True)
output, errors = p.communicate()
include = [str.split("%s%s%s" %(os.sep, name, os.sep))[0] for str in output.split("\n") if str.endswith(".h")]
include = set(include)
assert len(include) == 1
return list(include)[0]
def main():
extensions = [
Extension('_pyopenbabel',
glob.glob('src/*.pyx'),
[get_include('openbabel')],
language="c++", libraries=['openbabel'])
,
]
setup(name = 'pyopenbabel',
ext_package = 'pyopenbabel',
cmdclass = {'build_ext': build_ext},
ext_modules = extensions,
packages = find_packages()
)
if __name__ == '__main__':
main()
The actual wrapper is in src/openbabel.pxd
. In it you register which things from the openbabel headers you’re going to use/wrap. Note that all the “cdef extern from openbabel/…” statements will fail if the inculudes defined in setup.py are not actually pointing to a place where a openbabel headers live. If you want to find out what a header file is, or how to find it - for openbabel or any other piece of code - google will provide a fast soultion.
# distutils: language = c++
from libcpp.string cimport string
from libcpp cimport bool
cdef extern from "openbabel/base.h" namespace "OpenBabel":
cdef cppclass OBBase:
pass
cdef extern from "openbabel/mol.h" namespace "OpenBabel":
cdef cppclass OBMol(OBBase):
OBMol() except +
const char *GetTitle(bool replaceNewlines = true)
unsigned int NumAtoms()
OBAtom *GetAtom(int idx)
cdef extern from "openbabel/atom.h" namespace "OpenBabel":
cdef cppclass OBAtom(OBBase):
double GetX()
double GetY()
double GetZ()
cdef extern from "openbabel/obconversion.h" namespace "OpenBabel":
cdef cppclass OBConversion:
OBConversion() except +
bool SetInAndOutFormats(const char* inID, const char* outID)
bool SetInFormat(const char* inID)
bool ReadString(OBBase* pOb, string input)
bool ReadFile(OBBase* pOb, string filePath)
We register it’s use in src/pyopenbabel.pyx
cimport openbabel
def HandlePyOBMol(pyMol):
# https://groups.google.com/forum/?fromgroups#!topic/cython-users/YPzCqO4jxlA
cdef long ptr1 = long(pyMol.this)
cdef openbabel.OBMol* optr1 = <openbabel.OBMol*> ptr1
cdef openbabel.OBMol obMol = deref(optr1) # this is the c++ OBMol object
# ... handle further, get/set atoms, bonds, etc
Finally, in pyopenbabel/__init__.py
put
from _pyopenbabel import *
This will magically import everything from the wrapper (registered as cython extension in setup.py). Why do I think this is important? Because it’s fast, easy and may allow interesting chemistry to happen.