Add Python class for mgp_edge
Reviewers: teon.banek, ipaljak, mferencevic Reviewed By: teon.banek Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D2679
This commit is contained in:
parent
34db077cbd
commit
e7f363ecbe
@ -538,6 +538,9 @@ struct mgp_edge *mgp_edge_copy(const struct mgp_edge *e,
|
|||||||
/// Free the memory used by a mgp_edge.
|
/// Free the memory used by a mgp_edge.
|
||||||
void mgp_edge_destroy(struct mgp_edge *e);
|
void mgp_edge_destroy(struct mgp_edge *e);
|
||||||
|
|
||||||
|
/// Return non-zero if given edges are equal, otherwise 0.
|
||||||
|
int mgp_edge_equal(const struct mgp_edge *e1, const struct mgp_edge *e2);
|
||||||
|
|
||||||
/// Return the type of the given edge.
|
/// Return the type of the given edge.
|
||||||
struct mgp_edge_type mgp_edge_get_type(const struct mgp_edge *e);
|
struct mgp_edge_type mgp_edge_get_type(const struct mgp_edge *e);
|
||||||
|
|
||||||
|
@ -63,6 +63,9 @@ Property = namedtuple('Property', ('name', 'value'))
|
|||||||
class Properties:
|
class Properties:
|
||||||
'''A collection of properties either on a Vertex or an Edge.'''
|
'''A collection of properties either on a Vertex or an Edge.'''
|
||||||
|
|
||||||
|
def __init__(self, obj):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
def get(self, property_name: str, default=None) -> object:
|
def get(self, property_name: str, default=None) -> object:
|
||||||
'''Get the value of a property with the given name or return default.
|
'''Get the value of a property with the given name or return default.
|
||||||
|
|
||||||
@ -111,10 +114,14 @@ class Properties:
|
|||||||
|
|
||||||
class EdgeType:
|
class EdgeType:
|
||||||
'''Type of an Edge.'''
|
'''Type of an Edge.'''
|
||||||
|
__slots__ = ('_name',)
|
||||||
|
|
||||||
|
def __init__(self, name):
|
||||||
|
self._name = name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
pass
|
return self._name
|
||||||
|
|
||||||
|
|
||||||
class InvalidEdgeError(Exception):
|
class InvalidEdgeError(Exception):
|
||||||
@ -129,30 +136,50 @@ class Edge:
|
|||||||
a query. You should not globally store an instance of an Edge. Using an
|
a query. You should not globally store an instance of an Edge. Using an
|
||||||
invalid Edge instance will raise InvalidEdgeError.
|
invalid Edge instance will raise InvalidEdgeError.
|
||||||
'''
|
'''
|
||||||
|
__slots__ = ('_edge',)
|
||||||
|
|
||||||
|
def __init__(self, edge):
|
||||||
|
if not isinstance(edge, _mgp.Edge):
|
||||||
|
raise TypeError("Expected '_mgp.Edge', got '{}'".fmt(type(edge)))
|
||||||
|
self._edge = edge
|
||||||
|
|
||||||
|
def is_valid(self) -> bool:
|
||||||
|
'''Return True if `self` is in valid context and may be used.'''
|
||||||
|
return self._edge.is_valid()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type(self) -> EdgeType:
|
def type(self) -> EdgeType:
|
||||||
'''Raise InvalidEdgeError.'''
|
'''Raise InvalidEdgeError.'''
|
||||||
pass
|
if not self.is_valid():
|
||||||
|
raise InvalidEdgeError()
|
||||||
|
return EdgeType(self._edge.get_type_name())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def from_vertex(self): # -> Vertex:
|
def from_vertex(self): # -> Vertex:
|
||||||
'''Raise InvalidEdgeError.'''
|
'''Raise InvalidEdgeError.'''
|
||||||
pass
|
if not self.is_valid():
|
||||||
|
raise InvalidEdgeError()
|
||||||
|
return Vertex(self._edge.from_vertex())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def to_vertex(self): # -> Vertex:
|
def to_vertex(self): # -> Vertex:
|
||||||
'''Raise InvalidEdgeError.'''
|
'''Raise InvalidEdgeError.'''
|
||||||
pass
|
if not self.is_valid():
|
||||||
|
raise InvalidEdgeError()
|
||||||
|
return Vertex(self._edge.to_vertex())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def properties(self) -> Properties:
|
def properties(self) -> Properties:
|
||||||
'''Raise InvalidEdgeError.'''
|
'''Raise InvalidEdgeError.'''
|
||||||
pass
|
if not self.is_valid():
|
||||||
|
raise InvalidEdgeError()
|
||||||
|
return Properties(self._edge)
|
||||||
|
|
||||||
def __eq__(self, other) -> bool:
|
def __eq__(self, other) -> bool:
|
||||||
'''Raise InvalidEdgeError.'''
|
'''Raise InvalidEdgeError.'''
|
||||||
pass
|
if not self.is_valid():
|
||||||
|
raise InvalidEdgeError()
|
||||||
|
return self._edge == other._edge
|
||||||
|
|
||||||
|
|
||||||
VertexId = typing.NewType('VertexId', int)
|
VertexId = typing.NewType('VertexId', int)
|
||||||
|
@ -1122,6 +1122,10 @@ mgp_edge *mgp_edge_copy(const mgp_edge *v, mgp_memory *memory) {
|
|||||||
|
|
||||||
void mgp_edge_destroy(mgp_edge *e) { delete_mgp_object(e); }
|
void mgp_edge_destroy(mgp_edge *e) { delete_mgp_object(e); }
|
||||||
|
|
||||||
|
int mgp_edge_equal(const struct mgp_edge *e1, const struct mgp_edge *e2) {
|
||||||
|
return e1->impl == e2->impl ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
mgp_edge_type mgp_edge_get_type(const mgp_edge *e) {
|
mgp_edge_type mgp_edge_get_type(const mgp_edge *e) {
|
||||||
const auto &name = e->from.graph->impl->EdgeTypeToName(e->impl.EdgeType());
|
const auto &name = e->from.graph->impl->EdgeTypeToName(e->impl.EdgeType());
|
||||||
static_assert(
|
static_assert(
|
||||||
|
@ -6,183 +6,6 @@
|
|||||||
|
|
||||||
namespace query::procedure {
|
namespace query::procedure {
|
||||||
|
|
||||||
py::Object MgpValueToPyObject(const mgp_value &value) {
|
|
||||||
switch (mgp_value_get_type(&value)) {
|
|
||||||
case MGP_VALUE_TYPE_NULL:
|
|
||||||
Py_INCREF(Py_None);
|
|
||||||
return py::Object(Py_None);
|
|
||||||
case MGP_VALUE_TYPE_BOOL:
|
|
||||||
return py::Object(PyBool_FromLong(mgp_value_get_bool(&value)));
|
|
||||||
case MGP_VALUE_TYPE_INT:
|
|
||||||
return py::Object(PyLong_FromLongLong(mgp_value_get_int(&value)));
|
|
||||||
case MGP_VALUE_TYPE_DOUBLE:
|
|
||||||
return py::Object(PyFloat_FromDouble(mgp_value_get_double(&value)));
|
|
||||||
case MGP_VALUE_TYPE_STRING:
|
|
||||||
return py::Object(PyUnicode_FromString(mgp_value_get_string(&value)));
|
|
||||||
case MGP_VALUE_TYPE_LIST: {
|
|
||||||
const auto *list = mgp_value_get_list(&value);
|
|
||||||
const size_t len = mgp_list_size(list);
|
|
||||||
py::Object py_list(PyList_New(len));
|
|
||||||
CHECK(py_list);
|
|
||||||
for (size_t i = 0; i < len; ++i) {
|
|
||||||
auto elem = MgpValueToPyObject(*mgp_list_at(list, i));
|
|
||||||
CHECK(elem);
|
|
||||||
// Explicitly convert `py_list`, which is `py::Object`, via static_cast.
|
|
||||||
// Then the macro will cast it to `PyList *`.
|
|
||||||
PyList_SET_ITEM(static_cast<PyObject *>(py_list), i, elem.Steal());
|
|
||||||
}
|
|
||||||
return py_list;
|
|
||||||
}
|
|
||||||
case MGP_VALUE_TYPE_MAP: {
|
|
||||||
const auto *map = mgp_value_get_map(&value);
|
|
||||||
py::Object py_dict(PyDict_New());
|
|
||||||
CHECK(py_dict);
|
|
||||||
for (const auto &[key, val] : map->items) {
|
|
||||||
auto py_val = MgpValueToPyObject(val);
|
|
||||||
CHECK(py_val);
|
|
||||||
// Unlike PyList_SET_ITEM, PyDict_SetItem does not steal the value.
|
|
||||||
CHECK(PyDict_SetItemString(py_dict, key.c_str(), py_val) == 0);
|
|
||||||
}
|
|
||||||
return py_dict;
|
|
||||||
}
|
|
||||||
case MGP_VALUE_TYPE_VERTEX:
|
|
||||||
case MGP_VALUE_TYPE_EDGE:
|
|
||||||
case MGP_VALUE_TYPE_PATH:
|
|
||||||
throw utils::NotYetImplemented("MgpValueToPyObject");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
// Definitions of types wrapping C API types
|
||||||
//
|
//
|
||||||
// These should all be in the private `_mgp` Python module, which will be used
|
// These should all be in the private `_mgp` Python module, which will be used
|
||||||
@ -264,7 +87,6 @@ static PyTypeObject PyVerticesIteratorType = {
|
|||||||
.tp_dealloc = reinterpret_cast<destructor>(PyVerticesIteratorDealloc),
|
.tp_dealloc = reinterpret_cast<destructor>(PyVerticesIteratorDealloc),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
PyObject *PyGraphIsValid(PyGraph *self, PyObject *Py_UNUSED(ignored)) {
|
PyObject *PyGraphIsValid(PyGraph *self, PyObject *Py_UNUSED(ignored)) {
|
||||||
return PyBool_FromLong(!!self->graph);
|
return PyBool_FromLong(!!self->graph);
|
||||||
}
|
}
|
||||||
@ -518,6 +340,118 @@ static PyModuleDef PyMgpModule = {
|
|||||||
.m_size = -1,
|
.m_size = -1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct PyEdge {
|
||||||
|
PyObject_HEAD
|
||||||
|
mgp_edge *edge;
|
||||||
|
PyGraph *py_graph;
|
||||||
|
};
|
||||||
|
|
||||||
|
PyObject *PyEdgeGetTypeName(PyEdge *self, PyObject *Py_UNUSED(ignored)) {
|
||||||
|
CHECK(self);
|
||||||
|
CHECK(self->edge);
|
||||||
|
CHECK(self->py_graph);
|
||||||
|
CHECK(self->py_graph->graph);
|
||||||
|
return PyUnicode_FromString(mgp_edge_get_type(self->edge).name);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *PyEdgeFromVertex(PyEdge *self, PyObject *Py_UNUSED(ignored)) {
|
||||||
|
CHECK(self);
|
||||||
|
CHECK(self->edge);
|
||||||
|
CHECK(self->py_graph);
|
||||||
|
CHECK(self->py_graph->graph);
|
||||||
|
const auto *vertex = mgp_edge_get_from(self->edge);
|
||||||
|
CHECK(vertex);
|
||||||
|
// TODO: Wrap mgp_vertex_copy(vertex) into _mgp.Vertex and return it.
|
||||||
|
PyErr_SetString(PyExc_NotImplementedError, "from_vertex");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *PyEdgeToVertex(PyEdge *self, PyObject *Py_UNUSED(ignored)) {
|
||||||
|
CHECK(self);
|
||||||
|
CHECK(self->edge);
|
||||||
|
CHECK(self->py_graph);
|
||||||
|
CHECK(self->py_graph->graph);
|
||||||
|
const auto *vertex = mgp_edge_get_to(self->edge);
|
||||||
|
CHECK(vertex);
|
||||||
|
// TODO: Wrap mgp_vertex_copy(vertex) into _mgp.Vertex and return it.
|
||||||
|
PyErr_SetString(PyExc_NotImplementedError, "to_vertex");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PyEdgeDealloc(PyEdge *self) {
|
||||||
|
CHECK(self->edge);
|
||||||
|
CHECK(self->py_graph);
|
||||||
|
// Avoid invoking `mgp_edge_destroy` if we are not in valid execution context.
|
||||||
|
// The query execution should free all memory used during execution, so we may
|
||||||
|
// cause a double free issue.
|
||||||
|
if (self->py_graph->graph) mgp_edge_destroy(self->edge);
|
||||||
|
Py_DECREF(self->py_graph);
|
||||||
|
Py_TYPE(self)->tp_free(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *PyEdgeIsValid(PyEdge *self, PyObject *Py_UNUSED(ignored)) {
|
||||||
|
return PyBool_FromLong(!!self->py_graph->graph);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyMethodDef PyEdgeMethods[] = {
|
||||||
|
{"is_valid", reinterpret_cast<PyCFunction>(PyEdgeIsValid), METH_NOARGS,
|
||||||
|
"Return True if Edge is in valid context and may be used."},
|
||||||
|
{"get_type_name", reinterpret_cast<PyCFunction>(PyEdgeGetTypeName),
|
||||||
|
METH_NOARGS, "Return the edge's type name."},
|
||||||
|
{"from_vertex", reinterpret_cast<PyCFunction>(PyEdgeFromVertex),
|
||||||
|
METH_NOARGS, "Return the edge's source vertex."},
|
||||||
|
{"to_vertex", reinterpret_cast<PyCFunction>(PyEdgeToVertex), METH_NOARGS,
|
||||||
|
"Return the edge's destination vertex."},
|
||||||
|
{nullptr}};
|
||||||
|
|
||||||
|
PyObject *PyEdgeRichCompare(PyObject *self, PyObject *other, int op);
|
||||||
|
|
||||||
|
static PyTypeObject PyEdgeType = {
|
||||||
|
PyVarObject_HEAD_INIT(nullptr, 0).tp_name = "_mgp.Edge",
|
||||||
|
.tp_doc = "Wraps struct mgp_edge.",
|
||||||
|
.tp_basicsize = sizeof(PyEdge),
|
||||||
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||||
|
.tp_new = PyType_GenericNew,
|
||||||
|
.tp_methods = PyEdgeMethods,
|
||||||
|
.tp_dealloc = reinterpret_cast<destructor>(PyEdgeDealloc),
|
||||||
|
.tp_richcompare = PyEdgeRichCompare,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Create an instance of `_mgp.Edge` class.
|
||||||
|
///
|
||||||
|
/// The ownership of the edge is given to the created instance and will be
|
||||||
|
/// destroyed once the instance itself is destroyed, taking care that the
|
||||||
|
/// execution context is still valid.
|
||||||
|
///
|
||||||
|
/// The created instance references an existing `_mgp.Graph` instance, which
|
||||||
|
/// marks the execution context.
|
||||||
|
PyObject *MakePyEdge(mgp_edge *edge, PyGraph *py_graph) {
|
||||||
|
CHECK(edge->GetMemoryResource() == py_graph->memory->impl);
|
||||||
|
auto *py_edge = PyObject_New(PyEdge, &PyEdgeType);
|
||||||
|
if (!py_edge) return nullptr;
|
||||||
|
py_edge->edge = edge;
|
||||||
|
py_edge->py_graph = py_graph;
|
||||||
|
Py_INCREF(py_graph);
|
||||||
|
return PyObject_Init(reinterpret_cast<PyObject *>(py_edge), &PyEdgeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *PyEdgeRichCompare(PyObject *self, PyObject *other, int op) {
|
||||||
|
CHECK(self);
|
||||||
|
CHECK(other);
|
||||||
|
|
||||||
|
if (Py_TYPE(self) != &PyEdgeType || Py_TYPE(other) != &PyEdgeType ||
|
||||||
|
op != Py_EQ) {
|
||||||
|
Py_RETURN_NOTIMPLEMENTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto *e1 = reinterpret_cast<PyEdge *>(self);
|
||||||
|
auto *e2 = reinterpret_cast<PyEdge *>(other);
|
||||||
|
CHECK(e1->edge);
|
||||||
|
CHECK(e2->edge);
|
||||||
|
|
||||||
|
return PyBool_FromLong(mgp_edge_equal(e1->edge, e2->edge));
|
||||||
|
}
|
||||||
|
|
||||||
PyObject *PyInitMgpModule() {
|
PyObject *PyInitMgpModule() {
|
||||||
PyObject *mgp = PyModule_Create(&PyMgpModule);
|
PyObject *mgp = PyModule_Create(&PyMgpModule);
|
||||||
if (!mgp) return nullptr;
|
if (!mgp) return nullptr;
|
||||||
@ -537,6 +471,7 @@ PyObject *PyInitMgpModule() {
|
|||||||
if (!register_type(&PyVerticesIteratorType, "VerticesIterator"))
|
if (!register_type(&PyVerticesIteratorType, "VerticesIterator"))
|
||||||
return nullptr;
|
return nullptr;
|
||||||
if (!register_type(&PyGraphType, "Graph")) return nullptr;
|
if (!register_type(&PyGraphType, "Graph")) return nullptr;
|
||||||
|
if (!register_type(&PyEdgeType, "Edge")) return nullptr;
|
||||||
if (!register_type(&PyQueryProcType, "Proc")) return nullptr;
|
if (!register_type(&PyQueryProcType, "Proc")) return nullptr;
|
||||||
if (!register_type(&PyQueryModuleType, "Module")) return nullptr;
|
if (!register_type(&PyQueryModuleType, "Module")) return nullptr;
|
||||||
Py_INCREF(Py_None);
|
Py_INCREF(Py_None);
|
||||||
@ -583,4 +518,198 @@ py::Object ReloadPyModule(PyObject *py_module, mgp_module *module_def) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
py::Object MgpValueToPyObject(const mgp_value &value, PyGraph *py_graph) {
|
||||||
|
switch (mgp_value_get_type(&value)) {
|
||||||
|
case MGP_VALUE_TYPE_NULL:
|
||||||
|
Py_INCREF(Py_None);
|
||||||
|
return py::Object(Py_None);
|
||||||
|
case MGP_VALUE_TYPE_BOOL:
|
||||||
|
return py::Object(PyBool_FromLong(mgp_value_get_bool(&value)));
|
||||||
|
case MGP_VALUE_TYPE_INT:
|
||||||
|
return py::Object(PyLong_FromLongLong(mgp_value_get_int(&value)));
|
||||||
|
case MGP_VALUE_TYPE_DOUBLE:
|
||||||
|
return py::Object(PyFloat_FromDouble(mgp_value_get_double(&value)));
|
||||||
|
case MGP_VALUE_TYPE_STRING:
|
||||||
|
return py::Object(PyUnicode_FromString(mgp_value_get_string(&value)));
|
||||||
|
case MGP_VALUE_TYPE_LIST: {
|
||||||
|
const auto *list = mgp_value_get_list(&value);
|
||||||
|
const size_t len = mgp_list_size(list);
|
||||||
|
py::Object py_list(PyList_New(len));
|
||||||
|
CHECK(py_list);
|
||||||
|
for (size_t i = 0; i < len; ++i) {
|
||||||
|
auto elem = MgpValueToPyObject(*mgp_list_at(list, i), py_graph);
|
||||||
|
CHECK(elem);
|
||||||
|
// Explicitly convert `py_list`, which is `py::Object`, via static_cast.
|
||||||
|
// Then the macro will cast it to `PyList *`.
|
||||||
|
PyList_SET_ITEM(static_cast<PyObject *>(py_list), i, elem.Steal());
|
||||||
|
}
|
||||||
|
return py_list;
|
||||||
|
}
|
||||||
|
case MGP_VALUE_TYPE_MAP: {
|
||||||
|
const auto *map = mgp_value_get_map(&value);
|
||||||
|
py::Object py_dict(PyDict_New());
|
||||||
|
CHECK(py_dict);
|
||||||
|
for (const auto &[key, val] : map->items) {
|
||||||
|
auto py_val = MgpValueToPyObject(val, py_graph);
|
||||||
|
CHECK(py_val);
|
||||||
|
// Unlike PyList_SET_ITEM, PyDict_SetItem does not steal the value.
|
||||||
|
CHECK(PyDict_SetItemString(py_dict, key.c_str(), py_val) == 0);
|
||||||
|
}
|
||||||
|
return py_dict;
|
||||||
|
}
|
||||||
|
case MGP_VALUE_TYPE_VERTEX:
|
||||||
|
throw utils::NotYetImplemented("MgpValueToPyObject");
|
||||||
|
case MGP_VALUE_TYPE_EDGE: {
|
||||||
|
// Copy the edge and pass the ownership to the created _mgp.Edge
|
||||||
|
// instance.
|
||||||
|
auto *e = mgp_edge_copy(mgp_value_get_edge(&value), py_graph->memory);
|
||||||
|
if (!e) {
|
||||||
|
PyErr_NoMemory();
|
||||||
|
return py::Object();
|
||||||
|
}
|
||||||
|
return py::Object(reinterpret_cast<PyObject *>(MakePyEdge(e, py_graph)));
|
||||||
|
}
|
||||||
|
case MGP_VALUE_TYPE_PATH:
|
||||||
|
throw utils::NotYetImplemented("MgpValueToPyObject");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 if (Py_TYPE(o) == &PyEdgeType) {
|
||||||
|
// Copy the edge and pass the ownership to the created mgp_value.
|
||||||
|
auto *e = mgp_edge_copy(reinterpret_cast<PyEdge *>(o)->edge, memory);
|
||||||
|
if (!e) {
|
||||||
|
throw std::bad_alloc();
|
||||||
|
}
|
||||||
|
mgp_v = mgp_value_make_edge(e);
|
||||||
|
} else {
|
||||||
|
// TODO: Check for Vertex and Path. Throw std::invalid_argument for
|
||||||
|
// everything else.
|
||||||
|
throw utils::NotYetImplemented("PyObjectToMgpValue");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mgp_v) {
|
||||||
|
throw std::bad_alloc();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mgp_v;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace query::procedure
|
} // namespace query::procedure
|
||||||
|
@ -11,8 +11,18 @@ struct mgp_value;
|
|||||||
|
|
||||||
namespace query::procedure {
|
namespace query::procedure {
|
||||||
|
|
||||||
py::Object MgpValueToPyObject(const mgp_value &);
|
struct PyGraph;
|
||||||
|
|
||||||
|
/// Convert an `mgp_value` into a Python object, referencing the given `PyGraph`
|
||||||
|
/// instance and using the same allocator as the graph.
|
||||||
|
///
|
||||||
|
/// 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);
|
||||||
|
|
||||||
|
/// Convert a Python object into `mgp_value`, constructing it using the given
|
||||||
|
/// `mgp_memory` allocator.
|
||||||
|
///
|
||||||
/// @throw std::bad_alloc
|
/// @throw std::bad_alloc
|
||||||
/// @throw std::overflow_error if attempting to convert a Python integer which
|
/// @throw std::overflow_error if attempting to convert a Python integer which
|
||||||
/// too large to fit into int64_t.
|
/// too large to fit into int64_t.
|
||||||
|
@ -27,7 +27,10 @@ TEST(PyModule, MgpValueToPyObject) {
|
|||||||
mgp_value_destroy(list_val);
|
mgp_value_destroy(list_val);
|
||||||
auto *map_val = mgp_value_make_map(map);
|
auto *map_val = mgp_value_make_map(map);
|
||||||
auto gil = py::EnsureGIL();
|
auto gil = py::EnsureGIL();
|
||||||
auto py_dict = query::procedure::MgpValueToPyObject(*map_val);
|
py::Object py_graph(query::procedure::MakePyGraph(nullptr, &memory));
|
||||||
|
auto py_dict = query::procedure::MgpValueToPyObject(
|
||||||
|
*map_val, reinterpret_cast<query::procedure::PyGraph *>(
|
||||||
|
static_cast<PyObject *>(py_graph)));
|
||||||
mgp_value_destroy(map_val);
|
mgp_value_destroy(map_val);
|
||||||
// We should now have in Python:
|
// We should now have in Python:
|
||||||
// {"list": [None, False, True, 42, 0.1, "some text"]}
|
// {"list": [None, False, True, 42, 0.1, "some text"]}
|
||||||
@ -101,12 +104,16 @@ int main(int argc, char **argv) {
|
|||||||
// Set program name, so Python can find its way to runtime libraries relative
|
// Set program name, so Python can find its way to runtime libraries relative
|
||||||
// to executable.
|
// to executable.
|
||||||
Py_SetProgramName(program_name);
|
Py_SetProgramName(program_name);
|
||||||
|
PyImport_AppendInittab("_mgp", &query::procedure::PyInitMgpModule);
|
||||||
Py_InitializeEx(0 /* = initsigs */);
|
Py_InitializeEx(0 /* = initsigs */);
|
||||||
PyEval_InitThreads();
|
PyEval_InitThreads();
|
||||||
int test_result;
|
int test_result;
|
||||||
Py_BEGIN_ALLOW_THREADS;
|
{
|
||||||
test_result = RUN_ALL_TESTS();
|
py::Object mgp(PyImport_ImportModule("_mgp"));
|
||||||
Py_END_ALLOW_THREADS;
|
Py_BEGIN_ALLOW_THREADS;
|
||||||
|
test_result = RUN_ALL_TESTS();
|
||||||
|
Py_END_ALLOW_THREADS;
|
||||||
|
}
|
||||||
// Shutdown Python
|
// Shutdown Python
|
||||||
Py_Finalize();
|
Py_Finalize();
|
||||||
PyMem_RawFree(program_name);
|
PyMem_RawFree(program_name);
|
||||||
|
Loading…
Reference in New Issue
Block a user