collection.py 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. from __future__ import annotations
  2. from typing import TYPE_CHECKING, Iterable, Iterator, List, Optional
  3. from rdflib.namespace import RDF
  4. from rdflib.term import BNode, Node
  5. if TYPE_CHECKING:
  6. from rdflib.graph import Graph
  7. __all__ = ["Collection"]
  8. class Collection:
  9. """See "Emulating container types": <https://docs.python.org/reference/datamodel.html#emulating-container-types>
  10. ```python
  11. >>> from rdflib.term import Literal
  12. >>> from rdflib.graph import Graph
  13. >>> from pprint import pprint
  14. >>> listname = BNode()
  15. >>> g = Graph('Memory')
  16. >>> listItem1 = BNode()
  17. >>> listItem2 = BNode()
  18. >>> g.add((listname, RDF.first, Literal(1))) # doctest: +ELLIPSIS
  19. <Graph identifier=... (<class 'rdflib.graph.Graph'>)>
  20. >>> g.add((listname, RDF.rest, listItem1)) # doctest: +ELLIPSIS
  21. <Graph identifier=... (<class 'rdflib.graph.Graph'>)>
  22. >>> g.add((listItem1, RDF.first, Literal(2))) # doctest: +ELLIPSIS
  23. <Graph identifier=... (<class 'rdflib.graph.Graph'>)>
  24. >>> g.add((listItem1, RDF.rest, listItem2)) # doctest: +ELLIPSIS
  25. <Graph identifier=... (<class 'rdflib.graph.Graph'>)>
  26. >>> g.add((listItem2, RDF.rest, RDF.nil)) # doctest: +ELLIPSIS
  27. <Graph identifier=... (<class 'rdflib.graph.Graph'>)>
  28. >>> g.add((listItem2, RDF.first, Literal(3))) # doctest: +ELLIPSIS
  29. <Graph identifier=... (<class 'rdflib.graph.Graph'>)>
  30. >>> c = Collection(g,listname)
  31. >>> pprint([term.n3() for term in c])
  32. ['"1"^^<http://www.w3.org/2001/XMLSchema#integer>',
  33. '"2"^^<http://www.w3.org/2001/XMLSchema#integer>',
  34. '"3"^^<http://www.w3.org/2001/XMLSchema#integer>']
  35. >>> Literal(1) in c
  36. True
  37. >>> len(c)
  38. 3
  39. >>> c._get_container(1) == listItem1
  40. True
  41. >>> c.index(Literal(2)) == 1
  42. True
  43. ```
  44. The collection is immutable if `uri` is the empty list (`http://www.w3.org/1999/02/22-rdf-syntax-ns#nil`).
  45. """
  46. def __init__(self, graph: Graph, uri: Node, seq: List[Node] = []):
  47. self.graph = graph
  48. self.uri = uri or BNode()
  49. if seq:
  50. self += seq
  51. def n3(self) -> str:
  52. """
  53. ```python
  54. >>> from rdflib.term import Literal
  55. >>> from rdflib.graph import Graph
  56. >>> listname = BNode()
  57. >>> g = Graph('Memory')
  58. >>> listItem1 = BNode()
  59. >>> listItem2 = BNode()
  60. >>> g.add((listname, RDF.first, Literal(1))) # doctest: +ELLIPSIS
  61. <Graph identifier=... (<class 'rdflib.graph.Graph'>)>
  62. >>> g.add((listname, RDF.rest, listItem1)) # doctest: +ELLIPSIS
  63. <Graph identifier=... (<class 'rdflib.graph.Graph'>)>
  64. >>> g.add((listItem1, RDF.first, Literal(2))) # doctest: +ELLIPSIS
  65. <Graph identifier=... (<class 'rdflib.graph.Graph'>)>
  66. >>> g.add((listItem1, RDF.rest, listItem2)) # doctest: +ELLIPSIS
  67. <Graph identifier=... (<class 'rdflib.graph.Graph'>)>
  68. >>> g.add((listItem2, RDF.rest, RDF.nil)) # doctest: +ELLIPSIS
  69. <Graph identifier=... (<class 'rdflib.graph.Graph'>)>
  70. >>> g.add((listItem2, RDF.first, Literal(3))) # doctest: +ELLIPSIS
  71. <Graph identifier=... (<class 'rdflib.graph.Graph'>)>
  72. >>> c = Collection(g, listname)
  73. >>> print(c.n3()) #doctest: +NORMALIZE_WHITESPACE
  74. ( "1"^^<http://www.w3.org/2001/XMLSchema#integer>
  75. "2"^^<http://www.w3.org/2001/XMLSchema#integer>
  76. "3"^^<http://www.w3.org/2001/XMLSchema#integer> )
  77. ```
  78. """
  79. return "( %s )" % (" ".join([i.n3() for i in self]))
  80. def _get_container(self, index: int) -> Optional[Node]:
  81. """Gets the first, rest holding node at index."""
  82. assert isinstance(index, int)
  83. graph = self.graph
  84. container: Optional[Node] = self.uri
  85. i = 0
  86. while i < index:
  87. i += 1
  88. container = graph.value(container, RDF.rest)
  89. if container is None:
  90. break
  91. return container
  92. def __len__(self) -> int:
  93. """length of items in collection."""
  94. return len(list(self.graph.items(self.uri)))
  95. def index(self, item: Node) -> int:
  96. """
  97. Returns the 0-based numerical index of the item in the list
  98. """
  99. listname = self.uri
  100. index = 0
  101. while True:
  102. if (listname, RDF.first, item) in self.graph:
  103. return index
  104. else:
  105. newlink = list(self.graph.objects(listname, RDF.rest))
  106. index += 1
  107. if newlink == [RDF.nil]:
  108. raise ValueError("%s is not in %s" % (item, self.uri))
  109. elif not newlink:
  110. raise Exception("Malformed RDF Collection: %s" % self.uri)
  111. else:
  112. assert len(newlink) == 1, "Malformed RDF Collection: %s" % self.uri
  113. listname = newlink[0]
  114. def __getitem__(self, key: int) -> Node:
  115. """TODO"""
  116. c = self._get_container(key)
  117. if c:
  118. v = self.graph.value(c, RDF.first)
  119. if v:
  120. return v
  121. else:
  122. raise KeyError(key)
  123. else:
  124. raise IndexError(key)
  125. def __setitem__(self, key: int, value: Node) -> None:
  126. """TODO"""
  127. c = self._get_container(key)
  128. if c:
  129. self.graph.set((c, RDF.first, value))
  130. else:
  131. raise IndexError(key)
  132. def __delitem__(self, key: int) -> None:
  133. """
  134. ```python
  135. >>> from rdflib.namespace import RDF, RDFS
  136. >>> from rdflib import Graph
  137. >>> from pprint import pformat
  138. >>> g = Graph()
  139. >>> a = BNode('foo')
  140. >>> b = BNode('bar')
  141. >>> c = BNode('baz')
  142. >>> g.add((a, RDF.first, RDF.type)) # doctest: +ELLIPSIS
  143. <Graph identifier=... (<class 'rdflib.graph.Graph'>)>
  144. >>> g.add((a, RDF.rest, b)) # doctest: +ELLIPSIS
  145. <Graph identifier=... (<class 'rdflib.graph.Graph'>)>
  146. >>> g.add((b, RDF.first, RDFS.label)) # doctest: +ELLIPSIS
  147. <Graph identifier=... (<class 'rdflib.graph.Graph'>)>
  148. >>> g.add((b, RDF.rest, c)) # doctest: +ELLIPSIS
  149. <Graph identifier=... (<class 'rdflib.graph.Graph'>)>
  150. >>> g.add((c, RDF.first, RDFS.comment)) # doctest: +ELLIPSIS
  151. <Graph identifier=... (<class 'rdflib.graph.Graph'>)>
  152. >>> g.add((c, RDF.rest, RDF.nil)) # doctest: +ELLIPSIS
  153. <Graph identifier=... (<class 'rdflib.graph.Graph'>)>
  154. >>> len(g)
  155. 6
  156. >>> def listAncestry(node, graph):
  157. ... for i in graph.subjects(RDF.rest, node):
  158. ... yield i
  159. >>> [str(node.n3())
  160. ... for node in g.transitiveClosure(listAncestry, RDF.nil)]
  161. ['_:baz', '_:bar', '_:foo']
  162. >>> lst = Collection(g, a)
  163. >>> len(lst)
  164. 3
  165. >>> b == lst._get_container(1)
  166. True
  167. >>> c == lst._get_container(2)
  168. True
  169. >>> del lst[1]
  170. >>> len(lst)
  171. 2
  172. >>> len(g)
  173. 4
  174. ```
  175. """
  176. self[key] # to raise any potential key exceptions
  177. graph = self.graph
  178. current = self._get_container(key)
  179. assert current
  180. if len(self) == 1 and key > 0:
  181. pass
  182. elif key == len(self) - 1:
  183. # the tail
  184. priorlink = self._get_container(key - 1)
  185. # type error: Argument 1 to "set" of "Graph" has incompatible type "Tuple[Optional[Node], URIRef, URIRef]"; expected "Tuple[Node, Node, Any]"
  186. self.graph.set((priorlink, RDF.rest, RDF.nil)) # type: ignore[arg-type]
  187. graph.remove((current, None, None))
  188. else:
  189. next = self._get_container(key + 1)
  190. prior = self._get_container(key - 1)
  191. assert next and prior
  192. graph.remove((current, None, None))
  193. graph.set((prior, RDF.rest, next))
  194. def __iter__(self) -> Iterator[Node]:
  195. """Iterator over items in Collections"""
  196. return self.graph.items(self.uri)
  197. def _end(self) -> Node:
  198. # find end of list
  199. container = self.uri
  200. while True:
  201. rest = self.graph.value(container, RDF.rest)
  202. if rest is None or rest == RDF.nil:
  203. return container
  204. else:
  205. container = rest
  206. def append(self, item: Node) -> Collection:
  207. """
  208. ```python
  209. >>> from rdflib.term import Literal
  210. >>> from rdflib.graph import Graph
  211. >>> listname = BNode()
  212. >>> g = Graph()
  213. >>> c = Collection(g,listname,[Literal(1),Literal(2)])
  214. >>> links = [
  215. ... list(g.subjects(object=i, predicate=RDF.first))[0] for i in c]
  216. >>> len([i for i in links if (i, RDF.rest, RDF.nil) in g])
  217. 1
  218. ```
  219. """
  220. end = self._end()
  221. if end == RDF.nil:
  222. raise ValueError("Cannot append to empty list")
  223. if (end, RDF.first, None) in self.graph:
  224. # append new node to the end of the linked list
  225. node = BNode()
  226. self.graph.set((end, RDF.rest, node))
  227. end = node
  228. self.graph.add((end, RDF.first, item))
  229. self.graph.add((end, RDF.rest, RDF.nil))
  230. return self
  231. def __iadd__(self, other: Iterable[Node]):
  232. end = self._end()
  233. if end == RDF.nil:
  234. raise ValueError("Cannot append to empty list")
  235. self.graph.remove((end, RDF.rest, None))
  236. for item in other:
  237. if (end, RDF.first, None) in self.graph:
  238. nxt = BNode()
  239. self.graph.add((end, RDF.rest, nxt))
  240. end = nxt
  241. self.graph.add((end, RDF.first, item))
  242. self.graph.add((end, RDF.rest, RDF.nil))
  243. return self
  244. def clear(self):
  245. container: Optional[Node] = self.uri
  246. graph = self.graph
  247. while container:
  248. rest = graph.value(container, RDF.rest)
  249. graph.remove((container, RDF.first, None))
  250. graph.remove((container, RDF.rest, None))
  251. container = rest
  252. return self