Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: RDF, SPARQL, SKOSMOS #23

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
"treebeard",
"corsheaders",
"rules.apps.AutodiscoverRulesConfig",
"rdflib_django",
]

LOCAL_APPS = [
Expand All @@ -101,6 +102,7 @@
"metadata_catalogue.datasets.csw",
"metadata_catalogue.datasets.geoapi",
"metadata_catalogue.maps",
"metadata_catalogue.dictionaries",
]
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
Expand Down
16 changes: 16 additions & 0 deletions config/settings/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,19 @@


INSTALLED_APPS += ["models2puml"]

DICTIONARIES_EXAMPLE_QUERIES = {
"Bio2RDF query": {
"endpoint": "https://bio2rdf.org/sparql",
"query": """SELECT DISTINCT * WHERE {
?s a ?o .
} LIMIT 10""",
},
"Custom function": {
"query": """PREFIX myfunctions: <https://w3id.org/um/sparql-functions/>
SELECT ?concat ?concatLength WHERE {
BIND("First" AS ?first)
BIND(myfunctions:custom_concat(?first, "last") AS ?concat)
}""",
},
}
1 change: 1 addition & 0 deletions config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
path("geoapi/", include("metadata_catalogue.datasets.geoapi.urls", namespace="geoapi")),
path("datasets/", include("metadata_catalogue.datasets.urls")),
path("api/", api.urls),
path("", include("metadata_catalogue.dictionaries.urls")),
]


Expand Down
7 changes: 7 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,10 @@ services:
context: ./varnish
ports:
- 8000:80

skosmos:
image: ghcr.io/nicokant/skosmos:master
ports:
- 8050:80
volumes:
- ./skosmos.ttl:/var/www/html/config.ttl
Empty file.
76 changes: 76 additions & 0 deletions metadata_catalogue/dictionaries/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from appconf import AppConf
from django.conf import settings

from .rdf_functions import custom_concat


class DictionariesConf(AppConf):
DEFAULT_TITLE: str = "SPARQL endpoint for RDFLib graph"
DEFAULT_DESCRIPTION: str = (
"A SPARQL endpoint to serve machine learning models, or any other logic implemented in Python."
)
DEFAULT_VERSION: str = "0.1.0"
DEFAULT_FAVICON: str = "https://rdflib.readthedocs.io/en/stable/_static/RDFlib.png"
DEFAULT_EXAMPLE = """\
PREFIX myfunctions: <https://w3id.org/um/sparql-functions/>

SELECT ?concat ?concatLength WHERE {
BIND("First" AS ?first)
BIND(myfunctions:custom_concat(?first, "last") AS ?concat)
}
""".rstrip()

SERVICE_DESCRIPTION_TTL_FMT = """\
@prefix sd: <http://www.w3.org/ns/sparql-service-description#> .
@prefix ent: <http://www.w3.org/ns/entailment/> .
@prefix prof: <http://www.w3.org/ns/owl-profile/> .
@prefix void: <http://rdfs.org/ns/void#> .
@prefix dc: <http://purl.org/dc/elements/1.1/> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .

<{public_url}> a sd:Service ;
rdfs:label "{title}" ;
dc:description "{description}" ;
sd:endpoint <{public_url}> ;
sd:supportedLanguage sd:SPARQL11Query ;
sd:resultFormat <http://www.w3.org/ns/formats/SPARQL_Results_JSON>, <http://www.w3.org/ns/formats/SPARQL_Results_CSV> ;
sd:feature sd:DereferencesURIs ;
sd:defaultEntailmentRegime ent:RDFS ;
sd:defaultDataset [
a sd:Dataset ;
sd:defaultGraph [
a sd:Graph ;
]
] .
""".rstrip()

#: This is default for federated queries
DEFAULT_CONTENT_TYPE = "application/xml"

#: A mapping from content types to the keys used for serializing
#: in :meth:`rdflib.Graph.serialize` and other serialization functions
CONTENT_TYPE_TO_RDFLIB_FORMAT = {
# https://www.w3.org/TR/sparql11-results-json/
"application/sparql-results+json": "json",
"application/json": "json",
"text/json": "json",
# https://www.w3.org/TR/rdf-sparql-XMLres/
"application/sparql-results+xml": "xml",
"application/xml": "xml", # for compatibility
"application/rdf+xml": "xml", # for compatibility
"text/xml": "xml", # not standard
# https://www.w3.org/TR/sparql11-results-csv-tsv/
"application/sparql-results+csv": "csv",
"text/csv": "csv", # for compatibility
# Extras
"text/turtle": "ttl",
}

CUSTOM_EVAL = None
FUNCTIONS = {
"https://w3id.org/um/sparql-functions/custom_concat": custom_concat,
}
EXAMPLE_QUERIES = None
ENABLE_UPDATE = False
PROCESSOR = "sparql"
RDFLIB_APIKEY = None
33 changes: 33 additions & 0 deletions metadata_catalogue/dictionaries/rdf_functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import rdflib
from rdflib import Literal
from rdflib.plugins.sparql.evalutils import _eval


def custom_concat(query_results, ctx, part, eval_part):
"""
Concat 2 string and return the length as additional Length variable
\f
:param query_results: An array with the query results objects
:param ctx: <class 'rdflib.plugins.sparql.sparql.QueryContext'>
:param part: Part of the query processed (e.g. Extend or BGP) <class 'rdflib.plugins.sparql.parserutils.CompValue'>
:param eval_part: Part currently evaluated
:return: the same query_results provided in input param, with additional results
"""
argument1 = str(_eval(part.expr.expr[0], eval_part.forget(ctx, _except=part.expr._vars)))
argument2 = str(_eval(part.expr.expr[1], eval_part.forget(ctx, _except=part.expr._vars)))
evaluation = []
scores = []
concat_string = argument1 + argument2
reverse_string = argument2 + argument1
# Append the concatenated string to the results
evaluation.append(concat_string)
evaluation.append(reverse_string)
# Append the scores for each row of results
scores.append(len(concat_string))
scores.append(len(reverse_string))
# Append our results to the query_results
for i, result in enumerate(evaluation):
query_results.append(
eval_part.merge({part.var: Literal(result), rdflib.term.Variable(part.var + "Length"): Literal(scores[i])})
)
return query_results, ctx, part, eval_part
69 changes: 69 additions & 0 deletions metadata_catalogue/dictionaries/templates/dictionaries/yasgui.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{{ title }}</title>
<meta name="description" content="{{ description }}" />
<link href="https://unpkg.com/@triply/yasgui@4/build/yasgui.min.css"
rel="stylesheet"
type="text/css" />
<script src="https://unpkg.com/@triply/yasgui@4/build/yasgui.min.js"></script>
</head>
<body>
<div id="yasgui"></div>
{% if example_queries %}{{ example_queries|json_script:"example-queries" }}{% endif %}
<script>
let queries_obj = []
try {
queries_obj = JSON.parse(document.getElementById('example-queries').textContent) || []
} catch (e) {
console.log(e)
}

const url = window.location.href;
// Make sure the endpoints list is unique
const endpointsList = [...new Set([url, ...Object.keys(queries_obj).map((label) => {
if (queries_obj[label]["endpoint"]) return queries_obj[label]["endpoint"]
})])]
const yasguiEndpoints = endpointsList.map((endpoint) => {
return {
endpoint: endpoint
}
})

const yasgui = new Yasgui(document.getElementById("yasgui"), {
requestConfig: {
endpoint: url,
copyEndpointOnNewTab: true,
},
endpointCatalogueOptions: {
getData: function() {
return yasguiEndpoints
},
keys: [],
},
});

// Add tab to yasgui for each query
Object.keys(queries_obj).map((label) => {
const tabsLabel = Object.keys(yasgui._tabs).map(tab => yasgui._tabs[tab].persistentJson.name)
if (!tabsLabel.includes(label)) {
yasgui.addTab(
false, // set as active tab not really working
{
...Yasgui.Tab.getDefaults(),
name: label,
requestConfig: {
endpoint: queries_obj[label]['endpoint']
},
yasqe: {
value: queries_obj[label]['query']
}
}
);
}
})
</script>
</body>
</html>
Empty file.
21 changes: 21 additions & 0 deletions metadata_catalogue/dictionaries/tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import pytest

from ..utils import parse_accept_header

accept_cases = [
("text/xml", "text/xml"),
("text/rdf+xml, text/xml, */*", "text/rdf+xml"),
("text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8", "text/html"),
("text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=", "text/html"),
("text/html;q=0.3, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8", "application/xhtml+xml"),
(
'text/turtle;q=0.9;profile="urn:example:profile-1", text/turtle;q=0.7;profile="urn:example:profile-2"',
"text/turtle",
),
]


@pytest.mark.parametrize("accept,expected", accept_cases)
def test_accept_preference(accept, expected):
pref = parse_accept_header(accept)
assert pref[0] == expected
Loading