Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Slices as syntactic sugar for graphs / resources #271

Merged
merged 5 commits into from
May 2, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()