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:
parent
31a4c55e76
commit
1d30a2e5cb
@ -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");
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user