Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,8 @@ cmake_minimum_required (VERSION 2.8)
project (wrappy)

# Dependencies
find_package(PythonLibs 2.7 REQUIRED)
# If i had one word to describe python versioning,
# it would be "broken as fuck"
if (NOT PYTHON_VERSION VERSION_LESS 3.0)
error("Requires python 2.x")
endif()
# If PYTHON_VERSION isn't set, use the default version on this system
find_package(PythonLibs ${PYTHON_VERSION} REQUIRED)

option( WRAPPY_BUILD_DEMOS "Build the wrappy tests" ON)

Expand All @@ -20,7 +16,7 @@ add_library(wrappy SHARED wrappy.cpp)
set_target_properties(wrappy PROPERTIES VERSION 1.0.0)
set_target_properties(wrappy PROPERTIES SOVERSION 1)

target_include_directories(wrappy PRIVATE ${PYTHON_INCLUDE_DIRS})
include_directories(wrappy PUBLIC ${PYTHON_INCLUDE_DIRS})
target_include_directories(wrappy PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include/)

if(${CMAKE_VERSION} VERSION_LESS 3.1)
Expand Down
31 changes: 23 additions & 8 deletions include/wrappy/wrappy.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
#pragma once

// Python header must be included first since they insist on
// unconditionally defining some system macros
// (http://bugs.python.org/issue1045893, still broken in python3.4)
#include <Python.h>

#include <map>
#include <vector>
#include <string>
Expand All @@ -21,6 +26,16 @@ typedef _object PyObject;

namespace wrappy {

/*
* Python 3 uses unicode strings, so we have to allocate memory and copy the
* byte array out. Using std::string seems the tidiest way of doing this.
*/
#if PY_MAJOR_VERSION >= 3
typedef std::string string_t;
#else
typedef const char *string_t;
#endif

class WrappyError : public std::runtime_error {
public:
WrappyError(const std::string& str)
Expand All @@ -38,7 +53,7 @@ class PythonObject {
// not to do non-const things with it.
long long num() const;
double floating() const;
const char* str() const;
string_t str() const;
PyObject* get() const;
PythonObject attr(const std::string& x) const; // returns self.x

Expand Down Expand Up @@ -71,14 +86,14 @@ class PythonObject {
PyObject* obj_;
};

// Note that this is an input iterator, iterators cannot
// Note that this is an input iterator, iterators cannot
// be stored, rewound, or compared to anything but "end"
struct PythonIterator {
PythonIterator& operator++(); // pre-increment
PythonObject operator*(); // dereference
// *only* for comparison to "end", python iterators have
// *only* for comparison to "end", python iterators have
// no concept of position or comparability
bool operator!=(const PythonIterator&);
bool operator!=(const PythonIterator&);

private:
PythonIterator(bool, PythonObject);
Expand All @@ -102,12 +117,12 @@ void addModuleSearchPath(const std::string& path);

// There is one quirk of call() for the case of member methods:
//
// call("module.A.foo")
// call("module.A.foo")
//
// calls the unbound method "foo", so it is necessary to provide an instance
// of A as the first argument, while
//
// auto a = call("module.A"); call(a, "foo");
// auto a = call("module.A"); call(a, "foo");
//
// calls the method "foo" that is already bound to a, so providing an explicit
// self argument in that case is an error.
Expand All @@ -130,8 +145,8 @@ PythonObject construct(PythonObject); // identity
PythonObject construct(const std::vector<PythonObject>&); // python list

// TODO there is no good way to actually call these constructed functions
typedef PythonObject (*Lambda)(const std::vector<PythonObject>& args, const std::map<const char*, PythonObject>& kwargs);
typedef PythonObject (*LambdaWithData)(const std::vector<PythonObject>& args, const std::map<const char*, PythonObject>& kwargs, void* userdata);
typedef PythonObject (*Lambda)(const std::vector<PythonObject>& args, const std::map<string_t, PythonObject>& kwargs);
typedef PythonObject (*LambdaWithData)(const std::vector<PythonObject>& args, const std::map<string_t, PythonObject>& kwargs, void* userdata);
PythonObject construct(Lambda);
PythonObject construct(LambdaWithData, void*);

Expand Down
6 changes: 4 additions & 2 deletions tests/stdlib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ BOOST_AUTO_TEST_CASE(builtins)
args[0] = wrappy::construct(255ll);
auto longval = wrappy::callWithArgs("hex", args);

BOOST_CHECK_EQUAL(intval.str(), "0xff");
BOOST_CHECK_EQUAL(longval.str(), "0xffL");
std::string intval_str = intval.str();
std::string longval_str = longval.str();
BOOST_TEST(intval.str() == "0xff");
BOOST_TEST(longval.str() == "0xffL");
}

BOOST_AUTO_TEST_CASE(error)
Expand Down
91 changes: 65 additions & 26 deletions wrappy.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
// Python header must be included first since they insist on
// unconditionally defining some system macros
// (http://bugs.python.org/issue1045893, still broken in python3.4)
#include <Python.h>

#include <wrappy/wrappy.h>

#include <iostream>
Expand Down Expand Up @@ -40,7 +35,11 @@ void wrappyInitialize()
Py_Initialize();

// Setting a dummy value since many libraries require sys.argv[0] to exist
char* dummy_args[] = {const_cast<char*>("wrappy"), nullptr};
#if PY_MAJOR_VERSION >= 3
wchar_t *dummy_args[] = {const_cast<wchar_t *>(L"wrappy"), nullptr};
#else
char *dummy_args[] = {const_cast<char *>("wrappy"), nullptr};
#endif
PySys_SetArgvEx(1, dummy_args, 0);

wrappy::None = PythonObject(PythonObject::borrowed{}, Py_None);
Expand Down Expand Up @@ -177,9 +176,18 @@ double PythonObject::floating() const
return PyFloat_AsDouble(obj_);
}

const char* PythonObject::str() const
string_t PythonObject::str() const
{
#if PY_MAJOR_VERSION >= 3
// In Python 3, strings are unicode, so we have to do the necessary conversion here
PythonObject str{owning{}, PyUnicode_AsEncodedString(get(), "UTF-8", "strict")};
if (!str) {
throw WrappyError("Wrappy: Could not encode string");
}
return PyBytes_AsString(str.get());
#else
return PyString_AsString(obj_);
#endif
}

PythonObject::operator bool() const
Expand All @@ -200,7 +208,11 @@ PythonObject construct(long long ll)

PythonObject construct(int i)
{
#if PY_MAJOR_VERSION >= 3
return PythonObject(PythonObject::owning {}, PyLong_FromLong(i));
#else
return PythonObject(PythonObject::owning {}, PyInt_FromLong(i));
#endif
}

PythonObject construct(double d)
Expand All @@ -210,7 +222,13 @@ PythonObject construct(double d)

PythonObject construct(const std::string& str)
{
#if PY_MAJOR_VERSION >= 3
return PythonObject(PythonObject::owning {},
PyUnicode_FromKindAndData(PyUnicode_1BYTE_KIND,
str.c_str(), str.size()));
#else
return PythonObject(PythonObject::owning {}, PyString_FromString(str.c_str()));
#endif
}

PythonObject construct(const std::vector<PythonObject>& v)
Expand All @@ -234,12 +252,14 @@ void addModuleSearchPath(const std::string& path)
std::string pathString("path");
auto syspath = PySys_GetObject(&pathString[0]); // Borrowed reference

PythonObject pypath(PythonObject::owning {},
PyString_FromString(path.c_str()));
PythonObject pypath = construct(path);

// nullptr already checked for in Python 3 API
#if PY_MAJOR_VERSION < 3
if (!pypath) {
throw WrappyError("Wrappy: Can't allocate memory for string.");
}
#endif

auto pos = PyList_Insert(syspath, 0, pypath.get());
if (pos < 0) {
Expand Down Expand Up @@ -354,7 +374,7 @@ PythonObject callWithArgs(
PythonObject function = loadObject(from, name);

if (!function) {
throw WrappyError("Wrappy: "
throw WrappyError("Wrappy: "
"Lookup of function " + functionName + " failed.");
}

Expand Down Expand Up @@ -435,30 +455,53 @@ std::vector<PythonObject> to_vector(PyObject* pyargs)
return args;
}

std::map<const char*, PythonObject> to_map(PyObject* pykwargs)
std::map<wrappy::string_t, PythonObject> to_map(PyObject* pykwargs)
{
if (!PyDict_Check(pykwargs)) {
throw WrappyError("Trampoling kwargs was no dict");
}
std::map<const char*, PythonObject> kwargs;
std::map<wrappy::string_t, PythonObject> kwargs;
PyObject *key, *value;
Py_ssize_t pos = 0;
while (PyDict_Next(pykwargs, &pos, &key, &value)) {
const char* str = PyString_AsString(key);
PythonObject str(PythonObject::borrowed{}, key);
PythonObject obj(PythonObject::borrowed{}, value);
kwargs.emplace(str, obj);
kwargs.emplace(str.str(), obj);
}

return kwargs;
}

// In Python 3, CObjects were deprecated and removed in favour of PyCapsules
#if PY_MAJOR_VERSION >= 3
#define check_cobject PyCapsule_CheckExact
#define get_cobject_desc PyCapsule_GetContext
#define cobject_as_ptr(capsule) PyCapsule_GetPointer(capsule, nullptr)
#define cobject_from_ptr(ptr) PyCapsule_New(ptr, nullptr, nullptr)

PyObject *cobject_from_ptr_and_desc(void *ptr, void *desc)
{
PyObject *obj = cobject_from_ptr(ptr);
if (!PyCapsule_SetContext(obj, desc)) {
throw WrappyError("Wrappy: Could not set context for object");
}
return obj;
}
#else
#define check_cobject PyCObject_Check
#define get_cobject_desc PyCObject_GetDesc
#define cobject_as_ptr PyCObject_AsVoidPtr
#define cobject_from_ptr(ptr) PyCObject_FromVoidPtr(ptr, nullptr)
#define cobject_from_ptr_and_desc(ptr, desc) PyCObject_FromVoidPtrAndDesc(ptr, desc, nullptr)
#endif

PyObject* trampolineWithData(PyObject* data, PyObject* pyargs, PyObject* pykwargs) {
if (!PyCObject_Check(data)) {
if (!check_cobject(data)) {
throw WrappyError("Trampoline data corrupted");
}

LambdaWithData fun = reinterpret_cast<LambdaWithData>(PyCObject_AsVoidPtr(data));
void* userdata = PyCObject_GetDesc(data);
LambdaWithData fun = reinterpret_cast<LambdaWithData>(cobject_as_ptr(data));
void* userdata = get_cobject_desc(data);
auto args = to_vector(pyargs);
auto kwargs = to_map(pykwargs);

Expand All @@ -467,11 +510,11 @@ PyObject* trampolineWithData(PyObject* data, PyObject* pyargs, PyObject* pykwarg

PyObject* trampolineNoData(PyObject* data, PyObject* pyargs, PyObject* pykwargs)
{
if (!PyCObject_Check(data)) {
if (!check_cobject(data)) {
throw WrappyError("Trampoline data corrupted");
}

Lambda fun = reinterpret_cast<Lambda>(PyCObject_AsVoidPtr(data));
Lambda fun = reinterpret_cast<Lambda>(cobject_as_ptr(data));
auto args = to_vector(pyargs);
auto kwargs = to_map(pykwargs);

Expand All @@ -487,23 +530,19 @@ PyMethodDef trampolineWithDataMethod {"trampoline2", reinterpret_cast<PyCFunctio

PythonObject construct(Lambda lambda)
{
PyObject* pydata = PyCObject_FromVoidPtr(reinterpret_cast<void*>(lambda), nullptr);
PyObject* pydata = cobject_from_ptr(reinterpret_cast<void*>(lambda));
return PythonObject(PythonObject::owning{}, PyCFunction_New(&trampolineNoDataMethod, pydata));
}

PythonObject construct(LambdaWithData lambda, void* userdata)
{
PyObject* pydata;
if (!userdata) {
pydata = PyCObject_FromVoidPtr(reinterpret_cast<void*>(lambda), nullptr);
pydata = cobject_from_ptr(reinterpret_cast<void*>(lambda));
} else { // python returns an error if FromVoidPtrAndDesc is called with desc being null
pydata = PyCObject_FromVoidPtrAndDesc(reinterpret_cast<void*>(lambda), userdata, nullptr);
pydata = cobject_from_ptr_and_desc(reinterpret_cast<void*>(lambda), userdata);
}
return PythonObject(PythonObject::owning{}, PyCFunction_New(&trampolineWithDataMethod, pydata));
}


typedef PythonObject (*Lambda)(const std::vector<PythonObject>& args, const std::map<const char*, PythonObject>& kwargs);
typedef PythonObject (*LambdaWithData)(const std::vector<PythonObject>& args, const std::map<const char*, PythonObject>& kwargs, void* userdata);

} // end namespace wrappy