py_graph.h 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682
  1. #ifndef _PY_GRAPH_MOD_H
  2. #define _PY_GRAPH_MOD_H
  3. #define PY_SSIZE_T_CLEAN
  4. #include <Python.h>
  5. #include <structmember.h>
  6. #include "graph.h"
  7. #include "codec/codec_nt.h"
  8. #include "codec/codec_ttl.h"
  9. #include "py_triple.h"
  10. /*
  11. * Iterator helpers.
  12. */
  13. /*
  14. * String iterator for encoder output.
  15. *
  16. * Yields one string (one or more lines) at a time.
  17. */
  18. typedef struct {
  19. PyObject_HEAD
  20. void *it;
  21. const LSUP_Codec *codec;
  22. char *line;
  23. } StringIteratorObject;
  24. static void
  25. StringIterator_dealloc (StringIteratorObject *it_obj)
  26. { it_obj->codec->encode_graph_done (it_obj->it); }
  27. static PyObject *
  28. StringIterator_next (StringIteratorObject *it_obj)
  29. {
  30. LSUP_rc rc = it_obj->codec->encode_graph_iter (
  31. it_obj->it, &it_obj->line);
  32. if (rc != LSUP_OK) {
  33. if (rc != LSUP_END)
  34. PyErr_SetString (PyExc_ValueError, "Error encoding graph.");
  35. // If not an error, this raises StopIteration.
  36. return NULL;
  37. }
  38. return PyUnicode_FromString ((char *) it_obj->line);
  39. }
  40. /*
  41. * String iterator type.
  42. *
  43. * Objects of this type are never generated from Python code, rather from
  44. * Graph_encode, hence the type has no special new or init function.
  45. */
  46. PyTypeObject StringIteratorType = {
  47. PyVarObject_HEAD_INIT(&PyType_Type, 0)
  48. .tp_name = "graph.StringIterator",
  49. .tp_basicsize = sizeof (StringIteratorObject),
  50. .tp_itemsize = 0,
  51. .tp_flags = Py_TPFLAGS_DEFAULT,
  52. .tp_dealloc = (destructor) StringIterator_dealloc,
  53. .tp_iter = PyObject_SelfIter,
  54. .tp_iternext = (iternextfunc)StringIterator_next,
  55. };
  56. /*
  57. * Graph iterator.
  58. *
  59. * Yields one triple at a time.
  60. */
  61. typedef struct {
  62. PyObject_HEAD
  63. LSUP_GraphIterator *it;
  64. LSUP_Triple *spo;
  65. } GraphIteratorObject;
  66. static void
  67. GraphIterator_dealloc (GraphIteratorObject *it_obj)
  68. {
  69. LSUP_graph_iter_free (it_obj->it);
  70. free (it_obj->spo);
  71. }
  72. static PyObject *
  73. GraphIterator_next (GraphIteratorObject *it_obj)
  74. {
  75. LSUP_rc rc = LSUP_graph_iter_next (it_obj->it, &it_obj->spo);
  76. if (rc != LSUP_OK) {
  77. if (rc != LSUP_END)
  78. PyErr_SetString (PyExc_ValueError, "Error encoding graph.");
  79. // If not an error, this raises StopIteration.
  80. return NULL;
  81. }
  82. return build_triple (it_obj->spo);
  83. }
  84. /*
  85. * Graph iterator type.
  86. */
  87. PyTypeObject GraphIteratorType = {
  88. PyVarObject_HEAD_INIT(&PyType_Type, 0)
  89. .tp_name = "graph.GraphIterator",
  90. .tp_basicsize = sizeof (GraphIteratorObject),
  91. .tp_itemsize = 0,
  92. .tp_flags = Py_TPFLAGS_DEFAULT,
  93. .tp_dealloc = (destructor) GraphIterator_dealloc,
  94. .tp_iter = PyObject_SelfIter,
  95. .tp_iternext = (iternextfunc) GraphIterator_next,
  96. };
  97. /*
  98. * Graph stuff.
  99. */
  100. typedef struct {
  101. PyObject_HEAD
  102. LSUP_Graph *ob_struct;
  103. } GraphObject;
  104. static int
  105. Graph_init (GraphObject *self, PyObject *args, PyObject *kwargs)
  106. {
  107. unsigned char store_type;
  108. PyObject *uri_obj = NULL;
  109. char *uri_str = NULL;
  110. static char *kwlist[] = {"", "uri_str", NULL};
  111. if (!PyArg_ParseTupleAndKeywords (
  112. args, kwargs, "b|s", kwlist, &store_type, &uri_str))
  113. return -1;
  114. // Set up the store if a function for that is defined.
  115. const LSUP_StoreInt *sif = LSUP_store_int (store_type);
  116. if (UNLIKELY (!sif)) {
  117. PyErr_SetString (
  118. PyExc_TypeError,
  119. "No interface defined for given store type.");
  120. return -1;
  121. }
  122. // TODO Move store creation fn and handle into a separate module.
  123. LSUP_Store *store = LSUP_store_new (store_type, NULL, 0);
  124. if (sif->setup_fn) {
  125. if (sif->setup_fn(NULL, false) < LSUP_OK) {
  126. PyErr_SetString (
  127. PyExc_IOError, "Error initializing back end store.");
  128. return -1;
  129. }
  130. }
  131. // TODO Make store ID, nsm and initial size accessible.
  132. self->ob_struct = LSUP_graph_new (store, uri_str, NULL);
  133. if (!self->ob_struct) {
  134. PyErr_SetString (PyExc_ValueError, "Could not create graph.");
  135. return -1;
  136. }
  137. if (uri_str) free (uri_str);
  138. return 0;
  139. }
  140. static void
  141. Graph_dealloc (GraphObject *self)
  142. {
  143. LSUP_graph_free (self->ob_struct);
  144. Py_TYPE (self)->tp_free ((PyObject *) self);
  145. }
  146. static PyObject *
  147. Graph_get_uri (GraphObject *self, void *closure)
  148. {
  149. const LSUP_Term *uri = LSUP_graph_uri (self->ob_struct);
  150. LOG_DEBUG("Graph URI address: %p", uri);
  151. LOG_DEBUG("Graph URI: %s", uri->data);
  152. return PyUnicode_FromString (uri->data);
  153. }
  154. static int
  155. Graph_set_uri (GraphObject *self, PyObject *args)
  156. {
  157. char *uri_str = NULL;
  158. if (! PyArg_ParseTuple (args, "s", &uri_str)) return NULL;
  159. LSUP_rc rc = LSUP_graph_set_uri (self->ob_struct, uri_str);
  160. free (uri_str);
  161. return rc == LSUP_OK ? 0 : -1;
  162. }
  163. static PyGetSetDef Graph_getsetters[] = {
  164. {
  165. "uri", (getter) Graph_get_uri, (setter) Graph_set_uri,
  166. "Graph URI.", NULL
  167. },
  168. {NULL}
  169. };
  170. static PyObject *
  171. Graph_new_from_rdf (PyTypeObject *cls, PyObject *args)
  172. {
  173. PyObject *buf, *fileno_fn, *fileno_obj;
  174. const char *type;
  175. if (! PyArg_ParseTuple (args, "Os", &buf, &type)) return NULL;
  176. // Get the file descriptor from the Python BufferedIO object.
  177. // FIXME This is not sure to be reliable. See
  178. // https://docs.python.org/3/library/io.html?highlight=io%20bufferedreader#io.IOBase.fileno
  179. if (! (fileno_fn = PyObject_GetAttrString (buf, "fileno"))) {
  180. PyErr_SetString (PyExc_TypeError, "Object has no fileno function.");
  181. return NULL;
  182. }
  183. PyObject* fileno_args = PyTuple_New(0);
  184. if (! (fileno_obj = PyObject_CallObject (fileno_fn, fileno_args))) {
  185. PyErr_SetString (PyExc_SystemError, "Error calling fileno function.");
  186. return NULL;
  187. }
  188. int fd = PyLong_AsSize_t (fileno_obj);
  189. /*
  190. * From the Linux man page:
  191. *
  192. * > The file descriptor is not dup'ed, and will be closed when the stream
  193. * > created by fdopen() is closed. The result of applying fdopen() to a
  194. * > shared memory object is undefined.
  195. *
  196. * Hence the `dup()`.
  197. */
  198. fd = dup (fd);
  199. FILE *fh = fdopen (fd, "r");
  200. GraphObject *res = (GraphObject *) cls->tp_alloc(cls, 0);
  201. if (!res) return PyErr_NoMemory();
  202. const LSUP_Codec *codec;
  203. if (strcmp(type, "nt") == 0) codec = &nt_codec;
  204. else if (strcmp (type, "ttl") == 0) codec = &ttl_codec;
  205. // TODO other codecs here.
  206. else {
  207. PyErr_SetString (PyExc_ValueError, "Unsupported codec.");
  208. return NULL;
  209. }
  210. size_t ct;
  211. char *err;
  212. codec->decode_graph (fh, &res->ob_struct, &ct, &err);
  213. fclose (fh);
  214. LOG_DEBUG("Decoded %lu triples.", ct);
  215. if (UNLIKELY (err)) {
  216. PyErr_SetString (PyExc_IOError, err);
  217. return NULL;
  218. }
  219. Py_INCREF (res);
  220. return (PyObject *) res;
  221. }
  222. /** @brief Build a triple pattern for lookup purposes.
  223. */
  224. inline static int build_trp_pattern (PyObject *args, LSUP_Term *spo[])
  225. {
  226. PyObject *s_obj, *p_obj, *o_obj;
  227. if (! (PyArg_ParseTuple (args, "OOO", &s_obj, &p_obj, &o_obj)))
  228. return -1;
  229. if (s_obj != Py_None && !PyObject_TypeCheck (s_obj, &TermType)) {
  230. PyErr_SetString (PyExc_TypeError, "Subject must be a term or None.");
  231. return -1;
  232. }
  233. if (p_obj != Py_None && !PyObject_TypeCheck (p_obj, &TermType)) {
  234. PyErr_SetString (PyExc_TypeError, "Predicate must be a term or None.");
  235. return -1;
  236. }
  237. if (o_obj != Py_None && !PyObject_TypeCheck (o_obj, &TermType)) {
  238. PyErr_SetString (PyExc_TypeError, "Object must be a term or None.");
  239. return -1;
  240. }
  241. spo[0] = s_obj != Py_None ? ((TermObject *)s_obj)->ob_struct : NULL;
  242. spo[1] = p_obj != Py_None ? ((TermObject *)p_obj)->ob_struct : NULL;
  243. spo[2] = o_obj != Py_None ? ((TermObject *)o_obj)->ob_struct : NULL;
  244. return 0;
  245. }
  246. /** @brief Converter for a term struct in `O&` notation.
  247. *
  248. * See https://docs.python.org/3/c-api/arg.html#other-objects
  249. * Convert a PyObject argument into a LSUP_term. `Py_None` is acceptable.
  250. *
  251. * @param[in] obj Argument passed to calling function.
  252. *
  253. * @param[out] result Result of transform process.
  254. *
  255. * @return 1 on conversion success; 0 in failure.
  256. */
  257. static int arg_term_converter (PyObject *obj, void *result)
  258. {
  259. if (obj != Py_None && !PyObject_TypeCheck (obj, &TermType)) {
  260. PyErr_SetString (PyExc_TypeError, "Variable must be a Term or None.");
  261. return 0;
  262. }
  263. LSUP_Term **term = result;
  264. *term = obj != Py_None ? ((TermObject *)obj)->ob_struct : NULL;
  265. return 1;
  266. }
  267. /** @brief Converter for a graph struct in `O&` notation.
  268. *
  269. * See https://docs.python.org/3/c-api/arg.html#other-objects
  270. * Convert a PyObject argument into a LSUP_term.
  271. *
  272. * @param[in] obj Argument passed to calling function.
  273. *
  274. * @param[out] result Result of transform process.
  275. *
  276. * @return 1 on conversion success; 0 in failure.
  277. */
  278. static int arg_graph_converter (PyObject *obj, void *result);
  279. static PyObject *
  280. Graph_copy_contents (GraphObject *self, PyObject *args, PyObject *kwargs)
  281. {
  282. assert (!PyErr_Occurred());
  283. assert (args || kwargs);
  284. LSUP_Term *spo[3];
  285. LSUP_Graph *dest;
  286. static char *kwlist[] = {"", "s", "p", "o", NULL};
  287. if (!PyArg_ParseTupleAndKeywords(
  288. args, kwargs, "O&|O&O&O&", kwlist,
  289. arg_graph_converter, &dest,
  290. arg_term_converter, spo,
  291. arg_term_converter, spo + 1,
  292. arg_term_converter, spo + 2
  293. )) return NULL;
  294. if (LSUP_graph_copy_contents (
  295. self->ob_struct, dest,
  296. spo[0], spo[1], spo[2])
  297. < LSUP_OK)
  298. {
  299. PyErr_SetString (PyExc_ValueError, "Error copying graph contents.");
  300. return NULL;
  301. }
  302. Py_RETURN_NONE;
  303. };
  304. static PyObject *
  305. Graph_richcmp (PyObject *self, PyObject *other, int op)
  306. {
  307. // Only equality and non-equality are supported.
  308. if (op != Py_EQ && op != Py_NE) Py_RETURN_NOTIMPLEMENTED;
  309. LSUP_Graph *t1 = ((GraphObject *) self)->ob_struct;
  310. LSUP_Graph *t2 = ((GraphObject *) other)->ob_struct;
  311. if (LSUP_graph_equals (t1, t2) ^ (op == Py_NE)) Py_RETURN_TRUE;
  312. Py_RETURN_FALSE;
  313. }
  314. static inline PyObject *
  315. Graph_bool_op (
  316. PyTypeObject *cls, LSUP_bool_op op, PyObject *gr1, PyObject *gr2)
  317. {
  318. if (! PyObject_TypeCheck (gr1, cls) || ! PyObject_TypeCheck (gr2, cls))
  319. return NULL;
  320. GraphObject *res = (GraphObject *) cls->tp_alloc (cls, 0);
  321. if (!res) return NULL;
  322. LSUP_Graph *dest = LSUP_graph_new (NULL, NULL, NULL);
  323. if (!dest) {
  324. PyErr_SetString (PyExc_Exception, "Could not create destination graph.");
  325. return NULL;
  326. }
  327. LSUP_rc rc = LSUP_graph_bool_op (
  328. op, ((GraphObject *) gr1)->ob_struct,
  329. ((GraphObject *) gr2)->ob_struct, res->ob_struct);
  330. if (rc < LSUP_OK) {
  331. PyErr_SetString (PyExc_Exception, "Error performing boolean operation.");
  332. return NULL;
  333. }
  334. Py_INCREF(res);
  335. return (PyObject *) res;
  336. }
  337. static PyObject *
  338. Graph_add (PyObject *self, PyObject *triples)
  339. {
  340. // Triple may be any iterable.
  341. PyObject *iter = PyObject_GetIter (triples);
  342. if (! iter) {
  343. PyErr_SetString (
  344. PyExc_ValueError, "Triples object cannot be iterated.");
  345. return NULL;
  346. }
  347. PyObject *trp_obj;
  348. int rc = 0;
  349. size_t ct = 0;
  350. LSUP_GraphIterator *it = LSUP_graph_add_init (
  351. ((GraphObject *)self)->ob_struct);
  352. while ((trp_obj = PyIter_Next (iter))) {
  353. if (!PyObject_TypeCheck (trp_obj, &TripleType)) {
  354. PyErr_SetString (
  355. PyExc_ValueError, "Object is not a triple.");
  356. rc = -1;
  357. goto finally;
  358. }
  359. LOG_TRACE("Inserting triple #%lu", ct);
  360. LSUP_rc db_rc = LSUP_graph_add_iter (
  361. it, ((TripleObject *) trp_obj)->ob_struct);
  362. if (db_rc == LSUP_OK) {
  363. rc = LSUP_OK;
  364. ct++;
  365. } else if (UNLIKELY (db_rc < 0)) {
  366. PyErr_SetString (PyExc_ValueError, "Error while adding triples.");
  367. rc = -1;
  368. goto finally;
  369. }
  370. // If db_rc > 0, it's a no-op and the counter is not increased.
  371. }
  372. finally:
  373. LSUP_graph_add_done (it);
  374. if (rc == LSUP_OK) return PyLong_FromSize_t (ct);
  375. return NULL;
  376. }
  377. static PyObject *Graph_remove (PyObject *self, PyObject *args)
  378. {
  379. LSUP_rc rc;
  380. LSUP_Term *spo[3];
  381. if (!PyArg_ParseTuple (
  382. args,
  383. "O&O&O&",
  384. arg_term_converter, spo,
  385. arg_term_converter, spo + 1,
  386. arg_term_converter, spo + 2
  387. )) Py_RETURN_NONE;
  388. size_t ct;
  389. rc = LSUP_graph_remove (
  390. ((GraphObject *)self)->ob_struct, spo[0], spo[1], spo[2], &ct);
  391. if (rc < 0) {
  392. // TODO implement strerror for more details.
  393. PyErr_SetString (PyExc_SystemError, "Error removing triples.");
  394. goto finally;
  395. }
  396. LOG_DEBUG("Removed %lu triples.", ct);
  397. finally:
  398. if (rc < 0) return NULL;
  399. Py_RETURN_NONE;
  400. }
  401. static PyObject *Graph_lookup (PyObject *self, PyObject *args)
  402. {
  403. LSUP_rc rc;
  404. GraphIteratorObject *it_obj = NULL;
  405. LSUP_Term *spo[3];
  406. if (!PyArg_ParseTuple (
  407. args,
  408. "O&O&O&",
  409. arg_term_converter, spo,
  410. arg_term_converter, spo + 1,
  411. arg_term_converter, spo + 2
  412. )) Py_RETURN_NONE;
  413. size_t ct;
  414. LSUP_GraphIterator *it = LSUP_graph_lookup (
  415. ((GraphObject *)self)->ob_struct, spo[0], spo[1], spo[2], &ct);
  416. if (UNLIKELY (!it)) {
  417. // TODO implement LSUP_strerror for more details.
  418. PyErr_SetString (PyExc_SystemError, "Error looking up triples.");
  419. rc = -1;
  420. goto finally;
  421. }
  422. LOG_DEBUG("Found %lu triples.", ct);
  423. // Initialize the generator object.
  424. it_obj = PyObject_New (
  425. GraphIteratorObject, &GraphIteratorType);
  426. if (UNLIKELY (!it_obj)) return PyErr_NoMemory();
  427. it_obj->it = it;
  428. it_obj->spo = TRP_DUMMY;
  429. Py_INCREF (it_obj);
  430. finally:
  431. return (PyObject *)it_obj;
  432. }
  433. static PyObject *
  434. Graph_encode (PyObject *self, PyObject *args)
  435. {
  436. const char *type;
  437. if (! PyArg_ParseTuple (args, "s", &type)) return NULL;
  438. // Initialize the generator object.
  439. StringIteratorObject *it_obj = PyObject_New (
  440. StringIteratorObject, &StringIteratorType);
  441. if (!it_obj) return NULL;
  442. if (strcmp (type, "nt") == 0) it_obj->codec = &nt_codec;
  443. else if (strcmp (type, "ttl") == 0) it_obj->codec = &ttl_codec;
  444. // TODO other codecs here.
  445. else {
  446. PyErr_SetString (PyExc_ValueError, "Unsupported codec.");
  447. return NULL;
  448. }
  449. it_obj->it = it_obj->codec->encode_graph_init (
  450. ((GraphObject *)self)->ob_struct);
  451. it_obj->line = NULL;
  452. Py_INCREF (it_obj);
  453. return (PyObject *)it_obj;
  454. }
  455. static PyMethodDef Graph_methods[] = {
  456. {
  457. "copy", (PyCFunction) Graph_copy_contents,
  458. METH_VARARGS | METH_KEYWORDS,
  459. "Copy the contents of a graph into another."
  460. },
  461. {
  462. "from_rdf", (PyCFunction) Graph_new_from_rdf,
  463. METH_CLASS | METH_VARARGS,
  464. "Create a graph from a RDF file."
  465. },
  466. {"add", (PyCFunction) Graph_add, METH_O, "Add triples to a graph."},
  467. {
  468. "remove", (PyCFunction) Graph_remove, METH_VARARGS,
  469. "Remove triples from a graph by matching a pattern."
  470. },
  471. {
  472. "lookup", (PyCFunction) Graph_lookup, METH_VARARGS,
  473. "Look triples in a graph by matching a pattern."
  474. },
  475. {
  476. "to_rdf", (PyCFunction) Graph_encode, METH_VARARGS,
  477. "Encode a graph into a RDF byte buffer."
  478. },
  479. {NULL},
  480. };
  481. static inline PyObject *Graph_bool_and (
  482. PyTypeObject *cls, PyObject *gr1, PyObject *gr2)
  483. { return Graph_bool_op (cls, LSUP_BOOL_INTERSECTION, gr1, gr2); }
  484. static inline PyObject *Graph_bool_or (
  485. PyTypeObject *cls, PyObject *gr1, PyObject *gr2)
  486. { return Graph_bool_op (cls, LSUP_BOOL_UNION, gr1, gr2); }
  487. static inline PyObject *Graph_bool_subtract (
  488. PyTypeObject *cls, PyObject *gr1, PyObject *gr2)
  489. { return Graph_bool_op (cls, LSUP_BOOL_SUBTRACTION, gr1, gr2); }
  490. static inline PyObject *Graph_bool_xor (
  491. PyTypeObject *cls, PyObject *gr1, PyObject *gr2)
  492. { return Graph_bool_op (cls, LSUP_BOOL_XOR, gr1, gr2); }
  493. static PyNumberMethods Graph_number_methods = {
  494. .nb_and = (binaryfunc) Graph_bool_and,
  495. .nb_or = (binaryfunc) Graph_bool_or,
  496. .nb_subtract = (binaryfunc) Graph_bool_subtract,
  497. .nb_xor = (binaryfunc) Graph_bool_xor,
  498. };
  499. static int
  500. Graph_contains (PyObject *self, PyObject *value)
  501. {
  502. if (!PyObject_TypeCheck (value, &TripleType)) {
  503. PyErr_SetString (PyExc_ValueError, "Error parsing input value.");
  504. return -1;
  505. }
  506. int rc = LSUP_graph_contains (
  507. ((GraphObject *) self)->ob_struct,
  508. ((TripleObject *) value)->ob_struct);
  509. return rc;
  510. }
  511. static Py_ssize_t
  512. Graph_get_size (PyObject *self)
  513. { return LSUP_graph_size (((GraphObject *) self)->ob_struct); }
  514. static PySequenceMethods Graph_seq_methods = {
  515. .sq_length = (lenfunc) Graph_get_size,
  516. .sq_contains = (objobjproc) Graph_contains,
  517. };
  518. PyTypeObject GraphType = {
  519. PyVarObject_HEAD_INIT(NULL, 0)
  520. .tp_name = "graph.Graph",
  521. .tp_doc = "RDF graph",
  522. .tp_basicsize = sizeof (GraphObject),
  523. .tp_itemsize = 0,
  524. .tp_flags = Py_TPFLAGS_DEFAULT,
  525. .tp_new = PyType_GenericNew,
  526. .tp_init = (initproc) Graph_init,
  527. .tp_dealloc = (destructor) Graph_dealloc,
  528. .tp_getset = Graph_getsetters,
  529. .tp_methods = Graph_methods,
  530. .tp_richcompare = (richcmpfunc) Graph_richcmp,
  531. .tp_as_number = &Graph_number_methods,
  532. .tp_as_sequence = &Graph_seq_methods,
  533. };
  534. static int arg_graph_converter (PyObject *obj, void *result)
  535. {
  536. if (!PyObject_TypeCheck (obj, &GraphType)) {
  537. PyErr_SetString (PyExc_TypeError, "Variable must be a Graph object.");
  538. return 0;
  539. }
  540. LSUP_Graph **gr = result;
  541. *gr = ((GraphObject *)obj)->ob_struct;
  542. return 1;
  543. }
  544. #endif