Skip to content

Commit

Permalink
Merge pull request #271 from RDFLib/slice
Browse files Browse the repository at this point in the history
Slices as syntactic sugar for graphs / resources
  • Loading branch information
gromgull committed May 2, 2013
2 parents 56326db + 7a6ad5c commit 557b101
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 4 deletions.
6 changes: 3 additions & 3 deletions examples/foafpaths.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
p1 / p2 => Path sequence
p1 | p2 => Path alternative
p1 % '*' => chain of 0 or more p's
p1 % '+' => chain of 1 or more p's
p1 % '?' => 0 or 1 p
p1 * '*' => chain of 0 or more p's
p1 * '+' => chain of 1 or more p's
p1 * '?' => 0 or 1 p
~p1 => p1 is inverted order (s p1 o) <=> (o ~p1 s)
-p1 => NOT p1, i.e. any property by p1
Expand Down
51 changes: 51 additions & 0 deletions rdflib/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,57 @@ def triples(self, (s, p, o)):
for (s, p, o), cg in self.__store.triples((s, p, o), context=self):
yield (s, p, o)

@py3compat.format_doctest_out
def __getitem__(self, item):
"""
A graph can be "sliced" as a shortcut for the triples method
The python slice syntax is (ab)used for specifying triples.
A generator over matching triples is returned
>>> import rdflib
>>> g = rdflib.Graph()
>>> g.add((rdflib.URIRef('urn:bob'), rdflib.RDFS.label, rdflib.Literal('Bob')))
>>> list(g[rdflib.URIRef('urn:bob')]) # all triples about bob
[(rdflib.term.URIRef(%(u)s'urn:bob'), rdflib.term.URIRef(%(u)s'http://www.w3.org/2000/01/rdf-schema#label'), rdflib.term.Literal(%(u)s'Bob'))]
>>> list(g[:rdflib.RDFS.label]) # all label triples
[(rdflib.term.URIRef(%(u)s'urn:bob'), rdflib.term.URIRef(%(u)s'http://www.w3.org/2000/01/rdf-schema#label'), rdflib.term.Literal(%(u)s'Bob'))]
>>> list(g[::rdflib.Literal('Bob')]) # all label triples
[(rdflib.term.URIRef(%(u)s'urn:bob'), rdflib.term.URIRef(%(u)s'http://www.w3.org/2000/01/rdf-schema#label'), rdflib.term.Literal(%(u)s'Bob'))]
Combined with SPARQL paths, more complex queries can be
written concisely:
Name of all Bobs friends:
g[bob : FOAF.knows/FOAF.name ]
Some label for Bob:
g[bob : DC.title|FOAF.name|RDFS.label]
All friends and friends of friends of Bob
g[bob : FOAF.knows * '+']
etc.
"""

if isinstance(item, slice):

s,p,o=item.start,item.stop,item.step
return self.triples((s,p,o))

elif isinstance(item, Node):

return self.triples((item,None,None))

else:
raise TypeError("You can only index a graph by a single rdflib term or a slice of rdflib terms.")

def __len__(self):
"""Returns the number of triples in the graph
Expand Down
27 changes: 26 additions & 1 deletion rdflib/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@
""")

from rdflib.term import BNode, URIRef
from rdflib.term import Node, BNode, URIRef
from rdflib.namespace import RDF

__all__ = ['Resource']
Expand Down Expand Up @@ -434,6 +434,31 @@ def _cast(self, node):
else:
return node

def __getitem__(self, item):
"""
Resources can be sliced like graphs, but the subject is fixed.
r[RDFS.label] returns triples for (self.identifier, RDFS.label, None)
r[RDFS.label : Literal("Bob")] for (self.identifier, RDFS.label, "Bob")
etc.
"""

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
s=(self.identifier,)
return self.triples((s,p,o))

elif isinstance(item, Node):

return self.triples((self.identifier, item, None))

else:
raise TypeError("You can only index a resource by a single rdflib term, a slice of rdflib terms.")


def _new(self, subject):
return type(self)(self._graph, subject)

Expand Down
82 changes: 82 additions & 0 deletions test/test_slice.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@

from rdflib import Graph, URIRef
import unittest

class GraphSlice(unittest.TestCase):

def testSlice(self):
"""
We pervert the slice object,
and use start, stop, step as subject, predicate, object
all operations return generators over full triples
"""

sl=lambda x,y: self.assertEquals(len(list(x)),y)
soe=lambda x,y: self.assertEquals(set([a[2] for a in x]),set(y)) # equals objects
g=self.graph

# Single terms are all trivial:

# single index slices by subject, i.e. return triples((x,None,None))
# tell me everything about "tarek"
sl(g[self.tarek],2)

# single slice slices by s,p,o, with : used to split
# tell me everything about "tarek" (same as above)
sl(g[self.tarek::],2)

# give me every "likes" relationship
sl(g[:self.likes:],5)

# give me every relationship to pizza
sl(g[::self.pizza],3)

# give me everyone who likes pizza
sl(g[:self.likes:self.pizza],2)

# does tarek like pizza?
sl(g[self.tarek:self.likes:self.pizza],1)

# More intesting is using paths

# everything hated or liked
sl(g[:self.hates|self.likes], 7)





def setUp(self):
self.graph = Graph()

self.michel = URIRef(u'michel')
self.tarek = URIRef(u'tarek')
self.bob = URIRef(u'bob')
self.likes = URIRef(u'likes')
self.hates = URIRef(u'hates')
self.pizza = URIRef(u'pizza')
self.cheese = URIRef(u'cheese')

self.addStuff()

def addStuff(self):
tarek = self.tarek
michel = self.michel
bob = self.bob
likes = self.likes
hates = self.hates
pizza = self.pizza
cheese = self.cheese

self.graph.add((tarek, likes, pizza))
self.graph.add((tarek, likes, cheese))
self.graph.add((michel, likes, pizza))
self.graph.add((michel, likes, cheese))
self.graph.add((bob, likes, cheese))
self.graph.add((bob, hates, pizza))
self.graph.add((bob, hates, michel)) # gasp!


if __name__ == '__main__':
unittest.main()

0 comments on commit 557b101

Please sign in to comment.