Skip to content

Commit

Permalink
OGC Features bugfix for auto generated queryables.
Browse files Browse the repository at this point in the history
Add upfront validation of curie -> URI and raise URI not found exception if appropriate.
Add special characters to those excluded from CURIE generation: "aeiou!@#$%^&*()_+-=,."
  • Loading branch information
recalcitrantsupplant committed Oct 17, 2024
1 parent 71798f8 commit 860d20f
Show file tree
Hide file tree
Showing 7 changed files with 44 additions and 28 deletions.
19 changes: 17 additions & 2 deletions prez/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
JSONMediaType,
GeoJSONMediaType,
)
from prez.exceptions.model_exceptions import NoEndpointNodeshapeException
from prez.exceptions.model_exceptions import NoEndpointNodeshapeException, URINotFoundException
from prez.models.query_params import QueryParams
from prez.reference_data.prez_ns import ALTREXT, ONT, EP, OGCE, OGCFEAT
from prez.repositories import PyoxigraphRepo, RemoteSparqlRepo, OxrdflibRepo, Repo
Expand Down Expand Up @@ -489,7 +489,22 @@ async def get_endpoint_uri(
async def get_ogc_features_path_params(
request: Request,
):
return request.path_params
collection_id = request.path_params.get("collectionId")
feature_id = request.path_params.get("featureId")
path_params = {}
if feature_id:
try:
feature_uri = await get_uri_for_curie_id(feature_id)
except ValueError:
raise URINotFoundException(curie=feature_id)
path_params["feature_uri"] = feature_uri
if collection_id:
try:
collection_uri = await get_uri_for_curie_id(collection_id)
except ValueError:
raise URINotFoundException(curie=collection_id)
path_params["collection_uri"] = collection_uri
return path_params


async def get_ogc_features_mediatype(
Expand Down
7 changes: 5 additions & 2 deletions prez/exceptions/model_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ class URINotFoundException(Exception):
Raised when a URI is not found in the triplestore.
"""

def __init__(self, uri: URIRef):
self.message = f"URI {uri} not found at endpoint {settings.sparql_endpoint}."
def __init__(self, uri: URIRef = None, curie: str = None):
if uri:
self.message = f"URI \"{uri}\" not found at endpoint {settings.sparql_endpoint}."
if curie:
self.message = f"URI for curie \"{curie}\" not found at endpoint {settings.sparql_endpoint}."
super().__init__(self.message)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ ex:Object

ex:QueryablesGlobal
a sh:NodeShape ;
sh:targetClass prez:Queryable ;
sh:targetClass geo:Feature ;
ont:hierarchyLevel 1 ;
.

ex:QueryablesLocal
a sh:NodeShape ;
sh:targetClass prez:Queryable ;
sh:targetClass geo:Feature ;
ont:hierarchyLevel 2 ;
.
4 changes: 2 additions & 2 deletions prez/routers/ogc_features_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ async def listings_with_feature_collection(
system_repo,
cql_parser,
query_params,
**path_params,
path_params,
)
except Exception as e:
raise e
Expand Down Expand Up @@ -202,7 +202,7 @@ async def objects(
url,
data_repo,
system_repo,
**path_params,
path_params,
)
except Exception as e:
raise e
Expand Down
7 changes: 5 additions & 2 deletions prez/services/curie_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def generate_new_prefix(uri):
return
# otherwise, remove vowels to reduce length
proposed_prefix = "".join(
[c for c in to_generate_prefix_from if c not in "aeiou"]
[c for c in to_generate_prefix_from if c not in "aeiou!@#$%^&*()_+-=,."]
)
if not prefix_registered(proposed_prefix):
prefix_graph.bind(proposed_prefix, ns)
Expand Down Expand Up @@ -95,6 +95,9 @@ async def get_uri_for_curie_id(curie_id: str):
else:
separator = settings.curie_separator
curie = curie_id.replace(separator, ":")
uri = prefix_graph.namespace_manager.expand_curie(curie)
try:
uri = prefix_graph.namespace_manager.expand_curie(curie)
except ValueError:
raise
await curie_cache.set(curie_id, uri)
return uri
8 changes: 4 additions & 4 deletions prez/services/listings.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,11 @@ async def ogc_features_listing_function(
system_repo,
cql_parser,
query_params,
**path_params,
path_params,
):
count_query = None
count = 0
collectionId = path_params.get("collectionId")
collection_uri = path_params.get("collection_uri")
subselect_kwargs = merge_listing_query_grammar_inputs(
endpoint_nodeshape=endpoint_nodeshape,
cql_parser=cql_parser,
Expand Down Expand Up @@ -199,6 +199,7 @@ async def ogc_features_listing_function(
TriplesSameSubjectPath.from_spo(*innser_select_triple)
)
subselect_kwargs["inner_select_vars"] = [queryable_var]
subselect_kwargs["limit"] = 100
construct_triple = (
queryable_var,
IRI(value=RDF.type),
Expand All @@ -211,7 +212,7 @@ async def ogc_features_listing_function(
**subselect_kwargs,
).to_string()
queries.append(query)
elif not collectionId: # list Feature Collections
elif not collection_uri: # list Feature Collections
query = PrezQueryConstructor(
construct_tss_list=construct_tss_list,
profile_triples=profile_nodeshape.tssp_list,
Expand Down Expand Up @@ -240,7 +241,6 @@ async def ogc_features_listing_function(

# Features listing requires CBD of the Feature Collection as well; reuse items profile to get all props/bns to
# depth two.
collection_uri = await get_uri_for_curie_id(collectionId)
gpnt = GraphPatternNotTriples(
content=Bind(
expression=Expression.from_primary_expression(
Expand Down
23 changes: 9 additions & 14 deletions prez/services/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from fastapi.responses import PlainTextResponse, RedirectResponse
from rdf2geojson import convert
from rdflib import RDF, URIRef, RDFS
from rdflib import RDF, URIRef
from rdflib.namespace import GEO
from sparql_grammar_pydantic import TriplesSameSubject, IRI, Var, TriplesSameSubjectPath

Expand Down Expand Up @@ -129,25 +129,20 @@ async def ogc_features_object_function(
url,
data_repo,
system_repo,
**path_params,
path_params,
):
collectionId = path_params.get("collectionId")
featureId = path_params.get("featureId")
if featureId:
feature_uri = await get_uri_for_curie_id(featureId)
else:
feature_uri = None
collection_uri = await get_uri_for_curie_id(collectionId)
collection_uri = path_params.get("collection_uri")
feature_uri = path_params.get("feature_uri")
if template_query:
if featureId:
focus_uri = await get_uri_for_curie_id(featureId)
if feature_uri:
focus_uri = feature_uri
else:
focus_uri = collection_uri
query = template_query.replace(
"VALUES ?focusNode { UNDEF }", f"VALUES ?focusNode {{ {focus_uri.n3()} }}"
)
else:
if featureId is None: # feature collection
if feature_uri is None: # feature collection
collection_iri = IRI(value=collection_uri)
construct_tss_list = None
tssp_list = [
Expand All @@ -156,7 +151,6 @@ async def ogc_features_object_function(
)
]
else: # feature
feature_uri = await get_uri_for_curie_id(featureId)
feature_iri = IRI(value=feature_uri)
triples = [
(feature_iri, Var(value="prop"), Var(value="val")),
Expand All @@ -180,14 +174,15 @@ async def ogc_features_object_function(
item_graph, _ = await data_repo.send_queries([query], [])
if len(item_graph) == 0:
uri = feature_uri if feature_uri else collection_uri
raise URINotFoundException(uri)
raise URINotFoundException(uri=uri)
annotations_graph = await return_annotated_rdf(item_graph, data_repo, system_repo)
log.debug(f"Query time: {time.time() - query_start_time}")

link_headers = None
if selected_mediatype == "application/sparql-query":
content = io.BytesIO(query.encode("utf-8"))
elif selected_mediatype == "application/json":
collectionId = get_curie_id_for_uri(collection_uri)
collection = create_collection_json(
collectionId, collection_uri, annotations_graph, url
)
Expand Down

0 comments on commit 860d20f

Please sign in to comment.