diff --git a/README.md b/README.md index f7414ca..91f158b 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,12 @@ For more than millions of records it is recommended to either set a low connecti ### Hydration -To configure **stac-fastapi-pgstac** to [hydrate search result items in the API](https://stac-utils.github.io/pgstac/pgstac/#runtime-configurations), set the `USE_API_HYDRATE` environment variable to `true` or explicitly set the option in the PGStac Settings object. +To configure **stac-fastapi-pgstac** to [hydrate search result items at the API level](https://stac-utils.github.io/pgstac/pgstac/#runtime-configurations), set the `USE_API_HYDRATE` environment variable to `true`. If `false` (default) the hydration will be done in the database. + +| use_api_hydrate (API) | nohydrate (PgSTAC) | Hydration | +| --- | --- | --- | +| False | False | PgSTAC | +| True | True | API | ### Migrations diff --git a/stac_fastapi/pgstac/config.py b/stac_fastapi/pgstac/config.py index 74fa271..5a0421b 100644 --- a/stac_fastapi/pgstac/config.py +++ b/stac_fastapi/pgstac/config.py @@ -174,6 +174,16 @@ class Settings(ApiSettings): prefix_path: str = "" use_api_hydrate: bool = False + """ + When USE_API_HYDRATE=TRUE, PgSTAC database will receive `NO_HYDRATE=TRUE` + + | use_api_hydrate | nohydrate | Hydration | + | --- | --- | --- | + | False | False | PgSTAC | + | True | True | API | + + ref: https://stac-utils.github.io/pgstac/pgstac/#runtime-configurations + """ invalid_id_chars: List[str] = DEFAULT_INVALID_ID_CHARS base_item_cache: Type[BaseItemCache] = DefaultBaseItemCache diff --git a/tests/conftest.py b/tests/conftest.py index dbbb597..6bba3ef 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -100,7 +100,7 @@ async def pgstac(database): # Run all the tests that use the api_client in both db hydrate and api hydrate mode @pytest.fixture( params=[ - # hydratation, prefix, model_validation + # API hydratation, prefix, model_validation (False, "", False), (False, "/router_prefix", False), (True, "", False), diff --git a/tests/data/test2_item.json b/tests/data/test2_item.json index 62fa252..9bd7a1e 100644 --- a/tests/data/test2_item.json +++ b/tests/data/test2_item.json @@ -64,7 +64,7 @@ "nodata": 0, "offset": 2.03976, "data_type": "uint8", - "spatial_resolution": 60 + "spatial_resolution": 80 } ] }, @@ -172,7 +172,6 @@ "type": "image/tiff; application=geotiff; profile=cloud-optimized", "roles": ["cloud"], "title": "Pixel Quality Assessment Band (QA_PIXEL)", - "description": "Collection 2 Level-1 Pixel Quality Assessment Band", "raster:bands": [ { "unit": "bit index", diff --git a/tests/resources/test_item.py b/tests/resources/test_item.py index 490d652..09be3fc 100644 --- a/tests/resources/test_item.py +++ b/tests/resources/test_item.py @@ -1722,3 +1722,71 @@ async def test_item_search_freetext(app_client, load_test_data, load_test_collec params={"q": "yo"}, ) assert resp.json()["numberReturned"] == 0 + + +@pytest.mark.asyncio +async def test_item_asset_change(app_client, load_test_data): + """Check that changing item_assets in collection does + not affect existing items if hydration should not occur. + + """ + # load collection + data = load_test_data("test2_collection.json") + collection_id = data["id"] + + resp = await app_client.post("/collections", json=data) + assert "item_assets" in data + assert resp.status_code == 201 + assert "item_assets" in resp.json() + + # load items + test_item = load_test_data("test2_item.json") + resp = await app_client.post(f"/collections/{collection_id}/items", json=test_item) + assert resp.status_code == 201 + + # check list of items + resp = await app_client.get( + f"/collections/{collection_id}/items", params={"limit": 1} + ) + assert len(resp.json()["features"]) == 1 + assert resp.status_code == 200 + + # NOTE: API or PgSTAC Hydration we should get the same values as original Item + assert ( + test_item["assets"]["red"]["raster:bands"] + == resp.json()["features"][0]["assets"]["red"]["raster:bands"] + ) + + # NOTE: `description` is not in the item body but in the collection's item-assets + # because it's not in the original item it won't be hydrated + assert not resp.json()["features"][0]["assets"]["qa_pixel"].get("description") + + ########################################################################### + # Remove item_assets in collection + operations = [{"op": "remove", "path": "/item_assets"}] + resp = await app_client.patch(f"/collections/{collection_id}", json=operations) + assert resp.status_code == 200 + + # Make sure item_assets is not in collection response + resp = await app_client.get(f"/collections/{collection_id}") + assert resp.status_code == 200 + assert "item_assets" not in resp.json() + ########################################################################### + + resp = await app_client.get( + f"/collections/{collection_id}/items", params={"limit": 1} + ) + assert len(resp.json()["features"]) == 1 + assert resp.status_code == 200 + + # NOTE: here we should only get `scale`, `offset` and `spatial_resolution` + # because the other values were stripped on ingestion (dehydration is a default in PgSTAC) + # scale and offset are no in item-asset and spatial_resolution is different, so the value in the item body is kept + assert ["scale", "offset", "spatial_resolution"] == list( + resp.json()["features"][0]["assets"]["red"]["raster:bands"][0] + ) + + # NOTE: `description` is not in the original item but in the collection's item-assets + # We get "𒍟※" because PgSTAC set it when ingesting (`description`is item-assets) + # because we removed item-assets, pgstac cannot hydrate this field, and thus return "𒍟※" + assert resp.json()["features"][0]["assets"]["qa_pixel"]["description"] == "𒍟※"