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