#ifndef _PY_GRAPH_MOD_H #define _PY_GRAPH_MOD_H #define PY_SSIZE_T_CLEAN #include #include #include "graph.h" #include "codec_nt.h" #include "py_triple.h" /* * String iterator for codec output. * * Yields one string (one or more lines) at a time. */ typedef struct { PyObject_HEAD LSUP_CodecIterator *it; unsigned char *line; } StringIteratorObject; static void StringIterator_dealloc (StringIteratorObject *it_obj) { it_obj->it->codec->encode_graph_done (it_obj->it); } static PyObject * StringIterator_next (StringIteratorObject *it_obj) { LSUP_rc rc = it_obj->it->codec->encode_graph_iter ( it_obj->it, &it_obj->line); if (rc != LSUP_OK) { if (rc != LSUP_END) PyErr_SetString (PyExc_ValueError, "Error encoding graph."); // If not an error, this raises StopIteration. return NULL; } PyObject *rdf_obj = PyUnicode_FromString ((char*)it_obj->line); if (UNLIKELY (!rdf_obj)) return NULL; Py_INCREF (rdf_obj); return (rdf_obj); } /* * String iterator type. * * Objects of this type are never generated from Python code, rather from * Graph_encode, hence the type has no special new or init function. */ PyTypeObject StringIteratorType = { PyVarObject_HEAD_INIT(&PyType_Type, 0) .tp_name = "graph.StringIterator", .tp_basicsize = sizeof (StringIteratorObject), .tp_itemsize = 0, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_dealloc = (destructor) StringIterator_dealloc, .tp_iter = PyObject_SelfIter, .tp_iternext = (iternextfunc)StringIterator_next, }; /* * Graph stuff. */ typedef struct { PyObject_HEAD LSUP_Graph *ob_struct; } GraphObject; static int Graph_init (GraphObject *self, PyObject *args, PyObject *kwargs) { unsigned char store_type; if (!PyArg_ParseTuple (args, "b", &store_type)) return -1; self->ob_struct = LSUP_graph_new ((LSUP_store_type)store_type); if (!self->ob_struct) { PyErr_SetString (PyExc_ValueError, "Could not create graph."); return -1; } return 0; } static void Graph_dealloc (GraphObject *self) { LSUP_graph_free (self->ob_struct); Py_TYPE (self)->tp_free ((PyObject *) self); } static PyObject * Graph_get_uri (GraphObject *self, void *closure) { PyObject *uri = PyUnicode_FromString ( LSUP_graph_uri (self->ob_struct)->data); if ( UNLIKELY (!uri)) return NULL; Py_INCREF(uri); return uri; } static int Graph_set_uri (GraphObject *self, PyObject *value, void *closure) { if (!PyObject_TypeCheck (value, &TermType)) return -1; LSUP_rc rc = LSUP_graph_set_uri ( self->ob_struct, ((TermObject*)value)->ob_struct->data); return rc == LSUP_OK ? 0 : -1; } static PyGetSetDef Graph_getsetters[] = { { "uri", (getter) Graph_get_uri, (setter) Graph_set_uri, "Graph URI.", NULL }, {NULL} }; static PyObject * Graph_copy (PyTypeObject *cls, PyObject *src) { if (! PyObject_TypeCheck (src, cls)) return NULL; GraphObject *res = (GraphObject *) cls->tp_alloc(cls, 0); if (!res) return NULL; res->ob_struct = LSUP_graph_copy (((GraphObject *) src)->ob_struct); Py_INCREF(res); return (PyObject *) res; } static PyObject * Graph_new_from_rdf (PyTypeObject *cls, PyObject *args) { PyObject *buf, *fileno_fn, *fileno_obj; const char *type; if (! PyArg_ParseTuple (args, "Os", &buf, &type)) return NULL; // Get the file descriptor from the Python BufferedIO object. // FIXME This is not sure to be reliable. See // https://docs.python.org/3/library/io.html?highlight=io%20bufferedreader#io.IOBase.fileno if (! (fileno_fn = PyObject_GetAttrString (buf, "fileno"))) { PyErr_SetString (PyExc_TypeError, "Object has no fileno function."); return NULL; } PyObject* fileno_args = PyTuple_New(0); if (! (fileno_obj = PyObject_CallObject (fileno_fn, fileno_args))) { PyErr_SetString (PyExc_SystemError, "Error calling fileno function."); return NULL; } int fd = PyLong_AsSize_t (fileno_obj); /* * From the Linux man page: * * > The file descriptor is not dup'ed, and will be closed when the stream * > created by fdopen() is closed. The result of applying fdopen() to a * > shared memory object is undefined. * * This handle must not be closed. Leave open for the Python caller to * handle it. */ FILE *fh = fdopen (fd, "r"); GraphObject *res = (GraphObject *) cls->tp_alloc(cls, 0); if (!res) return PyErr_NoMemory(); const LSUP_Codec *codec; if (strcmp(type, "nt") == 0) codec = &nt_codec; // TODO other codecs here. else { PyErr_SetString (PyExc_ValueError, "Unsupported codec."); return NULL; } size_t ct; char *err; codec->decode_graph (fh, &res->ob_struct, &ct, &err); TRACE ("Decoded %lu triples.\n", ct); if (UNLIKELY (err)) { PyErr_SetString (PyExc_IOError, err); return NULL; } Py_INCREF(res); return (PyObject *) res; } static PyObject * Graph_richcmp (PyObject *self, PyObject *other, int op) { // Only equality and non-equality are supported. if (op != Py_EQ && op != Py_NE) Py_RETURN_NOTIMPLEMENTED; LSUP_Graph *t1 = ((GraphObject *) self)->ob_struct; LSUP_Graph *t2 = ((GraphObject *) other)->ob_struct; if (LSUP_graph_equals (t1, t2) ^ (op == Py_NE)) Py_RETURN_TRUE; Py_RETURN_FALSE; } static inline PyObject * Graph_bool_op ( PyTypeObject *cls, LSUP_bool_op op, PyObject *gr1, PyObject *gr2) { if (! PyObject_TypeCheck (gr1, cls) || ! PyObject_TypeCheck (gr2, cls)) return NULL; GraphObject *res = (GraphObject *) cls->tp_alloc (cls, 0); if (!res) return NULL; res->ob_struct = LSUP_graph_bool_op ( op, ((GraphObject *) gr1)->ob_struct, ((GraphObject *) gr2)->ob_struct); Py_INCREF(res); return (PyObject *) res; } static PyObject * Graph_add (PyObject *self, PyObject *triples) { // Triple may be any iterable. PyObject *iter = PyObject_GetIter (triples); if (! iter) { PyErr_SetString ( PyExc_ValueError, "Triples object cannot be iterated."); return NULL; } PyObject *trp_obj = NULL; int rc = 0; size_t i; LSUP_SerTriple *sspo = LSUP_striple_new (BUF_DUMMY, BUF_DUMMY, BUF_DUMMY); LSUP_GraphIterator *it = LSUP_graph_add_init ( ((GraphObject *)self)->ob_struct); for (i = 0; (trp_obj = PyIter_Next (iter)); i++) { if (!PyObject_TypeCheck (trp_obj, &TripleType)) { PyErr_SetString ( PyExc_ValueError, "Object is not a triple."); rc = -1; goto finalize; } TRACE ("Inserting triple #%lu\n", i); LSUP_triple_serialize (((TripleObject *)trp_obj)->ob_struct, sspo); LSUP_rc db_rc = LSUP_graph_add_iter (it, sspo); if (db_rc == LSUP_OK) rc = LSUP_OK; if (UNLIKELY (db_rc < 0)) { rc = -1; goto finalize; } } finalize: LSUP_graph_add_done (it); LSUP_striple_free (sspo); PyObject *ret = PyLong_FromSize_t (LSUP_graph_iter_cur (it)); if (rc == 0) { Py_INCREF (ret); return ret; } return NULL; } // TODO static int Graph_remove (PyObject *self, PyObject *s, PyObject *p, PyObject *o) { return 0; } // TODO static PyObject *Graph_lookup ( PyObject *self, PyObject *s, PyObject *p, PyObject *o) { return NULL; } static PyObject * Graph_encode (PyObject *self, PyObject *args) { char *type; if (! PyArg_ParseTuple (args, "s", &type)) return NULL; const LSUP_Codec *codec; if (strcmp(type, "nt") == 0) codec = &nt_codec; // TODO other codecs here. else { PyErr_SetString (PyExc_ValueError, "Unsupported codec."); return NULL; } LSUP_CodecIterator *it = codec->encode_graph_init ( ((GraphObject *)self)->ob_struct); // Initialize the generator object. StringIteratorObject *it_obj = PyObject_New ( StringIteratorObject, &StringIteratorType); if (!it_obj) return NULL; it_obj->it = it; it_obj->line = NULL; Py_INCREF (it_obj); return (PyObject *)it_obj; } static PyMethodDef Graph_methods[] = { { "copy", (PyCFunction) Graph_copy, METH_CLASS | METH_VARARGS, "Copy a graph." }, { "from_rdf", (PyCFunction) Graph_new_from_rdf, METH_CLASS | METH_VARARGS, "Create a graph from a RDF file." }, {"add", (PyCFunction) Graph_add, METH_O, "Add triples to a graph."}, { "remove", (PyCFunction) Graph_remove, METH_VARARGS, "Remove triples from a graph by matching a pattern." }, { "lookup", (PyCFunction) Graph_lookup, METH_VARARGS, "Look triples in a graph by matching a pattern." }, { "to_rdf", (PyCFunction) Graph_encode, METH_VARARGS, "Encode a graph into a RDF byte buffer." }, {NULL}, }; static inline PyObject *Graph_bool_and ( PyTypeObject *cls, PyObject *gr1, PyObject *gr2) { return Graph_bool_op (cls, LSUP_BOOL_INTERSECTION, gr1, gr2); } static inline PyObject *Graph_bool_or ( PyTypeObject *cls, PyObject *gr1, PyObject *gr2) { return Graph_bool_op (cls, LSUP_BOOL_UNION, gr1, gr2); } static inline PyObject *Graph_bool_subtract ( PyTypeObject *cls, PyObject *gr1, PyObject *gr2) { return Graph_bool_op (cls, LSUP_BOOL_SUBTRACTION, gr1, gr2); } static inline PyObject *Graph_bool_xor ( PyTypeObject *cls, PyObject *gr1, PyObject *gr2) { return Graph_bool_op (cls, LSUP_BOOL_XOR, gr1, gr2); } static PyNumberMethods Graph_number_methods = { .nb_and = (binaryfunc) Graph_bool_and, .nb_or = (binaryfunc) Graph_bool_or, .nb_subtract = (binaryfunc) Graph_bool_subtract, .nb_xor = (binaryfunc) Graph_bool_xor, }; static int Graph_contains (PyObject *self, PyObject *value) { if (!PyObject_TypeCheck (value, &TripleType)) { PyErr_SetString (PyExc_ValueError, "Error parsing input value."); return -1; } int rc = LSUP_graph_contains ( ((GraphObject *) self)->ob_struct, ((TripleObject *) value)->ob_struct); return rc; } static Py_ssize_t Graph_get_size (PyObject *self) { return LSUP_graph_size (((GraphObject *) self)->ob_struct); } static PySequenceMethods Graph_seq_methods = { .sq_length = (lenfunc) Graph_get_size, .sq_contains = (objobjproc) Graph_contains, }; PyTypeObject GraphType = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "graph.Graph", .tp_doc = "RDF graph", .tp_basicsize = sizeof (GraphObject), .tp_itemsize = 0, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_new = PyType_GenericNew, .tp_init = (initproc) Graph_init, .tp_dealloc = (destructor) Graph_dealloc, .tp_getset = Graph_getsetters, .tp_methods = Graph_methods, .tp_richcompare = (richcmpfunc) Graph_richcmp, .tp_as_number = &Graph_number_methods, .tp_as_sequence = &Graph_seq_methods, }; #endif