SuRF: what’s coming?

SuRF is an Object – RDF Mapper based on the popular rdflib python library. It exposes the RDF triple sets as sets of resources and seamlessly integrates them into the Object Oriented paradigm of python in a similar manner as ActiveRDF does for ruby.

I’m currently working on extending ways how one can select resources and their attributes. Short intro on what’s possible now. First, let’s look at how one can select resources:

from rdflib.URIRef import URIRef
import surf

sess = surf.Session(surf.Store(reader = "rdflib"))
Person = sess.get_class(surf.ns.FOAF["person"])
# Get all resources that are of type foaf:person
persons = Person.all()
# The same, but also load their direct and inverse attributes
persons = Person.all(full = True)
# Limit, offset
persons = Person.all(limit = 10, offset = 30)
# Look for resources in specific graph
persons = Person.all(context = URIRef("http://my_context"))
# For filtering use cls.get_by(). Filters are specified as 
# keyword arguments, argument names follow 
# "namespace_predicate" naming convention.
john_smiths = Person.get_by(foaf_name = "John", 
                                       foaf_surname = "Smith")

A couple of deficiencies here:

  • limit, offset, context, full arguments are only supported by all(), they are not supported by get_by()
  • for both methods, it’s not possible to specify order results
  • no shorthand notion to get first element from returned list

So I spent some time thinking and scribbling and getting inspiration from SQLAlchemy. I came up with this syntax:

# Iterator over all person resources
persons = Person.all()

# First person instance or None if there isn't any
first_person = Person.all().first()

# First person instance or raises exception if 
# total person count isn't "1"
only_person = Person.all().one()

# Iterator over all person resources with fully loaded attributes
persons = Person.all().full()

# Iterator over all person resources with only direct 
# attributes loaded, for performance
persons = Person.all().full(only_direct = True)

# Limit, offset
persons = Person.all().limit(10).offset(30)

# Sorted by subject URI
persons = Person.all().order()

# Sorted by name, in descending order
persons = Person.all().order(surf.ns.FOAF["name"]).desc()

# Specify graph
persons = Person.all().context(URIRef("http://my_context"))

# get_by also gets all the same features
first_five_johns = Person.get_by(foaf_name = "John").order().limit(5)

So that’s how to get hold of resources. How about resource attributes? Currently:

john = sess.get_resource(URIRef("http://johns_uri"), Person)
# john.foaf_name is list-like: can be accessed by index, 
# can be iterated, has length. It also has these two 
# convenience functions:
first_name = john.foaf_name.first
only_name =

Currently when accessing attribute all values are returned, in undefined order. There is no way to limit or sort them or to load them from specific graph. So, the idea is to allow all the same operations on attributes that we have on cls.all() and cls.get_by():

# Iterator over friend resources

# Iterator over loaded friend resources
john.foaf_knows.full(only_direct = True)

# First, one

# Limit, offset

# Sort friends by subject URI, sort by attribute

# Graph

# Filtering, only retrieve friends that are named "Jane"
john.foaf_knows.get_by(foaf_name = "Jane")

# Combine any of the previous
john.foaf_knows.get_by(foaf_name = "Jane").order().limit(10).full()

I’m implementing these changes in surf_1_0 branch. Work is progressing steadily, some of the cls.all() is working already. I’m exercising these new features in surf.sparql_protocol unit tests.

surf_1_0 branch turns SuRF’s innards upside down, nuts and bolts are all over the place but all in all it looks very promising!