# -*- coding: utf-8 -*- from __future__ import absolute_import from __future__ import division from __future__ import print_function from six import text_type, PY3 __doc__ = """ The :class:`~rdflib.resource.Resource` class wraps a :class:`~rdflib.graph.Graph` and a resource reference (i.e. a :class:`rdflib.term.URIRef` or :class:`rdflib.term.BNode`) to support a resource-oriented way of working with a graph. It contains methods directly corresponding to those methods of the Graph interface that relate to reading and writing data. The difference is that a Resource also binds a resource identifier, making it possible to work without tracking both the graph and a current subject. This makes for a "resource oriented" style, as compared to the triple orientation of the Graph API. Resulting generators are also wrapped so that any resource reference values (:class:`rdflib.term.URIRef`s and :class:`rdflib.term.BNode`s) are in turn wrapped as Resources. (Note that this behaviour differs from the corresponding methods in :class:`~rdflib.graph.Graph`, where no such conversion takes place.) Basic Usage Scenario -------------------- Start by importing things we need and define some namespaces:: >>> from rdflib import * >>> FOAF = Namespace("http://xmlns.com/foaf/0.1/") >>> CV = Namespace("http://purl.org/captsolo/resume-rdf/0.2/cv#") Load some RDF data:: >>> graph = Graph().parse(format='n3', data=''' ... @prefix rdfs: . ... @prefix xsd: . ... @prefix foaf: . ... @prefix cv: . ... ... @base . ... ... a foaf:Person; ... rdfs:comment "Just a Python & RDF hacker."@en; ... foaf:depiction ; ... foaf:homepage ; ... foaf:name "Some Body" . ... ... a foaf:Image; ... rdfs:label "some 1"@en; ... rdfs:comment "Just an image"@en; ... foaf:thumbnail . ... ... a foaf:Image . ... ... [] a cv:CV; ... cv:aboutPerson ; ... cv:hasWorkHistory [ cv:employedIn ; ... cv:startDate "2009-09-04"^^xsd:date ] . ... ''') Create a Resource:: >>> person = Resource( ... graph, URIRef("http://example.org/person/some1#self")) Retrieve some basic facts:: >>> person.identifier rdflib.term.URIRef(u'http://example.org/person/some1#self') >>> person.value(FOAF.name) rdflib.term.Literal(u'Some Body') >>> person.value(RDFS.comment) rdflib.term.Literal(u'Just a Python & RDF hacker.', lang=u'en') Resources can be sliced (like graphs, but the subject is fixed):: >>> for name in person[FOAF.name]: ... print(name) Some Body >>> person[FOAF.name : Literal("Some Body")] True Resources as unicode are represented by their identifiers as unicode:: >>> %(unicode)s(person) #doctest: +SKIP u'Resource(http://example.org/person/some1#self' Resource references are also Resources, so you can easily get e.g. a qname for the type of a resource, like:: >>> person.value(RDF.type).qname() u'foaf:Person' Or for the predicates of a resource:: >>> sorted( ... p.qname() for p in person.predicates() ... ) #doctest: +NORMALIZE_WHITESPACE +SKIP [u'foaf:depiction', u'foaf:homepage', u'foaf:name', u'rdf:type', u'rdfs:comment'] Follow relations and get more data from their Resources as well:: >>> for pic in person.objects(FOAF.depiction): ... print(pic.identifier) ... print(pic.value(RDF.type).qname()) ... print(pic.label()) ... print(pic.comment()) ... print(pic.value(FOAF.thumbnail).identifier) http://example.org/images/person/some1.jpg foaf:Image some 1 Just an image http://example.org/images/person/some1-thumb.jpg >>> for cv in person.subjects(CV.aboutPerson): ... work = list(cv.objects(CV.hasWorkHistory))[0] ... print(work.value(CV.employedIn).identifier) ... print(work.value(CV.startDate)) http://example.org/#company 2009-09-04 It's just as easy to work with the predicates of a resource:: >>> for s, p in person.subject_predicates(): ... print(s.value(RDF.type).qname()) ... print(p.qname()) ... for s, o in p.subject_objects(): ... print(s.value(RDF.type).qname()) ... print(o.value(RDF.type).qname()) cv:CV cv:aboutPerson cv:CV foaf:Person This is useful for e.g. inspection:: >>> thumb_ref = URIRef("http://example.org/images/person/some1-thumb.jpg") >>> thumb = Resource(graph, thumb_ref) >>> for p, o in thumb.predicate_objects(): ... print(p.qname()) ... print(o.qname()) rdf:type foaf:Image Similarly, adding, setting and removing data is easy:: >>> thumb.add(RDFS.label, Literal("thumb")) >>> print(thumb.label()) thumb >>> thumb.set(RDFS.label, Literal("thumbnail")) >>> print(thumb.label()) thumbnail >>> thumb.remove(RDFS.label) >>> list(thumb.objects(RDFS.label)) [] Schema Example -------------- With this artificial schema data:: >>> graph = Graph().parse(format='n3', data=''' ... @prefix rdf: . ... @prefix rdfs: . ... @prefix owl: . ... @prefix v: . ... ... v:Artifact a owl:Class . ... ... v:Document a owl:Class; ... rdfs:subClassOf v:Artifact . ... ... v:Paper a owl:Class; ... rdfs:subClassOf v:Document . ... ... v:Choice owl:oneOf (v:One v:Other) . ... ... v:Stuff a rdf:Seq; rdf:_1 v:One; rdf:_2 v:Other . ... ... ''') From this class:: >>> artifact = Resource(graph, URIRef("http://example.org/def/v#Artifact")) we can get at subclasses:: >>> subclasses = list(artifact.transitive_subjects(RDFS.subClassOf)) >>> [c.qname() for c in subclasses] [u'v:Artifact', u'v:Document', u'v:Paper'] and superclasses from the last subclass:: >>> [c.qname() for c in subclasses[-1].transitive_objects(RDFS.subClassOf)] [u'v:Paper', u'v:Document', u'v:Artifact'] Get items from the Choice:: >>> choice = Resource(graph, URIRef("http://example.org/def/v#Choice")) >>> [it.qname() for it in choice.value(OWL.oneOf).items()] [u'v:One', u'v:Other'] And the sequence of Stuff:: >>> stuff = Resource(graph, URIRef("http://example.org/def/v#Stuff")) >>> [it.qname() for it in stuff.seq()] [u'v:One', u'v:Other'] On add, other resources are auto-unboxed: >>> paper = Resource(graph, URIRef("http://example.org/def/v#Paper")) >>> paper.add(RDFS.subClassOf, artifact) >>> artifact in paper.objects(RDFS.subClassOf) # checks Resource instance True >>> (paper._identifier, RDFS.subClassOf, artifact._identifier) in graph True Technical Details ----------------- Comparison is based on graph and identifier:: >>> g1 = Graph() >>> t1 = Resource(g1, URIRef("http://example.org/thing")) >>> t2 = Resource(g1, URIRef("http://example.org/thing")) >>> t3 = Resource(g1, URIRef("http://example.org/other")) >>> t4 = Resource(Graph(), URIRef("http://example.org/other")) >>> t1 is t2 False >>> t1 == t2 True >>> t1 != t2 False >>> t1 == t3 False >>> t1 != t3 True >>> t3 != t4 True >>> t3 < t1 and t1 > t3 True >>> t1 >= t1 and t1 >= t3 True >>> t1 <= t1 and t3 <= t1 True >>> t1 < t1 or t1 < t3 or t3 > t1 or t3 > t3 False Hash is computed from graph and identifier:: >>> g1 = Graph() >>> t1 = Resource(g1, URIRef("http://example.org/thing")) >>> hash(t1) == hash(Resource(g1, URIRef("http://example.org/thing"))) True >>> hash(t1) == hash(Resource(Graph(), t1.identifier)) False >>> hash(t1) == hash(Resource(Graph(), URIRef("http://example.org/thing"))) False The Resource class is suitable as a base class for mapper toolkits. For example, consider this utility for accessing RDF properties via qname-like attributes:: >>> class Item(Resource): ... ... def __getattr__(self, p): ... return list(self.objects(self._to_ref(*p.split('_', 1)))) ... ... def _to_ref(self, pfx, name): ... return URIRef(self._graph.store.namespace(pfx) + name) It works as follows:: >>> graph = Graph().parse(format='n3', data=''' ... @prefix rdfs: . ... @prefix foaf: . ... ... @base . ... ... foaf:name "Some Body"; ... foaf:depiction . ... rdfs:comment "Just an image"@en . ... ''') >>> person = Item(graph, URIRef("http://example.org/person/some1#self")) >>> print(person.foaf_name[0]) Some Body The mechanism for wrapping references as resources cooperates with subclasses. Therefore, accessing referenced resources automatically creates new ``Item`` objects:: >>> isinstance(person.foaf_depiction[0], Item) True >>> print(person.foaf_depiction[0].rdfs_comment[0]) Just an image """ from rdflib.term import Node, BNode, URIRef from rdflib.namespace import RDF from rdflib.paths import Path __all__ = ['Resource'] class Resource(object): def __init__(self, graph, subject): self._graph = graph self._identifier = subject graph = property(lambda self: self._graph) identifier = property(lambda self: self._identifier) def __hash__(self): return hash(Resource) ^ hash(self._graph) ^ hash(self._identifier) def __eq__(self, other): return (isinstance(other, Resource) and self._graph == other._graph and self._identifier == other._identifier) def __ne__(self, other): return not self == other def __lt__(self, other): if isinstance(other, Resource): return self._identifier < other._identifier else: return False def __gt__(self, other): return not (self < other or self == other) def __le__(self, other): return self < other or self == other def __ge__(self, other): return not self < other def __unicode__(self): return text_type(self._identifier) if PY3: __str__ = __unicode__ def add(self, p, o): if isinstance(o, Resource): o = o._identifier self._graph.add((self._identifier, p, o)) def remove(self, p, o=None): if isinstance(o, Resource): o = o._identifier self._graph.remove((self._identifier, p, o)) def set(self, p, o): if isinstance(o, Resource): o = o._identifier self._graph.set((self._identifier, p, o)) def subjects(self, predicate=None): # rev return self._resources( self._graph.subjects(predicate, self._identifier)) def predicates(self, o=None): if isinstance(o, Resource): o = o._identifier return self._resources( self._graph.predicates(self._identifier, o)) def objects(self, predicate=None): return self._resources( self._graph.objects(self._identifier, predicate)) def subject_predicates(self): return self._resource_pairs( self._graph.subject_predicates(self._identifier)) def subject_objects(self): return self._resource_pairs( self._graph.subject_objects(self._identifier)) def predicate_objects(self): return self._resource_pairs( self._graph.predicate_objects(self._identifier)) def value(self, p=RDF.value, o=None, default=None, any=True): if isinstance(o, Resource): o = o._identifier return self._cast( self._graph.value(self._identifier, p, o, default, any)) def label(self): return self._graph.label(self._identifier) def comment(self): return self._graph.comment(self._identifier) def items(self): return self._resources(self._graph.items(self._identifier)) def transitive_objects(self, predicate, remember=None): return self._resources(self._graph.transitive_objects( self._identifier, predicate, remember)) def transitive_subjects(self, predicate, remember=None): return self._resources(self._graph.transitive_subjects( predicate, self._identifier, remember)) def seq(self): return self._resources(self._graph.seq(self._identifier)) def qname(self): return self._graph.qname(self._identifier) def _resource_pairs(self, pairs): for s1, s2 in pairs: yield self._cast(s1), self._cast(s2) def _resource_triples(self, triples): for s, p, o in triples: yield self._cast(s), self._cast(p), self._cast(o) def _resources(self, nodes): for node in nodes: yield self._cast(node) def _cast(self, node): if isinstance(node, (BNode, URIRef)): return self._new(node) else: return node def __iter__(self): return self._resource_triples(self._graph.triples((self.identifier, None, None))) def __getitem__(self, item): if isinstance(item, slice): if item.step: raise TypeError("Resources fix the subject for slicing, and can only be sliced by predicate/object. ") p, o = item.start, item.stop if isinstance(p, Resource): p = p._identifier if isinstance(o, Resource): o = o._identifier if p is None and o is None: return self.predicate_objects() elif p is None: return self.predicates(o) elif o is None: return self.objects(p) else: return (self.identifier, p, o) in self._graph elif isinstance(item, (Node, Path)): return self.objects(item) else: raise TypeError("You can only index a resource by a single rdflib term, a slice of rdflib terms, not %s (%s)"%(item, type(item))) def __setitem__(self, item, value): self.set(item, value) def _new(self, subject): return type(self)(self._graph, subject) def __str__(self): return 'Resource(%s)' % self._identifier def __repr__(self): return 'Resource(%s,%s)' % (self._graph, self._identifier)