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:
parent
ad740e4ae2
commit
5953f07be3
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user