describer.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. """
  2. A Describer is a stateful utility for creating RDF statements in a
  3. semi-declarative manner. It has methods for creating literal values, rel and
  4. rev resource relations (somewhat resembling RDFa).
  5. The `Describer.rel` and `Describer.rev` methods return a context manager which sets the current
  6. about to the referenced resource for the context scope (for use with the
  7. `with` statement).
  8. Full example in the `to_rdf` method below:
  9. ```python
  10. >>> import datetime
  11. >>> from rdflib.graph import Graph
  12. >>> from rdflib.namespace import Namespace, RDFS, FOAF
  13. >>> ORG_URI = "http://example.org/"
  14. >>> CV = Namespace("http://purl.org/captsolo/resume-rdf/0.2/cv#")
  15. >>> class Person:
  16. ... def __init__(self):
  17. ... self.first_name = "Some"
  18. ... self.last_name = "Body"
  19. ... self.username = "some1"
  20. ... self.presentation = "Just a Python & RDF hacker."
  21. ... self.image = "/images/persons/" + self.username + ".jpg"
  22. ... self.site = "http://example.net/"
  23. ... self.start_date = datetime.date(2009, 9, 4)
  24. ... def get_full_name(self):
  25. ... return " ".join([self.first_name, self.last_name])
  26. ... def get_absolute_url(self):
  27. ... return "/persons/" + self.username
  28. ... def get_thumbnail_url(self):
  29. ... return self.image.replace('.jpg', '-thumb.jpg')
  30. ...
  31. ... def to_rdf(self):
  32. ... graph = Graph()
  33. ... graph.bind('foaf', FOAF)
  34. ... graph.bind('cv', CV)
  35. ... lang = 'en'
  36. ... d = Describer(graph, base=ORG_URI)
  37. ... d.about(self.get_absolute_url()+'#person')
  38. ... d.rdftype(FOAF.Person)
  39. ... d.value(FOAF.name, self.get_full_name())
  40. ... d.value(FOAF.givenName, self.first_name)
  41. ... d.value(FOAF.familyName, self.last_name)
  42. ... d.rel(FOAF.homepage, self.site)
  43. ... d.value(RDFS.comment, self.presentation, lang=lang)
  44. ... with d.rel(FOAF.depiction, self.image):
  45. ... d.rdftype(FOAF.Image)
  46. ... d.rel(FOAF.thumbnail, self.get_thumbnail_url())
  47. ... with d.rev(CV.aboutPerson):
  48. ... d.rdftype(CV.CV)
  49. ... with d.rel(CV.hasWorkHistory):
  50. ... d.value(CV.startDate, self.start_date)
  51. ... d.rel(CV.employedIn, ORG_URI+"#company")
  52. ... return graph
  53. ...
  54. >>> person_graph = Person().to_rdf()
  55. >>> expected = Graph().parse(data='''<?xml version="1.0" encoding="utf-8"?>
  56. ... <rdf:RDF
  57. ... xmlns:foaf="http://xmlns.com/foaf/0.1/"
  58. ... xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
  59. ... xmlns:cv="http://purl.org/captsolo/resume-rdf/0.2/cv#"
  60. ... xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">
  61. ... <foaf:Person rdf:about="http://example.org/persons/some1#person">
  62. ... <foaf:name>Some Body</foaf:name>
  63. ... <foaf:givenName>Some</foaf:givenName>
  64. ... <foaf:familyName>Body</foaf:familyName>
  65. ... <foaf:depiction>
  66. ... <foaf:Image
  67. ... rdf:about=
  68. ... "http://example.org/images/persons/some1.jpg">
  69. ... <foaf:thumbnail
  70. ... rdf:resource=
  71. ... "http://example.org/images/persons/some1-thumb.jpg"/>
  72. ... </foaf:Image>
  73. ... </foaf:depiction>
  74. ... <rdfs:comment xml:lang="en">
  75. ... Just a Python &amp; RDF hacker.
  76. ... </rdfs:comment>
  77. ... <foaf:homepage rdf:resource="http://example.net/"/>
  78. ... </foaf:Person>
  79. ... <cv:CV>
  80. ... <cv:aboutPerson
  81. ... rdf:resource="http://example.org/persons/some1#person">
  82. ... </cv:aboutPerson>
  83. ... <cv:hasWorkHistory>
  84. ... <rdf:Description>
  85. ... <cv:startDate
  86. ... rdf:datatype="http://www.w3.org/2001/XMLSchema#date"
  87. ... >2009-09-04</cv:startDate>
  88. ... <cv:employedIn rdf:resource="http://example.org/#company"/>
  89. ... </rdf:Description>
  90. ... </cv:hasWorkHistory>
  91. ... </cv:CV>
  92. ... </rdf:RDF>
  93. ... ''', format="xml")
  94. >>> from rdflib.compare import isomorphic
  95. >>> isomorphic(person_graph, expected) #doctest: +SKIP
  96. True
  97. ```
  98. """
  99. from contextlib import contextmanager
  100. from rdflib.graph import Graph
  101. from rdflib.namespace import RDF
  102. from rdflib.term import BNode, Identifier, Literal, URIRef
  103. class Describer:
  104. def __init__(self, graph=None, about=None, base=None):
  105. if graph is None:
  106. graph = Graph()
  107. self.graph = graph
  108. self.base = base
  109. self._subjects = []
  110. self.about(about or None)
  111. def about(self, subject, **kws):
  112. """
  113. Sets the current subject. Will convert the given object into an
  114. `URIRef` if it's not an `Identifier`.
  115. Example:
  116. ```python
  117. >>> d = Describer()
  118. >>> d._current() #doctest: +ELLIPSIS
  119. rdflib.term.BNode(...)
  120. >>> d.about("http://example.org/")
  121. >>> d._current()
  122. rdflib.term.URIRef('http://example.org/')
  123. ```
  124. """
  125. kws.setdefault("base", self.base)
  126. subject = cast_identifier(subject, **kws)
  127. if self._subjects:
  128. self._subjects[-1] = subject
  129. else:
  130. self._subjects.append(subject)
  131. def value(self, p, v, **kws):
  132. """
  133. Set a literal value for the given property. Will cast the value to an
  134. `Literal` if a plain literal is given.
  135. Example:
  136. ```python
  137. >>> from rdflib import URIRef
  138. >>> from rdflib.namespace import RDF, RDFS
  139. >>> d = Describer(about="http://example.org/")
  140. >>> d.value(RDFS.label, "Example")
  141. >>> d.graph.value(URIRef('http://example.org/'), RDFS.label)
  142. rdflib.term.Literal('Example')
  143. ```
  144. """
  145. v = cast_value(v, **kws)
  146. self.graph.add((self._current(), p, v))
  147. def rel(self, p, o=None, **kws):
  148. """Set an object for the given property. Will convert the given object
  149. into an `URIRef` if it's not an `Identifier`. If none is given, a
  150. new `BNode` is used.
  151. Returns a context manager for use in a `with` block, within which the
  152. given object is used as current subject.
  153. Example:
  154. ```python
  155. >>> from rdflib import URIRef
  156. >>> from rdflib.namespace import RDF, RDFS
  157. >>> d = Describer(about="/", base="http://example.org/")
  158. >>> _ctxt = d.rel(RDFS.seeAlso, "/about")
  159. >>> d.graph.value(URIRef('http://example.org/'), RDFS.seeAlso)
  160. rdflib.term.URIRef('http://example.org/about')
  161. >>> with d.rel(RDFS.seeAlso, "/more"):
  162. ... d.value(RDFS.label, "More")
  163. >>> (URIRef('http://example.org/'), RDFS.seeAlso,
  164. ... URIRef('http://example.org/more')) in d.graph
  165. True
  166. >>> d.graph.value(URIRef('http://example.org/more'), RDFS.label)
  167. rdflib.term.Literal('More')
  168. ```
  169. """
  170. kws.setdefault("base", self.base)
  171. p = cast_identifier(p)
  172. o = cast_identifier(o, **kws)
  173. self.graph.add((self._current(), p, o))
  174. return self._subject_stack(o)
  175. def rev(self, p, s=None, **kws):
  176. """
  177. Same as `rel`, but uses current subject as *object* of the relation.
  178. The given resource is still used as subject in the returned context
  179. manager.
  180. Example:
  181. ```python
  182. >>> from rdflib import URIRef
  183. >>> from rdflib.namespace import RDF, RDFS
  184. >>> d = Describer(about="http://example.org/")
  185. >>> with d.rev(RDFS.seeAlso, "http://example.net/"):
  186. ... d.value(RDFS.label, "Net")
  187. >>> (URIRef('http://example.net/'), RDFS.seeAlso,
  188. ... URIRef('http://example.org/')) in d.graph
  189. True
  190. >>> d.graph.value(URIRef('http://example.net/'), RDFS.label)
  191. rdflib.term.Literal('Net')
  192. ```
  193. """
  194. kws.setdefault("base", self.base)
  195. p = cast_identifier(p)
  196. s = cast_identifier(s, **kws)
  197. self.graph.add((s, p, self._current()))
  198. return self._subject_stack(s)
  199. def rdftype(self, t):
  200. """Shorthand for setting rdf:type of the current subject.
  201. Example:
  202. ```python
  203. >>> from rdflib import URIRef
  204. >>> from rdflib.namespace import RDF, RDFS
  205. >>> d = Describer(about="http://example.org/")
  206. >>> d.rdftype(RDFS.Resource)
  207. >>> (URIRef('http://example.org/'),
  208. ... RDF.type, RDFS.Resource) in d.graph
  209. True
  210. ```
  211. """
  212. self.graph.add((self._current(), RDF.type, t))
  213. def _current(self):
  214. return self._subjects[-1]
  215. @contextmanager
  216. def _subject_stack(self, subject):
  217. self._subjects.append(subject)
  218. yield None
  219. self._subjects.pop()
  220. def cast_value(v, **kws):
  221. if not isinstance(v, Literal):
  222. v = Literal(v, **kws)
  223. return v
  224. def cast_identifier(ref, **kws):
  225. ref = ref or BNode()
  226. if not isinstance(ref, Identifier):
  227. ref = URIRef(ref, **kws)
  228. return ref