Add conversion of py::Object to mgp_value

Reviewers: teon.banek, ipaljak, mferencevic

Reviewed By: teon.banek, ipaljak

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D2667
This commit is contained in:
Lovro Lugovic 2020-02-12 14:29:29 +01:00
parent ad740e4ae2
commit 5953f07be3
4 changed files with 178 additions and 1 deletions

View File

@ -2,9 +2,9 @@
/// Provides a C++ API for working with Python's original C API.
#pragma once
#include <optional>
#include <ostream>
#include <string_view>
#include <optional>
// Define to use Py_ssize_t for API returning length of something. Some future
// Python version will only support Py_ssize_t, so it's best to always define

View File

@ -1,5 +1,7 @@
#include "query/procedure/py_module.hpp"
#include <stdexcept>
#include "query/procedure/mg_procedure_impl.hpp"
namespace query::procedure {
@ -50,6 +52,137 @@ py::Object MgpValueToPyObject(const mgp_value &value) {
}
}
mgp_value *PyObjectToMgpValue(PyObject *o, mgp_memory *memory) {
mgp_value *mgp_v{nullptr};
if (o == Py_None) {
mgp_v = mgp_value_make_null(memory);
} else if (PyBool_Check(o)) {
mgp_v = mgp_value_make_bool(static_cast<int>(o == Py_True), memory);
} else if (PyLong_Check(o)) {
int64_t value = PyLong_AsLong(o);
if (PyErr_Occurred()) {
PyErr_Clear();
throw std::overflow_error("Python integer is out of range");
}
mgp_v = mgp_value_make_int(value, memory);
} else if (PyFloat_Check(o)) {
mgp_v = mgp_value_make_double(PyFloat_AsDouble(o), memory);
} else if (PyUnicode_Check(o)) {
mgp_v = mgp_value_make_string(PyUnicode_AsUTF8(o), memory);
} else if (PyList_Check(o)) {
Py_ssize_t len = PyList_Size(o);
mgp_list *list = mgp_list_make_empty(len, memory);
if (!list) {
throw std::bad_alloc();
}
for (Py_ssize_t i = 0; i < len; ++i) {
PyObject *e = PyList_GET_ITEM(o, i);
mgp_value *v{nullptr};
try {
v = PyObjectToMgpValue(e, memory);
} catch (...) {
mgp_list_destroy(list);
throw;
}
if (!mgp_list_append(list, v)) {
mgp_value_destroy(v);
mgp_list_destroy(list);
throw std::bad_alloc();
}
mgp_value_destroy(v);
}
mgp_v = mgp_value_make_list(list);
} else if (PyTuple_Check(o)) {
Py_ssize_t len = PyTuple_Size(o);
mgp_list *list = mgp_list_make_empty(len, memory);
if (!list) {
throw std::bad_alloc();
}
for (Py_ssize_t i = 0; i < len; ++i) {
PyObject *e = PyTuple_GET_ITEM(o, i);
mgp_value *v{nullptr};
try {
v = PyObjectToMgpValue(e, memory);
} catch (...) {
mgp_list_destroy(list);
throw;
}
if (!mgp_list_append(list, v)) {
mgp_value_destroy(v);
mgp_list_destroy(list);
throw std::bad_alloc();
}
mgp_value_destroy(v);
}
mgp_v = mgp_value_make_list(list);
} else if (PyDict_Check(o)) {
mgp_map *map = mgp_map_make_empty(memory);
if (!map) {
throw std::bad_alloc();
}
PyObject *key{nullptr};
PyObject *value{nullptr};
Py_ssize_t pos{0};
while (PyDict_Next(o, &pos, &key, &value)) {
if (!PyUnicode_Check(key)) {
mgp_map_destroy(map);
throw std::invalid_argument("Dictionary keys must be strings");
}
const char *k = PyUnicode_AsUTF8(key);
mgp_value *v{nullptr};
if (!k) {
PyErr_Clear();
mgp_map_destroy(map);
throw std::bad_alloc();
}
try {
v = PyObjectToMgpValue(value, memory);
} catch (...) {
mgp_map_destroy(map);
throw;
}
if (!mgp_map_insert(map, k, v)) {
mgp_value_destroy(v);
mgp_map_destroy(map);
throw std::bad_alloc();
}
mgp_value_destroy(v);
}
mgp_v = mgp_value_make_map(map);
} else {
// TODO: Check for Vertex, Edge and Path. Throw std::invalid_argument for
// everything else.
throw utils::NotYetImplemented("PyObjectToMgpValue");
}
if (!mgp_v) {
throw std::bad_alloc();
}
return mgp_v;
}
// Definitions of types wrapping C API types
//
// These should all be in the private `_mgp` Python module, which will be used

View File

@ -12,6 +12,14 @@ namespace query::procedure {
py::Object MgpValueToPyObject(const mgp_value &);
/// @throw std::bad_alloc
/// @throw std::overflow_error if attempting to convert a Python integer which
/// too large to fit into int64_t.
/// @throw std::invalid_argument if the given Python object cannot be converted
/// to an mgp_value (e.g. a dictionary whose keys aren't strings or an object
/// of unsupported type).
mgp_value *PyObjectToMgpValue(PyObject *, mgp_memory *);
/// Create the _mgp module for use in embedded Python.
///
/// The function is to be used before Py_Initialize via the following code.

View File

@ -57,6 +57,42 @@ TEST(PyModule, MgpValueToPyObject) {
// TODO: Vertex, Edge and Path values
}
TEST(PyModule, PyObjectToMgpValue) {
mgp_memory memory{utils::NewDeleteResource()};
auto gil = py::EnsureGIL();
py::Object py_value{Py_BuildValue("[i f s (i f s) {s i s f}]", 1, 1.0, "one",
2, 2.0, "two", "three", 3, "four", 4.0)};
mgp_value *value = query::procedure::PyObjectToMgpValue(py_value, &memory);
ASSERT_TRUE(mgp_value_is_list(value));
const mgp_list *list1 = mgp_value_get_list(value);
EXPECT_EQ(mgp_list_size(list1), 5);
ASSERT_TRUE(mgp_value_is_int(mgp_list_at(list1, 0)));
EXPECT_EQ(mgp_value_get_int(mgp_list_at(list1, 0)), 1);
ASSERT_TRUE(mgp_value_is_double(mgp_list_at(list1, 1)));
EXPECT_EQ(mgp_value_get_double(mgp_list_at(list1, 1)), 1.0);
ASSERT_TRUE(mgp_value_is_string(mgp_list_at(list1, 2)));
EXPECT_STREQ(mgp_value_get_string(mgp_list_at(list1, 2)), "one");
ASSERT_TRUE(mgp_value_is_list(mgp_list_at(list1, 3)));
const mgp_list *list2 = mgp_value_get_list(mgp_list_at(list1, 3));
EXPECT_EQ(mgp_list_size(list2), 3);
ASSERT_TRUE(mgp_value_is_int(mgp_list_at(list2, 0)));
EXPECT_EQ(mgp_value_get_int(mgp_list_at(list2, 0)), 2);
ASSERT_TRUE(mgp_value_is_double(mgp_list_at(list2, 1)));
EXPECT_EQ(mgp_value_get_double(mgp_list_at(list2, 1)), 2.0);
ASSERT_TRUE(mgp_value_is_string(mgp_list_at(list2, 2)));
EXPECT_STREQ(mgp_value_get_string(mgp_list_at(list2, 2)), "two");
ASSERT_TRUE(mgp_value_is_map(mgp_list_at(list1, 4)));
const mgp_map *map = mgp_value_get_map(mgp_list_at(list1, 4));
EXPECT_EQ(mgp_map_size(map), 2);
const mgp_value *v1 = mgp_map_at(map, "three");
ASSERT_TRUE(mgp_value_is_int(v1));
EXPECT_EQ(mgp_value_get_int(v1), 3);
const mgp_value *v2 = mgp_map_at(map, "four");
ASSERT_TRUE(mgp_value_is_double(v2));
EXPECT_EQ(mgp_value_get_double(v2), 4.0);
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
// Initialize Python