Skip to content

Adding Collection Search Extension #681

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 25 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
adf4cc6
Added collection search extension
tjellicoe-tpzuk Apr 22, 2024
e01a758
Merge pull request #1 from UKEODHP/feature/EODHP-219-add-collection-s…
tjellicoe-tpzuk Apr 23, 2024
16e2443
Corrected collection search response class
tjellicoe-tpzuk Apr 26, 2024
948ce1e
Updating collection search response model
tjellicoe-tpzuk Apr 26, 2024
6a12507
Updating response model for collection search
tjellicoe-tpzuk Apr 26, 2024
cb30fbb
Updating response model for collection search
tjellicoe-tpzuk Apr 26, 2024
7c4c11f
Merge pull request #2 from UKEODHP/bugfix/json-response-type-error-co…
tjellicoe-tpzuk May 1, 2024
5072729
Add catalogs to stac fastapi and updated to maintain root link for co…
tjellicoe-tpzuk May 9, 2024
4c0694f
Added catalogs to stac fastapi
tjellicoe-tpzuk May 9, 2024
8b63cd1
Moving collection-search to a core extension
tjellicoe-tpzuk May 9, 2024
8efbe92
Adding discovery-level search for catalogue search
tjellicoe-tpzuk May 9, 2024
a27a7a4
Adding discovery-search extension
tjellicoe-tpzuk May 9, 2024
569eb0c
Adding free-text search to collection-search
tjellicoe-tpzuk May 9, 2024
441a252
Adding free-text search to collection-search
tjellicoe-tpzuk May 9, 2024
a84d63f
Updates to support catalog search )discovery-level) via free-text
tjellicoe-tpzuk May 9, 2024
5dab508
Added converter for datetime and updated cataloguri definition
tjellicoe-tpzuk May 15, 2024
a22d035
Added support for catalogues and reformatting code
tjellicoe-tpzuk May 15, 2024
31fbeeb
Code reformatting
tjellicoe-tpzuk May 15, 2024
d2b2863
Corrected catalogs endpoint spelling
tjellicoe-tpzuk May 15, 2024
61118e4
Corrected class name
tjellicoe-tpzuk May 15, 2024
d8700c8
Corrected link handling
tjellicoe-tpzuk May 15, 2024
43005fd
Removed old link comment
tjellicoe-tpzuk May 15, 2024
c59c3e3
Merge pull request #3 from UKEODHP/feature/EODHP-27-resource-catalogu…
tjellicoe-tpzuk May 15, 2024
bcd5c72
Corrected endpoint path for create collection
tjellicoe-tpzuk May 15, 2024
33d3431
Merge pull request #4 from UKEODHP/bugfix/EODHP-27-correct-create-col…
tjellicoe-tpzuk May 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 87 additions & 23 deletions stac_fastapi/api/stac_fastapi/api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from fastapi import APIRouter, FastAPI
from fastapi.openapi.utils import get_openapi
from fastapi.params import Depends
from stac_pydantic import Collection, Item, ItemCollection
from stac_pydantic import Catalog, Collection, Item, ItemCollection
from stac_pydantic.api import ConformanceClasses, LandingPage
from stac_pydantic.api.collections import Collections
from stac_pydantic.version import STAC_VERSION
Expand All @@ -16,6 +16,7 @@
from stac_fastapi.api.errors import DEFAULT_STATUS_CODES, add_exception_handlers
from stac_fastapi.api.middleware import CORSMiddleware, ProxyHeaderMiddleware
from stac_fastapi.api.models import (
CatalogUri,
CollectionUri,
EmptyRequest,
GeoJSONResponse,
Expand All @@ -32,6 +33,7 @@
from stac_fastapi.types.core import AsyncBaseCoreClient, BaseCoreClient
from stac_fastapi.types.extension import ApiExtension
from stac_fastapi.types.search import BaseSearchGetRequest, BaseSearchPostRequest
from stac_fastapi.types.stac import Catalogs


@attr.s
Expand Down Expand Up @@ -138,9 +140,9 @@ def register_landing_page(self):
self.router.add_api_route(
name="Landing Page",
path="/",
response_model=LandingPage
if self.settings.enable_response_models
else None,
response_model=(
LandingPage if self.settings.enable_response_models else None
),
response_class=self.response_class,
response_model_exclude_unset=False,
response_model_exclude_none=True,
Expand All @@ -157,9 +159,9 @@ def register_conformance_classes(self):
self.router.add_api_route(
name="Conformance Classes",
path="/conformance",
response_model=ConformanceClasses
if self.settings.enable_response_models
else None,
response_model=(
ConformanceClasses if self.settings.enable_response_models else None
),
response_class=self.response_class,
response_model_exclude_unset=True,
response_model_exclude_none=True,
Expand All @@ -175,7 +177,7 @@ def register_get_item(self):
"""
self.router.add_api_route(
name="Get Item",
path="/collections/{collection_id}/items/{item_id}",
path="/catalogs/{catalog_id}collections/{collection_id}/items/{item_id}",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😬 maybe this change wasn't made on purpose?

response_model=Item if self.settings.enable_response_models else None,
response_class=GeoJSONResponse,
response_model_exclude_unset=True,
Expand All @@ -194,9 +196,11 @@ def register_post_search(self):
self.router.add_api_route(
name="Search",
path="/search",
response_model=(ItemCollection if not fields_ext else None)
if self.settings.enable_response_models
else None,
response_model=(
(ItemCollection if not fields_ext else None)
if self.settings.enable_response_models
else None
),
response_class=GeoJSONResponse,
response_model_exclude_unset=True,
response_model_exclude_none=True,
Expand All @@ -216,9 +220,11 @@ def register_get_search(self):
self.router.add_api_route(
name="Search",
path="/search",
response_model=(ItemCollection if not fields_ext else None)
if self.settings.enable_response_models
else None,
response_model=(
(ItemCollection if not fields_ext else None)
if self.settings.enable_response_models
else None
),
response_class=GeoJSONResponse,
response_model_exclude_unset=True,
response_model_exclude_none=True,
Expand All @@ -237,25 +243,63 @@ def register_get_collections(self):
self.router.add_api_route(
name="Get Collections",
path="/collections",
response_model=Collections
if self.settings.enable_response_models
else None,
response_model=(
Collections if self.settings.enable_response_models else None
),
response_class=self.response_class,
response_model_exclude_unset=True,
response_model_exclude_none=True,
methods=["GET"],
endpoint=create_async_endpoint(self.client.all_collections, EmptyRequest),
)

def register_get_catalogs(self):
"""Register get catalogs endpoint (GET /catalogs).

Returns:
None
"""
self.router.add_api_route(
name="Get Catalogs",
path="/catalogs",
response_model=Catalogs if self.settings.enable_response_models else None,
response_class=self.response_class,
response_model_exclude_unset=True,
response_model_exclude_none=True,
methods=["GET"],
endpoint=create_async_endpoint(self.client.all_catalogs, EmptyRequest),
)

def register_get_catalog_collections(self):
"""Register get catalogs collections endpoint (GET /catalogs/{catalog_id}/collections).

Returns:
None
"""
self.router.add_api_route(
name="Get CatalogCollections",
path="/catalogs/{catalog_id}/collections",
response_model=(
Collections if self.settings.enable_response_models else None
),
response_class=self.response_class,
response_model_exclude_unset=True,
response_model_exclude_none=True,
methods=["GET"],
endpoint=create_async_endpoint(
self.client.get_catalog_collections, CatalogUri
),
)

def register_get_collection(self):
"""Register get collection endpoint (GET /collection/{collection_id}).
"""Register get collection endpoint (GET /catalogs/{catalog_id}/collection/{collection_id}).

Returns:
None
"""
self.router.add_api_route(
name="Get Collection",
path="/collections/{collection_id}",
path="/catalogs/{catalog_id}/collections/{collection_id}",
response_model=Collection if self.settings.enable_response_models else None,
response_class=self.response_class,
response_model_exclude_unset=True,
Expand All @@ -264,6 +308,23 @@ def register_get_collection(self):
endpoint=create_async_endpoint(self.client.get_collection, CollectionUri),
)

def register_get_catalog(self):
"""Register get catalog endpoint (GET /catalogs/{catalog_id}).

Returns:
None
"""
self.router.add_api_route(
name="Get Catalog",
path="/catalogs/{catalog_id}",
response_model=Catalog if self.settings.enable_response_models else None,
response_class=self.response_class,
response_model_exclude_unset=True,
response_model_exclude_none=True,
methods=["GET"],
endpoint=create_async_endpoint(self.client.get_catalog, CatalogUri),
)

def register_get_item_collection(self):
"""Register get item collection endpoint (GET /collection/{collection_id}/items).

Expand All @@ -282,10 +343,10 @@ def register_get_item_collection(self):
)
self.router.add_api_route(
name="Get ItemCollection",
path="/collections/{collection_id}/items",
response_model=ItemCollection
if self.settings.enable_response_models
else None,
path="/catalogs/{catalog_id}/collections/{collection_id}/items",
response_model=(
ItemCollection if self.settings.enable_response_models else None
),
response_class=GeoJSONResponse,
response_model_exclude_unset=True,
response_model_exclude_none=True,
Expand Down Expand Up @@ -316,8 +377,11 @@ def register_core(self):
self.register_post_search()
self.register_get_search()
self.register_get_collections()
self.register_get_catalogs()
self.register_get_collection()
self.register_get_catalog()
self.register_get_item_collection()
self.register_get_catalog_collections()

def customize_openapi(self) -> Optional[Dict[str, Any]]:
"""Customize openapi schema."""
Expand Down
2 changes: 2 additions & 0 deletions stac_fastapi/api/stac_fastapi/api/config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Application settings."""

import enum


Expand All @@ -17,6 +18,7 @@ class ApiExtensions(enum.Enum):
query = "query"
sort = "sort"
transaction = "transaction"
collection_search = "collection-search"


class AddOns(enum.Enum):
Expand Down
1 change: 1 addition & 0 deletions stac_fastapi/api/stac_fastapi/api/middleware.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Api middleware."""

import re
import typing
from http.client import HTTP_PORT, HTTPS_PORT
Expand Down
21 changes: 15 additions & 6 deletions stac_fastapi/api/stac_fastapi/api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from stac_pydantic.shared import BBox

from stac_fastapi.types.extension import ApiExtension
from stac_fastapi.types.rfc3339 import DateTimeType
from stac_fastapi.types.rfc3339 import DateTimeType, str_to_interval
from stac_fastapi.types.search import (
APIRequest,
BaseSearchGetRequest,
Expand Down Expand Up @@ -49,9 +49,11 @@ def create_request_model(
for k, v in model.__fields__.items():
field_info = v.field_info
body = Body(
None
if isinstance(field_info.default, UndefinedType)
else field_info.default,
(
None
if isinstance(field_info.default, UndefinedType)
else field_info.default
),
default_factory=field_info.default_factory,
alias=field_info.alias,
alias_priority=field_info.alias_priority,
Expand Down Expand Up @@ -101,7 +103,14 @@ def create_post_request_model(


@attr.s # type:ignore
class CollectionUri(APIRequest):
class CatalogUri(APIRequest):
"""Delete catalog."""

catalog_id: str = attr.ib(default=Path(..., description="Catalog ID"))


@attr.s # type:ignore
class CollectionUri(CatalogUri):
"""Delete collection."""

collection_id: str = attr.ib(default=Path(..., description="Collection ID"))
Expand All @@ -127,7 +136,7 @@ class ItemCollectionUri(CollectionUri):

limit: int = attr.ib(default=10)
bbox: Optional[BBox] = attr.ib(default=None, converter=str2bbox)
datetime: Optional[DateTimeType] = attr.ib(default=None)
datetime: Optional[DateTimeType] = attr.ib(default=None, converter=str_to_interval)


class POSTTokenPagination(BaseModel):
Expand Down
7 changes: 4 additions & 3 deletions stac_fastapi/api/stac_fastapi/api/openapi.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""openapi."""

import warnings

from fastapi import FastAPI
Expand Down Expand Up @@ -43,9 +44,9 @@ async def patched_openapi_endpoint(req: Request) -> Response:
# Get the response from the old endpoint function
response: JSONResponse = await old_endpoint(req)
# Update the content type header in place
response.headers[
"content-type"
] = "application/vnd.oai.openapi+json;version=3.0"
response.headers["content-type"] = (
"application/vnd.oai.openapi+json;version=3.0"
)
# Return the updated response
return response

Expand Down
1 change: 1 addition & 0 deletions stac_fastapi/api/stac_fastapi/api/version.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
"""Library version."""

__version__ = "2.5.2"
25 changes: 23 additions & 2 deletions stac_fastapi/api/tests/benchmarks.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

collection_links = link_factory.CollectionLinks("/", "test").create_links()
item_links = link_factory.ItemLinks("/", "test", "test").create_links()
catalog_links = link_factory.CatalogLinks("/", "test").create_links()


collections = [
Expand Down Expand Up @@ -62,7 +63,9 @@ def get_search(
) -> stac_types.ItemCollection:
raise NotImplementedError

def get_item(self, item_id: str, collection_id: str, **kwargs) -> stac_types.Item:
def get_item(
self, item_id: str, collection_id: str, catalog_id: str, **kwargs
) -> stac_types.Item:
raise NotImplementedError

def all_collections(self, **kwargs) -> stac_types.Collections:
Expand All @@ -75,9 +78,27 @@ def all_collections(self, **kwargs) -> stac_types.Collections:
],
)

def get_collection(self, collection_id: str, **kwargs) -> stac_types.Collection:
def all_catalogs(self, **kwargs) -> stac_types.Catalogs:
return stac_types.Catalogs(
catalogs=catalogs,
links=[
{"href": "test", "rel": "root"},
{"href": "test", "rel": "self"},
{"href": "test", "rel": "parent"},
],
)

def get_collection(
self, catalog_id: str, collection_id: str, **kwargs
) -> stac_types.Collection:
return collections[0]

def get_catalog_collection(self, catalog_id: str, **kwargs) -> stac_types.Catalogs:
return collections[0]

def get_catalog(self, catalog_id: str, **kwargs) -> stac_types.Catalog:
return catalogs[0]

def item_collection(
self,
collection_id: str,
Expand Down
27 changes: 15 additions & 12 deletions stac_fastapi/api/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,23 +86,17 @@ def test_add_route_dependencies_after_building_api(self):


class DummyCoreClient(core.BaseCoreClient):
def all_collections(self, *args, **kwargs):
...
def all_collections(self, *args, **kwargs): ...

def get_collection(self, *args, **kwargs):
...
def get_collection(self, *args, **kwargs): ...

def get_item(self, *args, **kwargs):
...
def get_item(self, *args, **kwargs): ...

def get_search(self, *args, **kwargs):
...
def get_search(self, *args, **kwargs): ...

def post_search(self, *args, **kwargs):
...
def post_search(self, *args, **kwargs): ...

def item_collection(self, *args, **kwargs):
...
def item_collection(self, *args, **kwargs): ...


class DummyTransactionsClient(core.BaseTransactionsClient):
Expand All @@ -126,6 +120,15 @@ def update_collection(self, *args, **kwargs):
def delete_collection(self, *args, **kwargs):
return "dummy response"

def create_catalog(self, *args, **kwargs):
return "dummy response"

def update_catalog(self, *args, **kwargs):
return "dummy response"

def delete_catalog(self, *args, **kwargs):
return "dummy response"


def must_be_bob(
credentials: security.HTTPBasicCredentials = Depends(security.HTTPBasic()),
Expand Down
Loading