inamidst.com ࢐ sw ࢐ trio ࢐ doc

Trio Documentation

You can import all of Trio...

>>> import trio

Though quite often you'll just want to use Graph and n3:

>>> from trio import Graph, n3

These make working with RDF pretty simple. The normal "Hello, world!" example with RDF is printing out a list of people in some FOAF file, so here's how to handle that in Trio:

>>> G = Graph('http://inamidst.com/sbp/foaf.rdf')
>>> Q = n3('[ foaf:knows [ foaf:name ?name ] ]')
>>> for name in G.query(Q, 'name'): 
...    print name
"Aaron Swartz"
"Al Gilman"
...

Graphs

Graphs have all the usual kind of methods you'd expect:

>>> G = Graph('http://inamidst.com/sbp/foaf.rdf')
>>> len(G)
135

That tells you how many triples there are.

>>> H = Graph('http://inamidst.com/sbp/foaf.rdf')
>>> G == H
True

You can get triples from a Graph too, in arbitrary order:

>>> homepage = n3['foaf:homepage']
>>> for triple in G.triples(predicate=homepage): 
...   print triple.object
<http://www.aaronsw.com/>
<http://inamidst.com/sbp/>
...

Graph takes a URI that can return RDF/XML, N3, Turtle, N-Triples, or GRDDL. Here's an example of GRDDL:

>>> G = Graph('http://www.w3.org/2004/lambda/Sites/index.html')

And we can query it for Joe Lambda's workplace homepage:

>>> Q = n3('[ foaf:workplaceHomepage ?homepage ]')
>>> for homepage in G.query(Q, 'homepage'): 
...    print homepage
...    break
<http://www.acme.com>

The nodes method returns all the nodes from a Graph instance, which you can then sort—URIs sort before Literals, which sort before BlankNodes and Variables:

>>> print sorted(G.nodes())[0]
<http://creativecommons.org/licenses/by-nd/1.0/>

Graph Isomorphism

Trio passes all of the NTC test cases:

>>> for index, expected in enumerate('ynynnnnyynyyn'): 
...    case = '%02i' % (index + 1)
...    A = Graph('http://inamidst.com/sw/test/ntc/' + case + 'a.nt')
...    B = Graph('http://inamidst.com/sw/test/ntc/' + case + 'b.nt')
... 
...    if expected == 'y': 
...       assert A == B, ValueError('Expected equal graphs')
...    else: assert A != B, ValueError('Expected unequal graphs')

Of course isomorphism testing is just a restricted kind of query, so this should also work though it doesn't do isomorphism testing strictly:

...    def empty(results): 
...       try: results.next()
...       except StopIteration: 
...          return True
...       return False
...    
...    if expected == 'y': 
...       assert not empty(A.query(B))
...    else: assert empty(A.query(B))

Here's the difference:

>>> n3('?p ?q ?r') == n3('_:p _:q _:r')
False
>>> len(list(n3('?p ?q ?r').query(n3('_:p _:q _:r'))))
1

hTurtle

So, how about an actual application? One thing that I use Trio for is to work the hTurtle service, which goes a bit like this:

>>> import re, urllib
>>> r_comment = re.compile(r'<!--\{((?:[^-]|(?:-[^-]))*)\}-->')
>>> 
>>> G = trio.Graph(baseURI='http://inamidst.com/sw/hturtle/')
>>> u = urllib.urlopen('http://inamidst.com/sw/hturtle/')
>>> bytes = u.read()
>>> u.close()
>>> 
>>> bindings = """\
...    @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
...    @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
...    @prefix owl: <http://www.w3.org/2002/07/owl#> .
...    @prefix dc: <http://purl.org/dc/elements/1.1/> .
...    @prefix foaf: <http://xmlns.com/foaf/0.1/> .
...    @prefix doap: <http://usefulinc.com/ns/doap#> .
...    @prefix skos: <http://www.w3.org/2004/02/skos/core#> .
... """
>>> 
>>> turtle = bindings + '\n'.join(r_comment.findall(bytes)) + '\n'
>>> turtle = turtle.decode('utf-8')
>>> G.parseText(turtle, 'turtle')

That should parse the hTurtle documentation in G and give one triple:

>>> len(G)
1
>>> for o in G.query(n3('?s ?p ?o'), 'o'): 
...    print o
"The hTurtle Microformat"

In the application itself we want to print it out as RDF/XML:

>>> for line in G.serialiseToLines('rdfxml'): 
...    print line
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about="http://inamidst.com/sw/hturtle/">
<e xmlns="http://purl.org/dc/elements/1.1/titl"
>The hTurtle Microformat</e>
</rdf:Description>
</rdf:RDF>

Meteo

>>> n3.prefix('', 'http://purl.org/ns/meteo#')
>>> G = Graph('http://inamidst.com/sw/meteo/rdf/London')
>>> Q = n3("""[ :time ?time; :cloudCover [ :percent ?cloud ]; 
...             :temperature [ :celsius ?temperature ]; 
...             :precipitation [ :inches ?precipitation ] ] .""")
>>> forecasts = len(list(G.query(Q)))
>>> assert 50 < forecasts < 70

SPARQL

There is very weak but burgeoning SPARQL support:

>>> manifest = (
...    ('algebra/data-1.ttl', 
...     'algebra/filter-nested-1.rq', 
...     'algebra/filter-nested-1.srx'), 
... )
>>> 
>>> tests = 'http://www.w3.org/2001/sw/DataAccess/tests/data-r2/'
>>> for data, query, expected in manifest: 
...    G = Graph(tests + data, format='turtle')
...    R = G.sparqlURI(tests + query, 'SELECT').graph()
...    E = Graph(tests + expected, format='sparql-results')
...    assert R == E, '%s failed' % query

And some actual queries:

>>> manifest = (
...    ('data-01.ttl', 'dawg-tp-01.rq', 'result-tp-01.ttl'), 
...    ('data-01.ttl', 'dawg-tp-02.rq', 'result-tp-02.ttl'), 
...    ('data-02.ttl', 'dawg-tp-03.rq', 'result-tp-03.ttl'), 
...    ('dawg-data-01.ttl', 'dawg-tp-04.rq', 'result-tp-04.ttl'), 
... )
>>> 
>>> tests = 'http://www.w3.org/2001/sw/DataAccess/tests/data-r2/triple-match/'
>>> for data, query, expected in manifest: 
...    G = Graph(tests + data, format='turtle')
...    R = G.sparqlURI(tests + query, 'SELECT').graph()
...    E = Graph(tests + expected, format='turtle')
...    assert R == E, '%s failed' % query

Micellany

>>> assert trio.turtle.__file__ == trio.rdf.turtle.__file__
>>> assert trio.rdf.turtle.__file__.startswith('./trio/turtle.py')

Colophon

All of the examples in this document, incidentally, form a part of the actual test suite for Trio. (The details are that this document is a GRDDL document, so then we slurp the test triples from this document and run them using Python's doctest module.) So they should always be up to date!

Sean B. Palmer, inamidst.com