/* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
* Author:
* Matias Fontanini
*
*/
#ifndef PYWRAPPER_H
#define PYWRAPPER_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace Python {
// Deleter that calls Py_XDECREF on the PyObject parameter.
struct PyObjectDeleter {
void operator()(PyObject *obj) {
Py_XDECREF(obj);
}
};
// unique_ptr that uses Py_XDECREF as the destructor function.
typedef std::unique_ptr pyunique_ptr;
// ------------ Conversion functions ------------
// Convert a PyObject to a std::string.
bool convert(PyObject *obj, std::string &val);
// Convert a PyObject to a std::vector.
bool convert(PyObject *obj, std::vector &val);
// Convert a PyObject to a bool value.
bool convert(PyObject *obj, bool &value);
// Convert a PyObject to any integral type.
template
bool convert(PyObject *obj, T &val) {
if(!PyInt_Check(obj))
return false;
val = PyInt_AsLong(obj);
return true;
}
// Convert a PyObject to an float.
bool convert(PyObject *obj, double &val);
template
typename std::enable_if::type
add_to_tuple(PyObject *obj, std::tuple &tup) {
return convert(PyTuple_GetItem(obj, n), std::get(tup));
}
template
typename std::enable_if::type
add_to_tuple(PyObject *obj, std::tuple &tup) {
add_to_tuple(obj, tup);
return convert(PyTuple_GetItem(obj, n), std::get(tup));
}
template
bool convert(PyObject *obj, std::tuple &tup) {
if(!PyTuple_Check(obj) ||
PyTuple_Size(obj) != sizeof...(Args))
return false;
return add_to_tuple(obj, tup);
}
// Convert a PyObject to a std::map
template
bool convert(PyObject *obj, std::map &mp) {
if(!PyDict_Check(obj))
return false;
PyObject *py_key, *py_val;
Py_ssize_t pos(0);
while (PyDict_Next(obj, &pos, &py_key, &py_val)) {
K key;
if(!convert(py_key, key))
return false;
V val;
if(!convert(py_val, val))
return false;
mp.insert(std::make_pair(key, val));
}
return true;
}
// Convert a PyObject to a generic container.
template
bool convert_list(PyObject *obj, C &container) {
if(!PyList_Check(obj))
return false;
for(Py_ssize_t i(0); i < PyList_Size(obj); ++i) {
T val;
if(!convert(PyList_GetItem(obj, i), val))
return false;
container.push_back(std::move(val));
}
return true;
}
// Convert a PyObject to a std::list.
template bool convert(PyObject *obj, std::list &lst) {
return convert_list(obj, lst);
}
// Convert a PyObject to a std::vector.
template bool convert(PyObject *obj, std::vector &vec) {
return convert_list(obj, vec);
}
template bool generic_convert(PyObject *obj,
const std::function &is_obj,
const std::function &converter,
T &val) {
if(!is_obj(obj))
return false;
val = converter(obj);
return true;
}
// -------------- PyObject allocators ----------------
// Generic python list allocation
template static PyObject *alloc_list(const T &container) {
PyObject *lst(PyList_New(container.size()));
Py_ssize_t i(0);
for(auto it(container.begin()); it != container.end(); ++it)
PyList_SetItem(lst, i++, alloc_pyobject(*it));
return lst;
}
// Creates a PyObject from a std::string
PyObject *alloc_pyobject(const std::string &str);
// Creates a PyObject from a std::vector
PyObject *alloc_pyobject(const std::vector &val, size_t sz);
// Creates a PyObject from a std::vector
PyObject *alloc_pyobject(const std::vector &val);
// Creates a PyObject from a const char*
PyObject *alloc_pyobject(const char *cstr);
// Creates a PyObject from any integral type(gets converted to PyInt)
template
PyObject *alloc_pyobject(T num) {
return PyInt_FromLong(num);
}
// Creates a PyObject from a bool
PyObject *alloc_pyobject(bool value);
// Creates a PyObject from a double
PyObject *alloc_pyobject(double num);
// Creates a PyObject from a std::vector
template PyObject *alloc_pyobject(const std::vector &container) {
return alloc_list(container);
}
// Creates a PyObject from a std::list
template PyObject *alloc_pyobject(const std::list &container) {
return alloc_list(container);
}
// Creates a PyObject from a std::map
template PyObject *alloc_pyobject(
const std::map &container) {
PyObject *dict(PyDict_New());
for(auto it(container.begin()); it != container.end(); ++it)
PyDict_SetItem(dict,
alloc_pyobject(it->first),
alloc_pyobject(it->second)
);
return dict;
}
void initialize();
void finalize();
void print_error();
void clear_error();
void print_object(PyObject *obj);
/**
* \class Object
* \brief This class represents a python object.
*/
class Object {
public:
/**
* \brief Constructs a default python object
*/
Object();
/**
* \brief Constructs a python object from a PyObject pointer.
*
* This Object takes ownership of the PyObject* argument. That
* means no Py_INCREF is performed on it.
* \param obj The pointer from which to construct this Object.
*/
Object(PyObject *obj);
/**
* \brief Calls the callable attribute "name" using the provided
* arguments.
*
* This function might throw a std::runtime_error if there is
* an error when calling the function.
*
* \param name The name of the attribute to be called.
* \param args The arguments which will be used when calling the
* attribute.
* \return Python::Object containing the result of the function.
*/
template
Object call_function(const std::string &name, const Args&... args) {
pyunique_ptr func(load_function(name));
// Create the tuple argument
pyunique_ptr tup(PyTuple_New(sizeof...(args)));
add_tuple_vars(tup, args...);
// Call our object
PyObject *ret(PyObject_CallObject(func.get(), tup.get()));
if(!ret)
throw std::runtime_error("Failed to call function " + name);
return {ret};
}
/**
* \brief Calls a callable attribute using no arguments.
*
* This function might throw a std::runtime_error if there is
* an error when calling the function.
*
* \sa Python::Object::call_function.
* \param name The name of the callable attribute to be executed.
* \return Python::Object containing the result of the function.
*/
Object call_function(const std::string &name);
/**
* \brief Finds and returns the attribute named "name".
*
* This function might throw a std::runtime_error if an error
* is encountered while fetching the attribute.
*
* \param name The name of the attribute to be returned.
* \return Python::Object representing the attribute.
*/
Object get_attr(const std::string &name);
/**
* \brief Checks whether this object contains a certain attribute.
*
* \param name The name of the attribute to be searched.
* \return bool indicating whether the attribute is defined.
*/
bool has_attr(const std::string &name);
/**
* \brief Returns the internal PyObject*.
*
* No reference increment is performed on the PyObject* before
* returning it, so any DECREF applied to it without INCREF'ing
* it will cause undefined behaviour.
* \return The PyObject* which this Object is representing.
*/
PyObject *get() const { return py_obj.get(); }
template
bool convert(T ¶m) {
return Python::convert(py_obj.get(), param);
}
/**
* \brief Constructs a Python::Object from a script.
*
* The returned Object will be the representation of the loaded
* script. If any errors are encountered while loading this
* script, a std::runtime_error is thrown.
*
* \param script_path The path of the script to be loaded.
* \return Object representing the loaded script.
*/
static Object from_script(const std::string &script_path);
private:
typedef std::shared_ptr pyshared_ptr;
PyObject *load_function(const std::string &name);
pyshared_ptr make_pyshared(PyObject *obj);
// Variadic template method to add items to a tuple
template
void add_tuple_vars(pyunique_ptr &tup, const First &head, const Rest&... tail) {
add_tuple_var(
tup,
PyTuple_Size(tup.get()) - sizeof...(tail) - 1,
head
);
add_tuple_vars(tup, tail...);
}
void add_tuple_vars(pyunique_ptr &tup, PyObject *arg) {
add_tuple_var(tup, PyTuple_Size(tup.get()) - 1, arg);
}
// Base case for add_tuple_vars
template
void add_tuple_vars(pyunique_ptr &tup, const Arg &arg) {
add_tuple_var(tup,
PyTuple_Size(tup.get()) - 1, alloc_pyobject(arg)
);
}
// Adds a PyObject* to the tuple object
void add_tuple_var(pyunique_ptr &tup, Py_ssize_t i, PyObject *pobj) {
PyTuple_SetItem(tup.get(), i, pobj);
}
// Adds a PyObject* to the tuple object
template void add_tuple_var(pyunique_ptr &tup, Py_ssize_t i,
const T &data) {
PyTuple_SetItem(tup.get(), i, alloc_pyobject(data));
}
pyshared_ptr py_obj;
};
};
#endif // PYWRAPPER_H