Skip to content

Commit

Permalink
Merge pull request #50 from ternaustralia/edmond/entrypoint-update
Browse files Browse the repository at this point in the history
Refactor entrypoint endpoint to support pagination. Add tern vocabs entrypoint
  • Loading branch information
edmondchuc authored Jan 19, 2023
2 parents 593ebd7 + 3c5844b commit 44dd291
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 36 deletions.
24 changes: 16 additions & 8 deletions src/linkeddata_api/domain/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,6 @@
from pydantic import BaseModel


class Item(BaseModel):
id: str
label: str
description: str = None
created: str = None
modified: str = None


class RDFListItemMixin(BaseModel):
"""An item in an RDF List"""

Expand Down Expand Up @@ -56,3 +48,19 @@ class Resource(BaseModel):
types: list[URI]
properties: list[PredicateObjects]
incoming_properties: list[SubjectPredicates]


class EntrypointItem(BaseModel):
id: str
label: str
description: str = None
created: str = None
modified: str = None


class EntrypointItems(BaseModel):
items: list[EntrypointItem]
more_pages_exists: bool
items_count: int
limit: int
total_pages: int
1 change: 1 addition & 0 deletions src/linkeddata_api/domain/viewer/entrypoints/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from . import exceptions
from . import nrm
from . import tern
94 changes: 70 additions & 24 deletions src/linkeddata_api/domain/viewer/entrypoints/nrm.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,64 @@
from typing import Optional
from jinja2 import Template

from linkeddata_api import data
from linkeddata_api.domain import schema
from linkeddata_api.domain.viewer.entrypoints.utils import (
ceiling_division,
get_optional_value,
)


def get_optional_value(row: dict, key: str) -> Optional[str]:
return row.get(key)["value"] if row.get(key) else None
def get_count(sparql_endpoint: str) -> int:
query = """
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
PREFIX dcterms: <http://purl.org/dc/terms/>
PREFIX reg: <http://purl.org/linked-data/registry/>
SELECT (COUNT(*) AS ?count)
FROM <http://www.ontotext.com/explicit>
FROM <https://linked.data.gov.au/def/nrm>
WHERE {
<https://linked.data.gov.au/def/nrm> dcterms:hasPart ?uri .
VALUES (?vocabularyType) {
(skos:ConceptScheme)
(skos:Collection)
}
?uri a ?vocabularyType ;
skos:prefLabel ?_label .
OPTIONAL { ?uri dcterms:description ?_description }
OPTIONAL { ?uri dcterms:created ?_created }
OPTIONAL { ?uri dcterms:modified ?_modified }
FILTER NOT EXISTS {
?uri owl:deprecated true .
}
}
"""

result = data.sparql.post(query, sparql_endpoint).json()

return int(result["results"]["bindings"][0]["count"]["value"])


def get(
sparql_endpoint: str,
) -> schema.Item:
page: int,
) -> schema.EntrypointItems:
"""Get
Raises RequestError and SPARQLResultJSONError
"""
limit = 20

query = """
query = Template(
"""
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
PREFIX dcterms: <http://purl.org/dc/terms/>
PREFIX reg: <http://purl.org/linked-data/registry/>
SELECT
SELECT
?uri
(SAMPLE(?_label) as ?label)
(SAMPLE(?_description) as ?description)
(SAMPLE(?_label) as ?label)
(SAMPLE(?_description) as ?description)
(SAMPLE(?_created) as ?created)
(SAMPLE(?_modified) as ?modified)
FROM <http://www.ontotext.com/explicit>
Expand All @@ -42,27 +77,38 @@ def get(
OPTIONAL { ?uri dcterms:modified ?_modified }
}
GROUP by ?uri
ORDER by ?label
ORDER by ?label
LIMIT {{ limit }}
OFFSET {{ offset }}
"""
).render(limit=20, offset=(page - 1) * limit)

result = data.sparql.post(query, sparql_endpoint).json()

count = get_count(sparql_endpoint)
more_pages_exist = False
if count > (page * limit):
more_pages_exist = True

total_pages = ceiling_division(count, limit)

vocabs = []

try:
for row in result["results"]["bindings"]:
vocabs.append(
schema.Item(
id=str(row["uri"]["value"]),
label=str(row["label"]["value"]),
description=get_optional_value(row, "description"),
created=get_optional_value(row, "created"),
modified=get_optional_value(row, "modified"),
)
for row in result["results"]["bindings"]:
vocabs.append(
schema.EntrypointItem(
id=str(row["uri"]["value"]),
label=str(row["label"]["value"]),
description=get_optional_value(row, "description"),
created=get_optional_value(row, "created"),
modified=get_optional_value(row, "modified"),
)
except KeyError as err:
raise data.exceptions.SPARQLResultJSONError(
f"Unexpected SPARQL result set.\n{result}\n{err}"
) from err
)

return vocabs
return schema.EntrypointItems(
items=vocabs,
more_pages_exists=more_pages_exist,
items_count=count,
limit=limit,
total_pages=total_pages,
)
116 changes: 116 additions & 0 deletions src/linkeddata_api/domain/viewer/entrypoints/tern.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
from jinja2 import Template

from linkeddata_api import data
from linkeddata_api.domain import schema
from linkeddata_api.domain.viewer.entrypoints.utils import (
ceiling_division,
get_optional_value,
)


def get_count(sparql_endpoint: str) -> int:
query = """
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
PREFIX dcterms: <http://purl.org/dc/terms/>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
SELECT (COUNT(*) AS ?count)
FROM <http://www.ontotext.com/explicit>
FROM <http://linked.data.gov.au/def/tern-cv/>
WHERE {
VALUES (?vocabularyType) {
(skos:ConceptScheme)
(skos:Collection)
}
?uri a ?vocabularyType ;
skos:prefLabel ?_label .
OPTIONAL { ?uri dcterms:description ?_description }
OPTIONAL { ?uri dcterms:created ?_created }
OPTIONAL { ?uri dcterms:modified ?_modified }
FILTER NOT EXISTS {
?uri owl:deprecated true .
}
}
"""

result = data.sparql.post(query, sparql_endpoint).json()

return int(result["results"]["bindings"][0]["count"]["value"])


def get(
sparql_endpoint: str,
page: int,
) -> schema.EntrypointItems:
"""Get
Raises RequestError and SPARQLResultJSONError
"""
limit = 20

query = Template(
"""
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
PREFIX dcterms: <http://purl.org/dc/terms/>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
SELECT
?uri
(SAMPLE(?_label) as ?label)
(SAMPLE(?_description) as ?description)
(SAMPLE(?_created) as ?created)
(SAMPLE(?_modified) as ?modified)
FROM <http://www.ontotext.com/explicit>
FROM <http://linked.data.gov.au/def/tern-cv/>
WHERE {
VALUES (?vocabularyType) {
(skos:ConceptScheme)
(skos:Collection)
}
?uri a ?vocabularyType ;
skos:prefLabel ?_label .
OPTIONAL { ?uri dcterms:description ?_description }
OPTIONAL { ?uri dcterms:created ?_created }
OPTIONAL { ?uri dcterms:modified ?_modified }
FILTER NOT EXISTS {
?uri owl:deprecated true .
}
}
GROUP by ?uri
ORDER by ?label
LIMIT {{ limit }}
OFFSET {{ offset }}
"""
).render(limit=20, offset=(page - 1) * limit)

result = data.sparql.post(query, sparql_endpoint).json()

count = get_count(sparql_endpoint)
more_pages_exist = False
if count > (page * limit):
more_pages_exist = True

total_pages = ceiling_division(count, limit)

vocabs = []

for row in result["results"]["bindings"]:
vocabs.append(
schema.EntrypointItem(
id=str(row["uri"]["value"]),
label=str(row["label"]["value"]),
description=get_optional_value(row, "description"),
created=get_optional_value(row, "created"),
modified=get_optional_value(row, "modified"),
)
)

return schema.EntrypointItems(
items=vocabs,
more_pages_exists=more_pages_exist,
items_count=count,
limit=limit,
total_pages=total_pages,
)
9 changes: 9 additions & 0 deletions src/linkeddata_api/domain/viewer/entrypoints/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from typing import Optional, Union


def get_optional_value(row: dict, key: str) -> Optional[str]:
return row.get(key)["value"] if row.get(key) else None


def ceiling_division(a: Union[int, float], b: Union[int, float]) -> int:
return int(-(a // -b))
18 changes: 14 additions & 4 deletions src/linkeddata_api/views/api_v1/viewer/entrypoint.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from flask import abort
from flask import abort, request
from flask_tern import openapi

from linkeddata_api.views.api_v1.blueprint import bp
Expand All @@ -15,25 +15,35 @@
"nrm": {
"func": domain.viewer.entrypoints.nrm.get,
"sparql_endpoint": "https://graphdb.tern.org.au/repositories/dawe_vocabs_core",
}
},
"tern": {
"func": domain.viewer.entrypoints.tern.get,
"sparql_endpoint": "https://graphdb.tern.org.au/repositories/tern_vocabs_core",
},
}


@bp.get("/viewer/entrypoint/<string:viewer_id>")
@openapi.validate(validate_request=False, validate_response=False)
def get_entrypoint(viewer_id: str):
page = int(request.args.get("page", 1))
if page < 1:
page = 1

try:
obj = mapping.get(viewer_id)
if obj is None:
raise ViewerIDNotFoundError(f"Key '{viewer_id}' not found")

sparql_endpoint = obj["sparql_endpoint"]
items = obj["func"](sparql_endpoint)
entrypoint_items = obj["func"](sparql_endpoint, page)
except ViewerIDNotFoundError as err:
abort(404, str(err))
except (RequestError, SPARQLResultJSONError) as err:
abort(502, err.description)
except Exception as err:
abort(500, err)

return jsonify(items, headers={"cache-control": "max-age=600, s-maxage=3600"})
return jsonify(
entrypoint_items, headers={"cache-control": "max-age=600, s-maxage=3600"}
)

0 comments on commit 44dd291

Please sign in to comment.