Skip to content

Commit

Permalink
Added simple form and constraints specific endpoints (#81)
Browse files Browse the repository at this point in the history
The endpoints are just alias, that redirect to the real resource
  • Loading branch information
keul authored Jul 29, 2024
1 parent a6ae678 commit 8d2d26f
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 9 deletions.
86 changes: 86 additions & 0 deletions cads_catalogue_api_service/collection_ext.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"""Additional collections endpoint (outside STAC)."""

# Copyright 2024, European Union.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# mypy: ignore-errors

import cads_catalogue
import fastapi
import sqlalchemy as sa
import stac_fastapi.types
import stac_fastapi.types.core

from cads_catalogue_api_service.client import collection_serializer

from . import dependencies

router = fastapi.APIRouter(
prefix="",
responses={fastapi.status.HTTP_404_NOT_FOUND: {"description": "Not found"}},
)


def query_collection(
session: sa.orm.Session,
collection_id: str,
request: fastapi.Request,
) -> stac_fastapi.types.stac.Collection:
"""Load a STAC collection from database."""
return collection_serializer(
session.query(cads_catalogue.database.Resource)
.filter(cads_catalogue.database.Resource.resource_uid == collection_id)
.one(),
session=session,
request=request,
)


@router.get(
"/collections/{collection_id}/form.json",
)
def form_redirect(
collection_id: str,
request: fastapi.Request,
session=fastapi.Depends(dependencies.get_session),
):
"""Redirect to form.json (if defined)."""
collection = query_collection(session, collection_id, request)
if "links" in collection and collection["links"]:
for link in collection["links"]:
if link["rel"] == "form":
return fastapi.responses.RedirectResponse(url=link["href"])
raise fastapi.HTTPException(
status_code=fastapi.status.HTTP_404_NOT_FOUND,
detail=f"Collection {collection_id} has no form definition",
)


@router.get(
"/collections/{collection_id}/constraints.json",
)
def constraints_redirect(
collection_id: str,
request: fastapi.Request,
session=fastapi.Depends(dependencies.get_session),
):
"""Redirect to constraints.json (if defined)."""
collection = query_collection(session, collection_id, request)
if "links" in collection and collection["links"]:
for link in collection["links"]:
if link["rel"] == "constraints":
return fastapi.responses.RedirectResponse(url=link["href"])
raise fastapi.HTTPException(
status_code=fastapi.status.HTTP_404_NOT_FOUND,
detail=f"Collection {collection_id} has no constraints definition",
)
2 changes: 2 additions & 0 deletions cads_catalogue_api_service/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@

from . import (
client,
collection_ext,
config,
exceptions,
extensions,
Expand Down Expand Up @@ -83,6 +84,7 @@ async def lifespan(application: fastapi.FastAPI):
app.include_router(vocabularies.router)
app.include_router(messages.router)
app.include_router(schema_org.router)
app.include_router(collection_ext.router)


def catalogue_openapi() -> dict[str, Any]:
Expand Down
22 changes: 13 additions & 9 deletions cads_catalogue_api_service/schema_org.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

router = fastapi.APIRouter(
prefix="",
tags=["messages"],
tags=["schema.org"],
responses={fastapi.status.HTTP_404_NOT_FOUND: {"description": "Not found"}},
)

Expand Down Expand Up @@ -91,9 +91,11 @@ def schema_org_json_ld(
name=collection["title"],
description=collection.get("description", None),
url=url,
identifier=[f"https://doi.org/{collection['sci:doi']}"]
if "sci:doi" in collection
else [],
identifier=(
[f"https://doi.org/{collection['sci:doi']}"]
if "sci:doi" in collection
else []
),
license=license,
keywords=collection.get("keywords", []),
isAccessibleForFree=True,
Expand All @@ -108,12 +110,14 @@ def schema_org_json_ld(
),
),
distribution=[
models.schema_org.DataDownload(
encodingFormat=collection.get("file_format"),
contentUrl=f"{url}?tab=download",
(
models.schema_org.DataDownload(
encodingFormat=collection.get("file_format"),
contentUrl=f"{url}?tab=download",
)
if distribution
else ""
)
if distribution
else ""
],
temporalCoverage="/".join(temporal_coverage) if temporal_coverage else None,
spatialCoverage=models.schema_org.Place(
Expand Down
151 changes: 151 additions & 0 deletions tests/test_40_collection_ext.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# Copyright 2024, European Union.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Any

import fastapi
import fastapi.testclient

from cads_catalogue_api_service.main import app

client = fastapi.testclient.TestClient(app)


def _static_query_collection(
session: Any,
collection_id: str,
request: fastapi.Request,
):
return {
"type": "Collection",
"id": "era5-something",
"stac_version": "1.0.0",
"title": "Era5 name",
"description": "This dataset provides a modelled time series of gridded river discharge.",
"keywords": [
"Temporal coverage: Past",
],
"license": "proprietary",
"links": [
{
"rel": "self",
"type": "application/json",
"href": "http://localhost:8080/api/catalogue/v1/collections/era5-something",
},
{
"rel": "parent",
"type": "application/json",
"href": "http://localhost:8080/api/catalogue/v1/",
},
{
"rel": "root",
"type": "application/json",
"href": "http://localhost:8080/api/catalogue/v1/",
},
{
"rel": "form",
"type": "application/json",
"href": "https://somewhere.or/s3/form.json",
},
{
"rel": "constraints",
"type": "application/json",
"href": "https://somewhere.or/s3/constraints.json",
},
],
}


def _static_query_collection_nolinks(
session: Any,
collection_id: str,
request: fastapi.Request,
):
return {
"type": "Collection",
"id": "era5-something",
"stac_version": "1.0.0",
"title": "Era5 name",
"description": "This dataset provides a modelled time series of gridded river discharge.",
"keywords": [
"Temporal coverage: Past",
],
"license": "proprietary",
"links": [
{
"rel": "self",
"type": "application/json",
"href": "http://localhost:8080/api/catalogue/v1/collections/era5-something",
},
{
"rel": "parent",
"type": "application/json",
"href": "http://localhost:8080/api/catalogue/v1/",
},
{
"rel": "root",
"type": "application/json",
"href": "http://localhost:8080/api/catalogue/v1/",
},
],
}


def test_ext_form(monkeypatch):
monkeypatch.setattr(
"cads_catalogue_api_service.collection_ext.query_collection",
_static_query_collection,
)
response = client.get(
"/collections/era5-something/form.json", allow_redirects=False
)
assert response.status_code == 307
assert response.headers["location"] == "https://somewhere.or/s3/form.json"

monkeypatch.setattr(
"cads_catalogue_api_service.collection_ext.query_collection",
_static_query_collection_nolinks,
)
response = client.get(
"/collections/era5-something/form.json", allow_redirects=False
)
assert response.status_code == 404
assert (
response.json()["title"] == "Collection era5-something has no form definition"
)


def test_ext_constraints(monkeypatch):
monkeypatch.setattr(
"cads_catalogue_api_service.collection_ext.query_collection",
_static_query_collection,
)
response = client.get(
"/collections/era5-something/constraints.json", allow_redirects=False
)
assert response.status_code == 307
assert response.headers["location"] == "https://somewhere.or/s3/constraints.json"

monkeypatch.setattr(
"cads_catalogue_api_service.collection_ext.query_collection",
_static_query_collection_nolinks,
)
response = client.get(
"/collections/era5-something/constraints.json", allow_redirects=False
)
assert response.status_code == 404
assert (
response.json()["title"]
== "Collection era5-something has no constraints definition"
)

0 comments on commit 8d2d26f

Please sign in to comment.