Skip to content

Commit

Permalink
feat: add support for JSON-LD, n3, n-triples, n-quads, trix and trig …
Browse files Browse the repository at this point in the history
…return types on CONSTRUCT queries
  • Loading branch information
vemonet committed May 24, 2024
1 parent 29331f3 commit 16a47c6
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 27 deletions.
8 changes: 2 additions & 6 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,14 @@ repos:
name: " 🪚 Fix end of files"
- id: trailing-whitespace
name: " ✂️ Trim trailing whitespaces"
- repo: https://github.com/psf/black
rev: 23.11.0
hooks:
- id: black
name: " ✒️ Formatting code with Black"
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.1.6
rev: v0.4.5
hooks:
- id: ruff
name: " ⚡️ Formatting code with Ruff"
args:
- --fix
- id: ruff-format
# Does not read the config from pyproject.toml
# - repo: https://github.com/pre-commit/mirrors-mypy
# rev: v1.4.1
Expand Down
14 changes: 4 additions & 10 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -160,19 +160,13 @@ disallow_untyped_defs = true
disallow_any_generics = true
disallow_untyped_calls = false # needed due to _eval() not being typed in rdflib


[tool.black]
color = true
line-length = 120
target-version = ['py38']
skip-string-normalization = false


# https://beta.ruff.rs/docs/rules
# https://docs.astral.sh/ruff/rules/
[tool.ruff]
src = ["src", "tests"]
target-version = "py38"
line-length = 120

[tool.ruff.lint]
select = [
"I", # isort
"N", # pep8-naming
Expand Down Expand Up @@ -206,7 +200,7 @@ ignore = [
"T201", "T203", # remove print and pprint
]

[tool.ruff.per-file-ignores]
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["I", "F401"] # module imported but unused


Expand Down
35 changes: 29 additions & 6 deletions src/rdflib_endpoint/sparql_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,25 @@
"application/json": {"example": {"results": {"bindings": []}, "head": {"vars": []}}},
"text/csv": {"example": "s,p,o"},
"application/sparql-results+csv": {"example": "s,p,o"},
"text/turtle": {"example": "service description"},
"application/sparql-results+xml": {"example": "<root></root>"},
"application/xml": {"example": "<root></root>"},
"text/turtle": {"example": "<http://subject> <http://predicate> <http://object> ."},
"application/n-triples": {"example": "<http://subject> <http://predicate> <http://object> ."},
"text/n3": {"example": "<http://subject> <http://predicate> <http://object> ."},
"application/n-quads": {"example": "<http://subject> <http://predicate> <http://object> <http://graph> ."},
"application/trig": {
"example": "GRAPH <http://graph> {<http://subject> <http://predicate> <http://object> .}"
},
"application/trix": {"example": "<xml></xml>"},
"application/ld+json": {
"example": [
{
"@id": "http://subject",
"@type": ["http://object"],
"http://www.w3.org/2000/01/rdf-schema#label": [{"@value": "foo"}],
}
]
},
# "application/rdf+xml": {
# "example": '<?xml version="1.0" encoding="UTF-8"?> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"></rdf:RDF>'
# },
Expand Down Expand Up @@ -100,11 +116,18 @@
"application/xml": "xml", # for compatibility
"application/rdf+xml": "xml", # for compatibility
"text/xml": "xml", # not standard
"application/ld+json": "json-ld",
# https://www.w3.org/TR/sparql11-results-csv-tsv/
"application/sparql-results+csv": "csv",
"text/csv": "csv", # for compatibility
# Extras
"text/turtle": "ttl",
"text/n3": "n3",
"application/n-triples": "nt",
"text/plain": "nt",
"application/trig": "trig",
"application/trix": "trix",
"application/n-quads": "nquads",
}


Expand Down Expand Up @@ -257,17 +280,17 @@ async def handle_sparql_request(

# Handle mime type for construct queries
if query_operation == "Construct Query":
if output_mime_type in {"application/json", "text/csv"}:
if output_mime_type == "text/csv":
output_mime_type = "text/turtle"
# TODO: support JSON-LD for construct query?
# g.serialize(format='json-ld', indent=4)
elif output_mime_type == "application/json":
output_mime_type = "application/ld+json"
elif output_mime_type == "application/xml":
output_mime_type = "application/rdf+xml"
else:
pass # TODO what happens here?
pass

try:
rdflib_format = CONTENT_TYPE_TO_RDFLIB_FORMAT[output_mime_type]
rdflib_format = CONTENT_TYPE_TO_RDFLIB_FORMAT.get(output_mime_type, output_mime_type)
response = Response(
query_results.serialize(format=rdflib_format),
media_type=output_mime_type,
Expand Down
19 changes: 14 additions & 5 deletions tests/test_rdflib_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ def clear_graph():

def test_service_description():
response = endpoint.get("/", headers={"accept": "text/turtle"})
# print(response.text.strip())
assert response.status_code == 200
assert response.text.strip() == service_description

Expand Down Expand Up @@ -63,6 +62,7 @@ def test_custom_concat_json():
def test_select_noaccept_xml():
response = endpoint.post("/", data={"query": concat_select})
assert response.status_code == 200
assert response.text.startswith("<?xml ")


def test_select_csv():
Expand Down Expand Up @@ -140,18 +140,26 @@ def test_multiple_accept_return_json2():
def test_fail_select_turtle():
response = endpoint.post("/", data={"query": concat_select}, headers={"accept": "text/turtle"})
assert response.status_code == 422
# assert response.json()['results']['bindings'][0]['concat']['value'] == "Firstlast"


def test_concat_construct_turtle():
# expected to return turtle
response = endpoint.post(
"/",
data={"query": custom_concat_construct},
headers={"accept": "text/turtle"},
)
assert response.status_code == 200
assert response.text.startswith("@prefix ")


def test_concat_construct_jsonld():
response = endpoint.post(
"/",
data={"query": custom_concat_construct},
headers={"accept": "application/json"},
)
assert response.status_code == 200
# assert response.json()['results']['bindings'][0]['concat']['value'] == "Firstlast"
assert response.json()[0]["@id"] == "http://example.com/test"


def test_concat_construct_xml():
Expand All @@ -162,6 +170,7 @@ def test_concat_construct_xml():
headers={"accept": "application/xml"},
)
assert response.status_code == 200
assert response.text.startswith("<?xml ")


def test_yasgui():
Expand All @@ -186,7 +195,7 @@ def test_bad_request():

custom_concat_construct = """PREFIX myfunctions: <https://w3id.org/um/sparql-functions/>
CONSTRUCT {
<http://test> <http://concat> ?concat, ?concatLength .
<http://example.com/test> <http://example.com/concat> ?concat, ?concatLength .
} WHERE {
BIND("First" AS ?first)
BIND(myfunctions:custom_concat(?first, "last") AS ?concat)
Expand Down

0 comments on commit 16a47c6

Please sign in to comment.