Skip to content

Commit

Permalink
test passing
Browse files Browse the repository at this point in the history
need to add more tests
  • Loading branch information
lalewis1 committed Feb 28, 2024
1 parent 679e420 commit 46fc9df
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 69 deletions.
33 changes: 11 additions & 22 deletions prez/services/connegp_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@
from textwrap import dedent

from pydantic import BaseModel
from pyoxigraph import Store
from rdflib import Graph, Namespace, URIRef

from prez.cache import prefix_graph, system_store
from prez.dependencies import get_system_repo
from prez.models.model_exceptions import NoProfilesException
from prez.repositories.base import Repo
from prez.services.curie_functions import get_curie_id_for_uri
from prez.services.curie_functions import get_curie_id_for_uri, get_uri_for_curie_id

logger = logging.getLogger("prez")

Expand All @@ -37,26 +34,18 @@ class NegotiatedPMTs(BaseModel):
headers: dict
params: dict
classes: list[URIRef]
system_repo: Repo
listing: bool = False
default_weighting: float = 1.0
requested_profiles: list[tuple[str, float]] | None = None
requested_mediatypes: list[tuple[str, float]] | None = None
available: list[dict] | None = None
selected: dict | None = None
_system_store: Store | None = None
_prefix_graph: Graph | None = None
_system_repo: Repo | None = None

class Config:
arbitrary_types_allowed = True

async def setup(self) -> bool:
if self._system_store is None:
self._system_store = system_store
if self._prefix_graph is None:
self._prefix_graph = prefix_graph
if self._system_repo is None:
self._system_repo = await get_system_repo(self._system_store)
self.requested_profiles = await self._get_requested_profiles()
self.requested_mediatypes = await self._get_requested_mediatypes()
self.available = await self._get_available()
Expand All @@ -69,16 +58,17 @@ async def _resolve_token(self, token: str) -> str:
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX prof: <http://www.w3.org/ns/dx/prof/>
SELECT ?s
SELECT ?profile
WHERE {
?s a prof:Profile .
?s dcterms:identifier ?o .
?profile a prof:Profile .
?profile dcterms:identifier ?o .
FILTER(?o="<token>"^^xsd:token)
}
""".replace("<token>", token))
try:
result = {result[0].value for result in self._system_store.query(query_str)}.pop() # TODO: use _system_repo.send_queries instead
except KeyError:
_, results = await self.system_repo.send_queries([], [(None, query_str)])
result: str = results[0][1][0]["profile"]["value"]
except (KeyError, IndexError, ValueError):
raise TokenError(f"Token: '{token}' could not be resolved to URI")
uri = "<" + result + ">"
return uri
Expand All @@ -92,7 +82,7 @@ async def _tupilize(self, string: str, is_profile: bool = False) -> tuple[str, f
except TokenError as e:
logger.error(e.args[0])
try: # if token resolution fails, try to resolve as a curie
result = str(self._prefix_graph.namespace_manager.expand_curie(parts[0]))
result = str(get_uri_for_curie_id(parts[0]))
parts[0] = "<" + result + ">"
except ValueError as e:
logger.error(e.args[0])
Expand All @@ -102,8 +92,7 @@ async def _tupilize(self, string: str, is_profile: bool = False) -> tuple[str, f
try:
parts[1] = float(parts[1]) # Type-check the seperated weighting
except ValueError as e:
log = logging.getLogger("prez")
log.debug(
logger.debug(
f"Could not cast q={parts[1]} as float. Defaulting to {self.default_weighting}. {e.args[0]}")
return parts[0], parts[1]

Expand Down Expand Up @@ -240,7 +229,7 @@ def _generate_mediatype_if_statements(self) -> str:
return ifs

async def _do_query(self, query: str) -> tuple[Graph, list]:
response = await self._system_repo.send_queries([], [(None, query)])
response = await self.system_repo.send_queries([], [(None, query)])
if not response[1][0][1]:
raise NoProfilesException(self.classes)
return response
28 changes: 15 additions & 13 deletions prez/services/listings.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,17 @@


async def listing_function(
request: Request,
repo: Repo,
system_repo: Repo,
endpoint_uri: URIRef,
hierarchy_level: int,
path_nodes: Dict[str, Var | IRI] = None,
page: int = 1,
per_page: int = 20,
cql_parser: CQLParser = None,
search_term: Optional[str] = None,
endpoint_structure: Tuple[str] = settings.endpoint_structure,
request: Request,
repo: Repo,
system_repo: Repo,
endpoint_uri: URIRef,
hierarchy_level: int,
path_nodes: Dict[str, Var | IRI] = None,
page: int = 1,
per_page: int = 20,
cql_parser: CQLParser = None,
search_term: Optional[str] = None,
endpoint_structure: Tuple[str] = settings.endpoint_structure,
):
"""
# determine the relevant node selection part of the query - from SHACL, CQL, Search
Expand All @@ -62,8 +62,10 @@ async def listing_function(
target_classes = frozenset([PREZ.CQLObjectList])
elif search_term:
target_classes = frozenset([PREZ.SearchResult])

# determine the relevant profile
pmts = NegotiatedPMTs(**{"headers": request.headers, "params": request.query_params, "classes": target_classes, "listing": True})
pmts = NegotiatedPMTs(headers=request.headers, params=request.query_params, classes=target_classes, listing=True,
system_repo=system_repo)
success = await pmts.setup()
if not success:
log.error("ConnegP Error. NegotiatedPMTs.setup() was not successful")
Expand Down Expand Up @@ -168,7 +170,7 @@ async def listing_function(


async def get_shacl_node_selection(
endpoint_uri, hierarchy_level, path_nodes, repo, system_repo
endpoint_uri, hierarchy_level, path_nodes, repo, system_repo
):
"""
Determines the relevant nodeshape based on the endpoint, hierarchy level, and parent URI
Expand Down
19 changes: 10 additions & 9 deletions prez/services/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,17 @@


async def object_function(
request: Request,
endpoint_uri: URIRef,
uri: URIRef,
request_url: str,
repo: Repo,
system_repo: Repo,
endpoint_structure: Tuple[str] = settings.endpoint_structure,
request: Request,
endpoint_uri: URIRef,
uri: URIRef,
request_url: str,
repo: Repo,
system_repo: Repo,
endpoint_structure: Tuple[str] = settings.endpoint_structure,
):
classes = await get_classes(uri=uri, repo=repo)
pmts = NegotiatedPMTs(**{"headers": request.headers, "params": request.query_params, "classes": classes})
pmts = NegotiatedPMTs(headers=request.headers, params=request.query_params, classes=classes,
system_repo=system_repo)
success = await pmts.setup()
if not success:
log.error("ConnegP Error. NegotiatedPMTs.setup() was not successful")
Expand All @@ -38,7 +39,7 @@ async def object_function(
runtime_values = {}
if pmts.selected["profile"] == URIRef("http://www.w3.org/ns/dx/conneg/altr-ext#alt-profile"):
endpoint_uri = URIRef("https://prez.dev/endpoint/system/alt-profiles-listing")
# runtime_values["selectedClass"] = prof_and_mt_info.selected_class
# runtime_values["selectedClass"] = prof_and_mt_info.selected_class

# runtime_values["object"] = uri
query_constructor = PrezQueryConstructor(
Expand Down
106 changes: 106 additions & 0 deletions tests/data/profiles/ogc_records_profile.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
PREFIX altr-ext: <http://www.w3.org/ns/dx/conneg/altr-ext#>
PREFIX dcat: <http://www.w3.org/ns/dcat#>
PREFIX dcterms: <http://purl.org/dc/terms/>
PREFIX geo: <http://www.opengis.net/ont/geosparql#>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
PREFIX prez: <https://prez.dev/>
PREFIX prof: <http://www.w3.org/ns/dx/prof/>
PREFIX prov: <http://www.w3.org/ns/prov#>
PREFIX reg: <http://purl.org/linked-data/registry#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX sh: <http://www.w3.org/ns/shacl#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX endpoint: <https://prez.dev/endpoint/ogcrecords/>
PREFIX shext: <http://example.com/shacl-extension#>


prez:OGCRecordsProfile
a prof:Profile ;
dcterms:identifier "ogc"^^xsd:token ;
dcterms:description "A system profile for OGC Records conformant API" ;
dcterms:title "OGC Profile" ;
altr-ext:constrainsClass prez:CatPrez ;
altr-ext:hasDefaultResourceFormat "text/anot+turtle" ;
altr-ext:hasNodeShape [
a sh:NodeShape ;
sh:targetClass dcat:Catalog , skos:Concept , geo:Feature , geo:FeatureCollection , skos:Collection , prez:SearchResult , prez:CQLObjectList ;
altr-ext:hasDefaultProfile prez:OGCListingProfile
] , [
a sh:NodeShape ;
sh:targetClass skos:ConceptScheme ;
altr-ext:hasDefaultProfile prez:OGCSchemesListProfile
] , [
a sh:NodeShape ;
sh:targetClass dcat:Catalog , skos:ConceptScheme , skos:Concept , geo:Feature , geo:FeatureCollection , skos:Collection ;
altr-ext:hasDefaultProfile prez:OGCItemProfile
]
.

prez:OGCListingProfile
a prof:Profile , prez:ListingProfile , sh:NodeShape ;
dcterms:title "OGC Listing Profile" ;
altr-ext:hasResourceFormat
"application/ld+json" ,
"application/anot+ld+json" ,
"application/rdf+xml" ,
"text/anot+turtle" ,
"text/turtle" ;
altr-ext:hasDefaultResourceFormat "text/anot+turtle" ;
altr-ext:constrainsClass dcat:Catalog , skos:Collection , geo:Feature , geo:FeatureCollection , skos:Concept ,
dcat:Resource , prof:Profile , prez:SearchResult , prez:CQLObjectList ;
sh:property [ sh:path rdf:type ]
.

prez:OGCSchemesListProfile
a prof:Profile , prez:ListingProfile , sh:NodeShape ;
dcterms:title "OGC Concept Scheme Listing Profile" ;
altr-ext:hasResourceFormat
"application/ld+json" ,
"application/anot+ld+json" ,
"application/rdf+xml" ,
"text/anot+turtle" ,
"text/turtle" ;
altr-ext:hasDefaultResourceFormat "text/anot+turtle" ;
altr-ext:constrainsClass skos:ConceptScheme ;
sh:property [
sh:minCount 0 ;
sh:path (
sh:union (
dcterms:publisher
reg:status
( prov:qualifiedDerivation prov:hadRole )
( prov:qualifiedDerivation prov:entity )
)
)
]
.

prez:OGCItemProfile
a prof:Profile , prez:ObjectProfile , sh:NodeShape ;
dcterms:title "OGC Object Profile" ;
altr-ext:hasResourceFormat
"application/ld+json" ,
"application/anot+ld+json" ,
"application/rdf+xml" ,
"text/anot+turtle" ,
"text/turtle" ;
altr-ext:hasDefaultResourceFormat "text/anot+turtle" ;
sh:property [
sh:path shext:allPredicateValues ;
] ,
[
sh:minCount 0 ;
sh:path [ sh:inversePath dcterms:hasPart ] ;
] ;
shext:bnode-depth 2 ;
altr-ext:constrainsClass dcat:Catalog ,
dcat:Resource ,
skos:ConceptScheme,
skos:Collection ,
skos:Concept ,
geo:FeatureCollection ,
geo:Feature ,
prof:Profile ;
.
55 changes: 30 additions & 25 deletions tests/test_connegp.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,51 +2,56 @@

import pytest
from pyoxigraph import Store
from rdflib import Graph, URIRef
from pyoxigraph.pyoxigraph import Store
from rdflib import URIRef

from prez.app import app
from prez.dependencies import get_repo
from prez.repositories import PyoxigraphRepo, Repo
from prez.services.connegp_service import NegotiatedPMTs


@pytest.fixture(scope="module")
@pytest.fixture(scope="session")
def test_store() -> Store:
# Create a new pyoxigraph Store
store = Store()
profiles = Path(__file__).parent / "data" / "profiles" / "remote_profile.ttl"
store.load(profiles, mime_type="text/turtle")

file = Path(__file__).parent / "data/profiles/ogc_records_profile.ttl"
store.load(file.read_bytes(), "text/turtle")

return store


@pytest.fixture(scope="module")
def test_prefix_graph():
graph = Graph(bind_namespaces="rdflib")
graph.bind("ex", "https://example.com/")
return graph
@pytest.fixture(scope="session")
def test_repo(test_store: Store) -> Repo:
# Create a PyoxigraphQuerySender using the test_store
return PyoxigraphRepo(test_store)


@pytest.mark.parametrize(
"headers, params, classes, expected_selected",
[
[
{"Accept-Profile": "<default>, <alternate>;q=0.9"},
{"_media": "text/anot+turtle, text/turtle;q=0.9"},
[URIRef("<http://www.w3.org/ns/dx/prof/profile>")],
{},
{},
[URIRef("http://www.w3.org/ns/dcat#Catalog")],
{
"profile": "",
"title": "",
"mediatype": "",
"class": ""
"profile": URIRef("https://prez.dev/OGCItemProfile"),
"title": "OGC Object Profile",
"mediatype": "text/anot+turtle",
"class": "http://www.w3.org/ns/dcat#Catalog"
}
],
]
)
@pytest.mark.asyncio
async def test_connegp(headers, params, classes, expected_selected, test_store, test_prefix_graph):
pmts = NegotiatedPMTs(**{
"headers": headers,
"params": params,
"classes": classes,
"_system_store": test_store,
"_prefix_graph": test_prefix_graph
})
success = await pmts.setup()
async def test_connegp(headers, params, classes, expected_selected, test_repo):
def override_get_repo():
return test_repo

app.dependency_overrides[get_repo] = override_get_repo

pmts = NegotiatedPMTs(headers=headers, params=params, classes=classes, system_repo=test_repo)
success = await pmts.setup()
assert success
assert pmts.selected == expected_selected

0 comments on commit 46fc9df

Please sign in to comment.