1# -*- coding: utf-8 -*- 2from __future__ import absolute_import 3from __future__ import division 4from __future__ import print_function 5 6from six import text_type, PY3 7 8 9__doc__ = """ 10The :class:`~rdflib.resource.Resource` class wraps a 11:class:`~rdflib.graph.Graph` 12and a resource reference (i.e. a :class:`rdflib.term.URIRef` or 13:class:`rdflib.term.BNode`) to support a resource-oriented way of 14working with a graph. 15 16It contains methods directly corresponding to those methods of the Graph 17interface that relate to reading and writing data. The difference is that a 18Resource also binds a resource identifier, making it possible to work without 19tracking both the graph and a current subject. This makes for a "resource 20oriented" style, as compared to the triple orientation of the Graph API. 21 22Resulting generators are also wrapped so that any resource reference values 23(:class:`rdflib.term.URIRef`s and :class:`rdflib.term.BNode`s) are in turn 24wrapped as Resources. (Note that this behaviour differs from the corresponding 25methods in :class:`~rdflib.graph.Graph`, where no such conversion takes place.) 26 27 28Basic Usage Scenario 29-------------------- 30 31Start by importing things we need and define some namespaces:: 32 33 >>> from rdflib import * 34 >>> FOAF = Namespace("http://xmlns.com/foaf/0.1/") 35 >>> CV = Namespace("http://purl.org/captsolo/resume-rdf/0.2/cv#") 36 37Load some RDF data:: 38 39 >>> graph = Graph().parse(format='n3', data=''' 40 ... @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . 41 ... @prefix xsd: <http://www.w3.org/2001/XMLSchema#>. 42 ... @prefix foaf: <http://xmlns.com/foaf/0.1/> . 43 ... @prefix cv: <http://purl.org/captsolo/resume-rdf/0.2/cv#> . 44 ... 45 ... @base <http://example.org/> . 46 ... 47 ... </person/some1#self> a foaf:Person; 48 ... rdfs:comment "Just a Python & RDF hacker."@en; 49 ... foaf:depiction </images/person/some1.jpg>; 50 ... foaf:homepage <http://example.net/>; 51 ... foaf:name "Some Body" . 52 ... 53 ... </images/person/some1.jpg> a foaf:Image; 54 ... rdfs:label "some 1"@en; 55 ... rdfs:comment "Just an image"@en; 56 ... foaf:thumbnail </images/person/some1-thumb.jpg> . 57 ... 58 ... </images/person/some1-thumb.jpg> a foaf:Image . 59 ... 60 ... [] a cv:CV; 61 ... cv:aboutPerson </person/some1#self>; 62 ... cv:hasWorkHistory [ cv:employedIn </#company>; 63 ... cv:startDate "2009-09-04"^^xsd:date ] . 64 ... ''') 65 66Create a Resource:: 67 68 >>> person = Resource( 69 ... graph, URIRef("http://example.org/person/some1#self")) 70 71Retrieve some basic facts:: 72 73 >>> person.identifier 74 rdflib.term.URIRef(u'http://example.org/person/some1#self') 75 76 >>> person.value(FOAF.name) 77 rdflib.term.Literal(u'Some Body') 78 79 >>> person.value(RDFS.comment) 80 rdflib.term.Literal(u'Just a Python & RDF hacker.', lang=u'en') 81 82Resources can be sliced (like graphs, but the subject is fixed):: 83 84 >>> for name in person[FOAF.name]: 85 ... print(name) 86 Some Body 87 >>> person[FOAF.name : Literal("Some Body")] 88 True 89 90Resources as unicode are represented by their identifiers as unicode:: 91 92 >>> %(unicode)s(person) #doctest: +SKIP 93 u'Resource(http://example.org/person/some1#self' 94 95Resource references are also Resources, so you can easily get e.g. a qname 96for the type of a resource, like:: 97 98 >>> person.value(RDF.type).qname() 99 u'foaf:Person' 100 101Or for the predicates of a resource:: 102 103 >>> sorted( 104 ... p.qname() for p in person.predicates() 105 ... ) #doctest: +NORMALIZE_WHITESPACE +SKIP 106 [u'foaf:depiction', u'foaf:homepage', 107 u'foaf:name', u'rdf:type', u'rdfs:comment'] 108 109Follow relations and get more data from their Resources as well:: 110 111 >>> for pic in person.objects(FOAF.depiction): 112 ... print(pic.identifier) 113 ... print(pic.value(RDF.type).qname()) 114 ... print(pic.label()) 115 ... print(pic.comment()) 116 ... print(pic.value(FOAF.thumbnail).identifier) 117 http://example.org/images/person/some1.jpg 118 foaf:Image 119 some 1 120 Just an image 121 http://example.org/images/person/some1-thumb.jpg 122 123 >>> for cv in person.subjects(CV.aboutPerson): 124 ... work = list(cv.objects(CV.hasWorkHistory))[0] 125 ... print(work.value(CV.employedIn).identifier) 126 ... print(work.value(CV.startDate)) 127 http://example.org/#company 128 2009-09-04 129 130It's just as easy to work with the predicates of a resource:: 131 132 >>> for s, p in person.subject_predicates(): 133 ... print(s.value(RDF.type).qname()) 134 ... print(p.qname()) 135 ... for s, o in p.subject_objects(): 136 ... print(s.value(RDF.type).qname()) 137 ... print(o.value(RDF.type).qname()) 138 cv:CV 139 cv:aboutPerson 140 cv:CV 141 foaf:Person 142 143This is useful for e.g. inspection:: 144 145 >>> thumb_ref = URIRef("http://example.org/images/person/some1-thumb.jpg") 146 >>> thumb = Resource(graph, thumb_ref) 147 >>> for p, o in thumb.predicate_objects(): 148 ... print(p.qname()) 149 ... print(o.qname()) 150 rdf:type 151 foaf:Image 152 153Similarly, adding, setting and removing data is easy:: 154 155 >>> thumb.add(RDFS.label, Literal("thumb")) 156 >>> print(thumb.label()) 157 thumb 158 >>> thumb.set(RDFS.label, Literal("thumbnail")) 159 >>> print(thumb.label()) 160 thumbnail 161 >>> thumb.remove(RDFS.label) 162 >>> list(thumb.objects(RDFS.label)) 163 [] 164 165 166Schema Example 167-------------- 168 169With this artificial schema data:: 170 171 >>> graph = Graph().parse(format='n3', data=''' 172 ... @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . 173 ... @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . 174 ... @prefix owl: <http://www.w3.org/2002/07/owl#> . 175 ... @prefix v: <http://example.org/def/v#> . 176 ... 177 ... v:Artifact a owl:Class . 178 ... 179 ... v:Document a owl:Class; 180 ... rdfs:subClassOf v:Artifact . 181 ... 182 ... v:Paper a owl:Class; 183 ... rdfs:subClassOf v:Document . 184 ... 185 ... v:Choice owl:oneOf (v:One v:Other) . 186 ... 187 ... v:Stuff a rdf:Seq; rdf:_1 v:One; rdf:_2 v:Other . 188 ... 189 ... ''') 190 191From this class:: 192 193 >>> artifact = Resource(graph, URIRef("http://example.org/def/v#Artifact")) 194 195we can get at subclasses:: 196 197 >>> subclasses = list(artifact.transitive_subjects(RDFS.subClassOf)) 198 >>> [c.qname() for c in subclasses] 199 [u'v:Artifact', u'v:Document', u'v:Paper'] 200 201and superclasses from the last subclass:: 202 203 >>> [c.qname() for c in subclasses[-1].transitive_objects(RDFS.subClassOf)] 204 [u'v:Paper', u'v:Document', u'v:Artifact'] 205 206Get items from the Choice:: 207 208 >>> choice = Resource(graph, URIRef("http://example.org/def/v#Choice")) 209 >>> [it.qname() for it in choice.value(OWL.oneOf).items()] 210 [u'v:One', u'v:Other'] 211 212And the sequence of Stuff:: 213 214 >>> stuff = Resource(graph, URIRef("http://example.org/def/v#Stuff")) 215 >>> [it.qname() for it in stuff.seq()] 216 [u'v:One', u'v:Other'] 217 218On add, other resources are auto-unboxed: 219 >>> paper = Resource(graph, URIRef("http://example.org/def/v#Paper")) 220 >>> paper.add(RDFS.subClassOf, artifact) 221 >>> artifact in paper.objects(RDFS.subClassOf) # checks Resource instance 222 True 223 >>> (paper._identifier, RDFS.subClassOf, artifact._identifier) in graph 224 True 225 226 227Technical Details 228----------------- 229 230Comparison is based on graph and identifier:: 231 232 >>> g1 = Graph() 233 >>> t1 = Resource(g1, URIRef("http://example.org/thing")) 234 >>> t2 = Resource(g1, URIRef("http://example.org/thing")) 235 >>> t3 = Resource(g1, URIRef("http://example.org/other")) 236 >>> t4 = Resource(Graph(), URIRef("http://example.org/other")) 237 238 >>> t1 is t2 239 False 240 241 >>> t1 == t2 242 True 243 >>> t1 != t2 244 False 245 246 >>> t1 == t3 247 False 248 >>> t1 != t3 249 True 250 251 >>> t3 != t4 252 True 253 254 >>> t3 < t1 and t1 > t3 255 True 256 >>> t1 >= t1 and t1 >= t3 257 True 258 >>> t1 <= t1 and t3 <= t1 259 True 260 261 >>> t1 < t1 or t1 < t3 or t3 > t1 or t3 > t3 262 False 263 264Hash is computed from graph and identifier:: 265 266 >>> g1 = Graph() 267 >>> t1 = Resource(g1, URIRef("http://example.org/thing")) 268 269 >>> hash(t1) == hash(Resource(g1, URIRef("http://example.org/thing"))) 270 True 271 272 >>> hash(t1) == hash(Resource(Graph(), t1.identifier)) 273 False 274 >>> hash(t1) == hash(Resource(Graph(), URIRef("http://example.org/thing"))) 275 False 276 277The Resource class is suitable as a base class for mapper toolkits. For 278example, consider this utility for accessing RDF properties via qname-like 279attributes:: 280 281 >>> class Item(Resource): 282 ... 283 ... def __getattr__(self, p): 284 ... return list(self.objects(self._to_ref(*p.split('_', 1)))) 285 ... 286 ... def _to_ref(self, pfx, name): 287 ... return URIRef(self._graph.store.namespace(pfx) + name) 288 289It works as follows:: 290 291 >>> graph = Graph().parse(format='n3', data=''' 292 ... @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . 293 ... @prefix foaf: <http://xmlns.com/foaf/0.1/> . 294 ... 295 ... @base <http://example.org/> . 296 ... </person/some1#self> 297 ... foaf:name "Some Body"; 298 ... foaf:depiction </images/person/some1.jpg> . 299 ... </images/person/some1.jpg> rdfs:comment "Just an image"@en . 300 ... ''') 301 302 >>> person = Item(graph, URIRef("http://example.org/person/some1#self")) 303 304 >>> print(person.foaf_name[0]) 305 Some Body 306 307The mechanism for wrapping references as resources cooperates with subclasses. 308Therefore, accessing referenced resources automatically creates new ``Item`` 309objects:: 310 311 >>> isinstance(person.foaf_depiction[0], Item) 312 True 313 314 >>> print(person.foaf_depiction[0].rdfs_comment[0]) 315 Just an image 316 317""" 318 319from rdflib.term import Node, BNode, URIRef 320from rdflib.namespace import RDF 321from rdflib.paths import Path 322 323__all__ = ['Resource'] 324 325 326class Resource(object): 327 328 def __init__(self, graph, subject): 329 self._graph = graph 330 self._identifier = subject 331 332 graph = property(lambda self: self._graph) 333 334 identifier = property(lambda self: self._identifier) 335 336 def __hash__(self): 337 return hash(Resource) ^ hash(self._graph) ^ hash(self._identifier) 338 339 def __eq__(self, other): 340 return (isinstance(other, Resource) and 341 self._graph == other._graph and 342 self._identifier == other._identifier) 343 344 def __ne__(self, other): return not self == other 345 346 def __lt__(self, other): 347 if isinstance(other, Resource): 348 return self._identifier < other._identifier 349 else: 350 return False 351 352 def __gt__(self, other): return not (self < other or self == other) 353 354 def __le__(self, other): return self < other or self == other 355 356 def __ge__(self, other): return not self < other 357 358 def __unicode__(self): 359 return text_type(self._identifier) 360 361 if PY3: 362 __str__ = __unicode__ 363 364 def add(self, p, o): 365 if isinstance(o, Resource): 366 o = o._identifier 367 368 self._graph.add((self._identifier, p, o)) 369 370 def remove(self, p, o=None): 371 if isinstance(o, Resource): 372 o = o._identifier 373 374 self._graph.remove((self._identifier, p, o)) 375 376 def set(self, p, o): 377 if isinstance(o, Resource): 378 o = o._identifier 379 380 self._graph.set((self._identifier, p, o)) 381 382 def subjects(self, predicate=None): # rev 383 return self._resources( 384 self._graph.subjects(predicate, self._identifier)) 385 386 def predicates(self, o=None): 387 if isinstance(o, Resource): 388 o = o._identifier 389 390 return self._resources( 391 self._graph.predicates(self._identifier, o)) 392 393 def objects(self, predicate=None): 394 return self._resources( 395 self._graph.objects(self._identifier, predicate)) 396 397 def subject_predicates(self): 398 return self._resource_pairs( 399 self._graph.subject_predicates(self._identifier)) 400 401 def subject_objects(self): 402 return self._resource_pairs( 403 self._graph.subject_objects(self._identifier)) 404 405 def predicate_objects(self): 406 return self._resource_pairs( 407 self._graph.predicate_objects(self._identifier)) 408 409 def value(self, p=RDF.value, o=None, default=None, any=True): 410 if isinstance(o, Resource): 411 o = o._identifier 412 413 return self._cast( 414 self._graph.value(self._identifier, p, o, default, any)) 415 416 def label(self): 417 return self._graph.label(self._identifier) 418 419 def comment(self): 420 return self._graph.comment(self._identifier) 421 422 def items(self): 423 return self._resources(self._graph.items(self._identifier)) 424 425 def transitive_objects(self, predicate, remember=None): 426 return self._resources(self._graph.transitive_objects( 427 self._identifier, predicate, remember)) 428 429 def transitive_subjects(self, predicate, remember=None): 430 return self._resources(self._graph.transitive_subjects( 431 predicate, self._identifier, remember)) 432 433 def seq(self): 434 return self._resources(self._graph.seq(self._identifier)) 435 436 def qname(self): 437 return self._graph.qname(self._identifier) 438 439 def _resource_pairs(self, pairs): 440 for s1, s2 in pairs: 441 yield self._cast(s1), self._cast(s2) 442 443 def _resource_triples(self, triples): 444 for s, p, o in triples: 445 yield self._cast(s), self._cast(p), self._cast(o) 446 447 def _resources(self, nodes): 448 for node in nodes: 449 yield self._cast(node) 450 451 def _cast(self, node): 452 if isinstance(node, (BNode, URIRef)): 453 return self._new(node) 454 else: 455 return node 456 457 def __iter__(self): 458 return self._resource_triples(self._graph.triples((self.identifier, None, None))) 459 460 def __getitem__(self, item): 461 if isinstance(item, slice): 462 if item.step: 463 raise TypeError("Resources fix the subject for slicing, and can only be sliced by predicate/object. ") 464 p, o = item.start, item.stop 465 if isinstance(p, Resource): 466 p = p._identifier 467 if isinstance(o, Resource): 468 o = o._identifier 469 if p is None and o is None: 470 return self.predicate_objects() 471 elif p is None: 472 return self.predicates(o) 473 elif o is None: 474 return self.objects(p) 475 else: 476 return (self.identifier, p, o) in self._graph 477 elif isinstance(item, (Node, Path)): 478 return self.objects(item) 479 else: 480 raise TypeError("You can only index a resource by a single rdflib term, a slice of rdflib terms, not %s (%s)"%(item, type(item))) 481 482 def __setitem__(self, item, value): 483 self.set(item, value) 484 485 def _new(self, subject): 486 return type(self)(self._graph, subject) 487 488 def __str__(self): 489 return 'Resource(%s)' % self._identifier 490 491 def __repr__(self): 492 return 'Resource(%s,%s)' % (self._graph, self._identifier) 493