Пользовательские функции SPARQL в rdflib

Как можно подключить пользовательскую функцию SPARQL к rdflib?

Я искал в rdflib точку входа для пользовательской функции. Я не нашел специальной точки входа, но обнаружил, что rdflib.plugins.sparql.CUSTOM_EVALS может быть местом для добавления пользовательской функции.

До сих пор я сделал попытку с кодом ниже. Мне он кажется "грязным". Я вызываю "скрытую" функцию (_eval) и не уверен, что правильно обновил все аргументы. Помимо кода примера custom_eval.py (который составляет основу моего кода) я нашел мало другого кода или документации по CUSTOM_EVALS.

import rdflib
from rdflib.plugins.sparql.evaluate import evalPart
from rdflib.plugins.sparql.sparql import SPARQLError
from rdflib.plugins.sparql.evalutils import _eval
from rdflib.namespace import Namespace
from rdflib.term import Literal

NAMESPACE = Namespace('//custom/')
LENGTH = rdflib.term.URIRef(NAMESPACE + 'length')

def customEval(ctx, part):
    """Evaluate custom function."""
    if part.name == 'Extend':
        cs = []
        for c in evalPart(ctx, part.p):
            if hasattr(part.expr, 'iri'):
                # A function
                argument = _eval(part.expr.expr[0], c.forget(ctx, _except=part.expr._vars))
                if part.expr.iri == LENGTH:
                    e = Literal(len(argument))
                else:
                    raise SPARQLError('Unhandled function {}'.format(part.expr.iri))
            else:
                e = _eval(part.expr, c.forget(ctx, _except=part._vars))
                if isinstance(e, SPARQLError):
                    raise e
            cs.append(c.merge({part.var: e}))
        return cs
    raise NotImplementedError()


QUERY = """
PREFIX custom: <%s>

SELECT ?s ?length WHERE {
  BIND("Hello, World" AS ?s)
  BIND(custom:length(?s) AS ?length)
}
""" % (NAMESPACE,)

rdflib.plugins.sparql.CUSTOM_EVALS['exampleEval'] = customEval
for row in rdflib.Graph().query(QUERY):
    print(row)

person Finn Årup Nielsen    schedule 15.05.2017    source источник
comment
В качестве дополнительного примечания я хотел бы отметить, что в других SPARQL определение пользовательских функций выглядит проще, см., например, stackoverflow.com/questions/16280758/   -  person Finn Årup Nielsen    schedule 15.05.2017
comment
Кажется проще, не имеет значения, возможно, это было дизайнерское решение или добавлено позже в RDFLib. Если ваш код работает, почему бы не продолжить ваш проект :D Но, может быть, какой-нибудь эксперт RDFLib знает больше. Пробовали спросить у разработчиков? Как насчет реализации примера examples/custom_eval.py?   -  person UninformedUser    schedule 16.05.2017
comment
Отношение examples/custom_eval.py: Мой пример фактически был разработан из этого файла Python.   -  person Finn Årup Nielsen    schedule 16.05.2017
comment
Теперь я вижу, что на странице GitHub rdflib были коммиты и проблемы, связанные с моим вопросом: >github.com/RDFLib/rdflib/pull/723/commits/ Я вижу, что это было сделано в марте 2017 года stackoverflow.com/users/1235487/pierre-antoine, но все же запрос на включение.   -  person Finn Årup Nielsen    schedule 16.05.2017
comment
Звучит неплохо. Затем вы можете разветвить проект и применить запрос на включение в свою вилку.   -  person UninformedUser    schedule 17.05.2017


Ответы (1)


Итак, во-первых, я хочу поблагодарить вас за то, что вы показали, как вы реализовали новую функцию SPARQL.

Во-вторых, с помощью вашего кода я смог создать функцию SPARQL, которая оценивает две строки с использованием расстояния Левенштейна. Это было действительно полезно, и я хотел бы поделиться им, поскольку он содержит дополнительную документацию, которая может помочь другим разработчикам создавать свои собственные функции SPARQL.

# Import needed to introduce new SPARQL function
import rdflib
from rdflib.plugins.sparql.evaluate import evalPart
from rdflib.plugins.sparql.sparql import SPARQLError
from rdflib.plugins.sparql.evalutils import _eval
from rdflib.namespace import Namespace
from rdflib.term import Literal

# Import for custom function calculation
from Levenshtein import distance as levenshtein_distance # python-Levenshtein==0.12.2



def SPARQL_levenshtein(ctx:object, part:object) -> object:
    """
    The first two variables retrieved from a SPARQL-query are compared using the Levenshtein distance.
    The distance value is then stored in Literal object and added to the query results.
    
    Example:

    Query:
        PREFIX custom: //custom/      # Note: this part refereces to the custom function

        SELECT ?label1 ?label2 ?levenshtein WHERE {
          BIND("Hello" AS ?label1)
          BIND("World" AS ?label2)
          BIND(custom:levenshtein(?label1, ?label2) AS ?levenshtein)
        }

    Retrieve:
        ?label1 ?label2

    Calculation:
        levenshtein_distance(?label1, ?label2) =  distance

    Output:
        Save distance in Literal object.

    :param ctx:     <class 'rdflib.plugins.sparql.sparql.QueryContext'>
    :param part:    <class 'rdflib.plugins.sparql.parserutils.CompValue'>
    :return:        <class 'rdflib.plugins.sparql.processor.SPARQLResult'>
    """

    # This part holds basic implementation for adding new functions
    if part.name == 'Extend':
        cs = []

        # Information is retrieved and stored and passed through a generator
        for c in evalPart(ctx, part.p):

            # Checks if the function holds an internationalized resource identifier
            # This will check if any custom functions are added.
            if hasattr(part.expr, 'iri'):

                # From here the real calculations begin.
                # First we get the variable arguments, for example ?label1 and ?label2
                argument1 = str(_eval(part.expr.expr[0], c.forget(ctx, _except=part.expr._vars)))
                argument2 = str(_eval(part.expr.expr[1], c.forget(ctx, _except=part.expr._vars)))

                # Here it checks if it can find our levenshtein IRI (example: //custom/levenshtein)
                # Please note that IRI and URI are almost the same.
                # Earlier this has been defined with the following:
                    # namespace = Namespace('//custom/')
                    # levenshtein = rdflib.term.URIRef(namespace + 'levenshtein')

                if part.expr.iri == levenshtein:

                    # After finding the correct path for the custom SPARQL function the evaluation can begin.
                    # Here the levenshtein distance is calculated using ?label1 and ?label2 and stored as an Literal object.
                    # This object is than stored as an output value of the SPARQL-query (example: ?levenshtein)
                    evaluation = Literal(levenshtein_distance(argument1, argument2))


    # Standard error handling and return statements
                else:
                    raise SPARQLError('Unhandled function {}'.format(part.expr.iri))
            else:
                evaluation = _eval(part.expr, c.forget(ctx, _except=part._vars))
                if isinstance(evaluation, SPARQLError):
                    raise evaluation
            cs.append(c.merge({part.var: evaluation}))
        return cs
    raise NotImplementedError()


namespace = Namespace('//custom/')
levenshtein = rdflib.term.URIRef(namespace + 'levenshtein')


query = """
PREFIX custom: <%s>

SELECT ?label1 ?label2 ?levenshtein WHERE {
  BIND("Hello" AS ?label1)
  BIND("World" AS ?label2)
  BIND(custom:levenshtein(?label1, ?label2) AS ?levenshtein)
}
""" % (namespace,)

# Save custom function in custom evaluation dictionary.
rdflib.plugins.sparql.CUSTOM_EVALS['SPARQL_levenshtein'] = SPARQL_levenshtein


for row in rdflib.Graph().query(query):
    print(row)

Чтобы ответить на ваш вопрос: как лучше подключить пользовательскую функцию SPARQL к rdflib?

В настоящее время я разрабатываю класс, который обрабатывает данные RDF, и я считаю, что лучше всего реализовать следующий код в __init__function.

Например:

class ClassName():
    """DOCSTRING"""

    def __init__(self):
        """DOCSTRING"""
        # Save custom function in custom evaluation dictionary.
        rdflib.plugins.sparql.CUSTOM_EVALS['SPARQL_levenshtein'] = SPARQL_levenshtein 

Обратите внимание, что эта функция SPARQL будет работать только для конечной точки, на которой она реализована. Несмотря на то, что синтаксис SPARQL в запросе правильный, невозможно применить функцию в SPARQL-запросах, используемых для таких баз данных, как DBPedia. Конечная точка DBPedia не поддерживает эту пользовательскую функцию (пока).

person Richard Scholtens    schedule 07.04.2021