diff --git a/CHANGES.md b/CHANGES.md index 0f057de71..ca786b01a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,11 +4,12 @@ ### Fixed +* Fix missing payload for the PUT `collection/{collection_id}` endpoint ([#665](https://github.com/stac-utils/stac-fastapi/issues/665)) * Return 400 for datetime errors ([#670](https://github.com/stac-utils/stac-fastapi/pull/670)) ## [2.5.3] - 2024-04-23 -### Fixed +### Fixed * Remove the str2list converter from intersection queries via BaseSearchGetRequest ([#668](https://github.com/stac-utils/stac-fastapi/pull/668)) * Apply datetime converter in ItemCollection endpoint model ([#667](https://github.com/stac-utils/stac-fastapi/pull/667)) diff --git a/stac_fastapi/api/stac_fastapi/api/models.py b/stac_fastapi/api/stac_fastapi/api/models.py index c1d70b3cf..0721413a9 100644 --- a/stac_fastapi/api/stac_fastapi/api/models.py +++ b/stac_fastapi/api/stac_fastapi/api/models.py @@ -103,14 +103,14 @@ def create_post_request_model( @attr.s # type:ignore class CollectionUri(APIRequest): - """Delete collection.""" + """Get or delete collection.""" collection_id: str = attr.ib(default=Path(..., description="Collection ID")) @attr.s class ItemUri(CollectionUri): - """Delete item.""" + """Get or delete item.""" item_id: str = attr.ib(default=Path(..., description="Item ID")) diff --git a/stac_fastapi/extensions/stac_fastapi/extensions/core/transaction.py b/stac_fastapi/extensions/stac_fastapi/extensions/core/transaction.py index 0ebcc6194..86e1bfc52 100644 --- a/stac_fastapi/extensions/stac_fastapi/extensions/core/transaction.py +++ b/stac_fastapi/extensions/stac_fastapi/extensions/core/transaction.py @@ -31,6 +31,13 @@ class PutItem(ItemUri): item: stac_types.Item = attr.ib(default=Body(None)) +@attr.s +class PutCollection(CollectionUri): + """Update Collection.""" + + collection: stac_types.Collection = attr.ib(default=Body(None)) + + @attr.s class TransactionExtension(ApiExtension): """Transaction Extension. @@ -128,7 +135,7 @@ def register_update_collection(self): response_model_exclude_none=True, methods=["PUT"], endpoint=create_async_endpoint( - self.client.update_collection, stac_types.Collection + self.client.update_collection, PutCollection ), ) diff --git a/stac_fastapi/extensions/tests/test_transaction.py b/stac_fastapi/extensions/tests/test_transaction.py index fc5acc2cf..e6416eaea 100644 --- a/stac_fastapi/extensions/tests/test_transaction.py +++ b/stac_fastapi/extensions/tests/test_transaction.py @@ -8,7 +8,7 @@ from stac_fastapi.extensions.core import TransactionExtension from stac_fastapi.types.config import ApiSettings from stac_fastapi.types.core import BaseCoreClient, BaseTransactionsClient -from stac_fastapi.types.stac import Item, ItemCollection +from stac_fastapi.types.stac import Collection, Item, ItemCollection class DummyCoreClient(BaseCoreClient): @@ -32,25 +32,32 @@ def item_collection(self, *args, **kwargs): class DummyTransactionsClient(BaseTransactionsClient): - """Defines a pattern for implementing the STAC transaction extension.""" + """Dummy client returning parts of the request, rather than proper STAC items.""" - def create_item(self, item: Union[Item, ItemCollection], *args, **kwargs): - return {"created": True, "type": item["type"]} + def create_item(self, item: Union[Item, ItemCollection], **kwargs): + return {"type": item["type"]} - def update_item(self, *args, **kwargs): - raise NotImplementedError + def update_item(self, collection_id: str, item_id: str, item: Item, **kwargs): + return { + "path_collection_id": collection_id, + "path_item_id": item_id, + "type": item["type"], + } - def delete_item(self, *args, **kwargs): - raise NotImplementedError + def delete_item(self, item_id: str, collection_id: str, **kwargs): + return { + "path_collection_id": collection_id, + "path_item_id": item_id, + } - def create_collection(self, *args, **kwargs): - raise NotImplementedError + def create_collection(self, collection: Collection, **kwargs): + return {"type": collection["type"]} - def update_collection(self, *args, **kwargs): - raise NotImplementedError + def update_collection(self, collection_id: str, collection: Collection, **kwargs): + return {"path_collection_id": collection_id, "type": collection["type"]} - def delete_collection(self, *args, **kwargs): - raise NotImplementedError + def delete_collection(self, collection_id: str, **kwargs): + return {"path_collection_id": collection_id} def test_create_item(client: TestClient, item: Item) -> None: @@ -69,6 +76,42 @@ def test_create_item_collection( assert response.json()["type"] == "FeatureCollection" +def test_update_item(client: TestClient, item: Item) -> None: + response = client.put( + "/collections/a-collection/items/an-item", content=json.dumps(item) + ) + assert response.is_success, response.text + assert response.json()["path_collection_id"] == "a-collection" + assert response.json()["path_item_id"] == "an-item" + assert response.json()["type"] == "Feature" + + +def test_delete_item(client: TestClient) -> None: + response = client.delete("/collections/a-collection/items/an-item") + assert response.is_success, response.text + assert response.json()["path_collection_id"] == "a-collection" + assert response.json()["path_item_id"] == "an-item" + + +def test_create_collection(client: TestClient, collection: Collection) -> None: + response = client.post("/collections", content=json.dumps(collection)) + assert response.is_success, response.text + assert response.json()["type"] == "Collection" + + +def test_update_collection(client: TestClient, collection: Collection) -> None: + response = client.put("/collections/a-collection", content=json.dumps(collection)) + assert response.is_success, response.text + assert response.json()["path_collection_id"] == "a-collection" + assert response.json()["type"] == "Collection" + + +def test_delete_collection(client: TestClient, collection: Collection) -> None: + response = client.delete("/collections/a-collection") + assert response.is_success, response.text + assert response.json()["path_collection_id"] == "a-collection" + + @pytest.fixture def client( core_client: DummyCoreClient, transactions_client: DummyTransactionsClient @@ -119,3 +162,19 @@ def item() -> Item: "assets": {}, "collection": "test_collection", } + + +@pytest.fixture +def collection() -> Collection: + return { + "type": "Collection", + "stac_version": "1.0.0", + "stac_extensions": [], + "id": "test_collection", + "extent": { + "spatial": {"bbox": [[-180, -90, 180, 90]]}, + "temporal": { + "interval": [["2000-01-01T00:00:00Z", "2024-01-01T00:00:00Z"]] + }, + }, + } diff --git a/stac_fastapi/types/stac_fastapi/types/core.py b/stac_fastapi/types/stac_fastapi/types/core.py index 77078ace3..2c18649fa 100644 --- a/stac_fastapi/types/stac_fastapi/types/core.py +++ b/stac_fastapi/types/stac_fastapi/types/core.py @@ -109,10 +109,10 @@ def update_collection( ) -> Optional[Union[stac_types.Collection, Response]]: """Perform a complete update on an existing collection. - Called with `PUT /collections/{collection_id}`. It is expected that this item - already exists. The update should do a diff against the saved collection and - perform any necessary updates. Partial updates are not supported by the - transactions extension. + Called with `PUT /collections/{collection_id}`. It is expected that this + collection already exists. The update should do a diff against the saved + collection and perform any necessary updates. Partial updates are not + supported by the transactions extension. Args: collection_id: id of the existing collection to be updated