Skip to content

Commit 17818d3

Browse files
authored
Merge branch 'main' into extend_datetime_search
2 parents 0d89769 + a416ec0 commit 17818d3

File tree

12 files changed

+135
-27
lines changed

12 files changed

+135
-27
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
77

88
## [Unreleased]
99

10+
### Added
11+
- Queryables landing page and collection links when the Filter Extension is enabled [#267](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/267)
12+
1013
### Changed
1114

1215
- Updated stac-fastapi libraries to v3.0.0a1 [#265](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/265)

data_loader.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,17 @@ def load_collection(base_url, collection_id, data_dir):
2222
collection["id"] = collection_id
2323
try:
2424
resp = requests.post(f"{base_url}/collections", json=collection)
25-
if resp.status_code == 200:
25+
if resp.status_code == 200 or resp.status_code == 201:
2626
click.echo(f"Status code: {resp.status_code}")
2727
click.echo(f"Added collection: {collection['id']}")
2828
elif resp.status_code == 409:
2929
click.echo(f"Status code: {resp.status_code}")
3030
click.echo(f"Collection: {collection['id']} already exists")
31+
else:
32+
click.echo(f"Status code: {resp.status_code}")
33+
click.echo(
34+
f"Error writing {collection['id']} collection. Message: {resp.text}"
35+
)
3136
except requests.ConnectionError:
3237
click.secho("Failed to connect", fg="red", err=True)
3338

sample_data/collection.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"id":"sentinel-s2-l2a-cogs-test",
33
"stac_version":"1.0.0",
4+
"type": "Collection",
45
"description":"Sentinel-2a and Sentinel-2b imagery, processed to Level 2A (Surface Reflectance) and converted to Cloud-Optimized GeoTIFFs",
56
"links":[
67
{"rel":"self","href":"https://earth-search.aws.element84.com/v0/collections/sentinel-s2-l2a-cogs"},

stac_fastapi/core/stac_fastapi/core/core.py

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,19 @@ async def landing_page(self, **kwargs) -> stac_types.LandingPage:
154154
conformance_classes=self.conformance_classes(),
155155
extension_schemas=[],
156156
)
157+
158+
if self.extension_is_enabled("FilterExtension"):
159+
landing_page["links"].append(
160+
{
161+
# TODO: replace this with Relations.queryables.value,
162+
"rel": "queryables",
163+
# TODO: replace this with MimeTypes.jsonschema,
164+
"type": "application/schema+json",
165+
"title": "Queryables",
166+
"href": urljoin(base_url, "queryables"),
167+
}
168+
)
169+
157170
collections = await self.all_collections(request=kwargs["request"])
158171
for collection in collections["collections"]:
159172
landing_page["links"].append(
@@ -206,7 +219,7 @@ async def all_collections(self, **kwargs) -> stac_types.Collections:
206219
token = request.query_params.get("token")
207220

208221
collections, next_token = await self.database.get_all_collections(
209-
token=token, limit=limit, base_url=base_url
222+
token=token, limit=limit, request=request
210223
)
211224

212225
links = [
@@ -240,10 +253,12 @@ async def get_collection(
240253
Raises:
241254
NotFoundError: If the collection with the given id cannot be found in the database.
242255
"""
243-
base_url = str(kwargs["request"].base_url)
256+
request = kwargs["request"]
244257
collection = await self.database.find_collection(collection_id=collection_id)
245258
return self.collection_serializer.db_to_stac(
246-
collection=collection, base_url=base_url
259+
collection=collection,
260+
request=request,
261+
extensions=[type(ext).__name__ for ext in self.extensions],
247262
)
248263

249264
async def item_collection(
@@ -749,12 +764,14 @@ async def create_collection(
749764
ConflictError: If the collection already exists.
750765
"""
751766
collection = collection.model_dump(mode="json")
752-
base_url = str(kwargs["request"].base_url)
753-
collection = self.database.collection_serializer.stac_to_db(
754-
collection, base_url
755-
)
767+
request = kwargs["request"]
768+
collection = self.database.collection_serializer.stac_to_db(collection, request)
756769
await self.database.create_collection(collection=collection)
757-
return CollectionSerializer.db_to_stac(collection, base_url)
770+
return CollectionSerializer.db_to_stac(
771+
collection,
772+
request,
773+
extensions=[type(ext).__name__ for ext in self.database.extensions],
774+
)
758775

759776
@overrides
760777
async def update_collection(
@@ -781,16 +798,18 @@ async def update_collection(
781798
"""
782799
collection = collection.model_dump(mode="json")
783800

784-
base_url = str(kwargs["request"].base_url)
801+
request = kwargs["request"]
785802

786-
collection = self.database.collection_serializer.stac_to_db(
787-
collection, base_url
788-
)
803+
collection = self.database.collection_serializer.stac_to_db(collection, request)
789804
await self.database.update_collection(
790805
collection_id=collection_id, collection=collection
791806
)
792807

793-
return CollectionSerializer.db_to_stac(collection, base_url)
808+
return CollectionSerializer.db_to_stac(
809+
collection,
810+
request,
811+
extensions=[type(ext).__name__ for ext in self.database.extensions],
812+
)
794813

795814
@overrides
796815
async def delete_collection(

stac_fastapi/core/stac_fastapi/core/models/links.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,39 @@ async def get_links(
107107
return links
108108

109109

110+
@attr.s
111+
class CollectionLinks(BaseLinks):
112+
"""Create inferred links specific to collections."""
113+
114+
collection_id: str = attr.ib()
115+
extensions: List[str] = attr.ib(default=attr.Factory(list))
116+
117+
def link_parent(self) -> Dict[str, Any]:
118+
"""Create the `parent` link."""
119+
return dict(rel=Relations.parent, type=MimeTypes.json.value, href=self.base_url)
120+
121+
def link_items(self) -> Dict[str, Any]:
122+
"""Create the `items` link."""
123+
return dict(
124+
rel="items",
125+
type=MimeTypes.geojson.value,
126+
href=urljoin(self.base_url, f"collections/{self.collection_id}/items"),
127+
)
128+
129+
def link_queryables(self) -> Dict[str, Any]:
130+
"""Create the `queryables` link."""
131+
if "FilterExtension" in self.extensions:
132+
return dict(
133+
rel="queryables",
134+
type=MimeTypes.json.value,
135+
href=urljoin(
136+
self.base_url, f"collections/{self.collection_id}/queryables"
137+
),
138+
)
139+
else:
140+
return None
141+
142+
110143
@attr.s
111144
class PagingLinks(BaseLinks):
112145
"""Create links for paging."""

stac_fastapi/core/stac_fastapi/core/serializers.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
"""Serializers."""
22
import abc
33
from copy import deepcopy
4-
from typing import Any
4+
from typing import Any, List, Optional
55

66
import attr
7+
from starlette.requests import Request
78

89
from stac_fastapi.core.datetime_utils import now_to_rfc3339_str
10+
from stac_fastapi.core.models.links import CollectionLinks
911
from stac_fastapi.types import stac as stac_types
10-
from stac_fastapi.types.links import CollectionLinks, ItemLinks, resolve_links
12+
from stac_fastapi.types.links import ItemLinks, resolve_links
1113

1214

1315
@attr.s
@@ -109,29 +111,34 @@ class CollectionSerializer(Serializer):
109111

110112
@classmethod
111113
def stac_to_db(
112-
cls, collection: stac_types.Collection, base_url: str
114+
cls, collection: stac_types.Collection, request: Request
113115
) -> stac_types.Collection:
114116
"""
115117
Transform STAC Collection to database-ready STAC collection.
116118
117119
Args:
118120
stac_data: the STAC Collection object to be transformed
119-
base_url: the base URL for the STAC API
121+
starlette.requests.Request: the API request
120122
121123
Returns:
122124
stac_types.Collection: The database-ready STAC Collection object.
123125
"""
124126
collection = deepcopy(collection)
125-
collection["links"] = resolve_links(collection.get("links", []), base_url)
127+
collection["links"] = resolve_links(
128+
collection.get("links", []), str(request.base_url)
129+
)
126130
return collection
127131

128132
@classmethod
129-
def db_to_stac(cls, collection: dict, base_url: str) -> stac_types.Collection:
133+
def db_to_stac(
134+
cls, collection: dict, request: Request, extensions: Optional[List[str]] = []
135+
) -> stac_types.Collection:
130136
"""Transform database model to STAC collection.
131137
132138
Args:
133139
collection (dict): The collection data in dictionary form, extracted from the database.
134-
base_url (str): The base URL for the collection.
140+
starlette.requests.Request: the API request
141+
extensions: A list of the extension class names (`ext.__name__`) or all enabled STAC API extensions.
135142
136143
Returns:
137144
stac_types.Collection: The STAC collection object.
@@ -157,13 +164,13 @@ def db_to_stac(cls, collection: dict, base_url: str) -> stac_types.Collection:
157164

158165
# Create the collection links using CollectionLinks
159166
collection_links = CollectionLinks(
160-
collection_id=collection_id, base_url=base_url
167+
collection_id=collection_id, request=request, extensions=extensions
161168
).create_links()
162169

163170
# Add any additional links from the collection dictionary
164171
original_links = collection.get("links")
165172
if original_links:
166-
collection_links += resolve_links(original_links, base_url)
173+
collection_links += resolve_links(original_links, str(request.base_url))
167174
collection["links"] = collection_links
168175

169176
# Return the stac_types.Collection object

stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/app.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@
5959
filter_extension,
6060
]
6161

62+
database_logic.extensions = [type(ext).__name__ for ext in extensions]
63+
6264
post_request_model = create_post_request_model(extensions)
6365

6466
api = StacApi(

stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/database_logic.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import attr
99
from elasticsearch_dsl import Q, Search
10+
from starlette.requests import Request
1011

1112
from elasticsearch import exceptions, helpers # type: ignore
1213
from stac_fastapi.core.extensions import filter
@@ -312,10 +313,12 @@ class DatabaseLogic:
312313
default=CollectionSerializer
313314
)
314315

316+
extensions: List[str] = attr.ib(default=attr.Factory(list))
317+
315318
"""CORE LOGIC"""
316319

317320
async def get_all_collections(
318-
self, token: Optional[str], limit: int, base_url: str
321+
self, token: Optional[str], limit: int, request: Request
319322
) -> Tuple[List[Dict[str, Any]], Optional[str]]:
320323
"""Retrieve a list of all collections from Elasticsearch, supporting pagination.
321324
@@ -342,7 +345,7 @@ async def get_all_collections(
342345
hits = response["hits"]["hits"]
343346
collections = [
344347
self.collection_serializer.db_to_stac(
345-
collection=hit["_source"], base_url=base_url
348+
collection=hit["_source"], request=request, extensions=self.extensions
346349
)
347350
for hit in hits
348351
]

stac_fastapi/opensearch/stac_fastapi/opensearch/app.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@
5959
filter_extension,
6060
]
6161

62+
database_logic.extensions = [type(ext).__name__ for ext in extensions]
63+
6264
post_request_model = create_post_request_model(extensions)
6365

6466
api = StacApi(

stac_fastapi/opensearch/stac_fastapi/opensearch/database_logic.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from opensearchpy.exceptions import TransportError
1212
from opensearchpy.helpers.query import Q
1313
from opensearchpy.helpers.search import Search
14+
from starlette.requests import Request
1415

1516
from stac_fastapi.core import serializers
1617
from stac_fastapi.core.extensions import filter
@@ -334,10 +335,12 @@ class DatabaseLogic:
334335
default=serializers.CollectionSerializer
335336
)
336337

338+
extensions: List[str] = attr.ib(default=attr.Factory(list))
339+
337340
"""CORE LOGIC"""
338341

339342
async def get_all_collections(
340-
self, token: Optional[str], limit: int, base_url: str
343+
self, token: Optional[str], limit: int, request: Request
341344
) -> Tuple[List[Dict[str, Any]], Optional[str]]:
342345
"""
343346
Retrieve a list of all collections from Opensearch, supporting pagination.
@@ -367,7 +370,7 @@ async def get_all_collections(
367370
hits = response["hits"]["hits"]
368371
collections = [
369372
self.collection_serializer.db_to_stac(
370-
collection=hit["_source"], base_url=base_url
373+
collection=hit["_source"], request=request, extensions=self.extensions
371374
)
372375
for hit in hits
373376
]

0 commit comments

Comments
 (0)