Add Python class for mgp_vertex

Reviewers: teon.banek

Reviewed By: teon.banek

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D2688
This commit is contained in:
Ivan Paljak 2020-02-26 13:13:36 +01:00
parent bfbace8168
commit 32e56684db
2 changed files with 154 additions and 44 deletions

View File

@ -25,34 +25,21 @@ import _mgp
class Label:
'''Label of a Vertex.'''
__slots__ = ('_name',)
def __init__(self, name):
self._name = name;
@property
def name(self) -> str:
pass
class Labels:
'''A collection of labels on a Vertex.'''
def __len__(self) -> int:
'''Raise InvalidVertexError.'''
pass
def __getitem__(self, index: int) -> Label:
'''Raise InvalidVertexError.'''
pass
def __iter__(self) -> typing.Iterable[Label]:
'''Raise InvalidVertexError.'''
pass
def __contains__(self, label: typing.Union[Label, str]) -> bool:
'''Test whether a label exists, either by name or another Label.
Raise InvalidVertexError.
'''
pass
return self._name;
def __eq__(self, other) -> bool:
if isinstance(other, Label):
return self._name == other.name
if isinstance(other, str):
return self._name == other
return NotImplemented
# Named property value of a Vertex or an Edge.
# It would be better to use typing.NamedTuple with typed fields, but that is
@ -197,38 +184,58 @@ class Vertex:
in a query. You should not globally store an instance of a Vertex. Using an
invalid Vertex instance will raise InvalidVertexError.
'''
__slots__ = ('_vertex',)
def __init__(self, vertex):
raise NotImplementedError()
if not isinstance(vertex, _mgp.Vertex):
raise TypeError("Expected '_mgp.Vertex', got '{}'".fmt(type(vertex)))
self._vertex = vertex
def is_valid(self) -> bool:
'''Return True if `self` is in valid context and may be used'''
return self._vertex.is_valid()
@property
def id(self) -> VertexId:
'''Raise InvalidVertexError.'''
pass
if not self.is_valid():
raise InvalidVertexError()
return self._vertex.get_id()
@property
def labels(self) -> Labels:
def labels(self) -> typing.List[Label]:
'''Raise InvalidVertexError.'''
pass
if not self.is_valid():
raise InvalidVertexError()
return tuple(Label(self._vertex.label_at(i))
for i in range(self._vertex.labels_count()))
@property
def properties(self) -> Properties:
'''Raise InvalidVertexError.'''
pass
if not self.is_valid():
raise InvalidVertexError()
return Properties(self._vertex)
@property
def in_edges(self) -> typing.Iterable[Edge]:
'''Raise InvalidVertexError.'''
pass
if not self.is_valid():
raise InvalidVertexError()
raise NotImplementedError()
@property
def out_edges(self) -> typing.Iterable[Edge]:
'''Raise InvalidVertexError.'''
pass
if not self.is_valid():
raise InvalidVertexError()
raise NotImplementedError()
def __eq__(self, other) -> bool:
'''Raise InvalidVertexError.'''
pass
'''Raise InvalidVertexError'''
if not self.is_valid():
raise InvalidVertexError()
return self._vertex == other._vertex
class Path:

View File

@ -34,6 +34,8 @@ struct PyVerticesIterator {
PyGraph *py_graph;
};
PyObject *MakePyVertex(mgp_vertex *vertex, PyGraph *py_graph);
void PyVerticesIteratorDealloc(PyVerticesIterator *self) {
CHECK(self->it);
CHECK(self->py_graph);
@ -51,9 +53,8 @@ PyObject *PyVerticesIteratorGet(PyVerticesIterator *self,
CHECK(self->py_graph->graph);
const auto *vertex = mgp_vertices_iterator_get(self->it);
if (!vertex) Py_RETURN_NONE;
// TODO: Wrap mgp_vertex_copy(vertex) into _mgp.Vertex and return it.
PyErr_SetString(PyExc_NotImplementedError, "get");
return nullptr;
return MakePyVertex(mgp_vertex_copy(vertex, self->py_graph->memory),
self->py_graph);
}
PyObject *PyVerticesIteratorNext(PyVerticesIterator *self,
@ -63,9 +64,8 @@ PyObject *PyVerticesIteratorNext(PyVerticesIterator *self,
CHECK(self->py_graph->graph);
const auto *vertex = mgp_vertices_iterator_next(self->it);
if (!vertex) Py_RETURN_NONE;
// TODO: Wrap mgp_vertex_copy(vertex) into _mgp.Vertex and return it.
PyErr_SetString(PyExc_NotImplementedError, "next");
return nullptr;
return MakePyVertex(mgp_vertex_copy(vertex, self->py_graph->memory),
self->py_graph);
}
static PyMethodDef PyVerticesIteratorMethods[] = {
@ -104,11 +104,7 @@ PyObject *PyGraphGetVertexById(PyGraph *self, PyObject *args) {
"Unable to find the vertex with given ID.");
return nullptr;
}
// TODO: Wrap into _mgp.Vertex and let it handle mgp_vertex_destroy via
// dealloc function.
mgp_vertex_destroy(vertex);
PyErr_SetString(PyExc_NotImplementedError, "get_vertex_by_id");
return nullptr;
return MakePyVertex(mgp_vertex_copy(vertex, self->memory), self);
}
PyObject *PyGraphIterVertices(PyGraph *self, PyObject *Py_UNUSED(ignored)) {
@ -452,6 +448,112 @@ PyObject *PyEdgeRichCompare(PyObject *self, PyObject *other, int op) {
return PyBool_FromLong(mgp_edge_equal(e1->edge, e2->edge));
}
struct PyVertex {
PyObject_HEAD
mgp_vertex *vertex;
PyGraph *py_graph;
};
void PyVertexDealloc(PyVertex *self) {
CHECK(self->vertex);
CHECK(self->py_graph);
// Avoid invoking `mgp_vertex_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_vertex_destroy(self->vertex);
Py_DECREF(self->py_graph);
Py_TYPE(self)->tp_free(self);
}
PyObject *PyVertexIsValid(PyVertex *self, PyObject *Py_UNUSED(ignored)) {
return PyBool_FromLong(!!self->py_graph->graph);
}
PyObject *PyVertexGetId(PyVertex *self, PyObject *Py_UNUSED(ignored)) {
CHECK(self);
CHECK(self->vertex);
CHECK(self->py_graph);
CHECK(self->py_graph->graph);
return PyLong_FromLongLong(mgp_vertex_get_id(self->vertex).as_int);
}
PyObject *PyVertexLabelsCount(PyVertex *self, PyObject *Py_UNUSED(ignored)) {
CHECK(self);
CHECK(self->vertex);
CHECK(self->py_graph);
CHECK(self->py_graph->graph);
return PyLong_FromSize_t(mgp_vertex_labels_count(self->vertex));
}
PyObject *PyVertexLabelAt(PyVertex *self, PyObject *args) {
CHECK(self);
CHECK(self->vertex);
CHECK(self->py_graph);
CHECK(self->py_graph->graph);
static_assert(std::numeric_limits<Py_ssize_t>::max() <=
std::numeric_limits<size_t>::max());
Py_ssize_t id;
if (!PyArg_ParseTuple(args, "n", &id)) return nullptr;
auto label = mgp_vertex_label_at(self->vertex, id);
if (label.name == nullptr || id < 0) {
PyErr_SetString(PyExc_IndexError,
"Unable to find the label with given ID.");
return nullptr;
}
return PyUnicode_FromString(label.name);
}
static PyMethodDef PyVertexMethods[] = {
{"is_valid", reinterpret_cast<PyCFunction>(PyVertexIsValid), METH_NOARGS,
"Return True if Vertex is in valid context and may be used."},
{"get_id", reinterpret_cast<PyCFunction>(PyVertexGetId), METH_NOARGS,
"Return vertex id."},
{"labels_count", reinterpret_cast<PyCFunction>(PyVertexLabelsCount),
METH_NOARGS, "Return number of lables of a vertex."},
{"label_at", reinterpret_cast<PyCFunction>(PyVertexLabelAt), METH_VARARGS,
"Return label of a vertex on a given index."},
{nullptr}};
PyObject *PyVertexRichCompare(PyObject *self, PyObject *other, int op);
static PyTypeObject PyVertexType = {
PyVarObject_HEAD_INIT(nullptr, 0).tp_name = "_mgp.Vertex",
.tp_doc = "Wraps struct mgp_vertex.",
.tp_basicsize = sizeof(PyVertex),
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_new = PyType_GenericNew,
.tp_methods = PyVertexMethods,
.tp_dealloc = reinterpret_cast<destructor>(PyVertexDealloc),
.tp_richcompare = PyVertexRichCompare,
};
PyObject *MakePyVertex(mgp_vertex *vertex, PyGraph *py_graph) {
CHECK(vertex->GetMemoryResource() == py_graph->memory->impl);
auto *py_vertex = PyObject_New(PyVertex, &PyVertexType);
if (!py_vertex) return nullptr;
py_vertex->vertex = vertex;
py_vertex->py_graph = py_graph;
Py_INCREF(py_graph);
return PyObject_Init(reinterpret_cast<PyObject *>(py_vertex), &PyVertexType);
}
PyObject *PyVertexRichCompare(PyObject *self, PyObject *other, int op) {
CHECK(self);
CHECK(other);
if (Py_TYPE(self) != &PyVertexType || Py_TYPE(other) != &PyVertexType ||
op != Py_EQ) {
Py_RETURN_NOTIMPLEMENTED;
}
auto *v1 = reinterpret_cast<PyVertex *>(self);
auto *v2 = reinterpret_cast<PyVertex *>(other);
CHECK(v1->vertex);
CHECK(v2->vertex);
return PyBool_FromLong(mgp_vertex_equal(v1->vertex, v2->vertex));
}
PyObject *PyInitMgpModule() {
PyObject *mgp = PyModule_Create(&PyMgpModule);
if (!mgp) return nullptr;
@ -474,6 +576,7 @@ PyObject *PyInitMgpModule() {
if (!register_type(&PyEdgeType, "Edge")) return nullptr;
if (!register_type(&PyQueryProcType, "Proc")) return nullptr;
if (!register_type(&PyQueryModuleType, "Module")) return nullptr;
if (!register_type(&PyVertexType, "Vertex")) return nullptr;
Py_INCREF(Py_None);
if (PyModule_AddObject(mgp, "_MODULE", Py_None) < 0) {
Py_DECREF(Py_None);