graph.pyx 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793
  1. import logging
  2. from functools import wraps
  3. from rdflib import Graph
  4. from rdflib.term import Node
  5. from lakesuperior import env
  6. from libc.string cimport memcmp, memcpy
  7. from libc.stdlib cimport free
  8. from cymem.cymem cimport Pool
  9. from lakesuperior.cy_include cimport calg
  10. from lakesuperior.cy_include cimport cylmdb as lmdb
  11. from lakesuperior.model.graph cimport term
  12. from lakesuperior.store.ldp_rs.lmdb_triplestore cimport (
  13. KLEN, DBL_KLEN, TRP_KLEN, TripleKey)
  14. from lakesuperior.model.structures.keyset cimport Keyset
  15. from lakesuperior.model.base cimport Buffer
  16. from lakesuperior.model.graph.triple cimport BufferTriple
  17. from lakesuperior.model.structures.hash cimport Hash32, hash32
  18. #BUF_PTR_SZ = sizeof(Buffer *)
  19. logger = logging.getLogger(__name__)
  20. def use_data(fn):
  21. """
  22. Decorator to indicate that a set operation between two SimpleGraph
  23. instances should use the ``data`` property of the second term. The second
  24. term can also be a simple set.
  25. """
  26. @wraps(fn)
  27. def _wrapper(self, other):
  28. if isinstance(other, SimpleGraph):
  29. other = other.data
  30. return _wrapper
  31. cdef unsigned int term_hash_fn(const calg.SetValue data):
  32. """
  33. Hash function for sets of terms.
  34. https://fragglet.github.io/c-algorithms/doc/set_8h.html#6c7986a2a80d7a3cb7b9d74e1c6fef97
  35. :param SetValue *data: Pointer to a Buffer structure.
  36. """
  37. cdef:
  38. Hash32 hash
  39. hash32(<const Buffer *>&data, &hash)
  40. return hash
  41. cdef unsigned int trp_hash_fn(calg.SetValue btrp):
  42. """
  43. Hash function for sets of (serialized) triples.
  44. https://fragglet.github.io/c-algorithms/doc/set_8h.html#6c7986a2a80d7a3cb7b9d74e1c6fef97
  45. This function computes the hash of the concatenated pointer values in the
  46. s, p, o members of the triple. The triple structure is treated as a byte
  47. string. This is safe in spite of byte-wise struct evaluation being a
  48. frowned-upon practice (due to padding issues), because it is assumed that
  49. the input value is always the same type of structure.
  50. :param SetItem *data: Pointer to a BufferTriple structure.
  51. """
  52. cdef:
  53. Buffer data
  54. Hash32 hash
  55. data.addr = &btrp
  56. data.sz = sizeof(btrp)
  57. hash32(&data, &hash)
  58. return hash
  59. cdef bint buffer_cmp_fn(const calg.SetValue v1, const calg.SetValue v2):
  60. """
  61. Compare function for two Buffer objects.
  62. https://fragglet.github.io/c-algorithms/doc/set_8h.html#40fa2c86d5b003c1b0b0e8dd1e4df9f4
  63. """
  64. cdef:
  65. Buffer b1 = (<Buffer *>v1)[0]
  66. Buffer b2 = (<Buffer *>v2)[0]
  67. if b1.sz != b2.sz:
  68. return False
  69. return memcmp(b1.addr, b2.addr, b1.sz) == 0
  70. cdef bint triple_cmp_fn(const calg.SetValue v1, const calg.SetValue v2):
  71. """
  72. Compare function for two triples in a CAlg set.
  73. Here, pointers to terms are compared for s, p, o. The pointers should be
  74. guaranteed to point to unique values (i.e. no two pointers have the same
  75. term value within a graph).
  76. https://fragglet.github.io/c-algorithms/doc/set_8h.html#40fa2c86d5b003c1b0b0e8dd1e4df9f4
  77. """
  78. cdef:
  79. BufferTriple t1 = (<BufferTriple *>v1)[0]
  80. BufferTriple t2 = (<BufferTriple *>v2)[0]
  81. return(
  82. t1.s == t2.s and
  83. t1.p == t2.p and
  84. t1.o == t2.o)
  85. cdef inline bint lookup_none_cmp_fn(
  86. const BufferTriple *trp, const Buffer *t1, const Buffer *t2):
  87. return True
  88. cdef inline bint lookup_s_cmp_fn(
  89. const BufferTriple *trp, const Buffer *t1, const Buffer *t2):
  90. """
  91. Lookup callback compare function for a given s in a triple.
  92. The function returns ``True`` if ``t1`` matches the first term.
  93. ``t2`` is not used and is declared only for compatibility with the
  94. other interchangeable functions.
  95. """
  96. return buffer_cmp_fn(t1, trp[0].s)
  97. cdef inline bint lookup_p_cmp_fn(
  98. const BufferTriple *trp, const Buffer *t1, const Buffer *t2):
  99. return buffer_cmp_fn(t1, trp[0].p)
  100. cdef inline bint lookup_o_cmp_fn(
  101. const BufferTriple *trp, const Buffer *t1, const Buffer *t2):
  102. return buffer_cmp_fn(t1, trp[0].o)
  103. cdef inline bint lookup_sp_cmp_fn(
  104. const BufferTriple *trp, const Buffer *t1, const Buffer *t2):
  105. return (
  106. buffer_cmp_fn(t1, trp[0].s)
  107. and buffer_cmp_fn(t2, trp[0].p))
  108. cdef inline bint lookup_so_cmp_fn(
  109. const BufferTriple *trp, const Buffer *t1, const Buffer *t2):
  110. return (
  111. buffer_cmp_fn(t1, trp[0].s)
  112. and buffer_cmp_fn(t2, trp[0].o))
  113. cdef inline bint lookup_po_cmp_fn(
  114. const BufferTriple *trp, const Buffer *t1, const Buffer *t2):
  115. return (
  116. buffer_cmp_fn(t1, trp[0].p)
  117. and buffer_cmp_fn(t2, trp[0].o))
  118. cdef class SimpleGraph:
  119. """
  120. Fast and simple implementation of a graph.
  121. Most functions should mimic RDFLib's graph with less overhead. It uses
  122. the same funny but functional slicing notation. No lookup functions within
  123. the graph are available at this time.
  124. Instances of this class hold a set of
  125. :py:class:`~lakesuperior.store.ldp_rs.term.Term` structures that stores
  126. unique terms within the graph, and a set of
  127. :py:class:`~lakesuperior.store.ldp_rs.triple.Triple` structures referencing
  128. those terms. Therefore, no data duplication occurs and the storage is quite
  129. sparse.
  130. A graph can be instantiated from a store lookup.
  131. A SimpleGraph can also be obtained from a
  132. :py:class:`lakesuperior.store.keyset.Keyset` which is convenient bacause
  133. a Keyset can be obtained very efficiently from querying a store, then also
  134. very efficiently filtered and eventually converted into a set of meaningful
  135. terms.
  136. An instance of this class can also be converted to and from a
  137. ``rdflib.Graph`` instance. TODO verify that this frees Cython pointers.
  138. """
  139. def __cinit__(
  140. self, Keyset keyset=None, store=None, set data=set()):
  141. """
  142. Initialize the graph with pre-existing data or by looking up a store.
  143. One of ``keyset``, or ``data`` can be provided. If more than
  144. one of these is provided, precedence is given in the mentioned order.
  145. If none of them is specified, an empty graph is initialized.
  146. :param rdflib.URIRef uri: The graph URI.
  147. This will serve as the subject for some queries.
  148. :param Keyset keyset: Keyset to create the graph from. Keys will be
  149. converted to set elements.
  150. :param lakesuperior.store.ldp_rs.LmdbTripleStore store: store to
  151. look up the keyset. Only used if ``keyset`` is specified. If not
  152. set, the environment store is used.
  153. :param set data: Initial data as a set of 3-tuples of RDFLib terms.
  154. :param tuple lookup: tuple of a 3-tuple of lookup terms, and a context.
  155. E.g. ``((URIRef('urn:ns:a'), None, None), URIRef('urn:ns:ctx'))``.
  156. Any and all elements may be ``None``.
  157. :param lmdbStore store: the store to look data up.
  158. """
  159. self.store = store or env.app_globals.rdf_store
  160. self._terms = calg.set_new(term_hash_fn, buffer_cmp_fn)
  161. self._triples = calg.set_new(trp_hash_fn, triple_cmp_fn)
  162. self._pool = Pool()
  163. cdef:
  164. size_t i = 0
  165. TripleKey spok
  166. term.Buffer pk_t
  167. # Initialize empty data set.
  168. if keyset:
  169. # Populate with triples extracted from provided key set.
  170. self._data_from_keyset(keyset)
  171. elif data is not None:
  172. # Populate with provided Python set.
  173. for s, p, o in data:
  174. self._add_from_rdflib(s, p, o)
  175. def __dealloc__(self):
  176. """
  177. Free the triple pointers. TODO use a Cymem pool
  178. """
  179. free(self._triples)
  180. free(self._terms)
  181. @property
  182. def data(self):
  183. """
  184. Triple data as a Python set.
  185. :rtype: set
  186. """
  187. return self._data_as_set()
  188. cdef void _data_from_lookup(self, tuple trp_ptn, ctx=None) except *:
  189. """
  190. Look up triples in the triplestore and load them into ``data``.
  191. :param tuple lookup: 3-tuple of RDFlib terms or ``None``.
  192. :param LmdbTriplestore store: Reference to a LMDB triplestore. This
  193. is normally set to ``lakesuperior.env.app_globals.rdf_store``.
  194. """
  195. cdef:
  196. size_t i
  197. unsigned char spok[TRP_KLEN]
  198. with self.store.txn_ctx():
  199. keyset = self.store.triple_keys(trp_ptn, ctx)
  200. self.data_from_keyset(keyset)
  201. cdef void _data_from_keyset(self, Keyset data) except *:
  202. """Populate a graph from a Keyset."""
  203. cdef TripleKey spok
  204. while data.next(spok):
  205. self._add_from_spok(spok)
  206. cdef inline void _add_from_spok(self, const TripleKey spok) except *:
  207. """
  208. Add a triple from a TripleKey of term keys.
  209. """
  210. cdef:
  211. SPOBuffer s_spo
  212. BufferTriple trp
  213. s_spo = <SPOBuffer>self._pool.alloc(3, sizeof(Buffer))
  214. self.store.lookup_term(spok, s_spo)
  215. self.store.lookup_term(spok + KLEN, s_spo + 1)
  216. self.store.lookup_term(spok + DBL_KLEN, s_spo + 2)
  217. self._add_triple(s_spo, s_spo + 1, s_spo + 2)
  218. cdef inline void _add_triple(
  219. self, const Buffer *ss, const Buffer *sp, const Buffer *so
  220. ) except *:
  221. """
  222. Add a triple from 3 (TPL) serialized terms.
  223. Each of the terms is added to the term set if not existing. The triple
  224. also is only added if not existing.
  225. """
  226. cdef int r
  227. trp = <BufferTriple *>self._pool.alloc(1, sizeof(BufferTriple))
  228. print('Inserting terms.')
  229. calg.set_insert(self._terms, ss)
  230. print(f'Insert ss result:')
  231. r = calg.set_insert(self._terms, sp)
  232. print(f'Insert sp result: {r}')
  233. r = calg.set_insert(self._terms, so)
  234. print(f'Insert so result: {r}')
  235. cdef calg.SetIterator ti
  236. cdef Buffer *t
  237. calg.set_iterate(self._terms, &ti)
  238. while calg.set_iter_has_more(&ti):
  239. t = <Buffer *>calg.set_iter_next(&ti)
  240. trp.s = ss
  241. trp.p = sp
  242. trp.o = so
  243. r = calg.set_insert(self._triples, trp)
  244. print(f'Insert triple result: {r}')
  245. cdef BufferTriple *tt
  246. calg.set_iterate(self._triples, &ti)
  247. while calg.set_iter_has_more(&ti):
  248. tt = <BufferTriple *>calg.set_iter_next(&ti)
  249. cdef set _data_as_set(self):
  250. """
  251. Convert triple data to a Python set.
  252. :rtype: set
  253. """
  254. cdef:
  255. calg.SetIterator ti
  256. BufferTriple *trp
  257. term.Term s, p, o
  258. graph_set = set()
  259. calg.set_iterate(self._triples, &ti)
  260. while calg.set_iter_has_more(&ti):
  261. trp = <BufferTriple *>calg.set_iter_next(&ti)
  262. if trp == NULL:
  263. print('Triple is NULL!')
  264. return graph_set
  265. graph_set.add((
  266. term.deserialize_to_rdflib(trp.s),
  267. term.deserialize_to_rdflib(trp.p),
  268. term.deserialize_to_rdflib(trp.o),
  269. ))
  270. return graph_set
  271. # Basic set operations.
  272. def add(self, triple):
  273. """ Add one triple to the graph. """
  274. cdef:
  275. Buffer *ss = <Buffer *>self._pool.alloc(1, sizeof(Buffer))
  276. Buffer *sp = <Buffer *>self._pool.alloc(1, sizeof(Buffer))
  277. Buffer *so = <Buffer *>self._pool.alloc(1, sizeof(Buffer))
  278. s, p, o = triple
  279. term.serialize_from_rdflib(s, ss, self._pool)
  280. term.serialize_from_rdflib(p, sp, self._pool)
  281. term.serialize_from_rdflib(o, so, self._pool)
  282. self._add_triple(ss, sp, so)
  283. def remove(self, item):
  284. """
  285. Remove one item from the graph.
  286. :param tuple item: A 3-tuple of RDFlib terms. Only exact terms, i.e.
  287. wildcards are not accepted.
  288. """
  289. self.data.remove(item)
  290. def __len__(self):
  291. """ Number of triples in the graph. """
  292. return calg.set_num_entries(self._triples)
  293. @use_data
  294. def __eq__(self, other):
  295. """ Equality operator between ``SimpleGraph`` instances. """
  296. return self.data == other
  297. def __repr__(self):
  298. """
  299. String representation of the graph.
  300. It provides the number of triples in the graph and memory address of
  301. the instance.
  302. """
  303. return (f'<{self.__class__.__name__} @{hex(id(self))} '
  304. f'length={len(self.data)}>')
  305. def __str__(self):
  306. """ String dump of the graph triples. """
  307. return str(self.data)
  308. @use_data
  309. def __sub__(self, other):
  310. """ Set subtraction. """
  311. return self.data - other
  312. @use_data
  313. def __isub__(self, other):
  314. """ In-place set subtraction. """
  315. self.data -= other
  316. return self
  317. @use_data
  318. def __and__(self, other):
  319. """ Set intersection. """
  320. return self.data & other
  321. @use_data
  322. def __iand__(self, other):
  323. """ In-place set intersection. """
  324. self.data &= other
  325. return self
  326. @use_data
  327. def __or__(self, other):
  328. """ Set union. """
  329. return self.data | other
  330. @use_data
  331. def __ior__(self, other):
  332. """ In-place set union. """
  333. self.data |= other
  334. return self
  335. @use_data
  336. def __xor__(self, other):
  337. """ Set exclusive intersection (XOR). """
  338. return self.data ^ other
  339. @use_data
  340. def __ixor__(self, other):
  341. """ In-place set exclusive intersection (XOR). """
  342. self.data ^= other
  343. return self
  344. def __contains__(self, item):
  345. """
  346. Whether the graph contains a triple.
  347. :rtype: boolean
  348. """
  349. return item in self.data
  350. def __iter__(self):
  351. """ Graph iterator. It iterates over the set triples. """
  352. return self.data.__iter__()
  353. # Slicing.
  354. def __getitem__(self, item):
  355. """
  356. Slicing function.
  357. It behaves similarly to `RDFLib graph slicing
  358. <https://rdflib.readthedocs.io/en/stable/utilities.html#slicing-graphs>`__
  359. """
  360. if isinstance(item, slice):
  361. s, p, o = item.start, item.stop, item.step
  362. return self._slice(s, p, o)
  363. else:
  364. raise TypeError(f'Wrong slice format: {item}.')
  365. cpdef void set(self, tuple trp) except *:
  366. """
  367. Set a single value for subject and predicate.
  368. Remove all triples matching ``s`` and ``p`` before adding ``s p o``.
  369. """
  370. if None in trp:
  371. raise ValueError(f'Invalid triple: {trp}')
  372. self.remove_triples((trp[0], trp[1], None))
  373. self.add(trp)
  374. cpdef void remove_triples(self, pattern) except *:
  375. """
  376. Remove triples by pattern.
  377. The pattern used is similar to :py:meth:`LmdbTripleStore.delete`.
  378. """
  379. s, p, o = pattern
  380. for match in self.lookup(s, p, o):
  381. logger.debug(f'Removing from graph: {match}.')
  382. self.data.remove(match)
  383. cpdef object as_rdflib(self):
  384. """
  385. Return the data set as an RDFLib Graph.
  386. :rtype: rdflib.Graph
  387. """
  388. gr = Graph()
  389. for trp in self.data:
  390. gr.add(trp)
  391. return gr
  392. def _slice(self, s, p, o):
  393. """
  394. Return terms filtered by other terms.
  395. This behaves like the rdflib.Graph slicing policy.
  396. """
  397. _data = self.data
  398. logger.debug(f'Slicing graph by: {s}, {p}, {o}.')
  399. if s is None and p is None and o is None:
  400. return _data
  401. elif s is None and p is None:
  402. return {(r[0], r[1]) for r in _data if r[2] == o}
  403. elif s is None and o is None:
  404. return {(r[0], r[2]) for r in _data if r[1] == p}
  405. elif p is None and o is None:
  406. return {(r[1], r[2]) for r in _data if r[0] == s}
  407. elif s is None:
  408. return {r[0] for r in _data if r[1] == p and r[2] == o}
  409. elif p is None:
  410. return {r[1] for r in _data if r[0] == s and r[2] == o}
  411. elif o is None:
  412. return {r[2] for r in _data if r[0] == s and r[1] == p}
  413. else:
  414. # all given
  415. return (s,p,o) in _data
  416. def lookup(self, s, p, o):
  417. """
  418. Look up triples by a pattern.
  419. This function converts RDFLib terms into the serialized format stored
  420. in the graph's internal structure and compares them bytewise.
  421. Any and all of the lookup terms can be ``None``.
  422. """
  423. cdef:
  424. BufferTriple trp
  425. BufferTriple *trp_p
  426. calg.SetIterator ti
  427. const Buffer t1
  428. const Buffer t2
  429. lookup_fn_t fn
  430. res = set()
  431. # Decide comparison logic outside the loop.
  432. if s is not None and p is not None and o is not None:
  433. # Return immediately if 3-term match is requested.
  434. term.serialize_from_rdflib(s, trp.s)
  435. term.serialize_from_rdflib(p, trp.p)
  436. term.serialize_from_rdflib(o, trp.o)
  437. if calg.set_query(self._triples, &trp):
  438. res.add((s, p, o))
  439. return res
  440. elif s is not None:
  441. term.serialize_from_rdflib(s, &t1)
  442. if p is not None:
  443. fn = lookup_sp_cmp_fn
  444. term.serialize_from_rdflib(p, &t2)
  445. elif o is not None:
  446. fn = lookup_so_cmp_fn
  447. term.serialize_from_rdflib(o, &t2)
  448. else:
  449. fn = lookup_s_cmp_fn
  450. elif p is not None:
  451. term.serialize_from_rdflib(p, &t1)
  452. if o is not None:
  453. fn = lookup_po_cmp_fn
  454. term.serialize_from_rdflib(o, &t2)
  455. else:
  456. fn = lookup_p_cmp_fn
  457. elif o is not None:
  458. fn = lookup_o_cmp_fn
  459. term.serialize_from_rdflib(o, &t1)
  460. else:
  461. fn = lookup_none_cmp_fn
  462. # Iterate over serialized triples.
  463. calg.set_iterate(self._triples, &ti)
  464. while calg.set_iter_has_more(&ti):
  465. trp_p = <BufferTriple *>calg.set_iter_next(&ti)
  466. if fn(trp_p, &t1, &t2):
  467. res.add((
  468. term.deserialize_to_rdflib(trp_p[0].s),
  469. term.deserialize_to_rdflib(trp_p[0].p),
  470. term.deserialize_to_rdflib(trp_p[0].o),
  471. ))
  472. return res
  473. cpdef set terms(self, str type):
  474. """
  475. Get all terms of a type: subject, predicate or object.
  476. :param str type: One of ``s``, ``p`` or ``o``.
  477. """
  478. i = 'spo'.index(type)
  479. return {r[i] for r in self.data}
  480. cdef class Imr(SimpleGraph):
  481. """
  482. In-memory resource data container.
  483. This is an extension of :py:class:`~SimpleGraph` that adds a subject URI to
  484. the data set and some convenience methods.
  485. An instance of this class can be converted to a ``rdflib.Resource``
  486. instance.
  487. Some set operations that produce a new object (``-``, ``|``, ``&``, ``^``)
  488. will create a new ``Imr`` instance with the same subject URI.
  489. """
  490. def __init__(self, str uri, *args, **kwargs):
  491. """
  492. Initialize the graph with pre-existing data or by looking up a store.
  493. Either ``data``, or ``lookup`` *and* ``store``, can be provide.
  494. ``lookup`` and ``store`` have precedence. If none of them is specified,
  495. an empty graph is initialized.
  496. :param rdflib.URIRef uri: The graph URI.
  497. This will serve as the subject for some queries.
  498. :param set data: Initial data as a set of 3-tuples of RDFLib terms.
  499. :param tuple lookup: tuple of a 3-tuple of lookup terms, and a context.
  500. E.g. ``((URIRef('urn:ns:a'), None, None), URIRef('urn:ns:ctx'))``.
  501. Any and all elements may be ``None``.
  502. :param lmdbStore store: the store to look data up.
  503. """
  504. super().__init__(*args, **kwargs)
  505. self.uri = uri
  506. @property
  507. def identifier(self):
  508. """
  509. IMR URI. For compatibility with RDFLib Resource.
  510. :rtype: string
  511. """
  512. return self.uri
  513. @property
  514. def graph(self):
  515. """
  516. Return a SimpleGraph with the same data.
  517. :rtype: SimpleGraph
  518. """
  519. return SimpleGraph(self.data)
  520. def __repr__(self):
  521. """
  522. String representation of an Imr.
  523. This includes the subject URI, number of triples contained and the
  524. memory address of the instance.
  525. """
  526. return (f'<{self.__class__.__name__} @{hex(id(self))} uri={self.uri}, '
  527. f'length={len(self.data)}>')
  528. @use_data
  529. def __sub__(self, other):
  530. """
  531. Set difference. This creates a new Imr with the same subject URI.
  532. """
  533. return self.__class__(uri=self.uri, data=self.data - other)
  534. @use_data
  535. def __and__(self, other):
  536. """
  537. Set intersection. This creates a new Imr with the same subject URI.
  538. """
  539. return self.__class__(uri=self.uri, data=self.data & other)
  540. @use_data
  541. def __or__(self, other):
  542. """
  543. Set union. This creates a new Imr with the same subject URI.
  544. """
  545. return self.__class__(uri=self.uri, data=self.data | other)
  546. @use_data
  547. def __xor__(self, other):
  548. """
  549. Set exclusive OR (XOR). This creates a new Imr with the same subject
  550. URI.
  551. """
  552. return self.__class__(uri=self.uri, data=self.data ^ other)
  553. def __getitem__(self, item):
  554. """
  555. Supports slicing notation.
  556. """
  557. if isinstance(item, slice):
  558. s, p, o = item.start, item.stop, item.step
  559. return self._slice(s, p, o)
  560. elif isinstance(item, Node):
  561. # If a Node is given, return all values for that predicate.
  562. return {
  563. r[2] for r in self.data
  564. if r[0] == self.uri and r[1] == item}
  565. else:
  566. raise TypeError(f'Wrong slice format: {item}.')
  567. def value(self, p, strict=False):
  568. """
  569. Get an individual value.
  570. :param rdflib.termNode p: Predicate to search for.
  571. :param bool strict: If set to ``True`` the method raises an error if
  572. more than one value is found. If ``False`` (the default) only
  573. the first found result is returned.
  574. :rtype: rdflib.term.Node
  575. """
  576. values = self[p]
  577. if strict and len(values) > 1:
  578. raise RuntimeError('More than one value found for {}, {}.'.format(
  579. self.uri, p))
  580. for ret in values:
  581. return ret
  582. return None
  583. cpdef as_rdflib(self):
  584. """
  585. Return the IMR as a RDFLib Resource.
  586. :rtype: rdflib.Resource
  587. """
  588. gr = Graph()
  589. for trp in self.data:
  590. gr.add(trp)
  591. return gr.resource(identifier=self.uri)