Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 15 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

### Fixed

## [v0.2.0] - 2026-10-14
## [v0.3.0] - 2026-05-20

### Added

- `update_catalog_collection` method and PUT /catalogs/{catalog_id}/collections/{collection_id} endpoint for updating collection metadata within a catalog context

### Changed

- Updated conformance class URIs from beta.4 to rc.1:
- https://api.stacspec.org/v1.0.0-rc.1/multi-tenant-catalogs
- https://api.stacspec.org/v1.0.0-rc.1/multi-tenant-catalogs/transaction

## [v0.2.0] - 2026-05-02

### Added

Expand Down Expand Up @@ -83,7 +95,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
with the Black profile.


[Unreleased]: https://github.com/StacLabs/stac-fastapi-catalogs-extension/compare/v0.2.0...main
[Unreleased]: https://github.com/StacLabs/stac-fastapi-catalogs-extension/compare/v0.3.0...main
[v0.3.0]: https://github.com/StacLabs/stac-fastapi-catalogs-extension/compare/v0.2.0...v0.3.0
[v0.2.0]: https://github.com/StacLabs/stac-fastapi-catalogs-extension/compare/v0.1.2...v0.2.0
[v0.1.2]: https://github.com/StacLabs/stac-fastapi-catalogs-extension/compare/v0.1.1...v0.1.2
[v0.1.1]: https://github.com/StacLabs/stac-fastapi-catalogs-extension/compare/v0.1.0...v0.1.1
Expand Down
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,24 @@ according to your enabled capabilities:

- Required:
- https://api.stacspec.org/v1.0.0/core
- https://api.stacspec.org/v1.0.0-beta.4/multi-tenant-catalogs
- https://api.stacspec.org/v1.0.0-rc.1/multi-tenant-catalogs
- Recommended:
- https://api.stacspec.org/v1.0.0-rc.2/children
- Optional (only if transaction endpoints are enabled):
- https://api.stacspec.org/v1.0.0-beta.4/multi-tenant-catalogs/transaction
- https://api.stacspec.org/v1.0.0-rc.1/multi-tenant-catalogs/transaction

Operational guidance:

- Read-only/public APIs should not expose POST/PUT/DELETE catalog endpoints and
should not advertise the transaction conformance class.
- If transactions are enabled, expose and document the management endpoints and
include the transaction conformance class in conformance responses.
- **Important**: To preserve the poly-hierarchy DAG structure, updates to
collections SHOULD be performed through scoped routes
(`/catalogs/{catalogId}/collections/{collectionId}`) rather than the core STAC
route (`/collections/{collectionId}`). The core route is flat and does not
maintain parent-child relationships, so updates through that route may not
properly preserve the hierarchical context.

## Supported projects

Expand Down Expand Up @@ -211,6 +217,7 @@ required async methods, including:
- get_catalog_collections
- create_catalog_collection
- get_catalog_collection
- update_catalog_collection
- unlink_catalog_collection
- get_catalog_collection_items
- get_catalog_collection_item
Expand Down Expand Up @@ -263,6 +270,7 @@ are available for catalog and collection management:
- PUT /catalogs/{catalog_id}
- DELETE /catalogs/{catalog_id}
- POST /catalogs/{catalog_id}/collections
- PUT /catalogs/{catalog_id}/collections/{collection_id}
- DELETE /catalogs/{catalog_id}/collections/{collection_id}
- POST /catalogs/{catalog_id}/catalogs
- DELETE /catalogs/{catalog_id}/catalogs/{sub_catalog_id}
Expand Down
2 changes: 1 addition & 1 deletion stac_fastapi_catalogs_extension/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Catalogs extension module."""
"""stac-fastapi-catalogs-extension: Multi-tenant catalog hierarchies for STAC FastAPI."""

from .catalogs import (
CATALOGS_CORE_CONFORMANCE,
Expand Down
26 changes: 24 additions & 2 deletions stac_fastapi_catalogs_extension/catalogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,20 @@
CreateSubCatalogRequest,
SubCatalogsRequest,
UnlinkSubCatalogRequest,
UpdateCatalogCollectionRequest,
UpdateCatalogRequest,
)

# Conformance Classes
CATALOGS_CORE_CONFORMANCE = [
"https://api.stacspec.org/v1.0.0/core",
"https://api.stacspec.org/v1.0.0-beta.4/multi-tenant-catalogs",
"https://api.stacspec.org/v1.0.0-rc.1/multi-tenant-catalogs",
"https://api.stacspec.org/v1.0.0-rc.2/children",
"https://api.stacspec.org/v1.0.0-rc.2/children#type-filter",
]

CATALOGS_TRANSACTION_CONFORMANCE = [
"https://api.stacspec.org/v1.0.0-beta.4/multi-tenant-catalogs/transaction"
"https://api.stacspec.org/v1.0.0-rc.1/multi-tenant-catalogs/transaction"
]


Expand Down Expand Up @@ -404,6 +405,27 @@ def register(self, app: FastAPI) -> None:
tags=["Catalogs"],
)

# PUT /catalogs/{catalog_id}/collections/{collection_id}
self.router.add_api_route(
name="Update Catalog Collection",
path="/catalogs/{catalog_id}/collections/{collection_id}",
methods=["PUT"],
status_code=HTTP_200_OK,
endpoint=create_async_endpoint(
self.client.update_catalog_collection, UpdateCatalogCollectionRequest
),
response_model=Collection
if self.settings.get("enable_response_models", True)
else None,
response_class=self.response_class,
summary="Update Catalog Collection",
description=(
"Update collection metadata within a catalog context. "
"Preserves structural links to maintain poly-hierarchy."
),
tags=["Catalogs"],
)

# DELETE /catalogs/{catalog_id}/collections/{collection_id}
self.router.add_api_route(
name="Unlink Collection from Catalog",
Expand Down
29 changes: 29 additions & 0 deletions stac_fastapi_catalogs_extension/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,35 @@ async def unlink_catalog_collection(
"""
...

@abc.abstractmethod
async def update_catalog_collection(
self,
catalog_id: str,
collection_id: str,
collection: Collection,
request: Request | None = None,
**kwargs,
) -> Collection | Response:
"""Update a collection within a catalog context.

Updates the metadata (Title, Description, etc.) of the collection
within the scoped catalog context. This operation preserves the
collection's structural links (parent_ids) to maintain the poly-hierarchy.

For normative rules regarding Safety-First architecture during updates,
see the Multi-Tenant Catalogs specification.

Args:
catalog_id: The ID of the catalog.
collection_id: The ID of the collection.
collection: The updated collection data.
request: Optional FastAPI request object.

Returns:
The updated collection.
"""
...

@abc.abstractmethod
async def get_catalog_collection_items(
self,
Expand Down
7 changes: 7 additions & 0 deletions stac_fastapi_catalogs_extension/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,13 @@ class CreateCatalogCollectionRequest(CatalogsUri):
collection: Annotated[Collection | ObjectUri, Body()] = attr.ib(default=None)


@attr.s
class UpdateCatalogCollectionRequest(CatalogCollectionUri):
"""Update catalog collection with body."""

collection: Annotated[Collection, Body()] = attr.ib(default=None)


@attr.s
class CreateSubCatalogRequest(CatalogsUri):
"""Create sub-catalog with body."""
Expand Down
35 changes: 34 additions & 1 deletion tests/test_catalogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,16 @@ async def unlink_catalog_collection(
) -> None:
return None

async def update_catalog_collection(
self,
catalog_id: str,
collection_id: str,
collection: Collection,
request: Request | None = None,
**kwargs,
) -> Collection | Response:
return collection

async def get_catalog_collection_items(
self,
catalog_id: str,
Expand Down Expand Up @@ -535,6 +545,29 @@ def test_update_catalog(client: TestClient) -> None:
assert data["id"] == "test-catalog-1"


def test_update_catalog_collection(client: TestClient) -> None:
"""Test PUT /catalogs/{catalog_id}/collections/{collection_id} endpoint."""
collection_data = {
"type": "Collection",
"id": "collection-1",
"description": "Updated collection description",
"stac_version": "1.0.0",
"license": "MIT",
"extent": {
"spatial": {"bbox": [[-180, -90, 180, 90]]},
"temporal": {"interval": [["2021-01-01T00:00:00Z", None]]},
},
"links": [],
}
response = client.put(
"/catalogs/test-catalog-1/collections/collection-1", json=collection_data
)
assert response.status_code == 200, response.text
data = response.json()
assert data["id"] == "collection-1"
assert data["description"] == "Updated collection description"


def test_delete_catalog(client: TestClient) -> None:
"""Test DELETE /catalogs/{catalog_id} endpoint."""
response = client.delete("/catalogs/test-catalog-1")
Expand Down Expand Up @@ -870,6 +903,6 @@ def test_enabled_conformance_includes_transaction_class(client: TestClient) -> N
data = response.json()
assert "conformsTo" in data
transaction_class = (
"https://api.stacspec.org/v1.0.0-beta.4/multi-tenant-catalogs/transaction"
"https://api.stacspec.org/v1.0.0-rc.1/multi-tenant-catalogs/transaction"
)
assert transaction_class in data["conformsTo"]
Loading