Convert mgp_value to 'mgp' Python types, not '_mgp' ones

Summary:
When invoking a Python registered procedures we want to convert
`mgp_value` types to user facing 'mgp' types.

Depends on D2706

Reviewers: mferencevic, ipaljak

Reviewed By: mferencevic

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D2707
This commit is contained in:
Teon Banek 2020-03-04 13:53:32 +01:00
parent 31a4c55e76
commit 1d30a2e5cb
4 changed files with 91 additions and 3 deletions

View File

@ -1123,12 +1123,20 @@ py::Object MgpValueToPyObject(const mgp_value &value, PyGraph *py_graph) {
case MGP_VALUE_TYPE_VERTEX:
throw utils::NotYetImplemented("MgpValueToPyObject");
case MGP_VALUE_TYPE_EDGE: {
py::Object py_mgp(PyImport_ImportModule("mgp"));
if (!py_mgp) return nullptr;
const auto *e = mgp_value_get_edge(&value);
return py::Object(reinterpret_cast<PyObject *>(MakePyEdge(*e, py_graph)));
py::Object py_edge(
reinterpret_cast<PyObject *>(MakePyEdge(*e, py_graph)));
return py_mgp.CallMethod("Edge", py_edge);
}
case MGP_VALUE_TYPE_PATH: {
py::Object py_mgp(PyImport_ImportModule("mgp"));
if (!py_mgp) return nullptr;
const auto *p = mgp_value_get_path(&value);
return py::Object(reinterpret_cast<PyObject *>(MakePyPath(*p, py_graph)));
py::Object py_path(
reinterpret_cast<PyObject *>(MakePyPath(*p, py_graph)));
return py_mgp.CallMethod("Path", py_path);
}
}
}
@ -1164,6 +1172,31 @@ mgp_value *PyObjectToMgpValue(PyObject *o, mgp_memory *memory) {
return v;
};
auto is_mgp_instance = [](PyObject *obj, const char *mgp_type_name) {
py::Object py_mgp(PyImport_ImportModule("mgp"));
if (!py_mgp) {
PyErr_Clear();
// This way we skip conversions of types from user-facing 'mgp' module.
return false;
}
auto mgp_type = py_mgp.GetAttr(mgp_type_name);
if (!mgp_type) {
PyErr_Clear();
std::stringstream ss;
ss << "'mgp' module is missing '" << mgp_type_name << "' type";
throw std::invalid_argument(ss.str());
}
int res = PyObject_IsInstance(obj, mgp_type);
if (res == -1) {
PyErr_Clear();
std::stringstream ss;
ss << "Error when checking object is instance of 'mgp." << mgp_type_name
<< "' type";
throw std::invalid_argument(ss.str());
}
return static_cast<bool>(res);
};
mgp_value *mgp_v{nullptr};
if (o == Py_None) {
@ -1258,6 +1291,28 @@ mgp_value *PyObjectToMgpValue(PyObject *o, mgp_memory *memory) {
}
} else if (Py_TYPE(o) == &PyVertexType) {
throw utils::NotYetImplemented("PyObjectToMgpValue");
} else if (is_mgp_instance(o, "Edge")) {
py::Object edge(PyObject_GetAttrString(o, "_edge"));
if (!edge) {
PyErr_Clear();
throw std::invalid_argument("'mgp.Edge' is missing '_edge' attribute");
}
return PyObjectToMgpValue(edge, memory);
} else if (is_mgp_instance(o, "Vertex")) {
py::Object vertex(PyObject_GetAttrString(o, "_vertex"));
if (!vertex) {
PyErr_Clear();
throw std::invalid_argument(
"'mgp.Vertex' is missing '_vertex' attribute");
}
return PyObjectToMgpValue(vertex, memory);
} else if (is_mgp_instance(o, "Path")) {
py::Object path(PyObject_GetAttrString(o, "_path"));
if (!path) {
PyErr_Clear();
throw std::invalid_argument("'mgp.Path' is missing '_path' attribute");
}
return PyObjectToMgpValue(path, memory);
} else {
throw std::invalid_argument("Unsupported PyObject conversion");
}

View File

@ -16,6 +16,12 @@ struct PyGraph;
/// Convert an `mgp_value` into a Python object, referencing the given `PyGraph`
/// instance and using the same allocator as the graph.
///
/// Values of type `MGP_VALUE_TYPE_VERTEX`, `MGP_VALUE_TYPE_EDGE` and
/// `MGP_VALUE_TYPE_PATH` are returned as `mgp.Vertex`, `mgp.Edge` and
/// `mgp.Path` respectively, and *not* their internal `_mgp`
/// representations. Other value types are converted to equivalent builtin
/// Python objects.
///
/// Return a non-null `py::Object` instance on success. Otherwise, return a null
/// `py::Object` instance and set the appropriate Python exception.
py::Object MgpValueToPyObject(const mgp_value &value, PyGraph *py_graph);
@ -25,6 +31,9 @@ py::Object MgpValueToPyObject(const mgp_value &value, PyObject *py_graph);
/// Convert a Python object into `mgp_value`, constructing it using the given
/// `mgp_memory` allocator.
///
/// If the user-facing 'mgp' module can be imported, this function will handle
/// conversion of 'mgp.Vertex', 'mgp.Edge' and 'mgp.Path' values.
///
/// @throw std::bad_alloc
/// @throw std::overflow_error if attempting to convert a Python integer which
/// too large to fit into int64_t.

View File

@ -75,6 +75,11 @@ def get_runs(build_dir, include=None, exclude=None, outfile=None,
if name.endswith("storage_v2_durability"):
prefix = "TIMEOUT=300 "
# py_module unit test requires user-facing 'mgp' module
if name.endswith("py_module"):
mgp_path = os.path.join("..", "include", "mgp.py")
files.append(os.path.relpath(mgp_path, dirname))
# get output files
outfile_paths = []
if outfile:

View File

@ -1,5 +1,6 @@
#include <gtest/gtest.h>
#include <filesystem>
#include <string>
#include "query/procedure/mg_procedure_impl.hpp"
@ -180,7 +181,25 @@ int main(int argc, char **argv) {
PyEval_InitThreads();
int test_result;
{
py::Object mgp(PyImport_ImportModule("_mgp"));
// Setup importing 'mgp' module by adding its directory to `sys.path`.
std::filesystem::path invocation_path(argv[0]);
auto mgp_py_path =
invocation_path.parent_path() / "../../../include/mgp.py";
CHECK(std::filesystem::exists(mgp_py_path));
auto *py_path = PySys_GetObject("path");
CHECK(py_path);
py::Object import_dir(
PyUnicode_FromString(mgp_py_path.parent_path().c_str()));
if (PyList_Append(py_path, import_dir) != 0) {
auto exc_info = py::FetchError().value();
LOG(FATAL) << exc_info;
}
py::Object mgp(PyImport_ImportModule("mgp"));
if (!mgp) {
auto exc_info = py::FetchError().value();
LOG(FATAL) << exc_info;
}
// Now run tests.
Py_BEGIN_ALLOW_THREADS;
test_result = RUN_ALL_TESTS();
Py_END_ALLOW_THREADS;