Skip to content

Instantly share code, notes, and snippets.

@pchampin
Last active November 5, 2020 09:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pchampin/ab3d01d2c3c245042dc5 to your computer and use it in GitHub Desktop.
Save pchampin/ab3d01d2c3c245042dc5 to your computer and use it in GitHub Desktop.
A subclass of rdflib SPARQLUpdateStore with BNode support, for Virtuoso
from rdflib import BNode, URIRef
from rdflib.plugins.stores.sparqlstore import SPARQLUpdateStore, _node_to_sparql, _node_from_result, SPARQL_NS
from uuid import uuid4
def _virtuoso_compatible_generator():
return unicode(uuid4().int % 2**61)
# monkey patch BNode to make it virtuoso compatible
BNode.__new__.func_defaults = (None, _virtuoso_compatible_generator, 'b')
def _virtuoso_node_to_sparql(node):
if isinstance(node, BNode):
return '<nodeID://{}>'.format(node)
else:
return node.n3()
def _virtuoso_node_from_result(element):
if element.tag == '{%s}bnode' % SPARQL_NS:
return BNode(element.text[9:])
else:
return _node_from_result(element)
class VSPARQLStore(SPARQLUpdateStore):
def __init__(self, endpoint, username, password, **kwargs):
SPARQLUpdateStore.__init__(self, endpoint, endpoint,
node_to_sparql=_virtuoso_node_to_sparql,
node_from_result=_virtuoso_node_from_result,
**kwargs)
self.setHTTPAuth('digest')
self.setCredentials(username, password)
if __name__ == "__main__":
from rdflib import Namespace, Graph, Literal
from rdflib.collection import Collection
EX = Namespace('http://example.org/')
store = VSPARQLStore('http://localhost:8890/sparql-auth', 'dba', 'dba')
g = Graph(store, EX.g)
g.remove((None, None, None))
print(len(g))
bn = BNode()
g.add((EX.s, EX.pb, bn))
g.add((bn, EX.pb, Literal("foo")))
lh = BNode()
g.add((EX.s, EX.pl, lh))
lst = Collection(g, lh, map(Literal, [1,2,3]))
print(len(g))
print(g.serialize(format="turtle"))
g.remove((None, None, None))
@joernhees
Copy link

hmmm, i'm thinking about ways to get rid of that mapping dict, which would work if we could re-create the right BNode from the SPARQL result...

i'm not entirely sure if virtuoso lets us use non integer variants, that would be easiest...

bn = BNode()
sparqlBN = '<nodeID://b%s>' % unicode(bn[1:])  # not sure this works because of non-int
bn2 = BNode('N' + sparqlBN.split('<nodeID://b')[1][:-1])  # [:-1] for >
bn == bn2

if that doesn't work one could think about using the uuid int rep of the BNode... this only works for BNodes without specified identifier though:

bn = BNode()
sparqlBN = '<nodeID://b%d>' % int(unicode(bn)[1:], 16)  # not sure this works because of too long int
bn2 = BNode('N%032x' % int(sparqlBN.split('<nodeID://b')[1][:-1]))  # [:-1] for >
bn == bn2

but i suspect virtuoso nodeIDs not to be 128 bit long...

@pchampin
Copy link
Author

pchampin commented Mar 10, 2016

Unfortunetaly, both solutions do not work, because of the very problems you envisioned in your comments.
Indeed, Virtuoso only accept up to 19 decimal digits with nodeID:// (and even then, some numbers are too big...).

@pchampin
Copy link
Author

Ok, I found a way to get rid of the global mapping table (revision 2).

It involves an ugly hack (monkeypatching the default parameters of BNode.__new__), and is not 100% robust (it will break with BNodes created with an explicit ID, for example those generated by some parsers). So I have mixed feelings about it.

@joernhees
Copy link

... you seem to like living in the danger zone 😄

Monkeypatching obviously isn't that optimal, but to be honest i currently don't see a better way to do this (so being able to also configure BNode creation from a SPARQLStore in a way that doesn't have the potential to break things outside of it) ...

so, suggestions welcome, but till then 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment