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