# Copyright (c) 2004 LOGILAB S.A. (Paris, FRANCE).
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# Copyright (c) 2004 DoCoMo Euro-Labs GmbH (Munich, Germany).
# http://www.docomolab-euro.com/ -- mailto:tarlano@docomolab-euro.com
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""a RedLand (http://librdf.org/) based knowledge database
:version: $Revision:$
:author: Logilab
:copyright:
2004 LOGILAB S.A. (Paris, FRANCE)
2004 DoCoMo Euro-Labs GmbH (Munich, Germany)
:contact:
http://www.logilab.fr/ -- mailto:contact@logilab.fr
http://www.docomolab-euro.com/ -- mailto:tarlano@docomolab-euro.com
"""
__revision__ = '$Id$'
# FIXME
# from PyLogDB import KBError
class KBError(Exception): pass
from cStringIO import StringIO
import traceback
import re
import os
import RDF
from narval.public import AL_NS, url_to_file
from narval.elements.rdf import RDFStatementElement, RDQLResultLineElement
from narval.interfaces.rdf import IRDFStatement, IRDQLQuery
from narval.interfaces.base import ICommand
from narval.elements import create_error, create_command
from narval.elements.base import DataElement
######################################################################
DEFAULT_REDLAND_STORE_FILE_URL = 'file:$NARVAL_HOME/data/rdfstore.rdf'
MINIMAL_CONTENT = '''
'''
def redland_kb_file(obj=None):
"""return the path to the redland knowledge file
:param obj: object adaptable to IURL
:rtype: tuple(str, str)
"""
path, encoding = url_to_file(obj, DEFAULT_REDLAND_STORE_FILE_URL)
return path
class RedLandModel(RDF.Model):
"""convenience class initialized with a simple kb uri"""
def __init__(self, kb_uri):
self._filename = redland_kb_file(kb_uri)
try:
storage = RDF.FileStorage(self._filename)
# XXX FIXME: wait for Dave Beckett feedback about unifying
# RDF.RedlandError and Redland_python.Error
except:
# Naive way one of the possible errors : self._filename
# doesn't exist
if not os.path.exists(self._filename):
log(LOG_DEBUG, "%r doesn't exist, I'll create a default empty one" %
(self._filename))
fp = file(self._filename, 'w')
fp.write(MINIMAL_CONTENT)
fp.close()
else:
# For now, we don't know how to hanlde other errors
raise
RDF.Model.__init__(self, storage)
def save(self):
"""uses RDF.Serializer to serialize current model"""
RDF.Serializer().serialize_model_to_file(self._filename, self)
def exec_query(self, query, namespaces = {}):
"""execute query against current model"""
# FIXME: break if query defines different NS for existing alias
for alias, url in namespaces.items():
#query = query.replace('<%s:' % alias, '<%s'%url)
query = re.sub('%s:(\w+)' % alias, r'<%s\1>' % url, query)
log(LOG_DEBUG, "Query = %s" % query)
query = RDF.Query(query)
if query is None :
raise Exception("Bad Query")
# execute() returns an iterator, and we need a list
# result = [rlnode_as_string(elt) for elt in query.execute(self)]
result = query.execute(self)
if result is None :
result = []
else :
result = list(result)
log(LOG_DEBUG, "RESULT = %s" % result)
return result
def element_as_rlnode(element):
"""converts a simple 'full text' element into a RedLand Node"""
element = str(element)
if element.startswith('http://'):
return RDF.Uri(element)
return RDF.Node(element)
def rlnode_as_string(rl_node):
"""converts a RDF.Node into a raw string"""
if rl_node.is_resource():
return str(rl_node.uri)
elif rl_node.is_blank():
return str(rl_node.blank_identifier)
elif rl_node.is_literal():
return rl_node.literal_value['string']
return None
def rl2al_statement(rl_statement):
"""converts a RedLandStatement into a (Al)RDFStatement"""
return RDFStatementElement(subject = rlnode_as_string(rl_statement.subject),
predicate = rlnode_as_string(rl_statement.predicate),
object = rlnode_as_string(rl_statement.object))
def al2rl_statement(al_statement):
"""concerts a (Al)RDFStatement into a RedLandStatement"""
subj = al_statement.subject and element_as_rlnode(al_statement.subject)
pred = element_as_rlnode(al_statement.predicate)
obj = al_statement.object and element_as_rlnode(al_statement.object)
return RDF.Statement(subj, pred, obj)
def unify_foaf(model):
"""
XXX move to extension. Useful outside this module.
"""
# search duplicate names
names = {}
query = RDF.Query('SELECT ?x, ?n WHERE (?x ?n)')
results = query.execute(model)
for res in results:
names.setdefault(str(res['n']), []).append(res['x'])
remap_id = {}
for name, ids in names.items() :
remap_id[name] = ids[0].blank_identifier
# remap duplicates
for name, ids in names.items():
for nodeid in ids:
if nodeid.blank_identifier == remap_id[name] :
continue
blk = RDF.Node(blank=nodeid.blank_identifier)
# remap subject
to_suppress = []
search_stmt = RDF.Statement(blk, None, None)
for stmt in model.find_statements(search_stmt):
object = RDF.Node(stmt.object)
if RDF.node_type_name(stmt.object.type) == 'NODE_TYPE_BLANK' :
for other_name, other_ids in names.items() :
other_ids = [other_id.blank_identifier for other_id in other_ids]
if stmt.object.blank_identifier in other_ids :
object = RDF.Node(blank=remap_id[other_name])
new_stmt = RDF.Statement(RDF.Node(blank=remap_id[name]),
RDF.Node(stmt.predicate),
object)
model.add_statement(new_stmt)
to_suppress.append(stmt)
# remove statements
for stmt in to_suppress:
model.remove_statement(stmt)
# remap object
to_suppress = []
search_stmt = RDF.Statement(None, None, blk)
for stmt in model.find_statements(search_stmt):
subject = RDF.Node(stmt.subject)
if RDF.node_type_name(stmt.subject.type) == 'NODE_TYPE_BLANK' :
for other_name, other_ids in names.items() :
other_ids = [other_id.blank_identifier for other_id in other_ids]
if stmt.subject.blank_identifier in other_ids :
subject = RDF.Node(blank=remap_id[other_name])
new_stmt = RDF.Statement(subject,
RDF.Node(stmt.predicate),
RDF.Node(blank=remap_id[name]))
model.add_statement(stmt)
to_suppress.append(stmt)
# remove statements
for stmt in to_suppress:
model.remove_statement(stmt)
# actions definitions start here ##############################################
MOD_XML = '''
''' % AL_NS
def act_unify(inputs):
"""search for matching statements in the knowledge base"""
print 'using backend',inputs['kb']
model = RedLandModel(inputs['kb'])
result = []
for stmt in inputs['stmts']:
for rl_stmt in model.find_statements(al2rl_statement(IRDFStatement(stmt))):
result.append(rl2al_statement(rl_stmt))
return {'stmts' : result}
MOD_XML += """
%s
IRDFStatement(elmt)
elmt.getattr((TYPE_NS, 'name')) == 'uri:memory:redland'
IURL(elmt)
IRDFStatement(elmt)
""" % act_unify.__doc__
def act_add_statements(inputs):
"""add a list of `IRDFStatement` elements to the knowledge base"""
print 'using backend',inputs['kb']
model = RedLandModel(inputs['kb'])
for stmt in inputs['stmts']:
statement = al2rl_statement(IRDFStatement(stmt))
if statement is None:
raise KBError("new RDF.Statement failed")
print 'adding', statement
model.add_statement(statement)
print 'added'
## try:
## model.save()
## except Exception, ex:
## print 'error while saving model', ex
return {}
MOD_XML += """
%s
IRDFStatement(elmt)
elmt.getattr((TYPE_NS, 'name')) == 'uri:memory:redland'
IURL(elmt)
""" % act_add_statements.__doc__
def act_export(inputs):
"""export all statements in the knowledge base"""
model = RedLandModel(inputs['kb'])
return {'stmts': [rl2al_statement(stmt)
for stmt in model.find_statements(RDF.Statement())]}
MOD_XML = MOD_XML + """
%s
elmt.getattr((TYPE_NS, 'name')) == 'uri:memory:redland'
IURL(elmt)
IRDFStatement(elmt)
""" % act_export.__doc__
def act_import_url(inputs):
"""import a FOAF file fetched from given URL"""
log(LOG_DEBUG, "INPUTS %s" % inputs)
model = RedLandModel(inputs['kb'])
# XXX FIXME : RDF.Uri() doesn't accept unicode strings
uri = RDF.Uri(str(ICommand(inputs['import-command']).args[1]))
parser = RDF.Parser('raptor')
if parser is None:
raise Exception("Failed to create RDF.Parser raptor")
try:
parser.parse_into_model(model, uri, 'http://www.logilab.org/default.rdf')
except Exception, exc:
error = DataElement('Sorry, import failed\nlog: %s' % exc)
return {'rdql-error' : error}
unify_foaf(model)
res = DataElement('imported %s' % uri)
return {'rdql-info': res }
MOD_XML += """
%s
ICommand(elmt)
elmt.getattr((TYPE_NS, 'name')) == 'uri:memory:redland'
IURL(elmt)
isinstance(elmt, DataElement)
IData(elmt)
""" % act_import_url.__doc__
def act_import_string(inputs):
"""import a FOAF string: FIXME"""
log(LOG_DEBUG, "INPUTS %s" % inputs)
model = RedLandModel(inputs['kb'])
rdf_string = ICommand(inputs['command']).args[1]
parser = RDF.Parser('raptor')
if parser is None:
raise Exception("Failed to create RDF.Parser raptor")
parser.parse_string_into_model(model, rdf_string, 'http://www.logilab.org/default.rdf')
unify_foaf(model)
res = DataElement('imported')
return {'rdql-info': res }
MOD_XML += """
%s
ICommand(elmt)
elmt.getattr((TYPE_NS, 'name')) == 'uri:memory:redland'
IURL(elmt)
IData(elmt)
""" % act_import_url.__doc__
def act_query_base(inputs):
"""queries the RDF knowledge base using RDQL"""
namespaces = dict([(e.alias, e.address) for e in inputs['namespaces']])
model = RedLandModel(inputs['kb'])
query = IRDQLQuery(inputs['query']).query
error = None
query_results = None
try:
query_results = [RDQLResultLineElement(res) for res in
model.exec_query(query, namespaces)]
except Exception, exc:
data = StringIO()
data.write('An error occured :\n')
traceback.print_exc(file = data)
error = DataElement(data.getvalue())
if not error and not query_results:
error = DataElement('Sorry, no result found')
if error:
return {'rdql-error' : error}
return {'rdql-results' : query_results}
MOD_XML += """
%s
IRDFNamespace(elmt)
IRDQLQuery(elmt)
elmt.getattr((TYPE_NS, 'name')) == 'uri:memory:redland'
IURL(elmt)
isinstance(elmt, DataElement)
IRDQLResultLine(elmt)
""" % act_query_base.__doc__
MOD_XML += ""
# http://starship.python.net/~fdrake/fdrake.rdf