Skip to content

Commit

Permalink
MVP runtime editable profiles.
Browse files Browse the repository at this point in the history
  • Loading branch information
recalcitrantsupplant committed Sep 16, 2024
1 parent 9f045d5 commit e317e5c
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 10 deletions.
3 changes: 3 additions & 0 deletions prez/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from prez.routers.ogc_router import router as ogc_records_router
from prez.routers.ogc_features_router import features_subapi
from prez.routers.sparql import router as sparql_router
from prez.routers.configuration import router as configuration_router
from prez.services.app_service import (
healthcheck_sparql_endpoints,
count_objects,
Expand Down Expand Up @@ -173,6 +174,8 @@ def assemble_app(

app.include_router(management_router)
app.include_router(ogc_records_router)
if settings.configuration_mode:
app.include_router(configuration_router)
if _settings.enable_sparql_endpoint:
app.include_router(sparql_router)
app.mount(
Expand Down
1 change: 1 addition & 0 deletions prez/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ class Settings(BaseSettings):
enable_sparql_endpoint: bool = False
temporal_predicate: Optional[URIRef] = SDO.temporal
endpoint_to_template_query_filename: Optional[Dict[str, str]] = {}
configuration_mode: bool = False

@field_validator("prez_version")
@classmethod
Expand Down
22 changes: 16 additions & 6 deletions prez/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

import httpx
from fastapi import Depends, Request, HTTPException
from pyoxigraph import Store
from rdflib import Dataset, URIRef, Graph, SKOS, RDF
from pyoxigraph import Store, NamedNode
from rdflib import Dataset, URIRef, Graph, SKOS, RDF, PROF
from sparql_grammar_pydantic import IRI, Var

from prez.cache import (
Expand Down Expand Up @@ -114,12 +114,20 @@ async def load_system_data_to_oxigraph(store: Store):
"""
# TODO refactor to use the local files directly
for f in (Path(__file__).parent / "reference_data/profiles").glob("*.ttl"):
prof_bytes = Graph().parse(f).serialize(format="nt", encoding="utf-8")
prof_g = Graph().parse(f)
prof_uri = prof_g.value(None, RDF.type, PROF.Profile)
prof_bytes = prof_g.serialize(format="nt", encoding="utf-8")
# profiles_bytes = profiles_graph_cache.default_context.serialize(format="nt", encoding="utf-8")
store.load(prof_bytes, "application/n-triples")
store.load(
prof_bytes, "application/n-triples", to_graph=NamedNode(str(prof_uri))
)

endpoints_bytes = endpoints_graph_cache.serialize(format="nt", encoding="utf-8")
store.load(endpoints_bytes, "application/n-triples")
store.load(
endpoints_bytes,
"application/n-triples",
to_graph=NamedNode(str(ONT["endpoints"])),
)


async def load_annotations_data_to_oxigraph(store: Store):
Expand All @@ -130,7 +138,9 @@ async def load_annotations_data_to_oxigraph(store: Store):
for file in (Path(__file__).parent / "reference_data/annotations").glob("*"):
g.parse(file)
file_bytes = g.serialize(format="nt", encoding="utf-8")
store.load(file_bytes, "application/n-triples")
store.load(
file_bytes, "application/n-triples", to_graph=NamedNode(str(ONT["annotations"]))
)


async def cql_post_parser_dependency(request: Request) -> CQLParser:
Expand Down
6 changes: 3 additions & 3 deletions prez/repositories/pyoxigraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@ def _handle_query_triples_results(results: pyoxigraph.QueryTriples) -> Graph:
return g.parse(data=ntriples, format="ntriples")

def _sync_rdf_query_to_graph(self, query: str) -> Graph:
results = self.pyoxi_store.query(query)
results = self.pyoxi_store.query(query, use_default_graph_as_union=True)
result_graph = self._handle_query_triples_results(results)
return result_graph

def _sync_tabular_query_to_table(self, query: str, context: URIRef = None) -> tuple:
results = self.pyoxi_store.query(query)
named_graphs = list(self.pyoxi_store.named_graphs())
results = self.pyoxi_store.query(query, default_graph=named_graphs)
results_dict = self._handle_query_solution_results(results)
# only return the bindings from the results.
return context, results_dict["results"]["bindings"]

def _sparql(self, query: str) -> dict | Graph | bool:
Expand Down
65 changes: 65 additions & 0 deletions prez/routers/configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import logging
from pathlib import Path as PLPath

from fastapi import APIRouter, Depends, Body
from fastapi import HTTPException
from fastapi import Path as FAPath
from pyoxigraph import NamedNode
from rdflib import Graph
from rdflib.exceptions import ParserError

from prez.cache import profiles_graph_cache
from prez.dependencies import get_system_repo
from prez.repositories import Repo
from prez.services.curie_functions import get_uri_for_curie_id

router = APIRouter(tags=["Configuration"])
log = logging.getLogger(__name__)

# Read the example RDF data from a file
example_profile = (PLPath(__file__).parent / "example_profile.ttl").read_text()


@router.put(
"/update-profile/{profile_name}", summary="Update Profile", tags=["Configuration"]
)
async def update_profile(
profile_name: str = FAPath(
...,
title="Profile Name",
description="The name of the profile to update",
example="prez:ExProf",
),
profile_update: str = Body(
...,
example=example_profile,
media_type="text/turtle",
),
system_repo: Repo = Depends(get_system_repo),
):
profile_uri = await get_uri_for_curie_id(profile_name)
try:
new_profile_g = Graph().parse(data=profile_update, format="turtle")
except ParserError as e:
raise HTTPException(status_code=400, detail=f"Error parsing profile: {e}")
try:
old_profile = profiles_graph_cache.cbd(profile_uri)
except KeyError:
raise HTTPException(
status_code=404, detail=f"Profile {profile_name} not found."
)
for t in old_profile:
profiles_graph_cache.remove(t)
try:
# system_repo.pyoxi_store.update(f"DELETE DATA {{ {" ".join([i.n3() for i in t])} }}")
system_repo.pyoxi_store.remove_graph(NamedNode(str(profile_uri)))
except Exception as e:
raise HTTPException(status_code=500, detail=f"Error updating profile: {e}")
for t in new_profile_g:
profiles_graph_cache.add(t)
new_prof_bytes = new_profile_g.serialize(format="nt", encoding="utf-8")
system_repo.pyoxi_store.load(
new_prof_bytes, "application/n-triples", to_graph=NamedNode(str(profile_uri))
)
log.info(f"Profile {profile_name} updated.")
return {"message": f"Profile {profile_name} updated."}
2 changes: 2 additions & 0 deletions prez/services/connegp_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ def _compose_select_query(self) -> str:
SELECT ?profile ?title ?class (count(?mid) as ?distance) ?req_profile ?def_profile ?format ?req_format ?def_format
WHERE {{
GRAPH ?g {{
VALUES ?class {{{" ".join('<' + str(klass) + '>' for klass in self.classes)}}}
?class rdfs:subClassOf* ?mid .
?mid rdfs:subClassOf* ?base_class .
Expand All @@ -274,6 +275,7 @@ def _compose_select_query(self) -> str:
altr-ext:hasDefaultProfile ?profile }} AS ?def_profile)
{self._generate_mediatype_if_statements()}
BIND(EXISTS {{ ?profile altr-ext:hasDefaultResourceFormat ?format }} AS ?def_format)
}}
}}
GROUP BY ?class ?profile ?req_profile ?def_profile ?format ?req_format ?def_format ?title
ORDER BY DESC(?req_profile) DESC(?distance) DESC(?def_profile) DESC(?req_format) DESC(?def_format)
Expand Down
5 changes: 4 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os

from pyoxigraph import NamedNode
from rdflib import Graph, URIRef, RDF
from rdflib.namespace import GEO
from starlette.routing import Mount
Expand Down Expand Up @@ -28,7 +29,9 @@ def test_store() -> Store:
store = Store()

for file in Path(__file__).parent.glob("../test_data/*.ttl"):
store.load(file.read_bytes(), "text/turtle")
store.load(
file.read_bytes(), "text/turtle", to_graph=NamedNode(f"https://{file.stem}")
)

return store

Expand Down

0 comments on commit e317e5c

Please sign in to comment.