diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f683f94..a11d0722 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Fixed +- Allow additional top-level properties on collections [#191](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/191) - Exclude unset fields in search response [#166](https://github.com/stac-utils/stac-fastapi-elasticsearch/issues/166) - Upgrade stac-fastapi to v2.4.9 [#172](https://github.com/stac-utils/stac-fastapi-elasticsearch/pull/172) - Set correct default filter-lang for GET /search requests [#179](https://github.com/stac-utils/stac-fastapi-elasticsearch/issues/179) diff --git a/stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/serializers.py b/stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/serializers.py index 725e8f65..ee53320b 100644 --- a/stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/serializers.py +++ b/stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/serializers.py @@ -1,5 +1,6 @@ """Serializers.""" import abc +from copy import deepcopy from typing import Any import attr @@ -121,18 +122,24 @@ def db_to_stac(cls, collection: dict, base_url: str) -> stac_types.Collection: Returns: stac_types.Collection: The STAC collection object. """ - # Use dictionary unpacking to extract values from the collection dictionary + # Avoid modifying the input dict in-place ... doing so breaks some tests + collection = deepcopy(collection) + + # Set defaults collection_id = collection.get("id") - stac_extensions = collection.get("stac_extensions", []) - stac_version = collection.get("stac_version", "") - title = collection.get("title", "") - description = collection.get("description", "") - keywords = collection.get("keywords", []) - license = collection.get("license", "") - providers = collection.get("providers", {}) - summaries = collection.get("summaries", {}) - extent = collection.get("extent", {}) - collection_assets = collection.get("assets", {}) + collection.setdefault("type", "Collection") + collection.setdefault("stac_extensions", []) + collection.setdefault("stac_version", "") + collection.setdefault("title", "") + collection.setdefault("description", "") + collection.setdefault("keywords", []) + collection.setdefault("license", "") + collection.setdefault("providers", []) + collection.setdefault("summaries", {}) + collection.setdefault( + "extent", {"spatial": {"bbox": []}, "temporal": {"interval": []}} + ) + collection.setdefault("assets", {}) # Create the collection links using CollectionLinks collection_links = CollectionLinks( @@ -143,20 +150,7 @@ def db_to_stac(cls, collection: dict, base_url: str) -> stac_types.Collection: original_links = collection.get("links") if original_links: collection_links += resolve_links(original_links, base_url) + collection["links"] = collection_links # Return the stac_types.Collection object - return stac_types.Collection( - type="Collection", - id=collection_id, - stac_extensions=stac_extensions, - stac_version=stac_version, - title=title, - description=description, - keywords=keywords, - license=license, - providers=providers, - summaries=summaries, - extent=extent, - links=collection_links, - assets=collection_assets, - ) + return stac_types.Collection(**collection) diff --git a/stac_fastapi/elasticsearch/tests/resources/test_collection.py b/stac_fastapi/elasticsearch/tests/resources/test_collection.py index 9061ac1e..dbd88da2 100644 --- a/stac_fastapi/elasticsearch/tests/resources/test_collection.py +++ b/stac_fastapi/elasticsearch/tests/resources/test_collection.py @@ -5,6 +5,22 @@ from ..conftest import create_collection, delete_collections_and_items, refresh_indices +CORE_COLLECTION_PROPS = [ + "id", + "type", + "stac_extensions", + "stac_version", + "title", + "description", + "keywords", + "license", + "providers", + "summaries", + "extent", + "links", + "assets", +] + @pytest.mark.asyncio async def test_create_and_delete_collection(app_client, load_test_data): @@ -84,6 +100,32 @@ async def test_returns_valid_collection(ctx, app_client): collection.validate() +@pytest.mark.asyncio +async def test_collection_extensions(ctx, app_client): + """Test that extensions can be used to define additional top-level properties""" + ctx.collection.get("stac_extensions", []).append( + "https://stac-extensions.github.io/item-assets/v1.0.0/schema.json" + ) + test_asset = {"title": "test", "description": "test", "type": "test"} + ctx.collection["item_assets"] = {"test": test_asset} + resp = await app_client.put("/collections", json=ctx.collection) + + assert resp.status_code == 200 + assert resp.json().get("item_assets", {}).get("test") == test_asset + + +@pytest.mark.asyncio +async def test_collection_defaults(app_client): + """Test that properties omitted by client are populated w/ default values""" + minimal_coll = {"id": str(uuid.uuid4())} + resp = await app_client.post("/collections", json=minimal_coll) + + assert resp.status_code == 200 + resp_json = resp.json() + for prop in CORE_COLLECTION_PROPS: + assert prop in resp_json.keys() + + @pytest.mark.asyncio async def test_pagination_collection(app_client, ctx, txn_client): """Test collection pagination links"""